From cb2a0a5f02891b4aefa8c0cc488e1ffabb7148a9 Mon Sep 17 00:00:00 2001 From: Felix GV Date: Thu, 25 Jan 2024 20:49:12 -0500 Subject: [PATCH] [router] Refactored several allocation-heavy paths in Alpini (#827) This includes removing Optionals and some stream functions. Also removed some unused headers from error responses. Deleted a bunch of unused code and its associated tests. --- .../linkedin/alpini/base/misc/Headers.java | 8 - .../base/misc/MemoryPressureIndexMonitor.java | 231 ------ .../alpini/base/misc}/MetricNames.java | 2 +- .../linkedin/alpini/base/misc/Metrics.java | 242 +----- .../alpini/base/misc/StringToNumberUtils.java | 24 - .../AbstractQuantileEstimation.java | 15 +- .../alpini/base/misc/TestMetrics.java | 297 ------- .../alpini/base/cache/ByteBufHashMap.java | 729 ------------------ .../alpini/base/cache/PhantomHashCache.java | 403 ---------- .../alpini/base/cache/PhantomHashMap.java | 280 ------- .../alpini/base/cache/SerializedMap.java | 95 --- .../safealloc/DerivedReadableByteBuf.java | 7 +- .../alpini/base/safealloc/SafeByteBuf.java | 6 +- .../netty4/handlers/AllChannelsHandler.java | 111 --- .../handlers/ChunkedResponseLimiter.java | 103 --- .../handlers/ClientConnectionTracker.java | 224 ------ .../handlers/MemoryPressureIndexHandler.java | 80 -- .../handlers/MultipartResponseHandler.java | 263 ------- .../netty4/handlers/RequestLogHandler.java | 412 ---------- .../netty4/misc/BalancedEventLoopGroup.java | 152 ---- .../misc/BasicFullHttpMultiPartRequest.java | 15 +- .../alpini/netty4/misc/BasicHeaders.java | 9 - .../misc/BasicHttpRequestSerializer.java | 217 ------ .../alpini/netty4/misc/BasicHttpResponse.java | 6 +- .../alpini/netty4/misc/HttpMultiPart.java | 8 +- .../alpini/netty4/misc/HttpToStringUtils.java | 9 +- .../netty4/misc/MemoryPressureIndexUtils.java | 66 -- .../netty4/pool/ChannelPoolManager.java | 207 ----- .../netty4/pool/ChannelPoolManagerImpl.java | 399 +--------- .../alpini/netty4/pool/NettyDnsResolver.java | 129 ---- .../pool/SimpleChannelPoolManagerImpl.java | 145 +--- .../alpini/netty4/ssl/SSLEngineFactory.java | 21 +- .../netty4/ssl/SSLEngineFactoryImpl.java | 9 +- .../bootstrap/InstrumentedBootstrap.java | 13 +- .../netty/bootstrap/ResolveAllBootstrap.java | 5 +- .../http2/EspressoHttp2MultiplexHandler.java | 12 +- .../alpini/base/cache/TestPhantomHashMap.java | 267 ------- .../alpini/base/cache/TestSerializedMap.java | 455 ----------- .../handlers/TestAllChannelsHandler.java | 59 -- .../netty4/handlers/TestChunkedResponse.java | 203 ----- .../handlers/TestClientConnectionTracker.java | 257 ------ .../TestMemoryPressureIndexMonitor.java | 194 ----- .../TestMultipartResponseHandler.java | 224 ------ .../handlers/TestRequestLogHandler.java | 509 ------------ .../misc/TestBalancedEventLoopGroup.java | 178 ----- .../misc/TestBasicHttpRequestSerializer.java | 245 ------ .../TestChannelPoolManagerImplHttp2Ping.java | 8 - .../netty4/pool/TestChannelPoolResolver.java | 53 -- .../netty4/pool/TestNettyDnsResolver.java | 93 --- .../alpini/router/api/HostFinder.java | 46 -- .../router/api/ScatterGatherHelper.java | 21 +- .../alpini/router/api/ScatterGatherMode.java | 303 ++------ .../api/TestScatterGatherHelperBuilder.java | 13 - .../router/api/TestScatterGatherMode.java | 90 +-- .../router/ScatterGatherRequestHandler.java | 12 - .../router/ScatterGatherRequestHandler4.java | 50 +- .../ScatterGatherRequestHandlerImpl.java | 79 +- .../TestScatterGatherRequestHandler4.java | 13 - .../TestScatterGatherRequestHandlerImpl.java | 109 +-- .../venice/router/api/VeniceDelegateMode.java | 12 +- .../venice/router/api/VeniceHostFinder.java | 13 - .../router/api/VenicePathParserHelper.java | 18 +- .../router/api/VeniceResponseAggregator.java | 35 +- .../router/api/TestVeniceDelegateMode.java | 88 +-- .../api/TestVeniceResponseAggregator.java | 4 +- .../throttle/RouterRequestThrottlingTest.java | 8 +- 66 files changed, 307 insertions(+), 8306 deletions(-) delete mode 100644 internal/alpini/common/alpini-common-base/src/main/java/com/linkedin/alpini/base/misc/MemoryPressureIndexMonitor.java rename internal/alpini/{router/alpini-router-api/src/main/java/com/linkedin/alpini/router/api => common/alpini-common-base/src/main/java/com/linkedin/alpini/base/misc}/MetricNames.java (82%) delete mode 100644 internal/alpini/common/alpini-common-base/src/main/java/com/linkedin/alpini/base/misc/StringToNumberUtils.java delete mode 100644 internal/alpini/common/alpini-common-base/src/test/java/com/linkedin/alpini/base/misc/TestMetrics.java delete mode 100644 internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/base/cache/ByteBufHashMap.java delete mode 100644 internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/base/cache/PhantomHashCache.java delete mode 100644 internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/base/cache/PhantomHashMap.java delete mode 100644 internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/base/cache/SerializedMap.java delete mode 100644 internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/handlers/AllChannelsHandler.java delete mode 100644 internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/handlers/ChunkedResponseLimiter.java delete mode 100644 internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/handlers/ClientConnectionTracker.java delete mode 100644 internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/handlers/MemoryPressureIndexHandler.java delete mode 100644 internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/handlers/MultipartResponseHandler.java delete mode 100644 internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/handlers/RequestLogHandler.java delete mode 100644 internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/misc/BalancedEventLoopGroup.java delete mode 100644 internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/misc/BasicHttpRequestSerializer.java delete mode 100644 internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/misc/MemoryPressureIndexUtils.java delete mode 100644 internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/pool/NettyDnsResolver.java delete mode 100644 internal/alpini/netty4/alpini-netty4-base/src/test/java/com/linkedin/alpini/base/cache/TestPhantomHashMap.java delete mode 100644 internal/alpini/netty4/alpini-netty4-base/src/test/java/com/linkedin/alpini/base/cache/TestSerializedMap.java delete mode 100644 internal/alpini/netty4/alpini-netty4-base/src/test/java/com/linkedin/alpini/netty4/handlers/TestAllChannelsHandler.java delete mode 100644 internal/alpini/netty4/alpini-netty4-base/src/test/java/com/linkedin/alpini/netty4/handlers/TestChunkedResponse.java delete mode 100644 internal/alpini/netty4/alpini-netty4-base/src/test/java/com/linkedin/alpini/netty4/handlers/TestClientConnectionTracker.java delete mode 100644 internal/alpini/netty4/alpini-netty4-base/src/test/java/com/linkedin/alpini/netty4/handlers/TestMemoryPressureIndexMonitor.java delete mode 100644 internal/alpini/netty4/alpini-netty4-base/src/test/java/com/linkedin/alpini/netty4/handlers/TestMultipartResponseHandler.java delete mode 100644 internal/alpini/netty4/alpini-netty4-base/src/test/java/com/linkedin/alpini/netty4/handlers/TestRequestLogHandler.java delete mode 100644 internal/alpini/netty4/alpini-netty4-base/src/test/java/com/linkedin/alpini/netty4/misc/TestBalancedEventLoopGroup.java delete mode 100644 internal/alpini/netty4/alpini-netty4-base/src/test/java/com/linkedin/alpini/netty4/misc/TestBasicHttpRequestSerializer.java delete mode 100644 internal/alpini/netty4/alpini-netty4-base/src/test/java/com/linkedin/alpini/netty4/pool/TestNettyDnsResolver.java diff --git a/internal/alpini/common/alpini-common-base/src/main/java/com/linkedin/alpini/base/misc/Headers.java b/internal/alpini/common/alpini-common-base/src/main/java/com/linkedin/alpini/base/misc/Headers.java index 58f50486cf..2c77da89bd 100644 --- a/internal/alpini/common/alpini-common-base/src/main/java/com/linkedin/alpini/base/misc/Headers.java +++ b/internal/alpini/common/alpini-common-base/src/main/java/com/linkedin/alpini/base/misc/Headers.java @@ -4,7 +4,6 @@ import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Optional; /** @@ -51,8 +50,6 @@ default Headers set(Iterable> entries) { Iterator> iteratorCharSequence(); - Optional unwrap(Class type); - Headers EMPTY_HEADERS = new Headers() { @Override public boolean isEmpty() { @@ -99,11 +96,6 @@ public Iterator> iteratorCharSequence() { return Collections.emptyIterator(); } - @Override - public Optional unwrap(Class type) { - return Optional.empty(); - } - @Override public Iterator> iterator() { return Collections.emptyIterator(); diff --git a/internal/alpini/common/alpini-common-base/src/main/java/com/linkedin/alpini/base/misc/MemoryPressureIndexMonitor.java b/internal/alpini/common/alpini-common-base/src/main/java/com/linkedin/alpini/base/misc/MemoryPressureIndexMonitor.java deleted file mode 100644 index f788d39e0b..0000000000 --- a/internal/alpini/common/alpini-common-base/src/main/java/com/linkedin/alpini/base/misc/MemoryPressureIndexMonitor.java +++ /dev/null @@ -1,231 +0,0 @@ -package com.linkedin.alpini.base.misc; - -import java.lang.ref.PhantomReference; -import java.util.Objects; -import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.atomic.LongAdder; -import java.util.function.BiConsumer; -import java.util.function.Function; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - - -/** - * Monitor using the size of live objects, byte-based to indicate memory pressure. - * @param The referent that contains bytes information, for example, an HttpRequest that has Content-Length in - * its header - * @param The Key used by internal map to keep track of byte count by objects. - * @param The Stats - * @author solu - */ -public class MemoryPressureIndexMonitor { - private static final Logger LOG = LogManager.getLogger(MemoryPressureIndexMonitor.class); - // The adder records total in and out Bytes. - // The Overflow risk is small as per Antony: - // According to my calculations, assuming that we transfer 1GB of data per second (max 10ge NIC) for 24 hours per day, - // 365 days per year, it will take 272.2044146576142057201497055756490679200843125972807044709 years - private final LongAdder _totalInBytes = new LongAdder(); - private final LongAdder _totalOutBytes = new LongAdder(); - - private final ConcurrentMap> _byteCountConcurrentHashMap = new ConcurrentHashMap<>(); - - public Function> getIdSupplier() { - return _idSupplier; - } - - // Function that takes a generic T as input and return a K type as the key used by byCountMap - private final Function> _idSupplier; - - // usually this should be the Stats' value setter to receive the update of the MPI - private BiConsumer _statsValueConsumer; - - private final S _stats; - - private boolean _phantomMode = true; - - /** - * Create a Monitor by specifying the a function that takes a T as input and returns a Long as the key used - by byCountMap. - * - * @param idSupplier idSupplier - */ - public MemoryPressureIndexMonitor( - Function> idSupplier, - S stats, - BiConsumer statsValueConsumer) { - // Make sure there is no link between T and the Long by constructing a new Long every time. - // This may have performance penalty but it is safer for GC. - this._idSupplier = Objects.requireNonNull(idSupplier, "idSupplier"); - this._stats = Objects.requireNonNull(stats, "stats"); - this._statsValueConsumer = Objects.requireNonNull(statsValueConsumer, "statsValueConsumer"); - } - - public MemoryPressureIndexMonitor setPhantomMode(boolean phantomMode) { - this._phantomMode = phantomMode; - return this; - } - - public static class ByteCount { - // Ue AtomicLong instead of LongAdder as this is a counter per Referent - private final AtomicLong _count = new AtomicLong(0); - private PhantomReference _phantom; - // Since WeakReference could lose the grip about its referent, the .get() could return null. - // So we use a _hashCode to cache the hashCode result. - private final int _hashCode; - // use this to cache System.identityHashCode(referent) for equals to use.; - // Not a 100% bulletproof but good enough. - private final int _systemIdentityHashCode; - private final MemoryPressureIndexMonitor _memoryPressureIndexMonitor; - - public ByteCount(T referent, K key, MemoryPressureIndexMonitor memoryPressureIndexMonitor) { - this._hashCode = referent.hashCode(); - this._systemIdentityHashCode = System.identityHashCode(referent); - this._memoryPressureIndexMonitor = - Objects.requireNonNull(memoryPressureIndexMonitor, "memoryPressureIndexMonitor"); - Objects.requireNonNull(key, "key"); - if (_memoryPressureIndexMonitor._phantomMode) { - this._phantom = LeakDetect.newReference(referent, () -> { - if (LOG.isDebugEnabled()) { - LOG.debug("Finally removed {} bytes from request key={}", count(), key); - } - _memoryPressureIndexMonitor._totalOutBytes.add(count()); - _memoryPressureIndexMonitor._byteCountConcurrentHashMap.remove(key); - _memoryPressureIndexMonitor.updateStats(); - _phantom.clear(); - }); - } - } - - public ByteCount add(long delta) { - if (delta < 0) { - throw new IllegalArgumentException(String.format("Illegal input %d", delta)); - } - _count.addAndGet(delta); - _memoryPressureIndexMonitor._totalInBytes.add(delta); - return this; - } - - public long count() { - return _count.get(); - } - - @Override - public int hashCode() { - // Warning: we can't use _id.get().hashCode here as _id is a WeakReference and its referent could be - // garbage collected therefore, the _id.get() could return null. - return this._hashCode; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - - // Using raw type but we don't care here. - if (!(o instanceof ByteCount)) { - return false; - } - ByteCount other = (ByteCount) o; - - // Warning: we can't use _id.get() here as _id is a WeakReference and its referent could be garbage collected. - // therefore, the _id.get() could return null. - // So first of all, we try our best to obtain the referent of this and other. If they are not null, we use them - // for equals. Otherwise, we shall fall back to using _hashCode and _systemIdentityHashcode - return this._hashCode == other._hashCode && this._systemIdentityHashCode == other._systemIdentityHashCode; - } - } - - public long getBytesByReferent(T referent) { - if (referent == null) { - return 0; - } - Optional key = _idSupplier.apply(referent); - return key.isPresent() && _byteCountConcurrentHashMap.containsKey(key.get()) - ? _byteCountConcurrentHashMap.get(key.get()).count() - : 0; - } - - public boolean isPhantomSetForReferent(T referent) { - if (referent == null) { - return false; - } - Optional key = _idSupplier.apply(referent); - return key.isPresent() && _byteCountConcurrentHashMap.containsKey(key.get()) - ? _byteCountConcurrentHashMap.get(key.get())._phantom != null - : false; - } - - /** - * Remove the capacity and return the volume that would be removed in this method or deferred. - * @param key key - * @param immediatelyReduceCountAndRemoveEntry immediatelyReduceCountAndRemoveEntry - * @param valueToBeAdded valueToBeAdded - * @return the Optional of ByteCount - */ - public Optional> removeByteCount( - K key, - boolean immediatelyReduceCountAndRemoveEntry, - Optional valueToBeAdded) { - Objects.requireNonNull(key, "key"); - return Optional.ofNullable(_byteCountConcurrentHashMap.computeIfPresent(key, (k, v) -> { - // if immediatelyReduceCountAndRemoveEntry is true, the entry will be removed (by returning null) and the - // _totalOutBytes is immediately changed. Otherwise all the actions are deferred to LeakDetect - if (immediatelyReduceCountAndRemoveEntry) { - _totalOutBytes.add(v.count()); - updateStats(); - return null; - } else { - // if there is a valueToBeAdded, add it into ByteCount - valueToBeAdded.ifPresent(v::add); - } - return v; - })); - } - - /** - * Update ByteCount and add a PhantomReference to defer the ByteCount removal to it. - * @param key key - * @param valueToBeAdded valueToBeAdded - * @return ref - */ - public Optional> removeByteCountAndAddPhantom(K key, Optional valueToBeAdded) { - Objects.requireNonNull(key, "key"); - return removeByteCount(key, false, valueToBeAdded); - } - - /** - * Add an referent if it is not in the map, other wise add the ByteCount. - * @param referent reference - * @param bytes bytes in flight - * @return current memory pressure index - */ - public long addReferentAndByteCount(T referent, long bytes) { - Objects.requireNonNull(referent, "The reference must not be null."); - _idSupplier.apply(referent) - .map(key -> _byteCountConcurrentHashMap.computeIfAbsent(key, k -> new ByteCount<>(referent, key, this))) - .ifPresent(byteCount -> byteCount.add(bytes)); - updateStats(); - return currentMemoryPressureIndex(); - } - - public long currentMemoryPressureIndex() { - return _totalInBytes.longValue() - _totalOutBytes.longValue(); - } - - /** - * For test purpose. clearing the map would lose all the ByteCounts then the PhantomReference won't work - */ - public void reset() { - _totalOutBytes.reset(); - _totalInBytes.reset(); - _byteCountConcurrentHashMap.clear(); - } - - private void updateStats() { - _statsValueConsumer.accept(_stats, currentMemoryPressureIndex()); - } -} diff --git a/internal/alpini/router/alpini-router-api/src/main/java/com/linkedin/alpini/router/api/MetricNames.java b/internal/alpini/common/alpini-common-base/src/main/java/com/linkedin/alpini/base/misc/MetricNames.java similarity index 82% rename from internal/alpini/router/alpini-router-api/src/main/java/com/linkedin/alpini/router/api/MetricNames.java rename to internal/alpini/common/alpini-common-base/src/main/java/com/linkedin/alpini/base/misc/MetricNames.java index f0afdbf909..d323243e08 100644 --- a/internal/alpini/router/alpini-router-api/src/main/java/com/linkedin/alpini/router/api/MetricNames.java +++ b/internal/alpini/common/alpini-common-base/src/main/java/com/linkedin/alpini/base/misc/MetricNames.java @@ -1,4 +1,4 @@ -package com.linkedin.alpini.router.api; +package com.linkedin.alpini.base.misc; /** * @author Antony T Curtis {@literal } diff --git a/internal/alpini/common/alpini-common-base/src/main/java/com/linkedin/alpini/base/misc/Metrics.java b/internal/alpini/common/alpini-common-base/src/main/java/com/linkedin/alpini/base/misc/Metrics.java index 8d2ec57b46..1912939b64 100644 --- a/internal/alpini/common/alpini-common-base/src/main/java/com/linkedin/alpini/base/misc/Metrics.java +++ b/internal/alpini/common/alpini-common-base/src/main/java/com/linkedin/alpini/base/misc/Metrics.java @@ -3,261 +3,27 @@ */ package com.linkedin.alpini.base.misc; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonParseException; -import com.fasterxml.jackson.databind.ObjectMapper; -import java.io.IOException; -import java.io.StringReader; -import java.io.StringWriter; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; +import java.util.EnumMap; import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.concurrent.TimeUnit; -import java.util.function.Supplier; import javax.annotation.Nonnull; -/** - * @author Jemiah Westerman<jwesterman@linkedin.com> - * @version $Revision$ - */ -@JsonIgnoreProperties(ignoreUnknown = true) public class Metrics { - @JsonIgnore - private static final String READ_CAPACITY_UNITS = "READ_CAPACITY_UNITS"; - @JsonIgnore - private static final String WRITE_CAPACITY_UNITS = "WRITE_CAPACITY_UNITS"; - - private String _host; - private String _uri; - private String _method; - private boolean _timedOut; - private TimeValue _clientSideLatency; - private @Nonnull Map _metrics = new HashMap<>(); - private @Nonnull List _subrequests = new LinkedList<>(); - - @JsonIgnore + private @Nonnull Map _metrics = new EnumMap(MetricNames.class); private transient Object _path; - public Metrics() { - } - - public Metrics(String host, String method, String uri, boolean timedOut, TimeValue clientSideLatency) { - _host = host; - _uri = uri; - _method = method; - _timedOut = timedOut; - _clientSideLatency = clientSideLatency; - } - - public String getHost() { - return _host; - } - - public void setHost(String host) { - _host = host; - } - - public String getUri() { - return _uri; - } - - public void setUri(String uri) { - _uri = uri; - } - - public String getMethod() { - return _method; - } - - public void setMethod(String method) { - _method = method; - } - - public boolean isTimedOut() { - return _timedOut; - } - - public void setTimedOut(boolean timedOut) { - _timedOut = timedOut; - } - - public TimeValue getClientSideLatency() { - return _clientSideLatency; - } - - public void setClientSideLatency(TimeValue clientSideLatency) { - _clientSideLatency = clientSideLatency; - } - - @JsonIgnore - public long getNumReadCapacityUnits() { - return Optional.ofNullable(_metrics.get(READ_CAPACITY_UNITS)).map(TimeValue::getRawValue).orElse(0L); - } - - public void setNumReadCapacityUnits(long numReadCapacityUnits) { - _metrics.computeIfAbsent(READ_CAPACITY_UNITS, k -> new TimeValue()).setRawValue(numReadCapacityUnits); - } - - @JsonIgnore - public long getNumWriteCapacityUnits() { - return Optional.ofNullable(_metrics.get(WRITE_CAPACITY_UNITS)).map(TimeValue::getRawValue).orElse(0L); - } - - public void setNumWriteCapacityUnits(long numWriteCapacityUnits) { - _metrics.computeIfAbsent(WRITE_CAPACITY_UNITS, k -> new TimeValue()).setRawValue(numWriteCapacityUnits); - } - - /** - * Computes the total Read Capacity Units, summed across the top level request and all sub-requests - * @return summed total RCUs - */ - public long computeTotalReadCapacityUnits() { - return getNumReadCapacityUnits() - + getSubrequests().stream().mapToLong(Metrics::computeTotalReadCapacityUnits).sum(); - } - - /** - * Computes the total Write Capacity Units, summed across the top level request and all sub-requests - * @return summed total WCUs - */ - public long computeTotalWriteCapacityUnits() { - return getNumWriteCapacityUnits() - + getSubrequests().stream().mapToLong(Metrics::computeTotalWriteCapacityUnits).sum(); - } - - public Map getMetrics() { + public Map getMetrics() { return _metrics; } - public void setMetrics(Map metrics) { - _metrics = Objects.requireNonNull(metrics, "metrics cannot be null"); - } - - public void setMetric(String name, TimeValue value) { + public void setMetric(MetricNames name, TimeValue value) { _metrics.put(name, value); } - public > void setMetric(E name, TimeValue value) { - setMetric(name.toString(), value); - } - - /** - * Return the total TimeValue for a metric, summed across the top level request and all sub-requests. - * @param name metric to retrieve - * @return the summed total value for the metric - */ - public TimeValue getMetricTotal(String name) { - TimeValue total = new TimeValue(0, TimeUnit.NANOSECONDS); - TimeValue entry = _metrics.get(name); - if (entry != null) { - total = total.add(entry); - } - for (Metrics m: _subrequests) { - total = total.add(m.getMetricTotal(name)); - } - return total; - } - - public > TimeValue getMetricTotal(E name) { - return getMetricTotal(name.toString()); - } - - /** - * Return the maximum TimeValue for a metric, across the top level request and all sub-requests. - * @param name metric to retrieve - * @return the single largest value for the metric - */ - public TimeValue getMetricMax(String name) { - TimeValue max = new TimeValue(0, TimeUnit.NANOSECONDS); - TimeValue v = _metrics.get(name); - if (v != null) { - if (max.compareTo(v) < 0) { - max = v; - } - } - for (Metrics m: _subrequests) { - v = m.getMetricMax(name); - if (max.compareTo(v) < 0) { - max = v; - } - } - return max; - } - - public > TimeValue getMetricMax(E name) { - return getMetricMax(name.toString()); - } - - public @Nonnull List getSubrequests() { - return _subrequests; - } - - public void setSubrequests(@Nonnull List subrequests) { - _subrequests = Objects.requireNonNull(subrequests, "subrequests"); - } - - public void addSubrequest(Metrics subrequest) { - if (subrequest != null) { - _subrequests.add(subrequest); - } - } - - private static final Supplier OBJECT_MAPPER = SimpleJsonMapper::getObjectMapper; - - public static Metrics fromJson(String json) throws IOException { - StringReader sr = new StringReader(json); - try { - return OBJECT_MAPPER.get().readValue(sr, Metrics.class); - } catch (JsonParseException ex) { - throw new IOException("JSON Parsing Failed.", ex); - } - } - - public static String toJson(Metrics metrics) { - StringWriter sw = new StringWriter(); - ObjectMapper m = OBJECT_MAPPER.get(); - JsonFactory f = m.getFactory(); - return ExceptionUtil.checkException(() -> { - try (JsonGenerator g = f.createGenerator(sw)) { - m.writeValue(g, metrics); - sw.flush(); - return sw.toString(); - } - // Unlike "fromJson" where you could actually get a garbage string, toJson should always work. - // Since we never expect to see an Exception here, we won't force the client to catch it. If - // it ever does happen then raise as RuntimeException. Tests in TestEspressoResponseMetrics - // should prevent this from ever occurring. - }, "JSON Generation Failed"); - } - - @Override - public String toString() { - try { - return Metrics.toJson(this); - } catch (RuntimeException ex) { - // Should never get RuntimeException serializing response metrics. We know this class can be - // converted to JSON and have unit tests for this. Catching the RuntimeException anyway, - // because it would be really lame to crash an application because of a log write that uses - // toString() or something like that. - assert false; - return super.toString(); - } - } - - @JsonIgnore public void setPath(Object path) { _path = path; } - @JsonIgnore - @SuppressWarnings("unchecked") public

P getPath() { return (P) _path; } diff --git a/internal/alpini/common/alpini-common-base/src/main/java/com/linkedin/alpini/base/misc/StringToNumberUtils.java b/internal/alpini/common/alpini-common-base/src/main/java/com/linkedin/alpini/base/misc/StringToNumberUtils.java deleted file mode 100644 index de850f83fe..0000000000 --- a/internal/alpini/common/alpini-common-base/src/main/java/com/linkedin/alpini/base/misc/StringToNumberUtils.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.linkedin.alpini.base.misc; - -import java.util.Optional; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - - -public enum StringToNumberUtils { - SINGLETON; - - private static final Logger LOG = LogManager.getLogger(StringToNumberUtils.class); - - public static Optional fromString(String contentLengthString) { - if (contentLengthString == null) { - return Optional.empty(); - } - try { - return Optional.of(Integer.parseUnsignedInt(contentLengthString)); - } catch (NumberFormatException ex) { - LOG.warn("Caught a NumberFormatException when parsing {}", contentLengthString); - return Optional.empty(); - } - } -} diff --git a/internal/alpini/common/alpini-common-base/src/main/java/com/linkedin/alpini/base/statistics/AbstractQuantileEstimation.java b/internal/alpini/common/alpini-common-base/src/main/java/com/linkedin/alpini/base/statistics/AbstractQuantileEstimation.java index 3dafb4bb43..5ed0122b6f 100644 --- a/internal/alpini/common/alpini-common-base/src/main/java/com/linkedin/alpini/base/statistics/AbstractQuantileEstimation.java +++ b/internal/alpini/common/alpini-common-base/src/main/java/com/linkedin/alpini/base/statistics/AbstractQuantileEstimation.java @@ -9,7 +9,6 @@ import java.util.LinkedList; import java.util.List; import java.util.Objects; -import java.util.Optional; import java.util.SortedSet; import java.util.concurrent.ConcurrentSkipListSet; import java.util.function.Consumer; @@ -238,10 +237,12 @@ public void reset() { } protected @Nonnull List queryAndReset(@Nonnull Quantiles quantiles, Consumer consumer) { - return Optional.ofNullable(_accumulator.getThenReset()).map(data -> { + Data data = _accumulator.getThenReset(); + if (data != null) { consumer.accept(data); - return data; - }).map(data -> data.query(Objects.requireNonNull(quantiles))).orElseGet(Collections::emptyList); + return data.query(Objects.requireNonNull(quantiles)); + } + return Collections.emptyList(); } protected @Nonnull List queryAndReset(@Nonnull Quantiles quantiles) { @@ -254,7 +255,11 @@ public void reset() { * @return quantile */ protected final Quantile computeQuantile(@Nonnull SAMPLE v) { - return Optional.ofNullable(_accumulator.get()).map(data -> data.computeQuantile(v)).orElse(null); + Data data = _accumulator.get(); + if (data != null) { + return data.computeQuantile(v); + } + return null; } public int getNumberOfSamples() { diff --git a/internal/alpini/common/alpini-common-base/src/test/java/com/linkedin/alpini/base/misc/TestMetrics.java b/internal/alpini/common/alpini-common-base/src/test/java/com/linkedin/alpini/base/misc/TestMetrics.java deleted file mode 100644 index 64e1dec538..0000000000 --- a/internal/alpini/common/alpini-common-base/src/test/java/com/linkedin/alpini/base/misc/TestMetrics.java +++ /dev/null @@ -1,297 +0,0 @@ -package com.linkedin.alpini.base.misc; - -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import java.io.IOException; -import java.util.Map; -import java.util.concurrent.TimeUnit; -import org.testng.Assert; -import org.testng.annotations.Test; - - -/** - * @author Antony T Curtis {@literal } - */ -public class TestMetrics { - public enum MetricNames { - STORAGE_SERVER_LATENCY, STORAGE_MYSQL_LATENCY, STORAGE_INDEX_LATENCY, ROUTER_ROUTING_TIME, ROUTER_SERVER_TIME - } - - /** - * Create an EspressoResponseMetrics with some submetrics and test that it serializes and - * deserializes as expected. - * @throws IOException if serialization raises IOException - */ - @Test(groups = "unit") - public void testSerialization() throws IOException { - // Metrics for a call to storage node 1. Metrics for storage node 1 are all in the hundreds - Metrics storage1 = new Metrics( - "http://storage1:1234", - "GET", - "/EspressoDB/EmailTest/12345", - false, - new TimeValue(400, TimeUnit.MILLISECONDS)); - storage1.setNumReadCapacityUnits(3); - storage1.setNumWriteCapacityUnits(2); - storage1.setMetric(MetricNames.STORAGE_SERVER_LATENCY, new TimeValue(300, TimeUnit.MILLISECONDS)); - storage1.setMetric(MetricNames.STORAGE_MYSQL_LATENCY, new TimeValue(200, TimeUnit.MILLISECONDS)); - storage1.setMetric(MetricNames.STORAGE_INDEX_LATENCY, new TimeValue(100, TimeUnit.MILLISECONDS)); - - // Metrics for a call to storage node 2 - Metrics storage2 = new Metrics( - "http://storage2:1234", - "GET", - "/EspressoDB/EmailTest/67890", - false, - new TimeValue(4000, TimeUnit.MILLISECONDS)); - storage2.setNumReadCapacityUnits(5); - storage2.setNumWriteCapacityUnits(0); - storage2.setMetric(MetricNames.STORAGE_SERVER_LATENCY, new TimeValue(3000, TimeUnit.MILLISECONDS)); - storage2.setMetric(MetricNames.STORAGE_MYSQL_LATENCY, new TimeValue(2000, TimeUnit.MILLISECONDS)); - storage2.setMetric(MetricNames.STORAGE_INDEX_LATENCY, new TimeValue(1000, TimeUnit.MILLISECONDS)); - - // Metrics for a call to the router, which delegates to the two storage nodes above - Metrics routerMetrics = new Metrics( - "http://router:1234/", - "GET", - "/EspressoDB/EmailTest/12345,67890", - false, - new TimeValue(40000, TimeUnit.MILLISECONDS)); - routerMetrics.setNumReadCapacityUnits(15); - routerMetrics.setNumWriteCapacityUnits(15); - routerMetrics.setMetric(MetricNames.ROUTER_SERVER_TIME, new TimeValue(20000, TimeUnit.MILLISECONDS)); - routerMetrics.setMetric(MetricNames.ROUTER_ROUTING_TIME, new TimeValue(10000, TimeUnit.MILLISECONDS)); - - // Add the storage node calls as subrequests to the router call - routerMetrics.addSubrequest(storage1); - routerMetrics.addSubrequest(storage2); - - // Serialize the metrics, then deserialize into a new instance - String serialized = Metrics.toJson(routerMetrics); - routerMetrics = Metrics.fromJson(serialized); - - // Check that the totals in the deserialized instance are as expected. - Assert.assertEquals( - routerMetrics.getMetricTotal(MetricNames.STORAGE_SERVER_LATENCY), - new TimeValue(3300, TimeUnit.MILLISECONDS)); - Assert.assertEquals( - routerMetrics.getMetricTotal(MetricNames.STORAGE_MYSQL_LATENCY), - new TimeValue(2200, TimeUnit.MILLISECONDS)); - Assert.assertEquals( - routerMetrics.getMetricTotal(MetricNames.STORAGE_INDEX_LATENCY), - new TimeValue(1100, TimeUnit.MILLISECONDS)); - Assert.assertEquals( - routerMetrics.getMetricTotal(MetricNames.ROUTER_SERVER_TIME), - new TimeValue(20000, TimeUnit.MILLISECONDS)); - Assert.assertEquals( - routerMetrics.getMetricTotal(MetricNames.ROUTER_ROUTING_TIME), - new TimeValue(10000, TimeUnit.MILLISECONDS)); - - Assert.assertEquals(routerMetrics.getNumReadCapacityUnits(), 15); - Assert.assertEquals(routerMetrics.getNumWriteCapacityUnits(), 15); - Assert.assertEquals(routerMetrics.computeTotalReadCapacityUnits(), 23); - Assert.assertEquals(routerMetrics.computeTotalWriteCapacityUnits(), 17); - - // Check that toString returns the same text as toJson - Assert.assertEquals(routerMetrics.toString(), serialized); - } - - /** - * Create an EspressoResponseMetrics with some submetrics and test that getMetricTotal returns - * expected values. - */ - @Test(groups = "unit") - public void testTotalCalculation() { - // Metrics for a call to storage node 1. Metrics for storage node 1 are all in the hundreds - Metrics storage1 = new Metrics( - "http://storage1:1234", - "GET", - "/EspressoDB/EmailTest/12345", - false, - new TimeValue(400, TimeUnit.MILLISECONDS)); - storage1.setMetric(MetricNames.STORAGE_SERVER_LATENCY, new TimeValue(300, TimeUnit.MILLISECONDS)); - storage1.setMetric(MetricNames.STORAGE_MYSQL_LATENCY, new TimeValue(200, TimeUnit.MILLISECONDS)); - storage1.setMetric(MetricNames.STORAGE_INDEX_LATENCY, new TimeValue(100, TimeUnit.MILLISECONDS)); - - // Metrics for a call to storage node 2 - Metrics storage2 = new Metrics( - "http://storage2:1234", - "GET", - "/EspressoDB/EmailTest/67890", - false, - new TimeValue(4000, TimeUnit.MILLISECONDS)); - storage2.setMetric(MetricNames.STORAGE_SERVER_LATENCY, new TimeValue(3000, TimeUnit.MILLISECONDS)); - storage2.setMetric(MetricNames.STORAGE_MYSQL_LATENCY, new TimeValue(2000, TimeUnit.MILLISECONDS)); - - // Metrics for a call to the router, which delegates to the two storage nodes above - Metrics routerMetrics = new Metrics( - "http://router:1234/", - "GET", - "/EspressoDB/EmailTest/12345,67890", - false, - new TimeValue(40000, TimeUnit.MILLISECONDS)); - routerMetrics.setMetric(MetricNames.ROUTER_SERVER_TIME, new TimeValue(20000, TimeUnit.MILLISECONDS)); - routerMetrics.setMetric(MetricNames.ROUTER_ROUTING_TIME, new TimeValue(10000, TimeUnit.MILLISECONDS)); - - // This metric does not belong at the router level, but the getMetricTotal function should count it regardless of - // where it is at. - routerMetrics.setMetric(MetricNames.STORAGE_INDEX_LATENCY, new TimeValue(30000, TimeUnit.MILLISECONDS)); - - // This metric does not belong at the router level, but the getMetricTotal function should count it regardless of - // where it is at. - storage1.setMetric(MetricNames.ROUTER_ROUTING_TIME, new TimeValue(500, TimeUnit.MILLISECONDS)); - - // Add the storage node calls as subrequests to the router call - routerMetrics.addSubrequest(storage1); - routerMetrics.addSubrequest(storage2); - - // Check that the totals are as expected. - Assert.assertEquals( - routerMetrics.getMetricTotal(MetricNames.STORAGE_SERVER_LATENCY), - new TimeValue(3300, TimeUnit.MILLISECONDS)); - Assert.assertEquals( - routerMetrics.getMetricTotal(MetricNames.STORAGE_MYSQL_LATENCY), - new TimeValue(2200, TimeUnit.MILLISECONDS)); - Assert.assertEquals( - routerMetrics.getMetricTotal(MetricNames.STORAGE_INDEX_LATENCY), - new TimeValue(30100, TimeUnit.MILLISECONDS)); - Assert.assertEquals( - routerMetrics.getMetricTotal(MetricNames.ROUTER_SERVER_TIME), - new TimeValue(20000, TimeUnit.MILLISECONDS)); - Assert.assertEquals( - routerMetrics.getMetricTotal(MetricNames.ROUTER_ROUTING_TIME), - new TimeValue(10500, TimeUnit.MILLISECONDS)); - } - - /** - * Create an EspressoResponseMetrics with some submetrics and test that getMetricMax returns - * expected values. - */ - @Test(groups = "unit") - public void testMaxCalculation() throws IOException { - // Metrics for a call to storage node 1. Metrics for storage node 1 are all in the hundreds - Metrics storage1 = new Metrics( - "http://storage1:1234", - "GET", - "/EspressoDB/EmailTest/12345", - false, - new TimeValue(400, TimeUnit.MILLISECONDS)); - storage1.setMetric(MetricNames.STORAGE_SERVER_LATENCY, new TimeValue(300, TimeUnit.MILLISECONDS)); - storage1.setMetric(MetricNames.STORAGE_MYSQL_LATENCY, new TimeValue(200, TimeUnit.MILLISECONDS)); - storage1.setMetric(MetricNames.STORAGE_INDEX_LATENCY, new TimeValue(100, TimeUnit.MILLISECONDS)); - - // Metrics for a call to storage node 2 - Metrics storage2 = new Metrics( - "http://storage2:1234", - "GET", - "/EspressoDB/EmailTest/67890", - false, - new TimeValue(4000, TimeUnit.MILLISECONDS)); - storage2.setMetric(MetricNames.STORAGE_SERVER_LATENCY, new TimeValue(3000, TimeUnit.MILLISECONDS)); - storage2.setMetric(MetricNames.STORAGE_MYSQL_LATENCY, new TimeValue(2000, TimeUnit.MILLISECONDS)); - - // Metrics for a call to the router, which delegates to the two storage nodes above - Metrics routerMetrics = new Metrics( - "http://router:1234/", - "GET", - "/EspressoDB/EmailTest/12345,67890", - false, - new TimeValue(40000, TimeUnit.MILLISECONDS)); - routerMetrics.setMetric(MetricNames.ROUTER_SERVER_TIME, new TimeValue(20000, TimeUnit.MILLISECONDS)); - routerMetrics.setMetric(MetricNames.ROUTER_ROUTING_TIME, new TimeValue(10000, TimeUnit.MILLISECONDS)); - - // This metric does not belong at the router level, but the getMetricTotal function should count it regardless of - // where it is at. - routerMetrics.setMetric(MetricNames.STORAGE_INDEX_LATENCY, new TimeValue(30000, TimeUnit.MILLISECONDS)); - - // This metric does not belong at the router level, but the getMetricTotal function should count it regardless of - // where it is at. - storage1.setMetric(MetricNames.ROUTER_ROUTING_TIME, new TimeValue(500000, TimeUnit.MILLISECONDS)); - - // Add the storage node calls as subrequests to the router call - routerMetrics.addSubrequest(storage1); - routerMetrics.addSubrequest(storage2); - - // Check that the totals are as expected. - Assert.assertEquals( - routerMetrics.getMetricMax(MetricNames.STORAGE_SERVER_LATENCY), - new TimeValue(3000, TimeUnit.MILLISECONDS)); - Assert.assertEquals( - routerMetrics.getMetricMax(MetricNames.STORAGE_MYSQL_LATENCY), - new TimeValue(2000, TimeUnit.MILLISECONDS)); - Assert.assertEquals( - routerMetrics.getMetricMax(MetricNames.STORAGE_INDEX_LATENCY), - new TimeValue(30000, TimeUnit.MILLISECONDS)); - Assert.assertEquals( - routerMetrics.getMetricMax(MetricNames.ROUTER_SERVER_TIME), - new TimeValue(20000, TimeUnit.MILLISECONDS)); - Assert.assertEquals( - routerMetrics.getMetricMax(MetricNames.ROUTER_ROUTING_TIME), - new TimeValue(500000, TimeUnit.MILLISECONDS)); - } - - @Test(groups = "unit") - public void testSerialization2() throws IOException { - // Start with a know serialized value. Deserialize that to an EspressoResponseMetrics, then reserialize. - String expected = - "{\"method\":\"GET\",\"host\":null,\"uri\":\"/EspressoDB/EmailTest/298/1\",\"timedOut\":false,\"clientSideLatency\":null,\"metrics\":{\"ROUTER_SERVER_TIME\":{\"rawValue\":5001000,\"unit\":\"NANOSECONDS\"},\"ROUTER_RESPONSE_WAIT_TIME\":{\"rawValue\":4817000,\"unit\":\"NANOSECONDS\"},\"ROUTER_ROUTING_TIME\":{\"rawValue\":154000,\"unit\":\"NANOSECONDS\"}},\"subrequests\":[{\"method\":\"GET\",\"host\":\"jwesterm-md.linkedin.biz:12918\",\"uri\":\"/EspressoDB/EmailTest/298/1\",\"timedOut\":false,\"clientSideLatency\":{\"rawValue\":4817000,\"unit\":\"NANOSECONDS\"},\"metrics\":{\"STORAGE_INDEX_LATENCY\":null,\"STORAGE_PROCESSOR_LATENCY\":{\"rawValue\":1830000,\"unit\":\"NANOSECONDS\"},\"STORAGE_CONTAINER_LATENCY\":{\"rawValue\":407000,\"unit\":\"NANOSECONDS\"},\"STORAGE_SERVER_LATENCY\":{\"rawValue\":2241000,\"unit\":\"NANOSECONDS\"},\"STORAGE_INDEX_OPEN_LATENCY\":null,\"STORAGE_MYSQL_POOL_LATENCY\":{\"rawValue\":300000,\"unit\":\"NANOSECONDS\"},\"STORAGE_MYSQL_LATENCY\":{\"rawValue\":1253000,\"unit\":\"NANOSECONDS\"}},\"subrequests\":[]}]}"; - Metrics metrics = Metrics.fromJson(expected); - String actual = Metrics.toJson(metrics); - - // We cannot compare the "actual" and "expected" strings directly, because serialization may - // reorder elements (for example, it may put "host" after "uri"). Instead, we deserialized both - // the expected and actual to a raw Map, and then compare the contents of the maps. - ObjectMapper objectMapper = SimpleJsonMapper.getObjectMapper(); - Map expectedMap = objectMapper.readValue(expected, new TypeReference>() { - }); - Map actualMap = objectMapper.readValue(actual, new TypeReference>() { - }); - - // Compare the maps - Assert.assertEquals(actualMap, expectedMap); - - // Just to make sure nothing went terribly wrong, make sure the expectedMap has the right - // number of elements. - Assert.assertEquals(actualMap.size(), 7); - } - - /** - * Tests the workflow that will be typical in a client-server interaction. Specifically, the - * server returns a serialized EspressoResponseMetrics with metrics but no host, URI, or client - * side latency. The client must deserialize the metrics, then update these fields. - */ - @Test(groups = "unit") - public void testWorkflow() throws IOException { - String serialized = "{\n" + " \"metrics\": {\n" + " \"ROUTER_SERVER_TIME\": {\n" - + " \"rawValue\": 20000,\n" + " \"unit\": \"MILLISECONDS\"\n" + " },\n" - + " \"ROUTER_ROUTING_TIME\": {\n" + " \"rawValue\": 10000,\n" - + " \"unit\": \"MILLISECONDS\"\n" + " }\n" + " }\n" + "}"; - - Metrics routerMetrics = Metrics.fromJson(serialized); - routerMetrics.setHost("http://router:1234/"); - routerMetrics.setUri("/EspressoDB/EmailTest/12345,67890"); - routerMetrics.setMethod("GET"); - routerMetrics.setTimedOut(false); - routerMetrics.setClientSideLatency(new TimeValue(40000, TimeUnit.MILLISECONDS)); - - String expected = - "{\"method\":\"GET\",\"host\":\"http://router:1234/\",\"uri\":\"/EspressoDB/EmailTest/12345,67890\",\"timedOut\":false,\"clientSideLatency\":{\"rawValue\":40000,\"unit\":\"MILLISECONDS\"},\"metrics\":{\"ROUTER_SERVER_TIME\":{\"rawValue\":20000,\"unit\":\"MILLISECONDS\"},\"ROUTER_ROUTING_TIME\":{\"rawValue\":10000,\"unit\":\"MILLISECONDS\"}},\"subrequests\":[]}"; - String actual = Metrics.toJson(routerMetrics); - - // We cannot compare the "actual" and "expected" strings directly, because serialization may - // reorder elements (for example, it may put "host" after "uri"). Instead, we deserialized both - // the expected and actual to a raw Map, and then compare the contents of the maps. - ObjectMapper objectMapper = SimpleJsonMapper.getObjectMapper(); - Map expectedMap = objectMapper.readValue(expected, new TypeReference>() { - }); - Map actualMap = objectMapper.readValue(actual, new TypeReference>() { - }); - - // Compare the maps - Assert.assertEquals(actualMap, expectedMap); - - // Just to make sure nothing went terribly wrong, make sure the expectedMap has the right - // number of elements. - Assert.assertEquals(actualMap.size(), 7); - } -} diff --git a/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/base/cache/ByteBufHashMap.java b/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/base/cache/ByteBufHashMap.java deleted file mode 100644 index 0298247327..0000000000 --- a/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/base/cache/ByteBufHashMap.java +++ /dev/null @@ -1,729 +0,0 @@ -package com.linkedin.alpini.base.cache; - -import com.linkedin.alpini.base.misc.Time; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufInputStream; -import io.netty.buffer.ByteBufOutputStream; -import io.netty.buffer.PooledByteBufAllocator; -import io.netty.buffer.Unpooled; -import java.io.IOException; -import java.util.AbstractMap; -import java.util.AbstractSet; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; -import java.util.function.IntFunction; -import java.util.function.Supplier; -import javax.annotation.Nonnull; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - - -/** - * @author Antony T Curtis {@literal } - */ -public class ByteBufHashMap extends AbstractMap implements SerializedMap { - private static final Logger LOG = LogManager.getLogger(ByteBufHashMap.class); - - /* Default block size */ - public static final int DEFAULT_BLOCK_SIZE = 65536; - - /** - * Serialization/Deserialization interface. A simple implemmentation is retured by - * {@link #javaSerialization()} but users of this class may provide alternate implementations. - * @param class to serialize/deserialize - */ - public interface SerDes { - /** - * Perform deserialization - * @param inputStream {@link java.io.InputStream} of bytes - * @return Object deserialization of the presented bytes - */ - V deserialize(@Nonnull ByteBufInputStream inputStream); - - /** - * Perform serialization - * @param outputStream Target {@link java.io.OutputStream} - * @param value Object to deserialize - * @return {@code true} if successfully deserialized - */ - boolean serialize(@Nonnull ByteBufOutputStream outputStream, @Nonnull V value); - } - - /** - * Simple serialization/deserialization using Java's object serialization implementation. - * @param class to serialize/deserialize - * @return instance of {@linkplain java.io.Serializable} - */ - @SuppressWarnings("unchecked") - public static SerDes javaSerialization() { - return JavaSerialization.INSTANCE; - } - - /** Default allocator, currently {@link PooledByteBufAllocator#buffer(int)} */ - public static final IntFunction DEFAULT_ALLOCATOR = PooledByteBufAllocator.DEFAULT::buffer; - - private final SerDes _serDes; - - /** Map which maps from key to a position within a block */ - private final ConcurrentMap _keyMap; - - /** Allocator for {@link ByteBuf}s */ - private final IntFunction _allocator; - - /** List of {@link ByteBuf}s which are to be purged */ - private final LinkedList _oldBuffers; - - /** Semaphore to ensure that there is only one cleanup in progress */ - private final Semaphore _cleanupSemaphore; - - /** List of blocks used in this map, in order of construction */ - private final LinkedList _blockQueue; - - /** Current block to which new entries may be appended */ - private Block _currentBlock; - - /** Size for new blocks */ - private int _blockSize; - - /** Max age for a block before it is expired, in nanos */ - private long _maxBlockAge; - - /** Max duration that a block may be appended, in nanos */ - private long _incubationAge = TimeUnit.SECONDS.toNanos(1); - - /** Maximum memory to be used for blocks before old blocks are expired, in bytes */ - private long _maxAllocatedMemory = Long.MAX_VALUE; - - /** Number of bytes currently in use for blocks in {@linkplain #_blockQueue} */ - private long _allocatedBytes; - - /** {@code true} if there are blocks awaiting cleanup */ - private boolean _cleanupRequired; - - /** transient entrySet, returned by {@link #entrySet()} */ - private transient Set> _entrySet; - - /** - * Equivalent to calling {@link #ByteBufHashMap(SerDes, int)} with {@link #DEFAULT_ALLOCATOR} - * @param serDes Serialization/Deserialization interface - */ - public ByteBufHashMap(@Nonnull SerDes serDes) { - this(serDes, DEFAULT_ALLOCATOR); - } - - /** - * Equivalent to calling {@link #ByteBufHashMap(SerDes, int, IntFunction)} with {@link #DEFAULT_BLOCK_SIZE} - * @param serDes Serialization/Deserialization interface - * @param allocator block allocator - */ - public ByteBufHashMap(@Nonnull SerDes serDes, @Nonnull IntFunction allocator) { - this(serDes, DEFAULT_BLOCK_SIZE, allocator, ConcurrentHashMap::new); - } - - /** - * Equivalent to calling {@link #ByteBufHashMap(SerDes, int, IntFunction)} with {#link #DEFAULT_ALLOCATOR} - * @param serDes Serialization/Deserialization interface - * @param blockSize size for data blocks - */ - public ByteBufHashMap(@Nonnull SerDes serDes, int blockSize) { - this(serDes, blockSize, DEFAULT_ALLOCATOR); - } - - /** - * Construct an instance of a {@link ByteBuf} backed hash map. - * @param serDes Serialization/Deserialization interface - * @param blockSize size for data blocks - * @param allocator block allocator - */ - public ByteBufHashMap(@Nonnull SerDes serDes, int blockSize, @Nonnull IntFunction allocator) { - this(serDes, blockSize, allocator, ConcurrentHashMap::new); - } - - /** - * Construct an instance of a {@link ByteBuf} backed hash map. - * @param serDes Serialization/Deserialization interface - * @param blockSize size for data blocks - * @param allocator block allocator - * @param initialCapacity see {@link ConcurrentHashMap#ConcurrentHashMap(int, float)} - * @param loadFactor see {@link ConcurrentHashMap#ConcurrentHashMap(int, float)} - */ - public ByteBufHashMap( - @Nonnull SerDes serDes, - int blockSize, - @Nonnull IntFunction allocator, - int initialCapacity, - float loadFactor) { - this(serDes, blockSize, allocator, () -> new ConcurrentHashMap<>(initialCapacity, loadFactor)); - } - - private ByteBufHashMap( - @Nonnull SerDes serDes, - int blockSize, - @Nonnull IntFunction allocator, - @Nonnull Supplier> suppier) { - _serDes = Objects.requireNonNull(serDes, "serDes"); - setBlockSize(blockSize); - _allocator = Objects.requireNonNull(allocator, "allocator"); - _keyMap = suppier.get(); - _cleanupSemaphore = new Semaphore(1); - _blockQueue = new LinkedList<>(); - _oldBuffers = new LinkedList<>(); - } - - /** - * {@inheritDoc} - */ - @Override - public @Nonnull ByteBufHashMap setBlockSize(int blockSize) { - if (blockSize < 4096) { - throw new IllegalArgumentException("Minimum block size of 4096 bytes"); - } - - // round up to a power of 2 - blockSize--; - blockSize |= blockSize >>> 1; - blockSize |= blockSize >>> 2; - blockSize |= blockSize >>> 4; - blockSize |= blockSize >>> 8; - blockSize |= blockSize >>> 16; - blockSize++; - - _blockSize = blockSize; - return this; - } - - /** - * {@inheritDoc} - */ - @Override - public @Nonnull ByteBufHashMap setMaxBlockAge(long time, @Nonnull TimeUnit unit) { - if (time <= 0 || time == Long.MAX_VALUE) { - _maxBlockAge = 0; - } else { - _maxBlockAge = unit.toNanos(time); - } - return this; - } - - /** - * {@inheritDoc} - */ - @Override - public @Nonnull ByteBufHashMap setIncubationAge(long time, @Nonnull TimeUnit unit) { - if (time <= 0 || time == Long.MAX_VALUE) { - _incubationAge = 0; - } else { - _incubationAge = unit.toNanos(time); - } - return this; - } - - /** - * {@inheritDoc} - */ - @Override - public @Nonnull ByteBufHashMap setMaxAllocatedMemory(long memory) { - if (memory < 4096) { - throw new IllegalArgumentException("Minumum max memory size of 4096 bytes"); - } - _maxAllocatedMemory = memory; - return this; - } - - /** - * {@inheritDoc} - */ - @Override - public long getMaxBlockAge(@Nonnull TimeUnit unit) { - if (_maxBlockAge <= 0) { - return Long.MAX_VALUE; - } else { - return unit.convert(_maxBlockAge, TimeUnit.NANOSECONDS); - } - } - - /** - * {@inheritDoc} - */ - @Override - public long getIncubationAge(@Nonnull TimeUnit unit) { - if (_incubationAge <= 0) { - return Long.MAX_VALUE; - } else { - return unit.convert(_incubationAge, TimeUnit.NANOSECONDS); - } - } - - /** - * {@inheritDoc} - */ - @Override - public int getBlockSize() { - return _blockSize; - } - - /** - * {@inheritDoc} - */ - @Override - public long getMaxAllocatedMemory() { - return _maxAllocatedMemory; - } - - /** - * {@inheritDoc} - */ - @Override - public long getAllocatedBytes() { - return _allocatedBytes; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean isEmpty() { - return entrySet().isEmpty(); - } - - @SuppressWarnings("unchecked") - protected final K castKey(Object key) { - return (K) key; - } - - /** - * {@inheritDoc} - */ - @Override - public boolean containsKey(Object key) { - Ref ref = _keyMap.get(castKey(key)); - return ref != null && ref._block._buffer != Unpooled.EMPTY_BUFFER; - } - - private V getValueFromRef(Ref ref) { - if (ref != null) { - ByteBuf buffer = ref._block._buffer.retain(); - if (buffer != Unpooled.EMPTY_BUFFER) { - try { - return ref.readValue(this, buffer); - } finally { - buffer.release(); - } - } - } - return null; - } - - /** - * {@inheritDoc} - */ - @Override - public V get(Object key) { - return getValueFromRef(_keyMap.get(castKey(key))); - } - - private synchronized Ref allocate(ByteBufOutputStream bbos) throws IOException { - long now = Time.nanoTime(); - int size = bbos.writtenBytes(); - - if (size >= _blockSize) { - checkBlocksToExpire(now); - Block block = newBlock(size); - _blockQueue.add(block); - return block.allocate(bbos); - } - - Block block = _currentBlock; - Ref ref; - if (block == null || (_incubationAge > 0 && block._creationTime + _incubationAge < now) - || (ref = block.allocate(bbos)) == null) { // SUPPRESS CHECKSTYLE InnerAssignment - if (block != null) { - checkBlocksToExpire(now); - _blockQueue.add(block); - } - _currentBlock = newBlock(size); - ref = _currentBlock.allocate(bbos); - } - return ref; - } - - private Block newBlock(int size) { - Block block = new Block(_allocator.apply(Math.max(_blockSize, size))); - _allocatedBytes += block._buffer.capacity(); - return block; - } - - private void checkBlocksToExpire(long now) { - long oldest = _maxBlockAge > 0 ? now - _maxBlockAge : Long.MIN_VALUE; - Block oldBlock; - while ((oldBlock = _blockQueue.peekFirst()) != null // SUPPRESS CHECKSTYLE InnerAssignment - && (oldBlock._creationTime < oldest || _allocatedBytes > _maxAllocatedMemory)) { - if (_blockQueue.remove(oldBlock)) { - ByteBuf buffer = oldBlock._buffer; - oldBlock._buffer = Unpooled.EMPTY_BUFFER; - if (buffer != Unpooled.EMPTY_BUFFER) { - _allocatedBytes -= buffer.capacity(); - _cleanupRequired = true; - _oldBuffers.add(buffer); - } - } - } - if (_cleanupRequired && _cleanupSemaphore.tryAcquire()) { - _cleanupRequired = false; - List clear = new ArrayList<>(_oldBuffers.size()); - clear.addAll(_oldBuffers); - _oldBuffers.clear(); - CompletableFuture - .runAsync( - () -> _keyMap.entrySet().removeIf(entry -> entry.getValue()._block._buffer == Unpooled.EMPTY_BUFFER)) - .whenComplete((aVoid, ex) -> { - _cleanupSemaphore.release(); - clear.forEach(ByteBuf::release); - }); - } - } - - /** - * {@inheritDoc} - */ - @Override - public V put(K key, V value) { - if (value == null) { - return remove(key); - } - - Ref ref = _keyMap.get(Objects.requireNonNull(key)); - Ref newRef; - if (ref != null) { - Ref[] newRefA = { null }; - ByteBuf buffer; - while ((buffer = ref._block._buffer.retain()) != Unpooled.EMPTY_BUFFER) { - try { - Optional> result = ref.put(this, key, value, buffer, newRefA); - if (result.isPresent()) { - return result.get().orElse(null); - } - } finally { - buffer.release(); - } - ref = _keyMap.get(key); - if (ref == null) { - break; - } - } - newRef = newRefA[0]; - } else { - newRef = null; - } - - if (newRef == null) { - ByteBuf buf = PooledByteBufAllocator.DEFAULT.heapBuffer(); - - try (ByteBufOutputStream bbos = new ByteBufOutputStream(buf)) { - _serDes.serialize(bbos, value); - newRef = allocate(bbos); - } catch (IOException e) { - LOG.warn("Failed to serialize for key={}", key, e); - throw new IllegalStateException(e); - } finally { - buf.release(); - } - } - - return getValueFromRef(_keyMap.put(key, newRef)); - } - - /** - * {@inheritDoc} - */ - @Override - public V remove(Object key) { - return getValueFromRef(_keyMap.remove(castKey(key))); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean removeEntry(K key) { - Ref ref = _keyMap.remove(key); - return ref != null && ref._block._buffer != Unpooled.EMPTY_BUFFER; - } - - /** - * {@inheritDoc} - */ - @Override - @Nonnull - public Set keySet() { - return _keyMap.keySet(); - } - - /** - * {@inheritDoc} - */ - @Override - @Nonnull - public Set> entrySet() { - if (_entrySet == null) { - _entrySet = new AbstractSet>() { - @Override - @Nonnull - public Iterator> iterator() { - Iterator> it = _keyMap.entrySet().iterator(); - return new Iterator>() { - Entry _nextEntry; - Entry _prevEntry; - @Nonnull - ByteBuf _nextBuffer = Unpooled.EMPTY_BUFFER; - - @Override - protected void finalize() throws Throwable { - _nextBuffer.release(); - super.finalize(); - } - - @Override - public boolean hasNext() { - while (_nextEntry == null) { - if (!it.hasNext()) { - return false; - } - _nextEntry = it.next(); - _nextBuffer.release(); - _nextBuffer = _nextEntry.getValue()._block._buffer.retain(); - if (_nextBuffer == Unpooled.EMPTY_BUFFER) { - _nextEntry = null; - } - } - return true; - } - - @Override - public Entry next() { - if (!hasNext()) { - throw new IndexOutOfBoundsException(); - } - Entry entry = _nextEntry; - return new Entry() { - private V value; - private ByteBuf buffer = _nextBuffer.retain(); - { - _prevEntry = entry; - _nextEntry = null; - _nextBuffer = Unpooled.EMPTY_BUFFER; - } - - @Override - protected void finalize() throws Throwable { - buffer.release(); - super.finalize(); - } - - @Override - public K getKey() { - return entry.getKey(); - } - - @Override - public V getValue() { - V value = this.value; - if (value == null) { - value = entry.getValue().getAndSet(ByteBufHashMap.this, buffer, v -> this.value = v); - buffer.release(); - buffer = Unpooled.EMPTY_BUFFER; - } - return value; - } - - @Override - public V setValue(V value) { - if (value == null) { - entry.getValue()._length = 0; - this.value = null; - return ByteBufHashMap.this.remove(entry.getKey()); - } - this.value = value; - return ByteBufHashMap.this.put(entry.getKey(), value); - } - - @Override - public boolean equals(Object other) { - return other == this; - } - - @Override - public int hashCode() { - int hashCode = getKey().hashCode(); - if (value != null) { - return hashCode * 31 + value.hashCode(); - } else { - return hashCode * 31 + entry.getValue().hashCode(); - } - } - }; - } - - @Override - public void remove() { - it.remove(); - } - }; - } - - @Override - public boolean isEmpty() { - return _keyMap.isEmpty(); - } - - @Override - public int size() { - return _keyMap.size(); - } - - @Override - public void clear() { - if (_currentBlock != null) { - _blockQueue.add(_currentBlock); - _blockQueue.removeIf(block -> _oldBuffers.add(block._buffer)); - _blockQueue.clear(); - _keyMap.clear(); - _cleanupRequired = true; - checkBlocksToExpire(Time.nanoTime()); - _currentBlock = null; - } - } - }; - } - return _entrySet; - } - - private static class Block { - private final long _creationTime = Time.nanoTime(); - private @Nonnull ByteBuf _buffer; - - private Block(ByteBuf buffer) { - _buffer = buffer; - } - - private synchronized Ref allocate(ByteBufOutputStream bbos) throws IOException { - int size = bbos.writtenBytes(); - if (_buffer == Unpooled.EMPTY_BUFFER || _buffer.writerIndex() + size > _buffer.capacity()) { - return null; - } - - int pos = _buffer.writerIndex(); - _buffer.writeBytes(bbos.buffer()); - return new Ref(this, pos, bbos.writtenBytes()); - } - } - - private static class Ref { - private final Block _block; - private final int _offset; - private final int _allocated; - private int _length; - - private Ref(Block block, int offset, int allocated) { - _block = block; - _offset = offset; - _allocated = allocated; - _length = allocated; - } - - private synchronized V readValue(ByteBufHashMap map, ByteBuf buffer) { - return _length > 0 ? map._serDes.deserialize(new ByteBufInputStream(buffer.slice(_offset, _length))) : null; - } - - public synchronized V getAndSet(ByteBufHashMap map, ByteBuf buffer, Consumer consumer) { - V value = readValue(map, buffer); - consumer.accept(value); - return value; - } - - public synchronized Optional> put( - ByteBufHashMap map, - K key, - V value, - ByteBuf buffer, - Ref[] newRef) { - V oldValue; - if (_length > 0) { - oldValue = readValue(map, buffer); - - try (ByteBufOutputStream bbos = new ByteBufOutputStream(_block._buffer.slice(_offset, _allocated))) { - if (map._serDes.serialize(bbos, value)) { - _length = bbos.writtenBytes(); - return Optional.of(Optional.ofNullable(oldValue)); - } - } catch (Exception e) { - LOG.debug("Failed to serialize into available space", e); - } - _length = 0; - } else { - oldValue = null; - } - - if (newRef[0] == null) { - ByteBuf buf = map._allocator.apply(map._blockSize); - try (ByteBufOutputStream bbos = new ByteBufOutputStream(buf)) { - if (map._serDes.serialize(bbos, value)) { - newRef[0] = _block.allocate(bbos); - if (newRef[0] == null) { - newRef[0] = map.allocate(bbos); - } - } else { - LOG.warn("Failed to serialize into available space"); - } - } catch (Exception e) { - LOG.warn("Failed to serialize into available space", e); - return Optional.empty(); - } finally { - buf.release(); - } - } - - if (newRef[0] != null && map._keyMap.replace(key, this, newRef[0])) { - return Optional.of(Optional.ofNullable(oldValue)); - } - return Optional.empty(); - } - } - - private static class JavaSerialization implements SerDes { - private static final SerDes INSTANCE = new JavaSerialization<>(); - - @Override - @SuppressWarnings("unchecked") - public V deserialize(@Nonnull ByteBufInputStream byteBuf) { - try (java.io.ObjectInputStream in = new java.io.ObjectInputStream(byteBuf)) { - return (V) in.readObject(); - } catch (Exception e) { - LOG.warn("Failed to deserialize", e); - } - return null; - } - - @Override - public boolean serialize(@Nonnull ByteBufOutputStream outputStream, @Nonnull V value) { - try (java.io.ObjectOutputStream out = new java.io.ObjectOutputStream(outputStream)) { - out.writeObject(value); - return true; - } catch (Exception e) { - LOG.debug("Failed to serialize", e); - } - return false; - } - } -} diff --git a/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/base/cache/PhantomHashCache.java b/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/base/cache/PhantomHashCache.java deleted file mode 100644 index be87cfb6ec..0000000000 --- a/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/base/cache/PhantomHashCache.java +++ /dev/null @@ -1,403 +0,0 @@ -package com.linkedin.alpini.base.cache; - -import io.netty.util.ReferenceCountUtil; -import java.lang.ref.PhantomReference; -import java.lang.ref.Reference; -import java.lang.ref.ReferenceQueue; -import java.util.LinkedList; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; -import java.util.function.Function; -import java.util.function.Supplier; -import java.util.stream.Collectors; -import javax.annotation.Nonnull; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - - -/** - * A cache backed which uses PhantomReferences to cache instances of values to avoid multiple instances of - * the same cached object. This cache should not prevent a Value object from being garbage collected. - * - * The purpose of this map is to ensure that there is little penalty in storing objects for a short period of time. - * - * Objects stored within the map are assumed to be immutable. - * - * The {@code clone} function provided to the constructor should return a lightweight clone of the supplied object - * which references the same objects within it. - * - * As long as the keys are unique, it is possible to use multiple backing stores with only one - * {@linkplain PhantomHashCache} providing a cache. - * - * @author Antony T Curtis {@literal } - */ -public final class PhantomHashCache { - private static final Logger LOG = LogManager.getLogger(PhantomHashCache.class); - private static final ReferenceQueue REFERENCE_QUEUE = new ReferenceQueue<>(); - public static final int DEFAULT_INITIAL_CAPACITY = 512 * 1024; - - /** Lightweight value clone function */ - private final Function _clone; - - /** Map to phantoms */ - final ConcurrentMap _phantomCache; - - private final Lock _purgeLock = new ReentrantLock(false); - private long _nextPurgeTime; - long _purgedEntriesCount; - - /** - * Creates a new, empty map with the default initial table size (512k). - * - * @param clone lightweight clone function - */ - public PhantomHashCache(@Nonnull Function clone) { - this(clone, DEFAULT_INITIAL_CAPACITY); - } - - /** - * Creates a new, empty map with an initial table size - * accommodating the specified number of elements without the need - * to dynamically resize. - * - * @param clone lightweight clone function - * @param initialCapacity the initial capacity. The implementation - * performs internal sizing to accommodate this many elements, - * given the specified load factor. - * @throws IllegalArgumentException if the initial capacity of - * elements is negative - */ - public PhantomHashCache(@Nonnull Function clone, int initialCapacity) { - this(clone, () -> new ConcurrentHashMap<>(initialCapacity)); - } - - /** - * Creates a new, empty phantom map with an initial table size based on - * the given number of elements ({@code initialCapacity}) and - * initial table density ({@code loadFactor}). - * - * @param clone lightweight clone function - * @param initialCapacity the initial capacity. The implementation - * performs internal sizing to accommodate this many elements, - * given the specified load factor. - * @param loadFactor the load factor (table density) for - * establishing the initial table size - * @throws IllegalArgumentException if the initial capacity of - * elements is negative or the load factor is nonpositive - */ - public PhantomHashCache(@Nonnull Function clone, int initialCapacity, float loadFactor) { - this(clone, initialCapacity, loadFactor, 1); - } - - /** - * Creates a new, empty phantom map with an initial table size based on - * the given number of elements ({@code initialCapacity}), table - * density ({@code loadFactor}), and number of concurrently - * updating threads ({@code concurrencyLevel}). - * - * @param clone lightweight clone function - * @param initialCapacity the initial capacity. The implementation - * performs internal sizing to accommodate this many elements, - * given the specified load factor. - * @param loadFactor the load factor (table density) for - * establishing the initial table size - * @param concurrencyLevel the estimated number of concurrently - * updating threads. The implementation may use this value as - * a sizing hint. - * @throws IllegalArgumentException if the initial capacity is - * negative or the load factor or concurrencyLevel are - * nonpositive - */ - public PhantomHashCache(@Nonnull Function clone, int initialCapacity, float loadFactor, int concurrencyLevel) { - this(clone, () -> new ConcurrentHashMap<>(initialCapacity, loadFactor, concurrencyLevel)); - } - - private PhantomHashCache(@Nonnull Function clone, @Nonnull Supplier> mapConstructor) { - _clone = Objects.requireNonNull(clone); - _phantomCache = mapConstructor.get(); - } - - /** - * Returns the number of key-value mappings in this map. - * Performance of this function is dependent upon the implementation used by - * {@link ConcurrentHashMap#size()}. - * - * @return the number of key-value mappings in this map - */ - public int size() { - return _phantomCache.size(); - } - - /** - * Remove all entries from the map. - */ - public void clear() { - _phantomCache.clear(); - } - - public void purge(@Nonnull Map store) { - if (_purgeLock.tryLock()) { - long purgedCount; - try { - long now = System.currentTimeMillis(); - if (now < _nextPurgeTime) { - return; - } - _nextPurgeTime = now + 5000L; - purgedCount = _phantomCache.values() - .stream() - .filter(Entry::hasNoPhantoms) - .filter(entry -> !store.containsKey(entry.key())) - .map(Entry::key) - .collect(Collectors.toList()) - .stream() - .map(_phantomCache::remove) - .filter(Objects::nonNull) - .count(); - if (purgedCount == 0) { - return; - } - _purgedEntriesCount += purgedCount; - } finally { - _purgeLock.unlock(); - } - LOG.debug("Purged: {}, Total Purged: {}", purgedCount, _purgedEntriesCount); - } - } - - /** - * Associates the specified value with the specified key in this map - * If the map previously contained a mapping for the key, the new value is ignored. - * - * If the map did not previously contain a mapping for the key, the new value is stored within the backing store - * and a {@linkplain PhantomReference} is made to it. - * - * @param key key with which the specified value is to be associated - * @param store backing store which the supplied key/value should be persisted within. - * @param value value to be associated with the specified key - */ - public void put(@Nonnull K key, @Nonnull Map store, @Nonnull V value) { - Objects.requireNonNull(store); - Objects.requireNonNull(value); - - Entry ent = new Entry(Objects.requireNonNull(key)); - Entry current = _phantomCache.putIfAbsent(key, ent); - if (current == null) { - store.put(key, ent.setValue(value, store)); - } else { - current.setValue(value, store); - } - } - - /** - * Removes the mapping for a key from this map if it is present. - * - * @param key key whose mapping is to be removed from the map - * @param store backing store from which the mapping should be removed - * @return {@code true} if this map contained a mapping for the key - */ - public boolean removeEntry(@Nonnull K key, Map store) { - try { - Entry ent = _phantomCache.get(Objects.requireNonNull(key)); - boolean removed = false; - if (ent != null && ent.purge()) { - return true; - } else if (ent != null) { - removed = _phantomCache.remove(key, ent); - } - return (store != null && store.keySet().remove(key)) || removed; - } finally { - derefLoop(); - } - } - - /** - * Retrieve a lightweight clone of the object from the map if it has not yet been garbage collected. - * - * @param key the key whose associated value is to be returned - * @param store backing store which should be queried if the cache does not have a mapping for the key - * @return a lightweight clone of the value to which the specified key is mapped, or - * {@code null} if this map contains no mapping for the key - */ - public V get(@Nonnull K key, @Nonnull Map store) throws InterruptedException { - Objects.requireNonNull(store); - - // We construct a new Entry because if there is a miss in the map of Phantoms, - // this new Entry becomes the placeholder for retrieving the entry from the store. - // If the store does not have an entry by the key, we will remove the entry. - // The getCurrent method is synchronized so that we do not end up deserializing the - // same object from the store on multiple threads. - - V value = new Entry(Objects.requireNonNull(key)).getCurrent(store); - if (value != null) { - return value; - } - - // Cache and store miss. May as well perform necessary cleanup. - derefLoop(); - return null; - } - - private interface PhantomEntry { - void deref(boolean purge); - } - - private @Nonnull V cloneValue(@Nonnull V value) { - V clone = _clone.apply(value); - assert clone != value; - return clone; - } - - private final class Entry { - private final K _key; - private boolean _deleted; - private final LinkedList _phantoms = new LinkedList<>(); - - private Entry(@Nonnull K key) { - _key = key; - } - - private K key() { - return _key; - } - - private synchronized boolean hasNoPhantoms() { - return _phantoms.isEmpty(); - } - - private synchronized boolean deref(Phantom phantom) { - boolean result = _phantoms.remove(phantom); - if (result && hasNoPhantoms()) { - if ((_deleted || !phantom._store.containsKey(key())) && _phantomCache.remove(_key, this)) { - phantom._store.keySet().remove(_key); - } - } - return result; - } - - private synchronized V setValue(V value, @Nonnull Map store) { - _phantoms.clear(); - if (value != null) { - _deleted = false; - V backing = value; - value = cloneValue(backing); - _phantoms.add(new Phantom(value, backing, store, REFERENCE_QUEUE)); - } - return value; - } - - private synchronized V fetch(@Nonnull Map store) throws InterruptedException { - // we should not remove phantom just because we are reading from the store. - Phantom current = _phantoms.peekLast(); - if (current != null) { - if (_deleted) { - return current._value; - } - V value = cloneValue(current._value); - _phantoms.add(new Phantom(value, current._value, current._store, REFERENCE_QUEUE)); - _phantoms.removeLastOccurrence(current); - return value; - } - - if (!_deleted) { - V backing = store.get(key()); - if (backing != null) { - V value = cloneValue(backing); - _phantoms.add(new Phantom(value, backing, store, REFERENCE_QUEUE)); - return value; - } - } - - // If we get here, the object has already been removed from the backing store. - _phantomCache.remove(key(), this); - return null; - } - - private synchronized boolean purge() { - if (!_phantoms.isEmpty()) { - _deleted = true; - return true; - } - return false; - } - - public synchronized V getCurrent(Map store) throws InterruptedException { - final Entry current = _phantomCache.putIfAbsent(_key, this); - if (current != null) { - V value = current.fetch(store); // Will retrieve it from backing store if required. - if (value != null) { - return value; - } - _phantomCache.remove(_key, current); - } else { - V value = store.get(_key); - if (value != null) { - return setValue(value, store); - } - _phantomCache.remove(_key, this); - - // record that this was a cache miss - _deleted = true; - } - return null; - } - - private final class Phantom extends PhantomReference implements PhantomEntry { - private final Map _store; - private final V _value; - - public Phantom(@Nonnull V referent, V value, @Nonnull Map store, @Nonnull ReferenceQueue q) { - super(referent, q); - _store = store; - _value = value; - } - - public void deref(boolean purge) { - if (Entry.this.deref(this)) { - ReferenceCountUtil.release(_value); - } - if (purge) { - PhantomHashCache.this.purge(_store); - } - } - } - } - - private static void derefLoop() { - while (deref(REFERENCE_QUEUE.poll(), false)) { - // Empty - } - } - - private static boolean deref(Reference reference, boolean purge) { - if (reference instanceof PhantomEntry) { - ((PhantomEntry) reference).deref(purge); - return true; - } - return false; - } - - private static final Thread INVALIDATOR = new Thread("PhantomHashMapInvalidator") { - @Override - public void run() { - // noinspection InfiniteLoopStatement - while (true) { - try { - deref(REFERENCE_QUEUE.remove(), true); - } catch (Throwable e) { - LOG.warn("Interrupted", e); - } - } - } - }; - - static { - INVALIDATOR.setDaemon(true); - INVALIDATOR.setPriority(Thread.MAX_PRIORITY); - INVALIDATOR.start(); - } -} diff --git a/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/base/cache/PhantomHashMap.java b/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/base/cache/PhantomHashMap.java deleted file mode 100644 index 387180c76d..0000000000 --- a/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/base/cache/PhantomHashMap.java +++ /dev/null @@ -1,280 +0,0 @@ -package com.linkedin.alpini.base.cache; - -import java.util.AbstractMap; -import java.util.AbstractSet; -import java.util.Iterator; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import javax.annotation.Nonnull; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - - -/** - * A simple wrapper around {@link PhantomHashMap} which makes it look like a standard {@link java.util.Map} collection. - * - * @author Antony T Curtis {@literal } - */ -public class PhantomHashMap extends AbstractMap implements Map { - private static final Logger LOG = LogManager.getLogger(PhantomHashMap.class); - - private final PhantomHashCache _phantomCache; - private final Map _backingStore; - private transient Set> _entrySet; - private transient Set _keySet; - - public PhantomHashMap(PhantomHashCache phantomCache, Map backingStore) { - _phantomCache = Objects.requireNonNull(phantomCache); - _backingStore = Objects.requireNonNull(backingStore); - } - - @SuppressWarnings("unchecked") - protected K castKey(Object key) { - return (K) key; - } - - /** - * {@inheritDoc} - *

- * Note: Calls the {@linkplain SerializedMap#size()} method - *

- */ - @Override - public int size() { - return _backingStore.size(); - } - - /** - * {@inheritDoc} - *

- * Note: Calls the {@linkplain SerializedMap#isEmpty()} method - *

- */ - @Override - public boolean isEmpty() { - return _backingStore.isEmpty(); - } - - /** - * {@inheritDoc} - *

- * Note: Calls the {@linkplain SerializedMap#containsKey(Object)} method - *

- */ - @Override - public boolean containsKey(Object key) { - return _backingStore.containsKey(key); - } - - /** - * {@inheritDoc} - *

- * Note: Attempts to retrieve the object by the named key via the phantom cache. - *

- */ - @Override - public V get(Object key) { - try { - return _phantomCache.get(castKey(key), _backingStore); - } catch (InterruptedException e) { - LOG.warn("interrupted", e); - return null; - } - } - - /** - * {@inheritDoc} - */ - @Override - public V put(K key, V value) { - V oldValue = get(key); - if (value != null) { - if (oldValue == null || !value.equals(oldValue)) { - _phantomCache.put(key, _backingStore, value); - } - } else { - _phantomCache.removeEntry(key, _backingStore); - } - return oldValue; - } - - /** - * {@inheritDoc} - */ - @Override - public V remove(Object key) { - V oldValue = get(key); - _phantomCache.removeEntry(castKey(key), _backingStore); - return oldValue; - } - - /** - * {@inheritDoc} - */ - @Override - public void clear() { - _phantomCache.clear(); - _backingStore.clear(); - } - - /** - * {@inheritDoc} - */ - @Override - @Nonnull - public Set> entrySet() { - if (_entrySet == null) { - _entrySet = new AbstractSet>() { - @Override - @Nonnull - public Iterator> iterator() { - Iterator it = keySet().iterator(); - return new Iterator>() { - private transient RemovableEntry _prevEntry; - private transient RemovableEntry _nextEntry; - - @Override - public boolean hasNext() { - if (_nextEntry == null) { - while (it.hasNext()) { - K key = it.next(); - try { - _nextEntry = new RemovableEntry() { - V _value = _phantomCache.get(key, _backingStore); - - @Override - public K getKey() { - return key; - } - - @Override - public V getValue() { - return _value; - } - - @Override - public V setValue(V value) { - V oldValue = _value; - if (value == null) { - remove(); - _value = null; - } else { - if (_value == null) { - throw new IllegalStateException(); - } - _phantomCache.put(key, _backingStore, value); - _value = value; - } - return oldValue; - } - - @Override - public boolean remove() { - if (_value == null) { - throw new IllegalStateException(); - } - _value = null; - return _phantomCache.removeEntry(key, _backingStore); - } - }; - if (_nextEntry.getValue() != null) { - break; - } - } catch (InterruptedException e) { - LOG.warn("interrupted", e); - } - _nextEntry = null; - } - } - return _nextEntry != null; - } - - @Override - public Entry next() { - if (hasNext()) { - _prevEntry = _nextEntry; - _nextEntry = null; - return _prevEntry; - } - throw new IndexOutOfBoundsException(); - } - - @Override - public void remove() { - if (_prevEntry != null) { - _prevEntry.remove(); - _prevEntry = null; - } else { - throw new IllegalStateException(); - } - } - }; - } - - @Override - public int size() { - return PhantomHashMap.this.size(); - } - - @Override - public boolean remove(Object o) { - return o instanceof Entry && _phantomCache.removeEntry(castKey(((Entry) o).getKey()), _backingStore); - } - }; - } - return _entrySet; - } - - /** - * {@inheritDoc} - */ - @Override - @Nonnull - public Set keySet() { - if (_keySet == null) { - _keySet = new AbstractSet() { - @Override - @Nonnull - public Iterator iterator() { - Iterator it = _backingStore.keySet().iterator(); - return new Iterator() { - private transient K _prev; - - @Override - public boolean hasNext() { - return it.hasNext(); - } - - @Override - public K next() { - _prev = it.next(); - return _prev; - } - - @Override - public void remove() { - it.remove(); - _phantomCache.removeEntry(_prev, null); - } - }; - } - - @Override - public int size() { - return PhantomHashMap.this.size(); - } - - @Override - public boolean remove(Object o) { - return _phantomCache.removeEntry(castKey(o), _backingStore); - } - }; - } - return _keySet; - } - - private interface RemovableEntry extends Entry { - boolean remove(); - } -} diff --git a/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/base/cache/SerializedMap.java b/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/base/cache/SerializedMap.java deleted file mode 100644 index 3d017279d0..0000000000 --- a/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/base/cache/SerializedMap.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.linkedin.alpini.base.cache; - -import java.util.Map; -import java.util.concurrent.TimeUnit; -import javax.annotation.Nonnull; - - -/** - * @author Antony T Curtis {@literal } - */ -public interface SerializedMap extends Map { - /** - * Set the block size for the map. - * @param blockSize block size in bytes - * @return {@code this} map - */ - default @Nonnull SerializedMap setBlockSize(int blockSize) { - return this; - } - - /** - * Set the maximum age of a block before it may be forgotten. - * @param time Time in units - * @param unit Unit of time - * @return {@code this} map - */ - default @Nonnull SerializedMap setMaxBlockAge(long time, @Nonnull TimeUnit unit) { - return this; - } - - /** - * Set the age at which no further new objects are appended to a new block. A block leaves incubation after - * this time has elapsed or it is too full to accept further objects. - * @param time Time in units - * @param unit Unit of time - * @return {@code this} map - */ - default @Nonnull SerializedMap setIncubationAge(long time, @Nonnull TimeUnit unit) { - return this; - } - - /** - * Set the maximum amount of bytes to be consumed by the map before blocks are forgotten. - * @param memory max memory in bytes - * @return {@code this} map - */ - default @Nonnull SerializedMap setMaxAllocatedMemory(long memory) { - return this; - } - - /** - * Return the block size for the stored entries. - * @return block size in bytes. - */ - int getBlockSize(); - - /** - * Return the current maximum age for entries in the map before they are "forgotten". - * @param unit Unit of time for returned value. - * @return time in units or {@linkplain Long#MAX_VALUE} for infinite. - */ - default long getMaxBlockAge(@Nonnull TimeUnit unit) { - return Long.MAX_VALUE; - } - - /** - * Return the age that a block is incubated. - * @param unit Unit of time for returned value. - * @return time in units or {@linkplain Long#MAX_VALUE} for infinite. - */ - default long getIncubationAge(@Nonnull TimeUnit unit) { - return Long.MAX_VALUE; - } - - /** - * Return the maximum number of bytes that this map should be permitted to allocate. - * @return max bytes - */ - long getMaxAllocatedMemory(); - - /** - * Returns the number of bytes currently consumed by the serialized map - * @return number of bytes - */ - long getAllocatedBytes(); - - /** - * Removes an entry from the map, similarly to {@link Map#remove(Object)}, except returning a {@code boolean} - * to indicate success without deserialization of the previously stored value. - * @param key Key of entry to be removed from map - * @return {@code true} if an entry was successfully removed - */ - boolean removeEntry(K key); - -} diff --git a/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/base/safealloc/DerivedReadableByteBuf.java b/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/base/safealloc/DerivedReadableByteBuf.java index 99fff61192..5bd346188a 100644 --- a/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/base/safealloc/DerivedReadableByteBuf.java +++ b/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/base/safealloc/DerivedReadableByteBuf.java @@ -16,7 +16,6 @@ import java.nio.channels.GatheringByteChannel; import java.nio.channels.ScatteringByteChannel; import java.nio.charset.Charset; -import java.util.Optional; /** @@ -380,7 +379,7 @@ public final ByteBuffer[] nioBuffers(int index, int length) { @Override public final boolean hasArray() { - return Optional.ofNullable(_buf).map(ByteBuf::hasArray).orElse(false); + return _buf != null && _buf.hasArray(); } @Override @@ -395,7 +394,7 @@ public final int arrayOffset() { @Override public boolean hasMemoryAddress() { - return Optional.ofNullable(_buf).map(ByteBuf::hasMemoryAddress).orElse(false); + return _buf != null && _buf.hasMemoryAddress(); } @Override @@ -411,7 +410,7 @@ public ByteBuf retain(int increment) { @Override public int refCnt() { - return Optional.ofNullable(_buf).map(ByteBuf::refCnt).orElse(0); + return _buf == null ? 0 : _buf.refCnt(); } @Override diff --git a/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/base/safealloc/SafeByteBuf.java b/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/base/safealloc/SafeByteBuf.java index 717ac71a0c..d19c21b4dc 100644 --- a/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/base/safealloc/SafeByteBuf.java +++ b/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/base/safealloc/SafeByteBuf.java @@ -13,7 +13,6 @@ import java.nio.channels.FileChannel; import java.nio.channels.GatheringByteChannel; import java.nio.channels.ScatteringByteChannel; -import java.util.Optional; /** @@ -61,7 +60,10 @@ public ByteBuf retain(int increment) { } private SafeReference ref() { - return Optional.ofNullable(_ref).orElseThrow(IllegalReferenceCountException::new); + if (_ref == null) { + throw new IllegalReferenceCountException(); + } + return _ref; } @Override diff --git a/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/handlers/AllChannelsHandler.java b/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/handlers/AllChannelsHandler.java deleted file mode 100644 index a620a91517..0000000000 --- a/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/handlers/AllChannelsHandler.java +++ /dev/null @@ -1,111 +0,0 @@ -package com.linkedin.alpini.netty4.handlers; - -import io.netty.channel.Channel; -import io.netty.channel.ChannelHandler; -import io.netty.channel.ChannelHandlerAdapter; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.EventLoop; -import io.netty.channel.group.ChannelGroup; -import io.netty.channel.group.DefaultChannelGroup; -import java.util.Collection; -import java.util.Iterator; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.stream.Collectors; -import javax.annotation.Nonnull; - - -@ChannelHandler.Sharable -public class AllChannelsHandler extends ChannelHandlerAdapter implements Set { - private final ConcurrentMap _maps = new ConcurrentHashMap<>(); - - @Override - public void handlerAdded(ChannelHandlerContext ctx) { - EventLoop eventLoop = ctx.channel().eventLoop(); - ChannelGroup group = _maps.get(eventLoop); - if (group == null) { - group = new DefaultChannelGroup(getClass().getSimpleName(), eventLoop); - _maps.put(eventLoop, group); - ctx.channel().eventLoop().terminationFuture().addListener(future -> _maps.remove(eventLoop)); - } - // Channel lifecycle is managed by ChannelGroup - group.add(ctx.channel()); - } - - public int sizeOf(EventLoop loop) { - ChannelGroup group = _maps.get(loop); - return group != null ? group.size() : 0; - } - - @Override - public int size() { - return _maps.values().stream().mapToInt(ChannelGroup::size).sum(); - } - - @Override - public boolean isEmpty() { - return _maps.values().stream().allMatch(ChannelGroup::isEmpty); - } - - @Override - public boolean contains(Object o) { - if (o instanceof Channel) { - EventLoop loop = ((Channel) o).eventLoop(); - ChannelGroup group = _maps.get(loop); - return group != null && group.contains(o); - } - return false; - } - - @Nonnull - @Override - public Iterator iterator() { - return _maps.values().stream().flatMap(ChannelGroup::stream).iterator(); - } - - @Override - public Object[] toArray() { - return _maps.values().stream().flatMap(ChannelGroup::stream).toArray(); - } - - @Override - public T[] toArray(T[] a) { - return _maps.values().stream().flatMap(ChannelGroup::stream).collect(Collectors.toList()).toArray(a); - } - - @Override - public boolean add(Channel channel) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean remove(Object o) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean containsAll(Collection c) { - return c.stream().allMatch(this::contains); - } - - @Override - public boolean addAll(Collection c) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean retainAll(Collection c) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean removeAll(Collection c) { - throw new UnsupportedOperationException(); - } - - @Override - public void clear() { - throw new UnsupportedOperationException(); - } -} diff --git a/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/handlers/ChunkedResponseLimiter.java b/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/handlers/ChunkedResponseLimiter.java deleted file mode 100644 index ccc4186af6..0000000000 --- a/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/handlers/ChunkedResponseLimiter.java +++ /dev/null @@ -1,103 +0,0 @@ -package com.linkedin.alpini.netty4.handlers; - -import com.linkedin.alpini.netty4.misc.ChannelTaskSerializer; -import io.netty.buffer.ByteBuf; -import io.netty.channel.Channel; -import io.netty.channel.ChannelFuture; -import io.netty.channel.ChannelHandler; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelOutboundHandlerAdapter; -import io.netty.channel.ChannelPromise; -import io.netty.handler.codec.http.DefaultHttpContent; -import io.netty.handler.codec.http.FullHttpMessage; -import io.netty.handler.codec.http.HttpContent; - - -/** - * @deprecated broken. use the content chunking in HttpMultiPartContentEncoder - * @author Antony T Curtis {@literal } - */ -@Deprecated -@ChannelHandler.Sharable -public class ChunkedResponseLimiter extends ChannelInitializer { - private final int _maxChunkSize; - private final int _chunkSize; - - public ChunkedResponseLimiter() { - this(8192, 3072); - } - - public ChunkedResponseLimiter(int maxChunkSize, int chunkSize) { - if (chunkSize > maxChunkSize || chunkSize < 512) { - throw new IllegalArgumentException("chunk size must be at least 512 and less than maxChunkSize"); - } - _maxChunkSize = maxChunkSize; - _chunkSize = chunkSize; - } - - @Override - protected void initChannel(Channel ch) throws Exception { - ch.pipeline().replace(this, "chunked-response-limiter", new Handler()); - } - - class Handler extends ChannelOutboundHandlerAdapter { - private ChannelTaskSerializer _serializer; - private ChannelFuture _afterWrite; - - @Override - public void handlerAdded(ChannelHandlerContext ctx) throws Exception { - _serializer = new ChannelTaskSerializer(ctx); - _afterWrite = ctx.newSucceededFuture(); - } - - /** - * Calls {@link ChannelHandlerContext#write(Object, ChannelPromise)} to forward - * to the next {@link io.netty.channel.ChannelOutboundHandler} in the {@link io.netty.channel.ChannelPipeline}. - *

- * Sub-classes may override this method to change behavior. - * - * @param ctx - * @param msg - * @param promise - */ - @Override - public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { - ChannelPromise afterWrite = ctx.newPromise(); - _serializer.executeTask(completion -> { - try { - write0(ctx, msg, promise); - } catch (Exception ex) { - promise.setFailure(ex); - } finally { - afterWrite.setSuccess(); - completion.setSuccess(); - } - }, future -> {}); - _afterWrite = afterWrite; - } - - @Override - public void flush(ChannelHandlerContext ctx) throws Exception { - if (_afterWrite.isDone()) { - super.flush(ctx); - } else { - _afterWrite.addListener(ignored -> super.flush(ctx)); - } - } - - private void write0(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { - if (msg instanceof HttpContent && !(msg instanceof FullHttpMessage)) { - HttpContent chunk = (HttpContent) msg; - if (chunk.content().readableBytes() > _maxChunkSize) { - ByteBuf data = chunk.content().duplicate(); - do { - ctx.write(new DefaultHttpContent(data.readBytes(_chunkSize))); - } while (data.readableBytes() > _chunkSize); - msg = chunk.replace(data.readBytes(data.readableBytes())); - chunk.release(); - } - } - super.write(ctx, msg, promise); - } - } -} diff --git a/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/handlers/ClientConnectionTracker.java b/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/handlers/ClientConnectionTracker.java deleted file mode 100644 index c94efa4eaa..0000000000 --- a/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/handlers/ClientConnectionTracker.java +++ /dev/null @@ -1,224 +0,0 @@ -package com.linkedin.alpini.netty4.handlers; - -import io.netty.channel.Channel; -import io.netty.channel.ChannelDuplexHandler; -import io.netty.channel.ChannelHandler; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelPromise; -import io.netty.handler.codec.http.HttpRequest; -import io.netty.handler.codec.http.HttpResponse; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.LongAdder; -import java.util.function.Function; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - - -/** - * A connectionTracker that tracks the connection count for each client. Right now, it only spits out a warning message - - */ -@ChannelHandler.Sharable -public class ClientConnectionTracker extends ChannelDuplexHandler { - private final int _connectionCountLimit; - private final int _connectionWarningResetThreshold; - - private boolean _shouldLogMetricForSiRootCausingTheirOwnClientConnectionSurge; - // This is supposed to be a static to ensure there be one instance in whole JVM. However, we have to accommodate the - // TestNG that would run tests in parallel, which would have those tests step on each other's foot. - private final Map _statsMap = new ConcurrentHashMap<>(); - private static final Logger LOG = LogManager.getLogger(ClientConnectionTracker.class); - - public ClientConnectionTracker(int connectionCountLimit, int connectionWarningResetThreshold) { - if (connectionWarningResetThreshold > connectionCountLimit) { - throw new IllegalArgumentException( - String.format( - "connectionWarningResetThreshold(%d) must not be higher than connectionCountLimit(%d).", - connectionWarningResetThreshold, - connectionCountLimit)); - } - this._connectionCountLimit = connectionCountLimit; - this._connectionWarningResetThreshold = connectionWarningResetThreshold; - } - - public ClientConnectionTracker setShouldLogMetricForSiRootCausingTheirOwnClientConnectionSurge(boolean should) { - _shouldLogMetricForSiRootCausingTheirOwnClientConnectionSurge = should; - return this; - } - - protected static class ConnectionStats { - private final String _clientHostName; - private final LongAdder _connectionCount = new LongAdder(); - // We are OK if the reported is a little bit staled here. - private boolean _reported = false; - private final LongAdder _activeRequestCount = new LongAdder(); - - public ConnectionStats(InetAddress clientAddress) { - // Use toString to avoid any DNS lookup - _clientHostName = clientAddress.toString(); - } - - public ConnectionStats increment() { - _connectionCount.increment(); - return this; - } - - public int activeRequestCount() { - return _activeRequestCount.intValue(); - } - - public int increaseActiveRequestAndGet() { - _activeRequestCount.increment(); - return _activeRequestCount.intValue(); - } - - public int decreaseActiveRequestAndGet() { - _activeRequestCount.decrement(); - return _activeRequestCount.intValue(); - } - - public int decrementAndGet() { - _connectionCount.decrement(); - return _connectionCount.intValue(); - } - - public int connectionCount() { - return _connectionCount.intValue(); - } - - public ConnectionStats reported() { - this._reported = true; - return this; - } - - public ConnectionStats resetReported() { - this._reported = false; - return this; - } - - public boolean isReported() { - return _reported; - } - - @Override - public String toString() { - int connections = connectionCount(); - int inflightRequest = activeRequestCount(); - return "[host: " + _clientHostName + ", connections: " + connections + ", inflight-requests: " + inflightRequest - + ", available connections: " + (connections - inflightRequest) + "]"; - } - } - - Map statsMap() { - return _statsMap; - } - - /** - * For testing use only - * @return - */ - ConnectionStats getStatsByContext(ChannelHandlerContext ctx) { - // ConcurrentHashMap wont' allow null key - return getHostInetAddressFromContext(ctx).map(_statsMap::get).orElse(null); - } - - @Override - public void channelActive(ChannelHandlerContext ctx) throws Exception { - checkConnectionLimit(ctx).ifPresent(s -> { - if (_shouldLogMetricForSiRootCausingTheirOwnClientConnectionSurge) { - LOG.info("channelActive for client: {}", s); - } - }); - super.channelActive(ctx); - } - - @Override - public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { - if (msg instanceof HttpRequest) { - onRequest(ctx); - } - super.channelRead(ctx, msg); - } - - @Override - public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { - if (msg instanceof HttpResponse) { - onWritingLastHttpContent(ctx); - } - super.write(ctx, msg, promise); - } - - @Override - public void channelInactive(ChannelHandlerContext ctx) throws Exception { - onInActive(ctx); - super.channelInactive(ctx); - } - - private static Optional getHostInetAddressFromContext(ChannelHandlerContext ctx) { - return getHostInetSocketAddressFromContext(ctx).map(InetSocketAddress::getAddress); - } - - private static Optional getHostInetSocketAddressFromContext(ChannelHandlerContext ctx) { - return Optional.ofNullable(ctx.channel()) - .map(Channel::remoteAddress) - .filter(addr -> addr instanceof InetSocketAddress) - .map(socketAddress -> ((InetSocketAddress) socketAddress)); - } - - protected Optional checkConnectionLimit(ChannelHandlerContext context) { - return getHostInetSocketAddressFromContext(context).map(address -> { - ConnectionStats stats = - _statsMap.computeIfAbsent(address.getAddress(), k -> new ConnectionStats(address.getAddress())).increment(); - if (stats.connectionCount() > _connectionCountLimit) { - whenOverLimit(stats, context); - } - return stats; - }); - } - - protected void whenOverLimit(ConnectionStats stats, ChannelHandlerContext context) { - if (!stats.isReported()) { - LOG.warn( - "Connection cap for {} is {}, exceeding the limit {}.", - stats._clientHostName, - stats.connectionCount(), - _connectionCountLimit); - stats.reported(); - } - } - - private int onRequest(ChannelHandlerContext ctx) { - return modifyRequestCount(ctx, ConnectionStats::increaseActiveRequestAndGet); - } - - private int onWritingLastHttpContent(ChannelHandlerContext ctx) { - return modifyRequestCount(ctx, ConnectionStats::decreaseActiveRequestAndGet); - } - - private int modifyRequestCount(ChannelHandlerContext ctx, Function integerSupplier) { - return getHostInetAddressFromContext(ctx) - // The stats should be in the MAP already by channelActive - .map(_statsMap::get) - .map(integerSupplier::apply) - .orElse(0); - } - - private void onInActive(ChannelHandlerContext ctx) { - getHostInetAddressFromContext(ctx).ifPresent(key -> _statsMap.computeIfPresent(key, (k, count) -> { - int countAfterDecrement = count.decrementAndGet(); - // only reset the reported when the count is below the 90% of the limit. - if (countAfterDecrement < _connectionWarningResetThreshold && count.isReported()) { - count.resetReported(); - } - // force COUNT_MAP to remove the entry if the count is less or equal 0. - if (countAfterDecrement <= 0) { - count = null; - } - return count; - })); - } -} diff --git a/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/handlers/MemoryPressureIndexHandler.java b/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/handlers/MemoryPressureIndexHandler.java deleted file mode 100644 index 8352c43262..0000000000 --- a/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/handlers/MemoryPressureIndexHandler.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.linkedin.alpini.netty4.handlers; - -import com.linkedin.alpini.base.misc.MemoryPressureIndexMonitor; -import com.linkedin.alpini.netty4.misc.MemoryPressureIndexUtils; -import io.netty.channel.ChannelDuplexHandler; -import io.netty.channel.ChannelHandler; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelPromise; -import io.netty.handler.codec.http.HttpRequest; -import io.netty.handler.codec.http.HttpResponse; -import java.util.Objects; -import java.util.Optional; -import java.util.function.Function; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - - -/** - * A handler that calculates request / response size and passes it to MemoryPressureIndexMonitor - * @param Referent used by MemoryPressureIndexMonitor - * @param Key used by MemoryPressureIndexMonitor - * @param The Stats - */ -@ChannelHandler.Sharable -public class MemoryPressureIndexHandler extends ChannelDuplexHandler { - private static final Logger LOG = LogManager.getLogger(MemoryPressureIndexHandler.class); - private volatile boolean _phantomMode = false; - - private final Function> _responseToKeyFunction; - private final MemoryPressureIndexMonitor _memoryPressureIndexMonitor; - - public MemoryPressureIndexHandler( - MemoryPressureIndexMonitor monitor, - Function> responseToKeyFunction) { - this._memoryPressureIndexMonitor = Objects.requireNonNull(monitor, "monitor"); - this._responseToKeyFunction = Objects.requireNonNull(responseToKeyFunction, "responseToKeyFunction"); - } - - public MemoryPressureIndexHandler phantomMode(boolean phantomMode) { - this._phantomMode = phantomMode; - return this; - } - - @Override - public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { - if (msg instanceof HttpRequest) { - HttpRequest request = (HttpRequest) msg; - MemoryPressureIndexUtils.getContentLength(request).ifPresent(contentLength -> { - LOG.debug( - "Adding request id={} with content-Length={}", - _memoryPressureIndexMonitor.getIdSupplier().apply((R) request).orElse(null), - contentLength); - _memoryPressureIndexMonitor.addReferentAndByteCount((R) request, contentLength); - }); - } - super.channelRead(ctx, msg); - } - - @Override - public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { - if (msg instanceof HttpResponse) { - HttpResponse response = (HttpResponse) msg; - Optional requestId = _responseToKeyFunction.apply(response); - requestId - .map( - key -> _phantomMode - ? _memoryPressureIndexMonitor - .removeByteCountAndAddPhantom(key, MemoryPressureIndexUtils.getContentLength(response)) - .orElse(null) - : _memoryPressureIndexMonitor.removeByteCount(key, true, Optional.empty()).orElse(null)) - .ifPresent( - b -> LOG.debug( - "Removing {} bytes{} for request id={}.", - b.count(), - _phantomMode ? " in delayed mode" : " immediately", - requestId.get())); - } - super.write(ctx, msg, promise); - } -} diff --git a/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/handlers/MultipartResponseHandler.java b/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/handlers/MultipartResponseHandler.java deleted file mode 100644 index f2add460c2..0000000000 --- a/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/handlers/MultipartResponseHandler.java +++ /dev/null @@ -1,263 +0,0 @@ -package com.linkedin.alpini.netty4.handlers; - -import com.linkedin.alpini.base.misc.CollectionUtil; -import com.linkedin.alpini.base.misc.HeaderNames; -import com.linkedin.alpini.base.misc.HeaderUtils; -import com.linkedin.alpini.base.misc.ImmutableMapEntry; -import com.linkedin.alpini.netty4.misc.ChannelTaskSerializer; -import com.linkedin.alpini.netty4.misc.ChunkedHttpResponse; -import com.linkedin.alpini.netty4.misc.MultipartContent; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufUtil; -import io.netty.buffer.Unpooled; -import io.netty.channel.Channel; -import io.netty.channel.ChannelFuture; -import io.netty.channel.ChannelHandler; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelOutboundHandlerAdapter; -import io.netty.channel.ChannelPromise; -import io.netty.handler.codec.ByteToMessageDecoder; -import io.netty.handler.codec.http.DefaultHttpContent; -import io.netty.handler.codec.http.FullHttpMessage; -import io.netty.handler.codec.http.HttpConstants; -import io.netty.handler.codec.http.HttpContent; -import io.netty.handler.codec.http.HttpHeaderNames; -import io.netty.handler.codec.http.HttpHeaders; -import io.netty.handler.codec.http.HttpMessage; -import io.netty.handler.codec.http.HttpUtil; -import io.netty.handler.codec.http.LastHttpContent; -import io.netty.util.AsciiString; -import java.nio.charset.StandardCharsets; -import java.util.Collections; -import java.util.Iterator; -import java.util.Map; -import javax.annotation.Nonnull; - - -/** - * @deprecated Use the HttpMultiPartContentEncoder - * @author Antony T Curtis {@literal } - */ -@ChannelHandler.Sharable -@Deprecated -public class MultipartResponseHandler extends ChannelInitializer { - private static final byte[] CRLF = { HttpConstants.CR, HttpConstants.LF }; - private static final byte[] DASH_DASH = { '-', '-' }; - private static final byte[] DASH_DASH_CRLF = { '-', '-', HttpConstants.CR, HttpConstants.LF }; - - @Override - protected void initChannel(Channel ch) throws Exception { - ch.pipeline().replace(this, "multipart-response-handler", new Handler()); - } - - class Handler extends ChannelOutboundHandlerAdapter { - private ChannelTaskSerializer _serializer; - private ChannelFuture _afterWrite; - - private HttpContent _boundary; - private int _partCount; - - @Override - public void handlerAdded(ChannelHandlerContext ctx) throws Exception { - _serializer = new ChannelTaskSerializer(ctx); - _afterWrite = ctx.newSucceededFuture(); - _boundary = null; - } - - /** - * Calls {@link ChannelHandlerContext#write(Object, ChannelPromise)} to forward - * to the next {@link io.netty.channel.ChannelOutboundHandler} in the {@link io.netty.channel.ChannelPipeline}. - *

- * Sub-classes may override this method to change behavior. - * - * @param ctx - * @param msg - * @param promise - */ - @Override - public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { - if (msg instanceof ChunkedHttpResponse && ((ChunkedHttpResponse) msg).content().isReadable()) { - throw new IllegalStateException("Chunked responses must not have content"); - } - - ChannelPromise afterWrite = ctx.newPromise(); - - _serializer.executeTask(completion -> { - try { - write0(ctx, msg, promise); - } catch (Exception ex) { - promise.setFailure(ex); - } finally { - afterWrite.setSuccess(); - completion.setSuccess(); - } - }, future -> {}); - _afterWrite = afterWrite.isDone() ? ctx.newSucceededFuture() : afterWrite; - } - - @Override - public void flush(ChannelHandlerContext ctx) throws Exception { - ChannelFuture afterWrite = _afterWrite; - if (afterWrite.isDone()) { - super.flush(ctx); - } else { - afterWrite.addListener(ignored -> super.flush(ctx)); - } - } - - private void write0(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { - if (!(msg instanceof FullHttpMessage)) { - if (msg instanceof HttpMessage && !(msg instanceof MultipartContent)) { - HttpMessage message = (HttpMessage) msg; - HeaderUtils.ContentType contentType = - HeaderUtils.parseContentType(message.headers().get(HttpHeaderNames.CONTENT_TYPE, "text/plain")); - - if (contentType.isMultipart()) { - String boundaryText = CollectionUtil.stream(contentType.parameters()) - .filter(e -> "boundary".equals(e.getKey())) - .map(Map.Entry::getValue) - .findAny() - .orElseGet(() -> { - String boundary = HeaderUtils.randomWeakUUID().toString(); - message.headers() - .set( - HeaderNames.CONTENT_TYPE, - HeaderUtils.buildContentType( - contentType.type(), - contentType.subType(), - CollectionUtil.concat( - contentType.parameters(), - Collections.singleton(ImmutableMapEntry.make("boundary", boundary)).iterator()))); - return boundary; - }); - - if (!boundaryText.isEmpty()) { - ByteBuf boundary = ctx.alloc().buffer(); - boundary.writeBytes(DASH_DASH); - boundary.writeCharSequence(boundaryText, StandardCharsets.US_ASCII); - boundary.writeBytes(CRLF); - - _boundary = new DefaultHttpContent(boundary); - _partCount = 0; - - HttpUtil.setTransferEncodingChunked(message, true); - message.headers().remove(HttpHeaderNames.CONTENT_LENGTH); - } - } - } else if (msg instanceof HttpContent) { - HttpContent httpContent = (HttpContent) msg; - - if (_boundary != null) { - if (httpContent instanceof MultipartContent) { - MultipartContent multipartContent = (MultipartContent) httpContent; - - if (_partCount > 0) { - ctx.write(_boundary.retainedDuplicate()); - } - - _partCount++; - - if (!multipartContent.headers().contains(HttpHeaderNames.CONTENT_TYPE)) { - multipartContent.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/binary"); - } - multipartContent.headers().setInt(HttpHeaderNames.CONTENT_LENGTH, httpContent.content().readableBytes()); - - ByteBuf part = ctx.alloc().buffer(); - encodeHeaders(multipartContent.headers(), part); - - if (httpContent.content().isReadable()) { - part = ByteToMessageDecoder.MERGE_CUMULATOR.cumulate(ctx.alloc(), part, httpContent.content()); - if (!endsInCrLf(httpContent.content())) { - part = ByteToMessageDecoder.MERGE_CUMULATOR.cumulate(ctx.alloc(), part, Unpooled.wrappedBuffer(CRLF)); - } - } - ctx.write(new DefaultHttpContent(part), promise); - return; - } else if (httpContent instanceof LastHttpContent) { - ByteBuf trailer = ctx.alloc().buffer(); - - trailer.writeBytes(_boundary.content().duplicate()); - trailer.writerIndex(trailer.writerIndex() - 2); // remove trailing CRLF - trailer.writeBytes(DASH_DASH_CRLF); - trailer.writeBytes(httpContent.content()); - - ctx.write(httpContent.replace(trailer), promise); - - _boundary.release(); - _boundary = null; - return; - } else { - if (_partCount == 0) { - _boundary.release(); - _boundary = null; - } - } - } - } - } - - super.write(ctx, msg, promise); - } - } - - /** - * Encode the {@link HttpHeaders} into a {@link ByteBuf}. - */ - protected void encodeHeaders(HttpHeaders headers, ByteBuf buf) throws Exception { - Iterator> iter = headers.iteratorCharSequence(); - while (iter.hasNext()) { - Map.Entry header = iter.next(); - encodeHeader(header.getKey(), header.getValue(), buf); - } - buf.ensureWritable(2); - int offset = buf.writerIndex(); - buf.setByte(offset++, '\r'); - buf.setByte(offset++, '\n'); - buf.writerIndex(offset); - } - - private static boolean endsInCrLf(ByteBuf buffer) { - return buffer.readableBytes() > 1 && buffer.getUnsignedByte(buffer.writerIndex() - 1) == '\n' - && buffer.getUnsignedByte(buffer.writerIndex() - 2) == '\r'; - } - - public static void encodeHeader(CharSequence name, CharSequence value, ByteBuf buf) throws Exception { - final int nameLen = name.length(); - final int valueLen = value.length(); - final int entryLen = nameLen + valueLen + 4; - buf.ensureWritable(entryLen); - int offset = buf.writerIndex(); - writeAscii(buf, offset, name, nameLen); - offset += nameLen; - buf.setByte(offset++, ':'); - buf.setByte(offset++, ' '); - writeAscii(buf, offset, value, valueLen); - offset += valueLen; - buf.setByte(offset++, '\r'); - buf.setByte(offset++, '\n'); - buf.writerIndex(offset); - } - - private static void writeAscii(ByteBuf buf, int offset, CharSequence value, int valueLen) { - if (value instanceof AsciiString) { - ByteBufUtil.copy((AsciiString) value, 0, buf, offset, valueLen); - } else { - writeCharSequence(buf, offset, value, valueLen); - } - } - - private static void writeCharSequence(ByteBuf buf, int offset, CharSequence value, int valueLen) { - for (int i = 0; i < valueLen; ++i) { - buf.setByte(offset++, AsciiString.c2b(value.charAt(i))); - } - } - - static class Info { - final HttpContent _boundary; - int _partCount; - - Info(@Nonnull ByteBuf boundary) { - _boundary = new DefaultHttpContent(boundary); - } - } -} diff --git a/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/handlers/RequestLogHandler.java b/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/handlers/RequestLogHandler.java deleted file mode 100644 index 4398f233e2..0000000000 --- a/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/handlers/RequestLogHandler.java +++ /dev/null @@ -1,412 +0,0 @@ -package com.linkedin.alpini.netty4.handlers; - -import com.linkedin.alpini.base.misc.BasicRequest; -import com.linkedin.alpini.base.misc.Msg; -import com.linkedin.alpini.base.misc.Time; -import io.netty.channel.Channel; -import io.netty.channel.ChannelDuplexHandler; -import io.netty.channel.ChannelHandler; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelPromise; -import io.netty.handler.codec.http.HttpContent; -import io.netty.handler.codec.http.HttpHeaderNames; -import io.netty.handler.codec.http.HttpMethod; -import io.netty.handler.codec.http.HttpRequest; -import io.netty.handler.codec.http.HttpResponse; -import io.netty.handler.codec.http.HttpResponseStatus; -import io.netty.handler.codec.http.HttpStatusClass; -import io.netty.handler.codec.http.HttpUtil; -import io.netty.handler.codec.http.HttpVersion; -import io.netty.handler.codec.http.LastHttpContent; -import io.netty.handler.codec.http2.Http2StreamChannel; -import io.netty.util.AttributeKey; -import java.net.SocketAddress; -import java.util.LinkedList; -import java.util.Optional; -import java.util.concurrent.Callable; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.util.StringBuilderFormattable; - - -/** - * Created by acurtis on 4/14/17. - */ -@ChannelHandler.Sharable -public class RequestLogHandler extends ChannelInitializer { - private static final Logger LOG = LogManager.getLogger(RequestLogHandler.class); - - private static final AttributeKey

INBOUND_TYPE = AttributeKey.valueOf(RequestLogHandler.class, "inboundType"); - private static final AttributeKey OUTBOUND_TYPE = AttributeKey.valueOf(RequestLogHandler.class, "outboundType"); - - private enum Dir { - Request, Response - } - - /** Writes to a special logfile for the requests. */ - private final Logger _requestLog; - - /** Name of this pipeline (e.g. "inbound", "outbound", etc.). */ - private final String _pipelineName; - - private final String _pipelineKey; - - /** - * Create a new RequestLogHandler which will write to the given named log. Direction specifies if this handler is used on - * and inbound or outbound connection. - * @param loggerName name of the logger. typically log4j.xml should send this logger to a separate file - * @param pipelineName name of this pipeline; e.g. "inbound", "outbound", etc. - */ - public RequestLogHandler(String loggerName, String pipelineName) { - this(LogManager.getLogger(loggerName), pipelineName); - } - - /* package private */ RequestLogHandler(Logger logger, String pipelineName) { - _requestLog = logger; - _pipelineName = pipelineName; - _pipelineKey = "request-log-" + logger.getName() + "-" + pipelineName; - } - - @Override - protected void initChannel(Channel ch) throws Exception { - ch.pipeline().replace(this, _pipelineKey, new Handler()); - } - - class Handler extends ChannelDuplexHandler { - ConnectInfo _connectInfo; - - @Override - public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { - catchException(LOG, () -> channelRead0(ctx, msg), "channelRead0"); - super.channelRead(ctx, msg); - } - - private Void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception { - ConnectInfo connectInfo = getConnectInfo(ctx); - if (msg instanceof HttpRequest) { - HttpRequest request = (HttpRequest) msg; - ctx.channel().attr(INBOUND_TYPE).set(Dir.Request); - handleHttpRequest(connectInfo, request); - } else if (msg instanceof HttpResponse) { - HttpResponse response = (HttpResponse) msg; - ctx.channel().attr(INBOUND_TYPE).set(Dir.Response); - handleHttpResponse(connectInfo, response, true); - } - if (msg instanceof HttpContent) { - HttpContent content = (HttpContent) msg; - if (Dir.Request.equals(ctx.channel().attr(INBOUND_TYPE).get())) { - if (content instanceof LastHttpContent) { - handleLastHttpRequestContent(connectInfo, (LastHttpContent) content, true, true); - } else { - handleHttpRequestContent(connectInfo, content); - } - } else /* HttpResponse */ { - if (content instanceof LastHttpContent) { - handleLastHttpResponseContent(connectInfo, (LastHttpContent) content, true, true); - } else { - handleHttpRequestContent(connectInfo, content); - } - } - } - return null; - } - - @Override - public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { - catchException(LOG, () -> write0(ctx, msg, promise), "write0"); - super.write(ctx, msg, promise); - } - - private Void write0(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { - ConnectInfo connectInfo = getConnectInfo(ctx); - if (msg instanceof HttpRequest) { - HttpRequest request = (HttpRequest) msg; - ctx.channel().attr(OUTBOUND_TYPE).set(Dir.Request); - handleHttpRequest(connectInfo, request); - } else if (msg instanceof HttpResponse) { - HttpResponse response = (HttpResponse) msg; - ctx.channel().attr(OUTBOUND_TYPE).set(Dir.Response); - handleHttpResponse(connectInfo, response, false); - } - if (msg instanceof LastHttpContent) { - LastHttpContent content = (LastHttpContent) msg; - if (Dir.Request.equals(ctx.channel().attr(OUTBOUND_TYPE).get())) { - promise.addListener(future -> { - handleLastHttpRequestContent(connectInfo, content, future.isSuccess(), false); - }); - } else /* HttpResponse */ { - promise.addListener(future -> { - handleLastHttpResponseContent(connectInfo, content, future.isSuccess(), false); - }); - } - } - return null; - } - - private ConnectInfo getConnectInfo(ChannelHandlerContext ctx) { - return Optional.ofNullable(_connectInfo) - .orElseGet(() -> _connectInfo = new ConnectInfo(ctx.channel().remoteAddress())); - } - - @Override - public void channelActive(ChannelHandlerContext ctx) throws Exception { - catchException(LOG, () -> channelActive0(ctx), "channelActive0"); - super.channelActive(ctx); - } - - private Void channelActive0(ChannelHandlerContext ctx) throws Exception { - // Log the CONNECT event and store ConnectInfo - ConnectInfo connectInfo = getConnectInfo(ctx); - - if (!(ctx.channel() instanceof Http2StreamChannel)) { - handleConnect(connectInfo); - } - return null; - } - - @Override - public void channelInactive(ChannelHandlerContext ctx) throws Exception { - catchException(LOG, () -> channelInactive0(ctx), "channelInactive0"); - super.channelInactive(ctx); - } - - private Void channelInactive0(ChannelHandlerContext ctx) throws Exception { - ConnectInfo connectInfo = getConnectInfo(ctx); - - if (!(ctx.channel() instanceof Http2StreamChannel)) { - ctx.executor().submit(() -> handleDisconnect(connectInfo)); - } - return null; - } - } - - void handleHttpRequest(ConnectInfo connectInfo, HttpRequest request) { - // Create and store the new request info - RequestInfo requestInfo = new RequestInfo(request); - connectInfo._requestInfos.addLast(requestInfo); - } - - void handleHttpRequestContent(ConnectInfo connectInfo, HttpContent content) { - Optional.ofNullable(connectInfo._requestInfos.peekLast()) - .filter(requestInfo -> !requestInfo._hasContentLength) - .ifPresent( - requestInfo -> requestInfo._contentLength = - Math.max(0, requestInfo._contentLength) + content.content().readableBytes()); - } - - void handleMissingRequestInfo(String method, ConnectInfo connectInfo, ResponseInfo responseInfo) { - _requestLog.error( - "{} {} without corresponding requestInfo or connectInfo. {}", - _pipelineName, - method, - infoToString(_pipelineName, connectInfo, null, responseInfo)); - } - - void handleHttpResponse(ConnectInfo connectInfo, HttpResponse response, boolean inbound) { - ResponseInfo responseInfo = new ResponseInfo(response, inbound); - - // Get the stored request and connection info. If either is null, that is an error - // and we cannot log message information. - RequestInfo requestInfo = connectInfo._requestInfos.peekFirst(); - - if (requestInfo == null) { - // Should never see a response without knowing about the connection or the request. - handleMissingRequestInfo("handleHttpResponse", connectInfo, responseInfo); - } else if (requestInfo._response != null) { - _requestLog.error( - "{} handleHttpResponse with duplicate responseInfo. {}", - _pipelineName, - infoToString(_pipelineName, connectInfo, requestInfo, responseInfo)); - } else { - requestInfo._response = responseInfo; - } - } - - void handleLastHttpRequestContent(ConnectInfo connectInfo, LastHttpContent last, boolean success, boolean inbound) { - // Get the stored request and connection info. If either is null, that is an error - // and we cannot log message information. - RequestInfo requestInfo = connectInfo._requestInfos.pollFirst(); - if (requestInfo == null) { - // Should never see a response without knowing about the connection or the request. - handleMissingRequestInfo("handleLastHttpRequestContent", connectInfo, null); - } else { - // Not done yet. This LastHttpContent is the end of the request portion. - connectInfo._requestInfos.addFirst(requestInfo); - } - } - - void handleLastHttpResponseContent(ConnectInfo connectInfo, LastHttpContent last, boolean success, boolean inbound) { - // Get the stored request and connection info. If either is null, that is an error - // and we cannot log message information. - RequestInfo requestInfo = connectInfo._requestInfos.pollFirst(); - if (requestInfo == null) { - // Should never see a response without knowing about the connection or the request. - handleMissingRequestInfo("handleLastHttpResponseContent", connectInfo, null); - } else if (requestInfo._response != null) { - ResponseInfo responseInfo = requestInfo._response; - responseInfo._completeMillis = Time.currentTimeMillis(); - if (last != null && last.trailingHeaders().contains(HttpHeaderNames.CONTENT_LENGTH)) { - try { - responseInfo._contentLength = - Long.parseUnsignedLong(last.trailingHeaders().get(HttpHeaderNames.CONTENT_LENGTH)); - } catch (Exception ignored) { - } - } - if (success && requestInfo._response._status.codeClass() != HttpStatusClass.SERVER_ERROR) { - _requestLog.debug("{}", infoToString(_pipelineName, connectInfo, requestInfo, responseInfo)); - } else { - _requestLog.error("{}", infoToString(_pipelineName, connectInfo, requestInfo, responseInfo)); - } - } else { - _requestLog.error("{}", infoToString(_pipelineName, connectInfo, requestInfo, null)); - } - } - - void handleConnect(ConnectInfo connectInfo) { - _requestLog.info("{} {} CONNECTED", _pipelineName, connectInfo._remoteAddress); - } - - void handleDisconnect(ConnectInfo connectInfo) { - if (connectInfo == null) { - _requestLog.error("{} handleHttpResponse without corresponding connectInfo.", _pipelineName); - } else { - long now = Time.currentTimeMillis(); - - // If there was a request that never received a response before the channel was closed (timeout?), log it. - while (!connectInfo._requestInfos.isEmpty()) { - RequestInfo requestInfo = connectInfo._requestInfos.removeFirst(); - - long durationMillis = now - requestInfo._startMillis; - _requestLog.info( - "{} {} {} {} {} --> CHANNEL-CLOSED {}", - _pipelineName, - connectInfo._remoteAddress, - requestInfo._protocolVersion, - requestInfo._method, - requestInfo._uri, - durationMillis); - } - - _requestLog - .info("{} {} DISCONNECTED {}", _pipelineName, connectInfo._remoteAddress, now - connectInfo._startMillis); - } - } - - void catchException(Logger log, Callable callable, String methodName) { - try { - callable.call(); - } catch (Throwable ex) { - log.warn("Error in {}", methodName, ex); - } - } - - /* package private */ static Msg infoToString( - String pipelineName, - ConnectInfo connectInfo, - RequestInfo requestInfo, - ResponseInfo responseInfo) { - return Msg.make(() -> (StringBuilderFormattable) sb -> { - sb.append(pipelineName).append(' '); - if (connectInfo != null) { - sb.append(connectInfo._remoteAddress).append(' '); - } else { - sb.append("UNKNOWN "); - } - if (requestInfo == null) { - sb.append("UNKNOWN UNKNOWN UNKNOWN UNKNOWN"); - } else { - sb.append(requestInfo._protocolVersion) - .append(' ') - .append(requestInfo._method) - .append(' ') - .append(requestInfo._uri) - .append(' '); - if (requestInfo._contentLengthException) { - sb.append("UNKNOWN"); - } else { - sb.append(requestInfo._contentLength); - } - } - sb.append("--> "); - if (responseInfo == null) { - sb.append("UNKNOWN UNKNOWN UNKNOWN UNKNOWN UNKNOWN"); - } else { - sb.append(responseInfo._status).append(' '); - if (responseInfo._contentLengthException) { - sb.append("UNKNOWN"); - } else { - sb.append(responseInfo._contentLength); - } - sb.append(' ').append(responseInfo._contentLocation).append(' '); - if (requestInfo != null) { - sb.append(responseInfo._endMillis - requestInfo._startMillis); - } else { - sb.append("UNKNOWN"); - } - sb.append(' ').append(responseInfo._completeMillis - responseInfo._endMillis); - } - }); - } - - /* package private */ static final class ConnectInfo { - ConnectInfo(SocketAddress remoteAddress) { - _startMillis = Time.currentTimeMillis(); - _remoteAddress = remoteAddress; - } - - SocketAddress _remoteAddress; - long _startMillis; - - final LinkedList _requestInfos = new LinkedList<>(); - } - - /* package private */ static final class RequestInfo { - RequestInfo(HttpRequest request) { - _startMillis = - request instanceof BasicRequest ? ((BasicRequest) request).getRequestTimestamp() : Time.currentTimeMillis(); - _uri = request.uri(); - _method = request.method(); - _protocolVersion = request.protocolVersion(); - try { - _contentLength = HttpUtil.getContentLength(request, -1); - _hasContentLength = _contentLength >= 0; - } catch (Exception ignored) { - _contentLengthException = true; - } - } - - String _uri; - HttpMethod _method; - HttpVersion _protocolVersion; - long _startMillis; - long _contentLength; - boolean _contentLengthException; - boolean _hasContentLength; - - ResponseInfo _response; - } - - /* package private */ static final class ResponseInfo { - ResponseInfo(HttpResponse response, boolean inbound) { - _endMillis = Time.currentTimeMillis(); - try { - _contentLength = HttpUtil.getContentLength(response, -1); - _hasContentLength = _contentLength >= 0; - } catch (Exception ignored) { - _contentLengthException = true; - } - _contentLocation = response.headers().get(HttpHeaderNames.CONTENT_LOCATION); - _status = response.status(); - _inbound = inbound; - } - - long _endMillis; - long _completeMillis; - long _contentLength; - boolean _contentLengthException; - String _contentLocation; - HttpResponseStatus _status; - boolean _inbound; - boolean _hasContentLength; - } -} diff --git a/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/misc/BalancedEventLoopGroup.java b/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/misc/BalancedEventLoopGroup.java deleted file mode 100644 index 03cca6f4aa..0000000000 --- a/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/misc/BalancedEventLoopGroup.java +++ /dev/null @@ -1,152 +0,0 @@ -package com.linkedin.alpini.netty4.misc; - -import com.linkedin.alpini.netty4.handlers.AllChannelsHandler; -import io.netty.channel.AbstractEventLoopGroup; -import io.netty.channel.Channel; -import io.netty.channel.ChannelFuture; -import io.netty.channel.ChannelPromise; -import io.netty.channel.EventLoop; -import io.netty.channel.EventLoopGroup; -import io.netty.util.concurrent.EventExecutor; -import io.netty.util.concurrent.Future; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.concurrent.ThreadLocalRandom; -import java.util.concurrent.TimeUnit; -import java.util.stream.StreamSupport; - - -/** - * An {@link EventLoopGroup} instance which may be passed to an instance of {@link io.netty.bootstrap.Bootstrap} - * to ensure that there is a reasonably fair balance of connections for the supplied {@link AllChannelsHandler}. - * - *

This can be used to ensure that incoming and outgoing connections are balanced. - * - *

Example: - *

- *
- *   AllChannelsHandler allChannelsHandler = new AllChannelsHandler();
- *
- *   Bootstrap bootstrap = new Bootstrap()
- *       .group(new BalancedEventLoopGroup(_eventLoopGroup, allChannelsHandler))
- *       ...
- *       .handler(new ChannelInitializer<Channel>() {
- *         protected void initChannel(Channel ch) {
- *           ch.pipeline().addLast(allChannelsHandler);
- *           ...
- *         }
- *       }
- *
- *
- * 
- * @author acurtis - */ -public class BalancedEventLoopGroup extends AbstractEventLoopGroup { - private final EventLoopGroup _eventLoopGroup; - private final AllChannelsHandler _allChannels; - private final int _eventLoopCount; - private final boolean _tightBalance; - - public BalancedEventLoopGroup(EventLoopGroup eventLoopGroup, AllChannelsHandler allChannels) { - this(eventLoopGroup, allChannels, true); - } - - public BalancedEventLoopGroup(EventLoopGroup eventLoopGroup, AllChannelsHandler allChannels, boolean tightBalance) { - _eventLoopGroup = eventLoopGroup; - _allChannels = allChannels; - _eventLoopCount = (int) StreamSupport.stream(_eventLoopGroup.spliterator(), false).count(); - _tightBalance = tightBalance; - } - - @Override - public EventLoop next() { - return _eventLoopGroup.next(); - } - - @Override - public Iterator iterator() { - return _eventLoopGroup.iterator(); - } - - private int averageChannelsPerEventLoop() { - int channels = _allChannels.size(); - return (channels + _eventLoopCount - 1) / _eventLoopCount; - } - - private int countChannelsInLoop(EventLoop eventLoop) { - return _allChannels.sizeOf(eventLoop); - } - - private boolean isUnbalanced(EventLoop eventLoop, int average) { - int channelsInLoop = countChannelsInLoop(eventLoop); - return _tightBalance ? channelsInLoop >= average : channelsInLoop > average; - } - - private EventLoop selectLoop() { - int average = averageChannelsPerEventLoop(); - EventLoop loop = next(); - if (_eventLoopCount > 1 && isUnbalanced(loop, average)) { - ArrayList list = new ArrayList<>(_eventLoopCount); - _eventLoopGroup.forEach(eventExecutor -> list.add((EventLoop) eventExecutor)); - Collections.shuffle(list, ThreadLocalRandom.current()); - Iterator it = list.iterator(); - do { - loop = it.next(); - } while (it.hasNext() && isUnbalanced(loop, average)); - } - return loop; - } - - @Override - public ChannelFuture register(Channel channel) { - return selectLoop().register(channel); - } - - @Override - public ChannelFuture register(ChannelPromise promise) { - return selectLoop().register(promise); - } - - @Deprecated - @Override - public ChannelFuture register(Channel channel, ChannelPromise promise) { - return selectLoop().register(channel, promise); - } - - @Override - public boolean isShuttingDown() { - return _eventLoopGroup.isShuttingDown(); - } - - @Override - public Future shutdownGracefully(long quietPeriod, long timeout, TimeUnit unit) { - return _eventLoopGroup.shutdownGracefully(quietPeriod, timeout, unit); - } - - @Override - public Future terminationFuture() { - return _eventLoopGroup.terminationFuture(); - } - - @Deprecated - @Override - public void shutdown() { - _eventLoopGroup.shutdown(); - } - - @Override - public boolean isShutdown() { - return _eventLoopGroup.isShutdown(); - } - - @Override - public boolean isTerminated() { - return _eventLoopGroup.isTerminated(); - } - - @Override - public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { - return _eventLoopGroup.awaitTermination(timeout, unit); - } -} diff --git a/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/misc/BasicFullHttpMultiPartRequest.java b/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/misc/BasicFullHttpMultiPartRequest.java index d78e1089b5..1d75441752 100644 --- a/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/misc/BasicFullHttpMultiPartRequest.java +++ b/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/misc/BasicFullHttpMultiPartRequest.java @@ -11,11 +11,8 @@ import java.util.Collection; import java.util.Collections; import java.util.Iterator; -import java.util.List; import java.util.Objects; -import java.util.Optional; import java.util.UUID; -import java.util.function.Function; import java.util.stream.Collectors; @@ -65,10 +62,8 @@ public BasicFullHttpMultiPartRequest( long requestTimestamp, long requestNanos) { super(httpVersion, method, uri, validateHeaders, requestTimestamp, requestNanos); - this.content = Optional.of(Objects.requireNonNull(content, "content")) - .filter(collection -> !collection.isEmpty()) - .map((Function, List>) ArrayList::new) - .orElseGet(Collections::emptyList); + Objects.requireNonNull(content, "content"); + this.content = content.isEmpty() ? Collections.emptyList() : new ArrayList<>(content); trailingHeader = new DefaultHttpHeaders(validateHeaders); headers().set(HttpHeaderNames.CONTENT_TYPE, "multipart/mixed"); } @@ -87,10 +82,8 @@ public BasicFullHttpMultiPartRequest( if (!HeaderUtils.parseContentType(headers.get(HttpHeaderNames.CONTENT_TYPE, "text/plain")).isMultipart()) { throw new IllegalArgumentException("Expected content-type to be multipart"); } - this.content = Optional.of(Objects.requireNonNull(content, "content")) - .filter(collection -> !collection.isEmpty()) - .map((Function, List>) ArrayList::new) - .orElseGet(Collections::emptyList); + Objects.requireNonNull(content, "content"); + this.content = content.isEmpty() ? Collections.emptyList() : new ArrayList<>(content); this.trailingHeader = Objects.requireNonNull(trailingHeader, "trailingHeader"); } diff --git a/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/misc/BasicHeaders.java b/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/misc/BasicHeaders.java index 50cc4a4227..93fe3ddf0f 100644 --- a/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/misc/BasicHeaders.java +++ b/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/misc/BasicHeaders.java @@ -6,7 +6,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Optional; import java.util.stream.Collectors; import javax.annotation.Nonnull; @@ -83,14 +82,6 @@ public boolean contains(CharSequence name, CharSequence value, boolean ignoreCas return _headers.contains(name, value, ignoreCase); } - @Override - public Optional unwrap(Class type) { - if (type.isAssignableFrom(_headers.getClass())) { - return Optional.of(type.cast(_headers)); - } - return Optional.empty(); - } - @Override public Iterator> iterator() { return _headers.iterator(); diff --git a/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/misc/BasicHttpRequestSerializer.java b/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/misc/BasicHttpRequestSerializer.java deleted file mode 100644 index f27c7201ee..0000000000 --- a/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/misc/BasicHttpRequestSerializer.java +++ /dev/null @@ -1,217 +0,0 @@ -package com.linkedin.alpini.netty4.misc; - -import com.linkedin.alpini.base.cache.ByteBufHashMap; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufAllocator; -import io.netty.buffer.ByteBufInputStream; -import io.netty.buffer.ByteBufOutputStream; -import io.netty.buffer.Unpooled; -import io.netty.handler.codec.http.DefaultHttpHeaders; -import io.netty.handler.codec.http.HttpHeaders; -import io.netty.handler.codec.http.HttpMethod; -import io.netty.handler.codec.http.HttpVersion; -import io.netty.util.AsciiString; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import javax.annotation.Nonnull; - - -/** - * Created by acurtis on 8/15/17. - */ -public class BasicHttpRequestSerializer implements ByteBufHashMap.SerDes { - private final ByteBufAllocator _alloc; - - private enum Type { - BASIC_REQUEST, - - FULL_REQUEST { - BasicHttpRequest deserialize(ByteBufAllocator alloc, ObjectInputStream input) throws Exception { - BasicHttpRequest request = super.deserialize(alloc, input); - int contentLength = input.readInt(); - ByteBuf content; - if (contentLength == 0) { - content = Unpooled.EMPTY_BUFFER; - } else { - content = alloc.buffer(contentLength); - try { - content.writeBytes(input, contentLength); - content.retain(); - } finally { - content.release(); - } - } - HttpHeaders trailingHeaders = deserializeHeaders(input); - return new BasicFullHttpRequest(request, request.headers(), trailingHeaders, content); - } - - boolean serialize(ObjectOutputStream output, BasicHttpRequest value) throws Exception { - if (super.serialize(output, value)) { - BasicFullHttpRequest request = (BasicFullHttpRequest) value; - ByteBuf content = request.content(); - int readableBytes = content.readableBytes(); - output.writeInt(readableBytes); - content.slice().readBytes(output, readableBytes); - serializeHeaders(output, request.trailingHeaders()); - return true; - } - return false; - } - }, - - MULTIPART_REQUEST { - BasicHttpRequest deserialize(ByteBufAllocator alloc, ObjectInputStream input) throws Exception { - BasicHttpRequest request = super.deserialize(alloc, input); - List parts = new LinkedList<>(); - try { - HttpHeaders partHeader = deserializeHeaders(input); - while (partHeader != null) { - int readableBytes = input.readInt(); - ByteBuf content; - if (readableBytes > 0) { - content = alloc.buffer(readableBytes); - try { - content.writeBytes(input, readableBytes); - content.retain(); - } finally { - content.release(); - } - } else { - content = Unpooled.EMPTY_BUFFER; - } - parts.add(new BasicFullHttpMultiPart(content, partHeader)); - partHeader = deserializeHeaders(input); - } - HttpHeaders trailingHeader = deserializeHeaders(input); - request = new BasicFullHttpMultiPartRequest( - request.protocolVersion(), - request.method(), - request.uri(), - parts, - request.headers(), - trailingHeader, - request.getRequestId(), - request.getRequestTimestamp(), - request.getRequestNanos()); - parts.forEach(FullHttpMultiPart::retain); - return request; - } finally { - parts.forEach(FullHttpMultiPart::release); - } - } - - boolean serialize(ObjectOutputStream output, BasicHttpRequest value) throws Exception { - if (super.serialize(output, value)) { - BasicFullHttpMultiPartRequest request = (BasicFullHttpMultiPartRequest) value; - for (FullHttpMultiPart part: request) { - serializeHeaders(output, part.headers()); - int readableBytes = part.content().readableBytes(); - output.writeInt(readableBytes); - part.content().slice().readBytes(output, readableBytes); - } - output.writeInt(-1); - serializeHeaders(output, request.trailingHeaders()); - return true; - } - return false; - } - }; - - BasicHttpRequest deserialize(ByteBufAllocator alloc, ObjectInputStream input) throws Exception { - HttpVersion protocol = HttpVersion.valueOf(input.readUTF()); - HttpMethod method = HttpMethod.valueOf(input.readUTF()); - String uri = input.readUTF(); - long requestTimestamp = input.readLong(); - long requestNanos = input.readLong(); - UUID uuid = new UUID(input.readLong(), input.readLong()); - HttpHeaders headers = deserializeHeaders(input); - return new BasicHttpRequest(protocol, method, uri, headers, uuid, requestTimestamp, requestNanos); - } - - boolean serialize(ObjectOutputStream output, BasicHttpRequest value) throws Exception { - output.writeUTF(value.protocolVersion().text()); - output.writeUTF(value.getMethodName()); - output.writeUTF(value.uri()); - output.writeLong(value.getRequestTimestamp()); - output.writeLong(value.getRequestNanos()); - output.writeLong(value.getRequestId().getMostSignificantBits()); - output.writeLong(value.getRequestId().getLeastSignificantBits()); - serializeHeaders(output, value.headers()); - return true; - } - } - - public BasicHttpRequestSerializer(ByteBufAllocator alloc) { - _alloc = alloc; - } - - @Override - public BasicHttpRequest deserialize(@Nonnull ByteBufInputStream inputStream) { - try (ObjectInputStream input = new ObjectInputStream(inputStream)) { - return ((Type) input.readUnshared()).deserialize(_alloc, input); - } catch (Throwable ex) { - return null; - } - } - - @Override - public boolean serialize(@Nonnull ByteBufOutputStream outputStream, @Nonnull BasicHttpRequest value) { - try (ObjectOutputStream output = new ObjectOutputStream(outputStream)) { - Type type; - if (value instanceof BasicFullHttpMultiPartRequest) { - type = Type.MULTIPART_REQUEST; - } else if (value instanceof BasicFullHttpRequest) { - type = Type.FULL_REQUEST; - } else { - type = Type.BASIC_REQUEST; - } - output.writeUnshared(type); - return type.serialize(output, value); - } catch (Throwable ex) { - return false; - } - } - - private static byte[] readBytes(ObjectInputStream input, byte[] bytes) throws IOException { - if (input.read(bytes) != bytes.length) { - throw new IllegalStateException(); - } - return bytes; - } - - private static HttpHeaders deserializeHeaders(ObjectInputStream input) throws Exception { - int size = input.readInt(); - if (size < 0) { - return null; - } - HttpHeaders headers = new DefaultHttpHeaders(false); - while (size != 0) { - AsciiString key = new AsciiString(readBytes(input, new byte[size]), false); - size = input.readInt(); - AsciiString value = new AsciiString(readBytes(input, new byte[size]), false); - headers.add(key, value); - size = input.readInt(); - } - return headers; - } - - private static void serializeHeaders(ObjectOutputStream output, HttpHeaders headers) throws Exception { - Iterator> it = headers.iteratorCharSequence(); - while (it.hasNext()) { - Map.Entry entry = it.next(); - byte[] bytes = AsciiString.of(entry.getKey()).toByteArray(); - output.writeInt(bytes.length); - output.write(bytes); - bytes = AsciiString.of(entry.getValue()).toByteArray(); - output.writeInt(bytes.length); - output.write(bytes); - } - output.writeInt(0); - } -} diff --git a/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/misc/BasicHttpResponse.java b/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/misc/BasicHttpResponse.java index 58fc0c7907..31de9dc3d5 100644 --- a/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/misc/BasicHttpResponse.java +++ b/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/misc/BasicHttpResponse.java @@ -9,7 +9,6 @@ import io.netty.util.AttributeKey; import io.netty.util.AttributeMap; import io.netty.util.DefaultAttributeMap; -import java.util.Optional; /** @@ -52,7 +51,10 @@ protected BasicHttpResponse(HttpResponse httpResponse, HttpHeaders headers) { } AttributeMap attributeMap() { - return Optional.ofNullable(_attributes).orElseGet(() -> _attributes = new DefaultAttributeMap()); + if (_attributes == null) { + _attributes = new DefaultAttributeMap(); + } + return _attributes; } /** diff --git a/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/misc/HttpMultiPart.java b/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/misc/HttpMultiPart.java index 3bde6ec73b..2d3fc47fe5 100644 --- a/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/misc/HttpMultiPart.java +++ b/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/misc/HttpMultiPart.java @@ -4,7 +4,6 @@ import com.linkedin.alpini.base.misc.HeaderUtils; import io.netty.handler.codec.http.HttpMessage; import io.netty.handler.codec.http.HttpResponseStatus; -import java.util.Optional; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -17,9 +16,10 @@ public interface HttpMultiPart extends HttpMessage { default HttpResponseStatus status() { try { - return Optional.ofNullable(headers().get(HeaderNames.X_MULTIPART_CONTENT_STATUS)) - .map(HttpResponseStatus::parseLine) - .orElse(HttpResponseStatus.OK); + String multiPartContentStatus = headers().get(HeaderNames.X_MULTIPART_CONTENT_STATUS); + return multiPartContentStatus == null + ? HttpResponseStatus.OK + : HttpResponseStatus.parseLine(multiPartContentStatus); } catch (Throwable ex) { MULTIPART_LOG.debug("Unparseable status", ex); return HttpResponseStatus.OK; diff --git a/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/misc/HttpToStringUtils.java b/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/misc/HttpToStringUtils.java index 50b4421b2c..6398406cb0 100644 --- a/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/misc/HttpToStringUtils.java +++ b/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/misc/HttpToStringUtils.java @@ -1,6 +1,5 @@ package com.linkedin.alpini.netty4.misc; -import io.netty.buffer.ByteBuf; import io.netty.handler.codec.http.FullHttpMessage; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.FullHttpResponse; @@ -9,7 +8,6 @@ import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpResponse; import io.netty.util.internal.StringUtil; -import java.util.Optional; /** @@ -34,10 +32,9 @@ static StringBuilder appendFullResponse(StringBuilder buf, FullHttpResponse res) appendInitialLine(buf, res); appendHeaders(buf, res.headers()); appendHeaders(buf, res.trailingHeaders()); - Optional.ofNullable(res) - .map(FullHttpResponse::content) - .filter(ByteBuf::isDirect) - .ifPresent(byteBuf -> buf.append("refCnt: ").append(byteBuf.refCnt()).append(StringUtil.NEWLINE)); + if (res != null && res.content().isDirect()) { + buf.append("refCnt: ").append(res.content().refCnt()).append(StringUtil.NEWLINE); + } removeLastNewLine(buf); return buf; } diff --git a/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/misc/MemoryPressureIndexUtils.java b/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/misc/MemoryPressureIndexUtils.java deleted file mode 100644 index ecf6d5ca14..0000000000 --- a/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/misc/MemoryPressureIndexUtils.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.linkedin.alpini.netty4.misc; - -import com.linkedin.alpini.base.misc.HeaderNames; -import com.linkedin.alpini.base.misc.MemoryPressureIndexMonitor; -import com.linkedin.alpini.base.misc.StringToNumberUtils; -import io.netty.handler.codec.http.FullHttpMessage; -import io.netty.handler.codec.http.HttpMessage; -import io.netty.handler.codec.http.HttpRequest; -import io.netty.handler.codec.http.HttpResponse; -import io.netty.util.AttributeKey; -import java.util.Objects; -import java.util.Optional; -import java.util.function.Function; - - -public enum MemoryPressureIndexUtils { - SINGLETON; - - // Some get Request's having contentLength set to zero. Such request would not change the value of MPI. - // So we added a MINIMUM_BYTES_TO_ADD for them. Given that MPI is a guessing instead of an accurate calculation of - // memory allocation, giving a consistent number to all simple requests that don't have body should be fine. - private static final int MINIMUM_BYTES_TO_ADD = 32; - - public static final String X_ESPRESSO_REQUEST_ID = "X-ESPRESSO-Request-Id"; - - private static final AttributeKey REQUEST_ID = - AttributeKey.valueOf(MemoryPressureIndexMonitor.class, "RequestID"); - - private static final Function> DEFAULT_REQUEST_TO_KEY_FUNCTION = - r -> Optional.ofNullable(r.headers().get(X_ESPRESSO_REQUEST_ID)); - - private static final Function> DEFAULT_RESPONSE_TO_KEY_FUNCTION = - r -> Optional.ofNullable(r.headers().get(X_ESPRESSO_REQUEST_ID)); - - public static Function> defaultRequestToKeyFunction() { - return DEFAULT_REQUEST_TO_KEY_FUNCTION; - } - - public static Function> defaultResponseToKeyFunction() { - return DEFAULT_RESPONSE_TO_KEY_FUNCTION; - } - - public static AttributeKey requestId() { - return REQUEST_ID; - } - - /** - * Try the best to get ContentLength - * 1. First it is simply get the length from the headers. If the contentLength is 0, return a fixed length - * @param m Http message - * @return Content length - */ - public static Optional getContentLength(HttpMessage m) { - Objects.requireNonNull(m, "HttpMessage"); - return StringToNumberUtils.fromString(m.headers().get(HeaderNames.CONTENT_LENGTH)).map(length -> { - if (length <= 0 && m instanceof FullHttpMessage && ((FullHttpMessage) m).content() != null) { - length = ((FullHttpMessage) m).content().readableBytes(); - } - return length <= 0 ? MINIMUM_BYTES_TO_ADD : length; - }); - } - - public static int getMinimumBytesToAdd() { - return MINIMUM_BYTES_TO_ADD; - } -} diff --git a/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/pool/ChannelPoolManager.java b/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/pool/ChannelPoolManager.java index a3806c971d..5d3e125e2a 100644 --- a/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/pool/ChannelPoolManager.java +++ b/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/pool/ChannelPoolManager.java @@ -1,7 +1,5 @@ package com.linkedin.alpini.netty4.pool; -import com.linkedin.alpini.base.monitoring.CallTracker; -import com.linkedin.alpini.base.monitoring.NullCallTracker; import com.linkedin.alpini.consts.QOS; import io.netty.channel.Channel; import io.netty.channel.EventLoop; @@ -9,10 +7,6 @@ import io.netty.util.concurrent.Future; import io.netty.util.concurrent.ImmediateEventExecutor; import io.netty.util.concurrent.Promise; -import java.net.SocketAddress; -import java.util.Collections; -import java.util.Map; -import java.util.Optional; import javax.annotation.CheckReturnValue; import javax.annotation.Nonnull; @@ -43,18 +37,6 @@ default int subpoolCount() { return executorCount(); } - /** - * Returns the total number of channels that are in use. - * @return channel count - */ - int activeCount(); - - /** - * Returns the total number of channels that are open. - * @return channel count - */ - int openConnections(); - /** * Acquire a channel from the pool. * @param hostNameAndPort host name and port @@ -121,193 +103,4 @@ default Future open(@Nonnull String hostNameAndPort) { */ @Nonnull Future close(@Nonnull String hostNameAndPort); - - /** - * Close all open connections. - * @return future which is completed when done - */ - @Nonnull - Future closeAll(); - - /** - * Obtain the PoolStats for the specified host and port. - * @param hostNameAndPort Host and port string - * @return Optional PoolStats - */ - @CheckReturnValue - @Nonnull - Optional getPoolStats(@Nonnull String hostNameAndPort); - - /** - * Obtain the PoolStats for all connection pools. - * @return Map of PoolStats - */ - @CheckReturnValue - @Nonnull - Map getPoolStats(); - - interface Stats { - /** - * Name of the stats - * @return stats name - */ - @Nonnull - String name(); - - /** - * The number of active (checked out) channels - * @return channel count - */ - int activeCount(); - - /** - * The number of pending requests for channels - * @return waiting count - */ - int waitingCount(); - - /** - * Call tracker for the {@link ChannelPoolManager#acquire(String, String, QOS)} method - * @return call tracker - */ - @Nonnull - CallTracker acquireCallTracker(); - - /** - * Call tracker which tracks busy (checked out) channels - * @return call tracker - */ - @Nonnull - CallTracker busyCallTracker(); - } - - interface PoolStats extends Stats { - SocketAddress remoteAddress(); - - /** - * The number of open channels - * @return channel count - */ - int openConnections(); - - long createCount(); - - long closeCount(); - - long closeErrorCount(); - - long closeBadCount(); - - default int inFlightCount() { - return 0; - } - - /** - * Returns {@literal true} when at least the minimum number of connections exist in the pool. - */ - boolean isHealthy(); - - long totalActiveStreamCounts(); - - long currentStreamChannelsReused(); - - long totalStreamChannelsReused(); - - default long totalStreamCreations() { - return 0; - } - - default long totalChannelReusePoolSize() { - return 0; - } - - default long getActiveStreamsLimitReachedCount() { - return 0; - } - - default long getTotalAcquireRetries() { - return 0; - } - - default long getTotalActiveStreamChannels() { - return 0; - } - - /** - * Exposes Pool Stats - * @return - */ - default Map getThreadPoolStats() { - return Collections.emptyMap(); - } - - default double getAvgResponseTimeOfLatestPings() { - return 0; - } - - default CallTracker http2PingCallTracker() { - return NullCallTracker.INSTANCE; - } - } - - /** - * Represents IoWorker stats - */ - interface ThreadPoolStats { - int getMaxConnections(); - - int getMaxPendingAcquires(); - - int getAcquiredChannelCount(); - - int getPendingAcquireCount(); - - long getActiveStreamCount(); - - long getActiveStreamChannelReUsed(); - - long getStreamChannelReUsedCount(); - - long getTotalStreamCreations(); - - long getChannelReusePoolSize(); - - long getActiveStreamsLimitReachedCount(); - - long getTotalAcquireRetries(); - - long getTotalActiveStreamChannels(); - - boolean isClosed(); - - int getConnectedChannels(); - - int getH2ActiveConnections(); - } - - /** - * Obtain the QueueStats for the specified host and port. - * @param queueName Queue name - * @return Optional QueueStats - */ - @CheckReturnValue - @Deprecated - @Nonnull - default Optional getQueueStats(@Nonnull String queueName) { - return Optional.empty(); - } - - /** - * Obtain the QueueStats for all named queues. - * @return Map of QueueStats - */ - @CheckReturnValue - @Deprecated - @Nonnull - default Map getQueueStats() { - return Collections.emptyMap(); - } - - interface QueueStats extends Stats { - } } diff --git a/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/pool/ChannelPoolManagerImpl.java b/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/pool/ChannelPoolManagerImpl.java index 56a62fa7d1..f196fe0c2e 100755 --- a/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/pool/ChannelPoolManagerImpl.java +++ b/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/pool/ChannelPoolManagerImpl.java @@ -10,9 +10,7 @@ import com.linkedin.alpini.base.monitoring.CallTracker; import com.linkedin.alpini.base.monitoring.NullCallTracker; import com.linkedin.alpini.consts.QOS; -import com.linkedin.alpini.netty4.handlers.AllChannelsHandler; import com.linkedin.alpini.netty4.handlers.Http2PingSendHandler; -import com.linkedin.alpini.netty4.misc.BalancedEventLoopGroup; import com.linkedin.alpini.netty4.misc.ExceptionWithResponseStatus; import com.linkedin.alpini.netty4.misc.Futures; import com.linkedin.alpini.netty4.misc.Http2Utils; @@ -39,22 +37,18 @@ import java.net.InetSocketAddress; import java.net.SocketAddress; import java.net.UnknownHostException; -import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.EnumMap; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; -import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Optional; import java.util.Queue; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; -import java.util.concurrent.CompletionStage; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.ConcurrentMap; @@ -71,7 +65,6 @@ import java.util.function.Function; import java.util.function.IntSupplier; import java.util.function.Supplier; -import java.util.stream.Collectors; import javax.annotation.Nonnegative; import javax.annotation.Nonnull; import org.apache.logging.log4j.Level; @@ -88,8 +81,6 @@ public class ChannelPoolManagerImpl implements ChannelPoolManager { private static final ImmediateEventExecutor IMMEDIATE = ImmediateEventExecutor.INSTANCE; private static final Future COMPLETED_VOID_FUTURE = IMMEDIATE.newSucceededFuture(null); - private static final long CLOSE_ALL_DELAY = 1000L; - private static final QOS[] QOS_HIGH_NORMAL_LOW = { QOS.HIGH, QOS.NORMAL, QOS.LOW }; private static final QOS[] QOS_NORMAL_HIGH_LOW = { QOS.NORMAL, QOS.HIGH, QOS.LOW }; private static final QOS[] QOS_LOW_HIGH_NORMAL = { QOS.LOW, QOS.HIGH, QOS.NORMAL }; @@ -115,15 +106,11 @@ public class ChannelPoolManagerImpl implements ChannelPoolManager { private final IntSupplier _eventLoopConcurrency; private final EventLoopGroup _workerEventLoopGroup; private final Consumer _runInEveryThread; - private final LongAdder _globalActiveCount = new LongAdder(); - private final LongAdder _globalChannelCount = new LongAdder(); - - private AllChannelsHandler _allChannelsHandler; - private int _maxWaitersPerPool; + private final int _maxWaitersPerPool; - private BooleanSupplier _enableFairScheduling = () -> true; - private BooleanSupplier _enableDeferredExecution = () -> false; + private final BooleanSupplier _enableFairScheduling = () -> true; + private final BooleanSupplier _enableDeferredExecution = () -> false; /** Use a named thread factory for the handling channel released */ private static final ThreadFactory CHANNEL_RELEASED_FACTORY = new NamedThreadFactory("channel-released"); @@ -239,89 +226,41 @@ public int subpoolCount() { return _eventLoopConcurrency.getAsInt(); } - @Override - public int activeCount() { - return _globalActiveCount.intValue(); - } - - @Override - public int openConnections() { - return _globalChannelCount.intValue(); - } - @Nonnull - protected CallTracker createHostAcquireCallTracker(@Nonnull String hostAndPort) { + protected CallTracker createHostAcquireCallTracker() { return NullCallTracker.INSTANCE; } @Nonnull - protected CallTracker createHostBusyCallTracker(@Nonnull String hostAndPort) { + protected CallTracker createHostBusyCallTracker() { return NullCallTracker.INSTANCE; } @Nonnull @Deprecated - protected CallTracker createQueueAcquireCallTracker(@Nonnull String queueName) { + protected CallTracker createQueueAcquireCallTracker() { return NullCallTracker.INSTANCE; } @Nonnull @Deprecated - protected CallTracker createQueueBusyCallTracker(@Nonnull String queueName) { + protected CallTracker createQueueBusyCallTracker() { return NullCallTracker.INSTANCE; } - @Override - @Nonnull - public Optional getPoolStats(@Nonnull String hostAndPort) { - return Optional.ofNullable(_pools.get(hostAndPort)).map(Pool::poolStats); - } - - @Override - @Nonnull - public Map getPoolStats() { - return new ArrayList<>(_pools.values()).stream() - .map(Pool::poolStats) - .collect(Collectors.toMap(PoolStats::name, Function.identity())); - } - - public void setEnableFairScheduling(@Nonnull BooleanSupplier enableFairScheduling) { - _enableFairScheduling = enableFairScheduling; - } - - public void setEnableDeferredExecution(@Nonnull BooleanSupplier enableDeferredExecution) { - _enableDeferredExecution = enableDeferredExecution; - } - - public void setAllChannelsHandler(@Nonnull AllChannelsHandler allChannelsHandler) { - _allChannelsHandler = allChannelsHandler; - } - - private class Pool implements ChannelPoolHandler, PoolStats { + private class Pool implements ChannelPoolHandler { final String _hostAndPort; final InetSocketAddress _address; final Set _all = new CopyOnWriteArraySet<>(); // Single Queue for all threads. private final ThreadQueue _globalThreadQueue; private final ThreadLocal _local; - private final LongAdder _activeCount = new LongAdder(); - private final LongAdder _createCount = new LongAdder(); - private final LongAdder _closeCount = new LongAdder(); private final LongAdder _waitingCount = new LongAdder(); - private final LongAdder _inFlightCount = new LongAdder(); - private final LongAdder _closeErrorCount = new LongAdder(); - private final LongAdder _closeBadCount = new LongAdder(); - private final Http2PingHelper _http2PingHelper; private Iterator _channelPoolIterator = Collections.emptyIterator(); private boolean _isClosing = false; private final ChannelFutureListener _closeCountListener = future -> { - if (!future.isSuccess()) { - _closeErrorCount.increment(); - } else if (Boolean.TRUE.equals(future.channel().attr(FAILED_HEALTH_CHECK).get())) { - _closeBadCount.increment(); - } channelClosed(future.channel()); }; @@ -332,8 +271,8 @@ private class Pool implements ChannelPoolHandler, PoolStats { assert address.isUnresolved(); _address = address; _hostAndPort = address.getHostString() + ":" + address.getPort(); - _acquireCallTracker = createHostAcquireCallTracker(_hostAndPort); - _busyCallTracker = createHostBusyCallTracker(_hostAndPort); + _acquireCallTracker = createHostAcquireCallTracker(); + _busyCallTracker = createHostBusyCallTracker(); if (enablePeriodicPing()) { _http2PingHelper = new Http2PingHelper(); } else { @@ -376,19 +315,6 @@ private Channel getHttp2ChannelToPingFromChannelPool() { } } - @Nonnull - PoolStats poolStats() { - return this; - } - - public Map getThreadPoolStats() { - Map map = new HashMap<>(); - for (ThreadQueue threadQueue: _all) { - map.put(threadQueue._threadName, threadQueue); - } - return map; - } - private void sendPing() { if (_http2PingHelper != null) { Channel channel = getHttp2ChannelToPingFromChannelPool(); @@ -402,55 +328,6 @@ public Http2PingSendHandler getHttp2PingSendHandler() { return _http2PingHelper == null ? null : _http2PingHelper.getHttp2PingSendHandler(); } - @Override - public double getAvgResponseTimeOfLatestPings() { - return _http2PingHelper == null ? 0 : _http2PingHelper.getAvgResponseTimeOfLatestPings(); - } - - @Override - public long totalActiveStreamCounts() { - return getThreadPoolStats().entrySet() - .stream() - .map(Map.Entry::getValue) - .mapToLong(ThreadPoolStats::getActiveStreamCount) - .sum(); - } - - @Override - public long currentStreamChannelsReused() { - return getThreadPoolStats().values().stream().mapToLong(ThreadPoolStats::getActiveStreamChannelReUsed).sum(); - } - - @Override - public long totalStreamChannelsReused() { - return getThreadPoolStats().values().stream().mapToLong(ThreadPoolStats::getStreamChannelReUsedCount).sum(); - } - - @Override - public long totalStreamCreations() { - return getThreadPoolStats().values().stream().mapToLong(ThreadPoolStats::getTotalStreamCreations).sum(); - } - - @Override - public long totalChannelReusePoolSize() { - return getThreadPoolStats().values().stream().mapToLong(ThreadPoolStats::getChannelReusePoolSize).sum(); - } - - @Override - public long getActiveStreamsLimitReachedCount() { - return getThreadPoolStats().values().stream().mapToLong(ThreadPoolStats::getActiveStreamsLimitReachedCount).sum(); - } - - @Override - public long getTotalAcquireRetries() { - return getThreadPoolStats().values().stream().mapToLong(ThreadPoolStats::getTotalAcquireRetries).sum(); - } - - @Override - public long getTotalActiveStreamChannels() { - return getThreadPoolStats().values().stream().mapToLong(ThreadPoolStats::getTotalActiveStreamChannels).sum(); - } - Future close() { _isClosing = true; Future[] futures = _all.stream().map(ThreadQueue::close).toArray(Future[]::new); @@ -469,8 +346,6 @@ private ThreadQueue get() { private void channelClosed(Channel ch) { _log.debug("channelClosed({}/{})", ch.id(), ch.eventLoop()); - _closeCount.increment(); - _globalChannelCount.decrement(); channelReleased(ch); } @@ -482,14 +357,6 @@ void incrementDone() { _waitingCount.decrement(); } - void incrementInFlight() { - _inFlightCount.increment(); - } - - void decrementInFlight() { - _inFlightCount.decrement(); - } - @Override public void channelReleased(Channel ch) { // TODO: Uncomment after we fix logging @@ -499,9 +366,6 @@ public void channelReleased(Channel ch) { void complete(long now, Supplier callCompletion) { if (callCompletion != null) { - _activeCount.decrement(); - _globalActiveCount.decrement(); - // Offload the call graphs to a different thread CHANNEL_RELEASED_EXECUTOR.get().execute(() -> callCompletion.get().close(now)); } @@ -516,72 +380,14 @@ public void channelAcquired(Channel ch) { public void channelCreated(Channel ch) { if (!(ch instanceof Http2StreamChannel)) { _log.debug("channelCreated({}/{})", ch.id(), ch.eventLoop()); - _createCount.increment(); - _globalChannelCount.increment(); ch.closeFuture().addListener(_closeCountListener); } } - @Override - @Nonnull - public String name() { - return _hostAndPort; - } - - @Override - public int activeCount() { - int h2ActiveCount = 0; - boolean isUsingHttp2Connections = false; - for (ThreadPoolStats stats: getThreadPoolStats().values()) { - int h2Active = stats.getH2ActiveConnections(); - if (h2Active > -1) { - h2ActiveCount += h2Active; - isUsingHttp2Connections = true; - } - } - return isUsingHttp2Connections ? h2ActiveCount : _activeCount.intValue(); - } - - @Override public SocketAddress remoteAddress() { return _address; } - @Override - public long createCount() { - return _createCount.longValue(); - } - - @Override - public long closeCount() { - return _closeCount.longValue(); - } - - @Override - public long closeErrorCount() { - return _closeErrorCount.longValue(); - } - - @Override - public long closeBadCount() { - return _closeBadCount.longValue(); - } - - @Override - public int inFlightCount() { - return _inFlightCount.intValue(); - } - - @Override - public int openConnections() { - return getThreadPoolStats().entrySet() - .stream() - .map(Map.Entry::getValue) - .mapToInt(ThreadPoolStats::getConnectedChannels) - .sum(); - } - - @Override public boolean isHealthy() { for (ThreadQueue q: _all.toArray(new ThreadQueue[0])) { // The concurrentHashMap can't have a null q. @@ -596,33 +402,19 @@ public boolean isClosing() { return _isClosing; } - @Override public int waitingCount() { return _waitingCount.intValue(); } - @Override @Nonnull public CallTracker acquireCallTracker() { return _acquireCallTracker; } - @Override @Nonnull public CallTracker busyCallTracker() { return _busyCallTracker; } - - @Override - public CallTracker http2PingCallTracker() { - return _http2PingHelper == null ? NullCallTracker.INSTANCE : _http2PingHelper.pingCallTracker(); - } - - @Override - public String toString() { - return "activeCount=" + activeCount() + ", openConnections=" + openConnections() + ", waitingCount=" - + waitingCount(); - } } /** @@ -638,7 +430,7 @@ public String toString() { * ThreadQueue - Maintains a map * PoolQueue - Maintains a map > */ - private class ThreadQueue implements ThreadPoolStats { + private class ThreadQueue { final ManagedChannelPool _pool; final Map _perDBPoolQueue = _useH2GlobalPool ? new ConcurrentHashMap<>() : new HashMap<>(); Supplier> _close; @@ -658,11 +450,7 @@ private class ThreadQueue implements ThreadPoolStats { _pool = _channelPoolFactory.construct( ChannelPoolManagerImpl.this, pool, - _createConnectionsOnWorkerGroup - ? (_allChannelsHandler != null - ? new BalancedEventLoopGroup(_workerEventLoopGroup, _allChannelsHandler) - : _workerEventLoopGroup) - : eventLoopGroup, + _createConnectionsOnWorkerGroup ? _workerEventLoopGroup : eventLoopGroup, Objects.requireNonNull(address)); Supplier> closeNow = Lazy.of(() -> { @@ -702,81 +490,6 @@ private Channel getHttp2ChannelToPingFromChannelGroup() { } } - @Override - public int getMaxConnections() { - return _pool.getMaxConnections(); - } - - @Override - public int getMaxPendingAcquires() { - return _pool.getMaxPendingAcquires(); - } - - @Override - public int getAcquiredChannelCount() { - return _pool.getAcquiredChannelCount(); - } - - @Override - public int getPendingAcquireCount() { - return _pool.getPendingAcquireCount(); - } - - @Override - public long getActiveStreamCount() { - return _pool.getTotalActiveStreams(); - } - - @Override - public long getActiveStreamChannelReUsed() { - return _pool.getCurrentStreamChannelsReused(); - } - - @Override - public long getStreamChannelReUsedCount() { - return _pool.getTotalStreamChannelsReused(); - } - - @Override - public long getTotalStreamCreations() { - return _pool.getTotalStreamCreations(); - } - - @Override - public long getChannelReusePoolSize() { - return _pool.getChannelReusePoolSize(); - } - - @Override - public long getActiveStreamsLimitReachedCount() { - return _pool.getActiveStreamsLimitReachedCount(); - } - - @Override - public long getTotalAcquireRetries() { - return _pool.getTotalAcquireRetries(); - } - - @Override - public long getTotalActiveStreamChannels() { - return _pool.getTotalActiveStreamChannels(); - } - - @Override - public boolean isClosed() { - return _pool.isClosed(); - } - - @Override - public int getConnectedChannels() { - return _pool.getConnectedChannels(); - } - - @Override - public int getH2ActiveConnections() { - return _pool.getH2ActiveConnections(); - } - public boolean isClosing() { return _pool.isClosing(); } @@ -853,19 +566,13 @@ void clearDoneWaiters(Pool pool) { private final class PromiseHolder implements Runnable { private final Promise _promise; private final Supplier _completion; - private final LongAdder _activeCount; private final Pool _pool; private long _completionTime; private Supplier _busyCallCompletion; - private PromiseHolder( - @Nonnull Promise promise, - @Nonnull Supplier completion, - LongAdder activeCount, - Pool pool) { + private PromiseHolder(@Nonnull Promise promise, @Nonnull Supplier completion, Pool pool) { _promise = promise; _completion = Lazy.of(completion); - _activeCount = activeCount; _pool = pool; } @@ -906,9 +613,6 @@ boolean trySuccess(Channel channel) { return false; } long now = Time.nanoTime(); - debugAcquireInTrySuccess(_promise); - _activeCount.increment(); - _globalActiveCount.increment(); Supplier supplier = Lazy.of(() -> _pool.busyCallTracker().startCall(now)); _pool.complete(now, channel.attr(_busyKey).getAndSet(supplier)); if (_promise.trySuccess(channel)) { @@ -918,8 +622,6 @@ boolean trySuccess(Channel channel) { return true; } else { channel.attr(_busyKey).set(null); - _activeCount.decrement(); - _globalActiveCount.decrement(); return false; } } @@ -1034,38 +736,6 @@ private Future close0(@Nonnull String hostName) { } } - @Override - @Nonnull - public Future closeAll() { - // stop the periodic ping if all host pools are closed - stopPeriodicPing(); - return Futures.asNettyFuture( - CompletableFuture.completedFuture(new ArrayList<>(_pools.keySet())) - .thenCompose(new Function, CompletionStage>() { - @Override - public CompletionStage apply(List poolNames) { - if (poolNames.isEmpty()) { - return CompletableFuture.completedFuture(null); - } - - return CompletableFuture - .allOf( - poolNames.stream() - .map(ChannelPoolManagerImpl.this::close) - .map(Futures::asCompletableFuture) - .toArray(CompletableFuture[]::new)) - .thenCompose(aVoid -> { - CompletableFuture delay = new CompletableFuture<>(); - _localThreadGroup.schedule(() -> delay.complete(null), CLOSE_ALL_DELAY, TimeUnit.MILLISECONDS); - return delay; - }) - .thenCompose( - (aVoid -> CompletableFuture.completedFuture(new ArrayList<>(_pools.keySet())) - .thenCompose(this))); - } - })); - } - protected Future localThreadGroup() { return _localThreadGroup.singleThreadGroup(); } @@ -1215,8 +885,6 @@ private Future acquireResolved( QOS qos, EventLoopGroup group, Promise promise) { - - debugAcquireResolved(promise); if (promise.isDone()) { _log.debug("promise completed before acquire"); return promise; @@ -1239,8 +907,6 @@ private Future acquireInEventLoop( String queueName, QOS qos, Promise promise) { - - debugAcquireInEventLoop(promise); if (promise.isDone()) { _log.debug("promise completed before acquire"); return promise; @@ -1255,7 +921,7 @@ private Future acquireInEventLoop( queue.incrementWait(); PromiseHolder promiseHolder = - new PromiseHolder(promise, () -> pool.acquireCallTracker().startCall(startTime), pool._activeCount, pool); + new PromiseHolder(promise, () -> pool.acquireCallTracker().startCall(startTime), pool); if (threadQueue._inFlight.get() > _maxWaitersPerPool / 2) { threadQueue.clearDoneWaiters(pool); @@ -1288,7 +954,6 @@ class Listener implements FutureListener, Runnable { : loop::execute; public void run() { - debugAcquireInEventLoopListener(promiseHolder.getFuture()); Promise acquirePromise = loop.newPromise(); acquirePromise.addListener(this); deferred.execute(() -> threadQueue._pool.acquire(acquirePromise)); @@ -1296,7 +961,7 @@ public void run() { @Override public void operationComplete(Future future) throws Exception { - decrementInFlightCount(threadQueue, pool); + decrementInFlightCount(threadQueue); if (!future.isSuccess() && pool.waitingCount() > 0) { if (isReschedulableError(future.cause())) { _log.debug("Retrying because {}", future.cause().getMessage()); @@ -1305,7 +970,7 @@ public void operationComplete(Future future) throws Exception { // Reschedule only if the waiters are greater than the number of requests inflight. if (threadQueue._inFlight.get() < waiters) { // small back-off - incrementInFlightCount(pool, threadQueue); + incrementInFlightCount(threadQueue); loop.schedule(this, 100, TimeUnit.MICROSECONDS); } return; @@ -1316,7 +981,7 @@ public void operationComplete(Future future) throws Exception { threadQueue.dispatch(pool, future); } } - incrementInFlightCount(pool, threadQueue); + incrementInFlightCount(threadQueue); new Listener().run(); } else { pool.incrementDone(); @@ -1340,14 +1005,12 @@ private static boolean isReschedulableError(Throwable t) { return t instanceof TimeoutException || Http2Utils.isTooManyActiveStreamsError(t); } - private void incrementInFlightCount(Pool pool, ThreadQueue threadQueue) { - pool.incrementInFlight(); + private void incrementInFlightCount(ThreadQueue threadQueue) { threadQueue._inFlight.incrementAndGet(); } - private void decrementInFlightCount(ThreadQueue threadQueue, Pool pool) { + private void decrementInFlightCount(ThreadQueue threadQueue) { threadQueue._inFlight.decrementAndGet(); - pool.decrementInFlight(); } static Throwable mapException(Throwable ex) { @@ -1389,30 +1052,6 @@ private Future release0(ChannelPool pool, Channel channel, Promise p } } - /** - * Used for unit-test verification - */ - void debugAcquireResolved(Future promise) { - } - - /** - * Used for unit-test verification - */ - void debugAcquireInEventLoop(Future promise) { - } - - /** - * Used for unit-test verification - */ - void debugAcquireInEventLoopListener(Future promise) { - } - - /** - * Used for unit-test verification - */ - void debugAcquireInTrySuccess(Future promise) { - } - /** * Used for unit-test verification */ diff --git a/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/pool/NettyDnsResolver.java b/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/pool/NettyDnsResolver.java deleted file mode 100644 index 9500492787..0000000000 --- a/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/pool/NettyDnsResolver.java +++ /dev/null @@ -1,129 +0,0 @@ -package com.linkedin.alpini.netty4.pool; - -import io.netty.channel.ChannelFactory; -import io.netty.channel.EventLoop; -import io.netty.channel.EventLoopGroup; -import io.netty.channel.socket.DatagramChannel; -import io.netty.handler.codec.dns.DnsRecord; -import io.netty.resolver.AddressResolver; -import io.netty.resolver.NameResolver; -import io.netty.resolver.dns.DefaultDnsCache; -import io.netty.resolver.dns.DefaultDnsServerAddressStreamProvider; -import io.netty.resolver.dns.DnsAddressResolverGroup; -import io.netty.resolver.dns.DnsCache; -import io.netty.resolver.dns.DnsCacheEntry; -import io.netty.resolver.dns.DnsNameResolverBuilder; -import io.netty.resolver.dns.DnsServerAddressStreamProvider; -import io.netty.util.concurrent.EventExecutor; -import io.netty.util.concurrent.Future; -import io.netty.util.concurrent.Promise; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.util.List; -import java.util.Optional; -import javax.annotation.Nonnull; - - -/** - * Created by acurtis on 3/30/17. - */ -public class NettyDnsResolver implements ChannelPoolResolver { - private static final int MIN_TTL = - Integer.parseUnsignedInt(System.getProperty("com.linkedin.alpini.netty4.pool.dnsTtlMin", "0")); - - private static final int MAX_TTL = - Integer.parseUnsignedInt(System.getProperty("com.linkedin.alpini.netty4.pool.dnsTtlMax", "" + Integer.MAX_VALUE)); - - private static final int NEG_TTL = - Integer.parseUnsignedInt(System.getProperty("com.linkedin.alpini.netty4.pool.dnsTtlNeg", "0")); - - private final DnsCache _cache = new DefaultDnsCache(MIN_TTL, MAX_TTL, NEG_TTL); - - private static final DnsRecord[] NO_ADDITIONALS = new DnsRecord[0]; - - private final DnsAddressResolverGroup _dnsResolverGroup; - - private final EventLoopGroup _eventLoopGroup; - - private final ThreadLocal> _resolver = new ThreadLocal<>(); - - public NettyDnsResolver(Class datagramChannelClass, EventLoopGroup eventLoopGroup) { - _dnsResolverGroup = - new DnsAddressResolverGroup(datagramChannelClass, DefaultDnsServerAddressStreamProvider.INSTANCE) { - @Override - protected NameResolver newNameResolver( - EventLoop eventLoop, - ChannelFactory channelFactory, - DnsServerAddressStreamProvider nameServerProvider) throws Exception { - return new DnsNameResolverBuilder(eventLoop).channelFactory(channelFactory) - .nameServerProvider(nameServerProvider) - .resolveCache(_cache) - .build(); - } - }; - _eventLoopGroup = eventLoopGroup; - } - - @Override - @Nonnull - public Future resolve( - @Nonnull InetSocketAddress address, - @Nonnull Promise promise) { - if (address.isUnresolved() && !promise.isDone()) { - - // Check the cache first - List cacheEntries = _cache.get(address.getHostString(), NO_ADDITIONALS); - if (cacheEntries != null) { - // Prefer IPv6 address - for (DnsCacheEntry entry: cacheEntries) { - if (entry.address() == null || entry.address().getAddress().length != 16) { - continue; - } - return promise.setSuccess(new InetSocketAddress(entry.address(), address.getPort())); - } - - // Otherwise, use IPv4 address - for (DnsCacheEntry entry: cacheEntries) { - if (entry.address() == null || entry.address().getAddress().length != 4) { - continue; - } - return promise.setSuccess(new InetSocketAddress(entry.address(), address.getPort())); - } - } - - EventLoop eventLoop = _eventLoopGroup.next(); - Thread current = Thread.currentThread(); - if (eventLoop.inEventLoop(current)) { - return resolve0(eventLoop, address, promise); - } else { - // Keep the work on the same IO Worker if possible - for (EventExecutor executor: _eventLoopGroup) { - if (executor.inEventLoop(current)) { - return resolve0((EventLoop) executor, address, promise); - } - } - - // Must perform a context switch since the current thread is not a member of EventLoopGroup - eventLoop.execute(() -> resolve0(eventLoop, address, promise)); - return promise; - } - } else { - return promise.setSuccess(address); - } - } - - private Future resolve0( - EventLoop eventLoop, - InetSocketAddress address, - Promise promise) { - return Optional.ofNullable(_resolver.get()).orElseGet(() -> { - AddressResolver resolver = _dnsResolverGroup.getResolver(eventLoop); - _resolver.set(resolver); - return resolver; - }).resolve(address, promise); - } - - public DnsAddressResolverGroup getAddressResolverGroup() { - return _dnsResolverGroup; - } -} diff --git a/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/pool/SimpleChannelPoolManagerImpl.java b/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/pool/SimpleChannelPoolManagerImpl.java index d92a856c28..95fbc6e25e 100644 --- a/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/pool/SimpleChannelPoolManagerImpl.java +++ b/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/pool/SimpleChannelPoolManagerImpl.java @@ -19,14 +19,10 @@ import io.netty.util.concurrent.FutureListener; import io.netty.util.concurrent.ImmediateEventExecutor; import io.netty.util.concurrent.Promise; -import io.netty.util.concurrent.PromiseCombiner; import io.netty.util.internal.PlatformDependent; import java.net.InetSocketAddress; import java.net.URI; import java.net.UnknownHostException; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; import java.util.Queue; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentMap; @@ -112,11 +108,7 @@ protected CallTracker createHostBusyCallTracker(@Nonnull String hostAndPort) { return CallTracker.create(); } - protected void initializeChannel(Channel ch) { - - } - - class HostPool implements ChannelPoolHandler, PoolStats { + class HostPool implements ChannelPoolHandler { private final Supplier _channelPool; private final ChannelGroup _channelGroup; private final ChannelGroup _activeGroup; @@ -164,21 +156,25 @@ public boolean remove(Object o) { public void channelReleased(Channel ch) throws Exception { CompletableFuture acquireTime = CompletableFuture.completedFuture(Time.nanoTime()); boolean success = _activeGroup.remove(ch); - Optional.ofNullable(ch.attr(BUSY_ATTRIBUTE_KEY).getAndSet(null)) - .ifPresent( - success - ? cc -> cc.thenAcceptBothAsync(acquireTime, CallCompletion::close, _statsExecutor) - : cc -> cc.thenAcceptBothAsync(acquireTime, CallCompletion::closeWithError, _statsExecutor)); + + CompletableFuture cc = ch.attr(BUSY_ATTRIBUTE_KEY).getAndSet(null); + if (cc != null) { + if (success) { + cc.thenAcceptBothAsync(acquireTime, CallCompletion::close, _statsExecutor); + } else { + cc.thenAcceptBothAsync(acquireTime, CallCompletion::closeWithError, _statsExecutor); + } + } } @Override public void channelAcquired(Channel ch) throws Exception { CompletableFuture acquireTime = CompletableFuture.completedFuture(Time.nanoTime()); - Optional - .ofNullable( - ch.attr(BUSY_ATTRIBUTE_KEY) - .getAndSet(acquireTime.thenApplyAsync(busyCallTracker()::startCall, _statsExecutor))) - .ifPresent(cc -> cc.thenAcceptBothAsync(acquireTime, CallCompletion::closeWithError, _statsExecutor)); + CompletableFuture cc = ch.attr(BUSY_ATTRIBUTE_KEY) + .getAndSet(acquireTime.thenApplyAsync(busyCallTracker()::startCall, _statsExecutor)); + if (cc != null) { + cc.thenAcceptBothAsync(acquireTime, CallCompletion::closeWithError, _statsExecutor); + } _activeGroup.add(ch); } @@ -186,40 +182,16 @@ public void channelAcquired(Channel ch) throws Exception { public void channelCreated(Channel ch) throws Exception { _channelGroup.add(ch); ch.attr(POOL_ATTRIBUTE_KEY).set(this); - initializeChannel(ch); } - @Override public InetSocketAddress remoteAddress() { return _socketAddress; } - @Override public int openConnections() { return _channelGroup.size(); } - @Override - public long createCount() { - return _createCount.longValue(); - } - - @Override - public long closeCount() { - return _closeCount.longValue(); - } - - @Override - public long closeErrorCount() { - return 0; - } - - @Override - public long closeBadCount() { - return 0; - } - - @Override public boolean isHealthy() { return _channelPool.get().isHealthy(); } @@ -228,21 +200,6 @@ public boolean isClosing() { return closing || _channelPool.get().isClosing(); } - @Override - public long totalActiveStreamCounts() { - return _channelPool.get().getTotalActiveStreams(); - } - - @Override - public long currentStreamChannelsReused() { - return _channelPool.get().getCurrentStreamChannelsReused(); - } - - @Override - public long totalStreamChannelsReused() { - return _channelPool.get().getTotalStreamChannelsReused(); - } - private FutureListener acquireListener0(Promise channelPromise) { return (Future future) -> { if (future.isSuccess()) { @@ -317,29 +274,20 @@ Future closeAsync() { } @Nonnull - @Override public String name() { return _channelGroup.name(); } - @Override public int activeCount() { return _activeGroup.size(); } - @Override - public int waitingCount() { - return acquireCallTracker().getCurrentConcurrency(); - } - @Nonnull - @Override public CallTracker acquireCallTracker() { return _acquireCallTracker; } @Nonnull - @Override public CallTracker busyCallTracker() { return _busyCallTracker; } @@ -356,22 +304,13 @@ public int subpoolCount() { return 1; } - @Override - public int activeCount() { - return _map.values().stream().mapToInt(HostPool::activeCount).sum(); - } - - @Override - public int openConnections() { - return _map.values().stream().mapToInt(HostPool::openConnections).sum(); - } - @Nonnull @Override public Future acquire(@Nonnull String hostNameAndPort, @Nonnull String queueName, @Nonnull QOS qos) { - return Optional.ofNullable(_map.get(hostNameAndPort)) - .map(HostPool::acquire) - .orElseGet(() -> ImmediateEventExecutor.INSTANCE.newFailedFuture(new UnknownHostException(hostNameAndPort))); + HostPool hostPool = _map.get(hostNameAndPort); + return hostPool == null + ? ImmediateEventExecutor.INSTANCE.newFailedFuture(new UnknownHostException(hostNameAndPort)) + : hostPool.acquire(); } @Nonnull @@ -381,15 +320,16 @@ public Future acquire( @Nonnull String hostNameAndPort, @Nonnull String queueName, @Nonnull QOS qos) { - return Optional.ofNullable(_map.get(hostNameAndPort)).map(hostPool -> { - if (eventLoop.inEventLoop()) { - return hostPool.acquire(eventLoop.newPromise()); - } else { - Promise promise = eventLoop.newPromise(); - eventLoop.execute(() -> hostPool.acquire(promise)); - return promise; - } - }).orElseGet(() -> eventLoop.newFailedFuture(new UnknownHostException(hostNameAndPort))); + HostPool hostPool = _map.get(hostNameAndPort); + if (hostPool == null) { + return eventLoop.newFailedFuture(new UnknownHostException(hostNameAndPort)); + } + if (eventLoop.inEventLoop()) { + return hostPool.acquire(eventLoop.newPromise()); + } + Promise promise = eventLoop.newPromise(); + eventLoop.execute(() -> hostPool.acquire(promise)); + return promise; } @Nonnull @@ -419,30 +359,7 @@ private Future open(@Nonnull String hostNameAndPort, Promise promise @Nonnull @Override public Future close(@Nonnull String hostNameAndPort) { - return Optional.ofNullable(_map.get(hostNameAndPort)) - .map(HostPool::closeAsync) - .orElseGet(() -> ImmediateEventExecutor.INSTANCE.newSucceededFuture(null)); - } - - @Nonnull - @Override - public Future closeAll() { - PromiseCombiner combiner = new PromiseCombiner(ImmediateEventExecutor.INSTANCE); - _map.values().stream().map(HostPool::closeAsync).forEach(combiner::add); - Promise promise = ImmediateEventExecutor.INSTANCE.newPromise(); - combiner.finish(promise); - return promise; - } - - @Nonnull - @Override - public Optional getPoolStats(@Nonnull String hostNameAndPort) { - return Optional.ofNullable(_map.get(hostNameAndPort)); - } - - @Nonnull - @Override - public Map getPoolStats() { - return new HashMap<>(_map); + HostPool hostPool = _map.get(hostNameAndPort); + return hostPool == null ? ImmediateEventExecutor.INSTANCE.newSucceededFuture(null) : hostPool.closeAsync(); } } diff --git a/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/ssl/SSLEngineFactory.java b/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/ssl/SSLEngineFactory.java index 004700c362..a651f5ab4c 100644 --- a/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/ssl/SSLEngineFactory.java +++ b/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/ssl/SSLEngineFactory.java @@ -3,7 +3,6 @@ import com.linkedin.alpini.base.ssl.SslFactory; import io.netty.buffer.ByteBufAllocator; import io.netty.handler.ssl.SslContext; -import java.util.Optional; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLParameters; @@ -36,24 +35,32 @@ static SSLEngineFactory adaptSSLFactory(SslFactory factory) { } boolean sslEnabled = factory.isSslEnabled(); - Optional sslContext = Optional.ofNullable(sslEnabled ? factory.getSSLContext() : null); + SSLContext sslContext = sslEnabled ? factory.getSSLContext() : null; SSLParameters sslParameters = sslEnabled ? factory.getSSLParameters() : null; return new SSLEngineFactory() { @Override public SSLEngine createSSLEngine(ByteBufAllocator alloc, String host, int port, boolean isServer) { - return init(sslContext.orElseThrow(IllegalStateException::new).createSSLEngine(host, port), isServer); + if (sslContext == null) { + throw new IllegalStateException(); + } + return init(sslContext.createSSLEngine(host, port), isServer); } @Override public SSLEngine createSSLEngine(ByteBufAllocator alloc, boolean isServer) { - return init(sslContext.orElseThrow(IllegalStateException::new).createSSLEngine(), isServer); + if (sslContext == null) { + throw new IllegalStateException(); + } + return init(sslContext.createSSLEngine(), isServer); } @Override public SSLSessionContext sessionContext(boolean isServer) { - return sslContext.map(sslCtx -> isServer ? sslCtx.getServerSessionContext() : sslCtx.getClientSessionContext()) - .orElseThrow(IllegalStateException::new); + if (sslContext == null) { + throw new IllegalStateException(); + } + return isServer ? sslContext.getServerSessionContext() : sslContext.getClientSessionContext(); } private SSLEngine init(SSLEngine engine, boolean isServer) { @@ -64,7 +71,7 @@ private SSLEngine init(SSLEngine engine, boolean isServer) { @Override public SSLContext getSSLContext() { - return sslContext.orElse(null); + return sslContext; } @Override diff --git a/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/ssl/SSLEngineFactoryImpl.java b/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/ssl/SSLEngineFactoryImpl.java index 5b675bcdd4..57064da36b 100644 --- a/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/ssl/SSLEngineFactoryImpl.java +++ b/internal/alpini/netty4/alpini-netty4-base/src/main/java/com/linkedin/alpini/netty4/ssl/SSLEngineFactoryImpl.java @@ -16,10 +16,8 @@ import java.util.Base64; import java.util.Collections; import java.util.HashSet; -import java.util.Optional; import java.util.Set; import java.util.function.Function; -import java.util.function.Predicate; import java.util.stream.Stream; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; @@ -49,8 +47,6 @@ public class SSLEngineFactoryImpl implements SSLEngineFactory { private String _keyStoreData; private SSLParameters _parameters; - private static final File NULL_FILE = new File("/dev/null"); - private SslContext _serverContext; private SslContext _clientContext; @@ -97,9 +93,8 @@ public SSLEngineFactoryImpl(Config config) throws Exception { _parameters.setWantClientAuth(true); } - Object keyStoreData = Optional.ofNullable(config.getKeyStoreData()) - .filter(((Predicate) String::isEmpty).negate()) - .orElse(null); + String keyStoreDataString = config.getKeyStoreData(); + Object keyStoreData = keyStoreDataString.isEmpty() ? null : keyStoreDataString; Function, SslContextBuilder> setup = SSLContextBuilder.setupContext( diff --git a/internal/alpini/netty4/alpini-netty4-base/src/main/java/io/netty/bootstrap/InstrumentedBootstrap.java b/internal/alpini/netty4/alpini-netty4-base/src/main/java/io/netty/bootstrap/InstrumentedBootstrap.java index 7b8a9b39e6..6d62b592fa 100644 --- a/internal/alpini/netty4/alpini-netty4-base/src/main/java/io/netty/bootstrap/InstrumentedBootstrap.java +++ b/internal/alpini/netty4/alpini-netty4-base/src/main/java/io/netty/bootstrap/InstrumentedBootstrap.java @@ -5,7 +5,6 @@ import io.netty.channel.ChannelFuture; import io.netty.channel.EventLoopGroup; import java.net.SocketAddress; -import java.util.Optional; import java.util.function.Function; import java.util.function.Supplier; import javax.annotation.Nonnull; @@ -33,9 +32,15 @@ public InstrumentedBootstrap(@Nonnull Function conne protected InstrumentedBootstrap(InstrumentedBootstrap old) { _connectCallTracker = old._connectCallTracker; - Optional.ofNullable(old.channelFactory()).ifPresent(this::channelFactory); - Optional.ofNullable(old.handler()).ifPresent(this::handler); - Optional.ofNullable(old.localAddress()).ifPresent(this::localAddress); + if (old.channelFactory() != null) { + channelFactory(old.channelFactory()); + } + if (old.handler() != null) { + handler(old.handler()); + } + if (old.localAddress() != null) { + localAddress(old.localAddress()); + } synchronized (old.options0()) { options0().putAll(old.options0()); } diff --git a/internal/alpini/netty4/alpini-netty4-base/src/main/java/io/netty/bootstrap/ResolveAllBootstrap.java b/internal/alpini/netty4/alpini-netty4-base/src/main/java/io/netty/bootstrap/ResolveAllBootstrap.java index da148842cb..730f357050 100644 --- a/internal/alpini/netty4/alpini-netty4-base/src/main/java/io/netty/bootstrap/ResolveAllBootstrap.java +++ b/internal/alpini/netty4/alpini-netty4-base/src/main/java/io/netty/bootstrap/ResolveAllBootstrap.java @@ -22,7 +22,6 @@ import java.util.Iterator; import java.util.List; import java.util.Objects; -import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.function.Function; @@ -222,8 +221,8 @@ private CompletionStage>> doResolve( if (resolveFailureCause != null || resolveFuture.getNow().isEmpty()) { // Failed to resolve immediately channel.close(); - resolve - .completeExceptionally(Optional.ofNullable(resolveFailureCause).orElseGet(UnresolvedAddressException::new)); + resolve.completeExceptionally( + resolveFailureCause == null ? new UnresolvedAddressException() : resolveFailureCause); } else { resolve.complete(Pair.make(channel, resolveFuture.getNow())); } diff --git a/internal/alpini/netty4/alpini-netty4-base/src/main/java/io/netty/handler/codec/http2/EspressoHttp2MultiplexHandler.java b/internal/alpini/netty4/alpini-netty4-base/src/main/java/io/netty/handler/codec/http2/EspressoHttp2MultiplexHandler.java index 2f0a94e3ec..5cd5b9f483 100644 --- a/internal/alpini/netty4/alpini-netty4-base/src/main/java/io/netty/handler/codec/http2/EspressoHttp2MultiplexHandler.java +++ b/internal/alpini/netty4/alpini-netty4-base/src/main/java/io/netty/handler/codec/http2/EspressoHttp2MultiplexHandler.java @@ -11,13 +11,11 @@ import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.EventLoop; -import io.netty.channel.EventLoopGroup; import io.netty.channel.ServerChannel; import io.netty.handler.codec.http2.Http2FrameCodec.DefaultHttp2FrameStream; import io.netty.util.AttributeKey; import io.netty.util.internal.ObjectUtil; import java.util.ArrayDeque; -import java.util.Optional; import java.util.Queue; import java.util.concurrent.atomic.AtomicInteger; import org.apache.logging.log4j.LogManager; @@ -241,12 +239,10 @@ public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exc // Register child channel on different event loop. ch = new EspressoHttp2MultiplexHandlerStreamChannel(stream, inboundStreamHandler); // Register the channel on either parent or offload to a different I/O worker. - ChannelFuture future = !offloadChildChannels - ? ctx.channel().eventLoop().register(ch) - : Optional.ofNullable(ctx.channel().eventLoop().parent()) - .map(EventLoopGroup::next) - .orElse(ctx.channel().eventLoop()) - .register(ch); + EventLoop eventLoop = ctx.channel().eventLoop(); + ChannelFuture future = (!offloadChildChannels || eventLoop.parent() == null) + ? eventLoop.register(ch) + : eventLoop.parent().next().register(ch); if (future.isDone()) { registerDone(future); } else { diff --git a/internal/alpini/netty4/alpini-netty4-base/src/test/java/com/linkedin/alpini/base/cache/TestPhantomHashMap.java b/internal/alpini/netty4/alpini-netty4-base/src/test/java/com/linkedin/alpini/base/cache/TestPhantomHashMap.java deleted file mode 100644 index cd7551f0cd..0000000000 --- a/internal/alpini/netty4/alpini-netty4-base/src/test/java/com/linkedin/alpini/base/cache/TestPhantomHashMap.java +++ /dev/null @@ -1,267 +0,0 @@ -package com.linkedin.alpini.base.cache; - -import com.linkedin.alpini.base.misc.Time; -import io.netty.buffer.UnpooledByteBufAllocator; -import java.io.ObjectStreamException; -import java.io.Serializable; -import java.lang.management.GarbageCollectorMXBean; -import java.lang.management.ManagementFactory; -import java.lang.management.MemoryMXBean; -import java.util.Arrays; -import java.util.List; -import java.util.LongSummaryStatistics; -import java.util.Random; -import java.util.concurrent.ThreadLocalRandom; -import org.mockito.Mockito; -import org.testng.Assert; -import org.testng.annotations.Test; - - -/** - * @author Antony T Curtis {@literal } - */ -public class TestPhantomHashMap { - public static class SerializableCharSequence implements Serializable, CharSequence { - private final char[] _backingArray; - private final int _start; - private final int _length; - - public SerializableCharSequence(String source) { - this(source.toCharArray()); - } - - public SerializableCharSequence(SerializableCharSequence source) { - this(source._backingArray, source._start, source._length); - } - - public SerializableCharSequence(char[] backingArray) { - this(backingArray, 0, backingArray.length); - } - - private SerializableCharSequence(char[] backingArray, int start, int length) { - if (start < 0 || length < 0 || start + length > backingArray.length) { - throw new ArrayIndexOutOfBoundsException(); - } - _backingArray = backingArray; - _start = start; - _length = length; - } - - public char[] toCharArray() { - if (_start == 0 && _length == _backingArray.length) { - return _backingArray; - } else { - return Arrays.copyOfRange(_backingArray, _start, _start + _length); - } - } - - @Override - public int length() { - return _length; - } - - @Override - public char charAt(int index) { - return _backingArray[index + _start]; - } - - @Override - public SerializableCharSequence subSequence(int start, int end) { - return new SerializableCharSequence(_backingArray, _start + start, end - start); - } - - @Override - public String toString() { - return String.valueOf(_backingArray, _start, _length); - } - - @Override - public int hashCode() { - return toString().hashCode(); - } - - @Override - public boolean equals(Object other) { - return toString().equals(other.toString()); - } - - private Object writeReplace() throws ObjectStreamException { - Proxy proxy = new Proxy(); - proxy.chars = toCharArray(); - return proxy; - } - - } - - private static class Proxy implements Serializable { - public char[] chars; - - private Object readResolve() throws ObjectStreamException { - return new SerializableCharSequence(chars); - } - } - - @Test(groups = "unit") - public void simpleTest() throws InterruptedException { - - SerializedMap store = - new ByteBufHashMap<>(ByteBufHashMap.javaSerialization(), UnpooledByteBufAllocator.DEFAULT::heapBuffer); - PhantomHashCache map = new PhantomHashCache<>(SerializableCharSequence::new); - - map.put(1L, store, new SerializableCharSequence("Hello World")); - map.put(42L, store, new SerializableCharSequence("Thanks for the fish!")); - - Assert.assertEquals(map.get(1L, store), new SerializableCharSequence("Hello World")); - Assert.assertEquals(map.get(42L, store), new SerializableCharSequence("Thanks for the fish!")); - - Assert.assertNotSame(map.get(1L, store), "Hello World"); - Assert.assertNotSame(map.get(42L, store), "Thanks for the fish!"); - - Assert.assertTrue(store.containsKey(1L)); - Assert.assertTrue(store.containsKey(42L)); - - Assert.assertTrue(map.removeEntry(1L, store)); - - // Do some horribly inefficient stuff, hope to cause the - // PhantomHashCache to eject the cached entry. - System.gc(); - for (int i = 1; i < 10000; i++) { - String s = "" + i; - for (int j = 1; j < 100; j++) { - s = s + j; - } - } - System.gc(); - - // Because we have done some horrible inefficient stuff to the heap, getting a value from - // the phantom cache would require retrieval from the backing store. Check that this occurs. - - // Deleted stuff should have been purged when their phantom references were collected. - Assert.assertNull(map.get(1L, store)); - - Assert.assertFalse(store.containsKey(1L)); - Assert.assertTrue(store.containsKey(42L)); - - SerializedMap mockStore = Mockito.mock(SerializedMap.class); - Mockito.when(mockStore.get(Mockito.any(Long.class))) - .thenAnswer(invocation -> store.get(invocation.getArguments()[0])); - - Assert.assertEquals(map.get(42L, mockStore), new SerializableCharSequence("Thanks for the fish!")); - - Mockito.verify(mockStore).get(Mockito.eq(Long.valueOf(42L))); - Mockito.verifyNoMoreInteractions(mockStore); - - store.clear(); - } - - @Test(groups = { "unit", "NoCoverage" }) - public void testMap() throws InterruptedException { - testMap(30000L, Integer.MAX_VALUE); // 30 second test, should enough to see the sawtooth capacity - } - - @Test(groups = { "unit", "Coverage" }) - public void testMapCoverage() throws InterruptedException { - testMap(5000L, Integer.MAX_VALUE); - } - - /** TODO: Fix test to use a predicable random seed with consistent result */ - @Test(groups = "functional", enabled = false) - public void testMapLeak() throws InterruptedException { - // a 5 minute test run - LongSummaryStatistics stats = testMap(300000L, 32767); - - // Check that the number of keys is reasonable. - Assert.assertTrue(stats.getCount() < 65536); - - // Check that the sliding window has vacated the lower value keys - Assert.assertTrue(stats.getMin() > 65536); - - // check that most of the keys are near the max. - Assert.assertTrue(stats.getAverage() > stats.getMax() - 65536); - } - - public LongSummaryStatistics testMap(long duration, long modulus) throws InterruptedException { - List mbs = ManagementFactory.getGarbageCollectorMXBeans(); - MemoryMXBean mmx = ManagementFactory.getMemoryMXBean(); - - SerializedMap store = - new ByteBufHashMap<>(ByteBufHashMap.javaSerialization(), UnpooledByteBufAllocator.DEFAULT::heapBuffer); - store.setMaxAllocatedMemory(1024 * 1024); - PhantomHashCache map = new PhantomHashCache<>(SerializableCharSequence::new); - StringBuilder builder = new StringBuilder(); - long endTime = Time.currentTimeMillis() + duration; - long nextTime = Time.currentTimeMillis(); - long count = 0; - long additions = 0; - long offset = 0; // makes a "sliding window" effect - - do { - long keyValue = ThreadLocalRandom.current().nextLong() & modulus; - keyValue += offset; - Random rnd = new Random(keyValue); - - for (int i = 10 + rnd.nextInt(100); i > 0; i--) { - builder.append(Integer.toHexString(rnd.nextInt())); - } - - SerializableCharSequence newValue = new SerializableCharSequence(builder.toString().toCharArray()); - SerializableCharSequence oldValue; - - // Do 20% puts, 5% deletes, the remainder are reads - int choice = ThreadLocalRandom.current().nextInt(100); - oldValue = map.get(keyValue, store); - if (choice > 80) { - if (oldValue == null) { - map.put(keyValue, store, newValue); - additions++; - } - } else if (choice > 75) { - if (!map.removeEntry(keyValue, store)) { - oldValue = null; - } - } else { - oldValue = map.get(keyValue, store); - } - - if (oldValue != null) { - if (oldValue == newValue) { - Assert.fail("That's strange!"); - } else { - Assert.assertEquals(oldValue, newValue); - } - } - - builder.setLength(0); // Thread.yield(); - - count++; - - long now = Time.currentTimeMillis(); - - if (nextTime + 1000L < now) { - offset += 1000; - int size = map.size(); - System.out.println( - "rate: " + ((count * 100) / (now - nextTime)) + " map size: " + size + " offset: " + offset + " additions: " - + additions + " purged: " + map._purgedEntriesCount); - nextTime = now; - count = 0; - mbs.forEach( - gc -> System.out.println( - "name=" + gc.getName() + " collectionCount=" + gc.getCollectionCount() + " collectionTime=" - + gc.getCollectionTime())); - System.out.println(mmx.getHeapMemoryUsage()); - builder.setLength(0); - } - - } while (Time.currentTimeMillis() < endTime); - - LongSummaryStatistics keyStatistics = - map._phantomCache.keySet().stream().mapToLong(Long::longValue).summaryStatistics(); - - System.out.println("key statistics: " + keyStatistics); - - store.clear(); - - return keyStatistics; - } -} diff --git a/internal/alpini/netty4/alpini-netty4-base/src/test/java/com/linkedin/alpini/base/cache/TestSerializedMap.java b/internal/alpini/netty4/alpini-netty4-base/src/test/java/com/linkedin/alpini/base/cache/TestSerializedMap.java deleted file mode 100644 index ee0512cae7..0000000000 --- a/internal/alpini/netty4/alpini-netty4-base/src/test/java/com/linkedin/alpini/base/cache/TestSerializedMap.java +++ /dev/null @@ -1,455 +0,0 @@ -package com.linkedin.alpini.base.cache; - -import com.linkedin.alpini.base.misc.Pair; -import com.linkedin.alpini.base.misc.Time; -import io.netty.buffer.ByteBufAllocator; -import io.netty.buffer.PooledByteBufAllocator; -import io.netty.buffer.UnpooledByteBufAllocator; -import java.io.Serializable; -import java.lang.management.GarbageCollectorMXBean; -import java.lang.management.ManagementFactory; -import java.lang.management.MemoryMXBean; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.IdentityHashMap; -import java.util.List; -import java.util.Map; -import java.util.Random; -import java.util.Set; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.Semaphore; -import java.util.concurrent.ThreadLocalRandom; -import java.util.concurrent.TimeUnit; -import javax.annotation.Nonnull; -import org.testng.Assert; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.Test; - - -/** - * Tests using Direct byte buffers are disabled because CRT tests have low default direct buffer size limits. - * Tests are also using a reduced MAX_ALLOCATED_SIZE because of the low CRT test heap limits. - * Unpooled tests are also disabled since they trigger OutOfMemoryErrors in the low heap limits, too. - * - * @author Antony T Curtis {@literal } - */ -public class TestSerializedMap { - private final static long TEST_MILLISECONDS = 30000L; // 100000L; - private final static long COVERAGE_MILLISECONDS = 5000L; // 100000L; - private final static long MAX_ALLOCATED_SIZE = 128 /* 640 */ * 1024 * 1024; - private final static int BLOCK_SIZE = 4 * 1024 * 1024; - - private final ByteBufAllocator _pooledAllocator = new PooledByteBufAllocator(); - - @AfterMethod(groups = "unit") - public void performGC() { - // System.gc() is a hint so we need to sleep in the hopes that it will do a gc sweep. - System.gc(); - sleep(1000); - System.gc(); - sleep(1000); - } - - // a spinning sleep because nothing much happens when main thread is sleeping. - private void sleep(long milliseconds) { - long endTime = Time.currentTimeMillis() + milliseconds; - do { - Thread.yield(); - } while (Time.currentTimeMillis() < endTime); - } - - @Test(groups = "unit") - public void simpleTest() { - - SerializedMap map = new ByteBufHashMap<>(ByteBufHashMap.javaSerialization(), 4097); - - Assert.assertEquals(map.getBlockSize(), 8192); - Assert.assertEquals(map.getMaxAllocatedMemory(), Long.MAX_VALUE); - Assert.assertEquals(map.getMaxBlockAge(TimeUnit.NANOSECONDS), Long.MAX_VALUE); - - map.put(1L, "Hello World"); - map.put(42L, "Thanks for the fish!"); - - Assert.assertEquals(map.get(1L), "Hello World"); - Assert.assertEquals(map.get(42L), "Thanks for the fish!"); - - Assert.assertNotSame(map.get(1L), "Hello World"); - Assert.assertNotSame(map.get(42L), "Thanks for the fish!"); - - Assert.assertTrue(map.containsKey(1L)); - Assert.assertTrue(map.containsKey(42L)); - - Assert.assertEquals(map.size(), 2); - - map.entrySet().removeIf(entry -> { - System.out.println("key=" + entry.getKey() + " value=" + entry.getValue()); - return true; - }); - - Assert.assertTrue(map.isEmpty()); - - map.clear(); // Must clear or Netty's ByteBuf complains about memory leak. - } - - @Test(groups = "unit", enabled = false) - public void testDefaultByteBuf() { - testMap(new TestRun<>(configure(new ByteBufHashMap<>(ByteBufHashMap.javaSerialization())), TEST_MILLISECONDS)); - } - - @Test(groups = "unit", enabled = false) - public void testPooledDirectByteBuf() { - testMap( - new TestRun<>( - configure(new ByteBufHashMap<>(ByteBufHashMap.javaSerialization(), _pooledAllocator::directBuffer)), - TEST_MILLISECONDS)); - } - - @Test(groups = "unit", enabled = false) - public void testPooledDirectByteBufMultithreaded() { - testMultithreadMap( - new TestRun<>( - configure(new ByteBufHashMap<>(ByteBufHashMap.javaSerialization(), _pooledAllocator::directBuffer)), - TEST_MILLISECONDS), - 4); - } - - @Test(groups = "unit", enabled = false) - public void testPooledDirectByteBufCache() { - SerializedMap map = - configure(new ByteBufHashMap<>(ByteBufHashMap.javaSerialization(), _pooledAllocator::directBuffer)); - testMap( - new TestRun<>( - new PhantomHashMap<>( - new PhantomHashCache<>(Element::new), - map.setMaxBlockAge(1500, TimeUnit.MILLISECONDS).setIncubationAge(500, TimeUnit.MILLISECONDS)), - TEST_MILLISECONDS)); - Assert.assertEquals(map.getMaxBlockAge(TimeUnit.MILLISECONDS), 1500); - Assert.assertEquals(map.getIncubationAge(TimeUnit.MILLISECONDS), 500); - } - - @Test(groups = "unit", enabled = false) - public void testPooledDirectByteBufCacheMultithreaded() { - SerializedMap map = - configure(new ByteBufHashMap<>(ByteBufHashMap.javaSerialization(), _pooledAllocator::directBuffer)); - testMultithreadMap( - new TestRun<>( - new PhantomHashMap<>( - new PhantomHashCache<>(Element::new), - map.setMaxBlockAge(1500, TimeUnit.MILLISECONDS).setIncubationAge(500, TimeUnit.MILLISECONDS)), - TEST_MILLISECONDS), - 4); - Assert.assertEquals(map.getMaxBlockAge(TimeUnit.MILLISECONDS), 1500); - Assert.assertEquals(map.getIncubationAge(TimeUnit.MILLISECONDS), 500); - } - - @Test(groups = { "unit", "NoCoverage" }) - public void testPooledHeapByteBuf() { - testPooledHeapByteBuf(TEST_MILLISECONDS); - } - - @Test(groups = { "unit", "Coverage" }) - public void testPooledHeapByteBufCoverage() { - testPooledHeapByteBuf(COVERAGE_MILLISECONDS); - } - - private void testPooledHeapByteBuf(long duration) { - testMap( - new TestRun<>( - configure(new ByteBufHashMap<>(ByteBufHashMap.javaSerialization(), _pooledAllocator::heapBuffer)), - duration)); - } - - @Test(groups = { "unit", "NoCoverage" }) - public void testPooledHeapByteBufMultithreaded() { - testPooledHeapByteBufMultithreaded(TEST_MILLISECONDS); - } - - @Test(groups = { "unit", "Coverage" }) - public void testPooledHeapByteBufMultithreadedCoverage() { - testPooledHeapByteBufMultithreaded(COVERAGE_MILLISECONDS); - } - - private void testPooledHeapByteBufMultithreaded(long duration) { - testMultithreadMap( - new TestRun<>( - configure(new ByteBufHashMap<>(ByteBufHashMap.javaSerialization(), _pooledAllocator::heapBuffer)), - duration), - 4); - } - - @Test(groups = { "unit", "NoCoverage" }) - public void testPooledHeapByteBufCache() { - testPooledHeapByteBufCache(TEST_MILLISECONDS); - } - - @Test(groups = { "unit", "Coverage" }) - public void testPooledHeapByteBufCacheCoverage() { - testPooledHeapByteBufCache(COVERAGE_MILLISECONDS); - } - - private void testPooledHeapByteBufCache(long duration) { - SerializedMap map = - configure(new ByteBufHashMap<>(ByteBufHashMap.javaSerialization(), _pooledAllocator::heapBuffer)); - testMap( - new TestRun<>( - new PhantomHashMap<>(new PhantomHashCache<>(Element::new), map.setMaxBlockAge(3, TimeUnit.SECONDS)), - duration)); - Assert.assertEquals(map.getMaxBlockAge(TimeUnit.SECONDS), 3); - } - - @Test(groups = { "unit", "NoCoverage" }) - public void testPooledHeapByteBufCacheMultithreaded() { - testPooledHeapByteBufCacheMultithreaded(TEST_MILLISECONDS); - } - - @Test(groups = { "unit", "Coverage" }) - public void testPooledHeapByteBufCacheMultithreadedCoverage() { - testPooledHeapByteBufCacheMultithreaded(COVERAGE_MILLISECONDS); - } - - private void testPooledHeapByteBufCacheMultithreaded(long duration) { - SerializedMap map = - configure(new ByteBufHashMap<>(ByteBufHashMap.javaSerialization(), _pooledAllocator::heapBuffer)); - testMultithreadMap( - new TestRun<>( - new PhantomHashMap<>(new PhantomHashCache<>(Element::new), map.setMaxBlockAge(3, TimeUnit.SECONDS)), - duration), - 4); - Assert.assertEquals(map.getMaxBlockAge(TimeUnit.SECONDS), 3); - } - - @Test(groups = "unit", enabled = false) - public void testUnpooledByteBuf() { - testMap( - new TestRun<>( - configure( - new ByteBufHashMap<>(ByteBufHashMap.javaSerialization(), UnpooledByteBufAllocator.DEFAULT::heapBuffer)), - TEST_MILLISECONDS)); - } - - @Test(groups = "unit", enabled = false) - public void testUnpooledByteBufMultithreaded() { - testMultithreadMap( - new TestRun<>( - configure( - new ByteBufHashMap<>(ByteBufHashMap.javaSerialization(), UnpooledByteBufAllocator.DEFAULT::heapBuffer)), - TEST_MILLISECONDS), - 4); - } - - @Test(groups = "unit", enabled = false) - public void testUnpooledByteBufCache() { - SerializedMap map = configure( - new ByteBufHashMap<>(ByteBufHashMap.javaSerialization(), UnpooledByteBufAllocator.DEFAULT::heapBuffer)); - testMap( - new TestRun<>( - new PhantomHashMap<>(new PhantomHashCache<>(Element::new), map.setMaxBlockAge(3, TimeUnit.SECONDS)), - TEST_MILLISECONDS)); - Assert.assertEquals(map.getMaxBlockAge(TimeUnit.SECONDS), 3); - } - - @Test(groups = "unit", enabled = false) - public void testUnpooledByteBufCacheMultithreaded() { - SerializedMap map = configure( - new ByteBufHashMap<>(ByteBufHashMap.javaSerialization(), UnpooledByteBufAllocator.DEFAULT::heapBuffer)); - testMultithreadMap( - new TestRun<>( - new PhantomHashMap<>(new PhantomHashCache<>(Element::new), map.setMaxBlockAge(3, TimeUnit.SECONDS)), - TEST_MILLISECONDS), - 4); - Assert.assertEquals(map.getMaxBlockAge(TimeUnit.SECONDS), 3); - } - - public SerializedMap configure(SerializedMap map) { - return map.setMaxAllocatedMemory(MAX_ALLOCATED_SIZE) - .setBlockSize(BLOCK_SIZE) - .setMaxBlockAge(Long.MAX_VALUE, TimeUnit.SECONDS); - } - - public Element generate(StringBuilder builder, long keyValue) { - Random rnd = new Random(keyValue); - - for (int i = 10 + rnd.nextInt(100); i > 0; i--) { - builder.append(Integer.toHexString(rnd.nextInt())); - } - return new Element(builder.toString().getBytes(StandardCharsets.US_ASCII)); - } - - public void testMultithreadMap(final TestRun run, int concurrency) { - Thread[] threads = new Thread[concurrency - 1]; - try { - CountDownLatch start = new CountDownLatch(1); - for (int i = 0; i < threads.length; i++) { - threads[i] = new Thread(() -> { - try { - start.await(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - testMap(run); - }); - } - - for (Thread t: threads) { - t.start(); - } - - start.countDown(); - testMap(run); - } finally { - for (Thread t: threads) { - try { - t.join(); - } catch (InterruptedException e) { - Assert.fail("join failed", e); - } - } - } - } - - public void testMap(final TestRun run) { - final Thd thd = run.add(new Thd()); - try { - StringBuilder builder = new StringBuilder(); - - do { - long keyValue = ThreadLocalRandom.current().nextLong() & Integer.MAX_VALUE; - - Element newValue; - Element oldValue; - - // Do 20% puts, 5% deletes, the remainder are reads - int choice = ThreadLocalRandom.current().nextInt(100); - if (choice > 80) { - newValue = generate(builder, keyValue); - oldValue = run.map.put(keyValue, newValue); - } else if (choice > 75) { - oldValue = run.map.remove(keyValue); - newValue = oldValue != null ? generate(builder, keyValue) : null; - } else { - oldValue = run.map.get(keyValue); - newValue = oldValue != null ? generate(builder, keyValue) : null; - } - - if (oldValue != null) { - if (oldValue == newValue) { - Assert.fail("That's strange!"); - } else { - Assert.assertEquals(oldValue, newValue); - } - } - - builder.setLength(0); - - thd.count++; - - } while (run.running()); - } finally { - run.remove(thd); - } - } - - private static class Thd { - long count; - } - - private static class TestRun { - final Map map; - final Set thdSet = Collections.newSetFromMap(new IdentityHashMap<>()); - final List mbs = ManagementFactory.getGarbageCollectorMXBeans(); - final MemoryMXBean mmx = ManagementFactory.getMemoryMXBean(); - final Semaphore semaphore = new Semaphore(1); - final Map> initialState = new HashMap<>(); - final long endTime; - long nextTime = Time.currentTimeMillis(); - long previousCount; - boolean ending; - - private TestRun(Map map, long testMillis) { - this.map = map; - endTime = Time.currentTimeMillis() + testMillis; - mbs.forEach(gc -> initialState.put(gc.getName(), Pair.make(gc.getCollectionCount(), gc.getCollectionTime()))); - } - - @Override - public void finalize() throws Throwable { - map.clear(); // Must clear or Netty's ByteBuf complains about memory leak. - super.finalize(); - } - - synchronized Thd add(Thd thd) { - if (this.thdSet.add(thd)) { - return thd; - } else { - throw new IllegalStateException("should not occur"); - } - } - - synchronized long count() { - return thdSet.stream().mapToLong(x -> x.count).sum(); - } - - synchronized boolean remove(Thd thd) { - return this.thdSet.remove(thd); - } - - boolean running() { - long now = Time.currentTimeMillis(); - boolean wasEnding = ending; - ending = now > endTime; - - if (((ending && !wasEnding) || nextTime + 1000L < now) && semaphore.tryAcquire()) { - try { - if (nextTime == now) { - return !wasEnding; - } - long count = count(); - long delta = count - previousCount; - System.out.println("rate: " + ((delta * 1000) / (now - nextTime)) + " map size: " + map.size()); - nextTime = now; - previousCount = count; - mbs.forEach(gc -> { - Pair initial = initialState.get(gc.getName()); - System.out.println( - "name=" + gc.getName() + " collectionCount=" + (gc.getCollectionCount() - initial.getFirst()) - + " collectionTime=" + (gc.getCollectionTime() - initial.getSecond())); - }); - System.out.println(mmx.getHeapMemoryUsage()); - } finally { - semaphore.release(); - } - } - - return !ending; - } - - } - - private static class Element implements Serializable { - private byte[] data; - - public Element() { - } - - public Element(@Nonnull byte[] data) { - this.data = data; - } - - public Element(@Nonnull Element orig) { - this(orig.data); - } - - @Override - public boolean equals(Object o) { - return this == o || (o instanceof Element && Arrays.equals(data, ((Element) o).data)); - } - - @Override - public int hashCode() { - return Arrays.hashCode(data); - } - } - -} diff --git a/internal/alpini/netty4/alpini-netty4-base/src/test/java/com/linkedin/alpini/netty4/handlers/TestAllChannelsHandler.java b/internal/alpini/netty4/alpini-netty4-base/src/test/java/com/linkedin/alpini/netty4/handlers/TestAllChannelsHandler.java deleted file mode 100644 index 9eecc4f60b..0000000000 --- a/internal/alpini/netty4/alpini-netty4-base/src/test/java/com/linkedin/alpini/netty4/handlers/TestAllChannelsHandler.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.linkedin.alpini.netty4.handlers; - -import io.netty.bootstrap.Bootstrap; -import io.netty.bootstrap.ServerBootstrap; -import io.netty.channel.Channel; -import io.netty.channel.DefaultEventLoopGroup; -import io.netty.channel.local.LocalAddress; -import io.netty.channel.local.LocalChannel; -import io.netty.channel.local.LocalServerChannel; -import org.testng.Assert; -import org.testng.annotations.Test; - - -/** - * @author Abhishek Andhavarapu - */ -@Test(groups = "unit") -public class TestAllChannelsHandler { - @Test - public void testLocalChannel() throws InterruptedException { - DefaultEventLoopGroup group = new DefaultEventLoopGroup(1); - LocalAddress addr = new LocalAddress("test"); - AllChannelsHandler allChannelsHandler = new AllChannelsHandler(); - - ServerBootstrap serverBootstrap = new ServerBootstrap().channel(LocalServerChannel.class) - .group(group) - .localAddress(addr) - .childHandler(allChannelsHandler); - - Channel server = serverBootstrap.bind().sync().channel(); - Assert.assertTrue(server.isRegistered()); - - Bootstrap clientBootstrap = new Bootstrap().channel(LocalChannel.class) - .group(group) - .remoteAddress(addr) - .handler(new SimpleChannelInitializer() { - @Override - protected void initChannel(LocalChannel ch) { - } - }); - - Channel client = clientBootstrap.connect().sync().channel(); - - String msg = "Hello World"; - - client.writeAndFlush(msg).sync(); - - // Assert channel is added to the Active HTTP2 connection. - Assert.assertEquals(allChannelsHandler.size(), 1); - - client.close().sync(); - server.close().sync(); - - group.shutdownGracefully().await(); - - // Assert channel is removed from the active HTTP/2 connections. - Assert.assertEquals(allChannelsHandler.size(), 0); - } -} diff --git a/internal/alpini/netty4/alpini-netty4-base/src/test/java/com/linkedin/alpini/netty4/handlers/TestChunkedResponse.java b/internal/alpini/netty4/alpini-netty4-base/src/test/java/com/linkedin/alpini/netty4/handlers/TestChunkedResponse.java deleted file mode 100644 index 7cfac3fa59..0000000000 --- a/internal/alpini/netty4/alpini-netty4-base/src/test/java/com/linkedin/alpini/netty4/handlers/TestChunkedResponse.java +++ /dev/null @@ -1,203 +0,0 @@ -package com.linkedin.alpini.netty4.handlers; - -import static io.netty.handler.codec.http.HttpMethod.GET; - -import com.linkedin.alpini.netty4.misc.BasicFullHttpResponse; -import com.linkedin.alpini.netty4.misc.ChunkedHttpResponse; -import io.netty.bootstrap.Bootstrap; -import io.netty.bootstrap.ServerBootstrap; -import io.netty.buffer.Unpooled; -import io.netty.channel.Channel; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelInitializer; -import io.netty.channel.ChannelOption; -import io.netty.channel.DefaultEventLoopGroup; -import io.netty.channel.EventLoopGroup; -import io.netty.channel.ServerChannel; -import io.netty.channel.SimpleChannelInboundHandler; -import io.netty.channel.local.LocalAddress; -import io.netty.channel.local.LocalChannel; -import io.netty.channel.local.LocalServerChannel; -import io.netty.handler.codec.http.DefaultFullHttpRequest; -import io.netty.handler.codec.http.DefaultHttpContent; -import io.netty.handler.codec.http.FullHttpRequest; -import io.netty.handler.codec.http.FullHttpResponse; -import io.netty.handler.codec.http.HttpClientCodec; -import io.netty.handler.codec.http.HttpContent; -import io.netty.handler.codec.http.HttpMessage; -import io.netty.handler.codec.http.HttpObjectAggregator; -import io.netty.handler.codec.http.HttpRequest; -import io.netty.handler.codec.http.HttpResponse; -import io.netty.handler.codec.http.HttpResponseStatus; -import io.netty.handler.codec.http.HttpUtil; -import io.netty.handler.codec.http.HttpVersion; -import io.netty.handler.codec.http.LastHttpContent; -import io.netty.handler.logging.LogLevel; -import io.netty.handler.logging.LoggingHandler; -import io.netty.util.concurrent.Promise; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.Iterator; -import java.util.Objects; -import java.util.Optional; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.testng.Assert; -import org.testng.annotations.Test; - - -/** - * Created by acurtis on 5/16/17. - */ -@Test(groups = { "unit", "unit-leak", "leak" }, singleThreaded = true) -public final class TestChunkedResponse extends AbstractLeakDetect { - private static final Logger LOG = LogManager.getLogger(TestChunkedResponse.class); - - @Test - public void testChunkedResponses() throws InterruptedException { - // org.apache.log4j.BasicConfigurator.configure(); - EventLoopGroup eventLoop = new DefaultEventLoopGroup(1); - ServerChannel serverChannel = null; - try { - - LocalAddress localAddress = new LocalAddress("testChunkedResponses"); - - ServerBootstrap serverBootstrap = new ServerBootstrap().group(eventLoop) - .channel(LocalServerChannel.class) - .childOption(ChannelOption.ALLOCATOR, POOLED_ALLOCATOR) - .childHandler(new ChannelInitializer() { - @Override - protected void initChannel(Channel ch) throws Exception { - ch.pipeline() - .addLast( - new BasicHttpServerCodec(), - new BasicHttpObjectAggregator(81920), - - // This handler limits the size of the sent chunks to 8192 by breaking it into 4000 byte chunks - new ChunkedResponseLimiter(8192, 4000), - - // This handler handles chunked responses which implement ChunkedHttpResponse - new ChunkedResponseHandler(), - - new LoggingHandler("server", LogLevel.DEBUG), - - new SimpleChannelInboundHandler() { - @Override - protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) - throws Exception { - - class ChunkedResponse extends BasicFullHttpResponse implements ChunkedHttpResponse { - private final Iterator _contentIterator; - - private ChunkedResponse( - HttpRequest httpRequest, - HttpResponseStatus status, - Iterator contentIterator) { - super(httpRequest, status, Unpooled.EMPTY_BUFFER); - HttpUtil.setTransferEncodingChunked(this, true); - _contentIterator = Objects.requireNonNull(contentIterator); - } - - @Override - public void writeChunkedContent( - ChannelHandlerContext ctx, - Promise writePromise) throws IOException { - - if (_contentIterator.hasNext()) { - - HttpContent chunk = _contentIterator.next(); - - if (chunk instanceof LastHttpContent) { - writePromise.setSuccess((LastHttpContent) chunk); - return; - } - - assert chunk.content().readableBytes() > 0; - assert !(chunk instanceof HttpMessage); - - ctx.writeAndFlush(chunk).addListener(future -> { - if (future.isSuccess()) { - writeChunkedContent(ctx, writePromise); - } else { - writePromise.setFailure(future.cause()); - } - }); - } else { - writePromise.setSuccess(LastHttpContent.EMPTY_LAST_CONTENT); - } - } - } - - HttpResponse response = new ChunkedResponse( - request, - HttpResponseStatus.OK, - Arrays - .asList( - new DefaultHttpContent( - encodeString("This is a simple chunk\n", StandardCharsets.UTF_8)), - new DefaultHttpContent(encodeString(request.uri(), StandardCharsets.UTF_8)), - LastHttpContent.EMPTY_LAST_CONTENT) - .iterator()); - - ctx.writeAndFlush(response); - } - }); - } - }); - - serverChannel = (ServerChannel) serverBootstrap.bind(localAddress).syncUninterruptibly().channel(); - BlockingQueue responses = new LinkedBlockingQueue<>(); - - Bootstrap bootstrap = - new Bootstrap().group(eventLoop).channel(LocalChannel.class).handler(new ChannelInitializer() { - @Override - protected void initChannel(Channel ch) throws Exception { - ch.pipeline() - .addLast( - new HttpClientCodec(), - new LoggingHandler("client", LogLevel.DEBUG), - new HttpObjectAggregator(81920), - new SimpleChannelInboundHandler() { - @Override - protected void channelRead0(ChannelHandlerContext ctx, FullHttpResponse msg) throws Exception { - responses.add(msg.retainedDuplicate()); - } - }); - } - }); - - Channel ch = bootstrap.connect(localAddress).syncUninterruptibly().channel(); - - HttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, GET, "/hello/world"); - - LOG.debug("Sending request: {}", request); - ch.writeAndFlush(request).syncUninterruptibly(); - - FullHttpResponse response = responses.take(); - LOG.debug("Received response: {}", response); - - Assert.assertEquals(response.status(), HttpResponseStatus.OK); - - Assert.assertEquals(response.content().toString(StandardCharsets.UTF_8), "This is a simple chunk\n/hello/world"); - - response.release(); - } finally { - Optional.ofNullable(serverChannel).ifPresent(Channel::close); - eventLoop.shutdownGracefully(); - } - } - - /** - * since TestNG tends to sort by method name, this tries to be the last test - * in the class. We do this because the AfterClass annotated methods may - * execute after other tests classes have run and doesn't execute immediately - * after the methods in this test class. - */ - @Test(alwaysRun = true) - public final void zz9PluralZAlpha() throws InterruptedException { - finallyLeakDetect(); - } -} diff --git a/internal/alpini/netty4/alpini-netty4-base/src/test/java/com/linkedin/alpini/netty4/handlers/TestClientConnectionTracker.java b/internal/alpini/netty4/alpini-netty4-base/src/test/java/com/linkedin/alpini/netty4/handlers/TestClientConnectionTracker.java deleted file mode 100644 index 1cdcfb7e31..0000000000 --- a/internal/alpini/netty4/alpini-netty4-base/src/test/java/com/linkedin/alpini/netty4/handlers/TestClientConnectionTracker.java +++ /dev/null @@ -1,257 +0,0 @@ -package com.linkedin.alpini.netty4.handlers; - -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.mockito.Mockito.withSettings; - -import io.netty.bootstrap.Bootstrap; -import io.netty.bootstrap.ServerBootstrap; -import io.netty.buffer.Unpooled; -import io.netty.channel.Channel; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelInitializer; -import io.netty.channel.ChannelPromise; -import io.netty.channel.EventLoopGroup; -import io.netty.channel.SimpleChannelInboundHandler; -import io.netty.channel.group.ChannelGroup; -import io.netty.channel.group.DefaultChannelGroup; -import io.netty.channel.nio.NioEventLoopGroup; -import io.netty.channel.socket.nio.NioServerSocketChannel; -import io.netty.channel.socket.nio.NioSocketChannel; -import io.netty.handler.codec.http.DefaultFullHttpRequest; -import io.netty.handler.codec.http.DefaultFullHttpResponse; -import io.netty.handler.codec.http.FullHttpRequest; -import io.netty.handler.codec.http.FullHttpResponse; -import io.netty.handler.codec.http.HttpClientCodec; -import io.netty.handler.codec.http.HttpMethod; -import io.netty.handler.codec.http.HttpObjectAggregator; -import io.netty.handler.codec.http.HttpRequest; -import io.netty.handler.codec.http.HttpResponse; -import io.netty.handler.codec.http.HttpResponseStatus; -import io.netty.handler.codec.http.HttpServerCodec; -import io.netty.handler.codec.http.HttpVersion; -import io.netty.util.NetUtil; -import java.io.IOException; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.UnknownHostException; -import java.nio.charset.StandardCharsets; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.ExecutionException; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.testng.Assert; -import org.testng.annotations.AfterClass; -import org.testng.annotations.Test; - - -public class TestClientConnectionTracker { - private EventLoopGroup _group = new NioEventLoopGroup(1);; - - private static final Logger LOG = LogManager.getLogger(InstrumentedTracker.class); - - @AfterClass - public void afterClass() { - Optional.ofNullable(_group).ifPresent(EventLoopGroup::shutdownGracefully); - } - - private ChannelHandlerContext preparedMockContextWithDifferentIpAddress(String hostName) throws UnknownHostException { - ChannelHandlerContext context = mock(ChannelHandlerContext.class, withSettings().stubOnly()); - Channel channel = mock(Channel.class, withSettings().stubOnly()); - when(context.channel()).thenReturn(channel); - - // the socketAddress's getAddress is final so can't be mocked. We have to use a real address. - InetSocketAddress socketAddress = new InetSocketAddress(hostName, 80); - when(channel.remoteAddress()).thenReturn(socketAddress); - - return context; - } - - private static class InstrumentedTracker extends ClientConnectionTracker { - public InstrumentedTracker(int connectionCountLimit, int connectionWarningResetThreshold) { - super(connectionCountLimit, connectionWarningResetThreshold); - } - - @Override - public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { - ClientConnectionTracker.ConnectionStats stat = getStatsByContext(ctx); - Assert.assertNotNull(stat); - LOG.warn("in Read stats before: {}", stat); - super.channelRead(ctx, msg); - } - - @Override - public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { - ClientConnectionTracker.ConnectionStats stat = getStatsByContext(ctx); - Assert.assertNotNull(stat); - int activeReqeustBefore = stat.activeRequestCount(); - LOG.warn("in Write stats: {}", stat); - if (msg instanceof HttpRequest) { - LOG.warn("in Read stats after: {}", stat); - Assert.assertTrue(activeReqeustBefore > 0); - } - super.write(ctx, msg, promise); - int activeRequestAfter = stat.activeRequestCount(); - if (msg instanceof HttpResponse) { - LOG.warn("in Write stats after: {}", stat); - Assert.assertEquals(activeReqeustBefore - 1, activeRequestAfter); - } - } - - @Override - public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { - ClientConnectionTracker.ConnectionStats stat = getStatsByContext(ctx); - Assert.assertNotNull(stat); - int activeCountBefore = stat.activeRequestCount(); - LOG.warn("in Close stats: {}", stat); - super.channelInactive(ctx); - LOG.warn("in Close map: {}", stat); - if (activeCountBefore == 1) { - Assert.assertEquals(statsMap().values(), 0); - } else { - Assert.assertEquals(activeCountBefore - 1, stat.connectionCount()); - } - } - - @Override - public void channelInactive(ChannelHandlerContext ctx) throws Exception { - ClientConnectionTracker.ConnectionStats stat = getStatsByContext(ctx); - Assert.assertNotNull(stat); - int activeCountBefore = stat.connectionCount(); - LOG.warn("in InActive stats: {}", statsMap().values().iterator().next()); - try { - super.channelInactive(ctx); - } catch (Throwable t) { - LOG.warn("ignoring reset by peer {}", t.getMessage()); - } - LOG.warn("in InActive after map: {}", statsMap().values()); - if (activeCountBefore == 1) { - Assert.assertEquals(statsMap().values().size(), 0); - } else { - Assert.assertEquals(activeCountBefore - 1, stat.connectionCount()); - } - } - - } - - static final FullHttpResponse RESPONSE = new DefaultFullHttpResponse( - HttpVersion.HTTP_1_1, - HttpResponseStatus.OK, - Unpooled.copiedBuffer("Hola", StandardCharsets.US_ASCII)); - - private ServerBootstrap prepareServer(ClientConnectionTracker tracker) { - ServerBootstrap sb = new ServerBootstrap().group(_group) - .channel(NioServerSocketChannel.class) - .childHandler(new io.netty.channel.ChannelInitializer() { - @Override - protected void initChannel(Channel ch) throws Exception { - ch.pipeline().addLast(new HttpServerCodec(), tracker, new SimpleChannelInboundHandler() { - @Override - protected void channelRead0(ChannelHandlerContext ctx, HttpRequest msg) { - ctx.channel().writeAndFlush(RESPONSE); - } - - @Override - public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { - if (cause instanceof IOException && cause.getMessage().contains("Connection reset by peer")) { - LOG.info("Client closed connection: "); - } else { - LOG.warn("Got an exception: ", cause); - } - } - - }); - } - }); - return sb; - } - - private Bootstrap prepareClient() { - - return new Bootstrap().group(_group).channel(NioSocketChannel.class).handler(new ChannelInitializer() { - @Override - protected void initChannel(Channel ch) throws Exception { - ch.pipeline() - .addLast( - new HttpClientCodec(), - new HttpObjectAggregator(1024 * 1024), - new SimpleChannelInboundHandler() { - @Override - protected void channelRead0(ChannelHandlerContext ctx, HttpResponse msg) { - LOG.warn("got response from Server: {}", msg); - } - }); - } - }); - } - - @Test(groups = "unit") - public void testSimpleActiveRequest() throws InterruptedException, UnknownHostException, ExecutionException { - InstrumentedTracker clientConnectionTracker = new InstrumentedTracker(200, 100); - ; - ChannelGroup channelGroup = new DefaultChannelGroup(_group.next()); - try { - ServerBootstrap sb = prepareServer(clientConnectionTracker); - Bootstrap cb = prepareClient(); - - Channel serverChannel = sb.bind(new InetSocketAddress(0)).sync().channel(); - channelGroup.add(serverChannel); - int port = ((InetSocketAddress) serverChannel.localAddress()).getPort(); - - Channel clientChannel = cb.connect(new InetSocketAddress(NetUtil.LOCALHOST, port)).sync().channel(); - channelGroup.add(clientChannel); - - Map map = clientConnectionTracker.statsMap(); - Assert.assertEquals(map.values().size(), 1); - ClientConnectionTracker.ConnectionStats stats = map.values().iterator().next(); - Assert.assertNotNull(stats); - Assert.assertEquals(stats.activeRequestCount(), 0); - Assert.assertEquals(stats.connectionCount(), 1); - - FullHttpRequest request = - new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/foo", Unpooled.EMPTY_BUFFER); - clientChannel.writeAndFlush(request).sync().get(); - // after flush, this should be zero - Assert.assertEquals(stats.activeRequestCount(), 0); - Assert.assertEquals(stats.connectionCount(), 1); - clientChannel.close().sync(); - } finally { - channelGroup.close().sync(); - } - } - - @Test(groups = "unit") - public void givenAClientConnectionWentOverLimitWarningFired() throws Exception { - - int limit = 3; - int resetThreshold = 1; - ClientConnectionTracker spy = spy(new ClientConnectionTracker(limit, resetThreshold)); - String hostAddress = "www.linkedIn.com"; - - spy.channelActive(preparedMockContextWithDifferentIpAddress(hostAddress)); - spy.channelActive(preparedMockContextWithDifferentIpAddress(hostAddress)); - verify(spy, times(2)).checkConnectionLimit(any(ChannelHandlerContext.class)); - verify(spy, times(0)) - .whenOverLimit(any(ClientConnectionTracker.ConnectionStats.class), any(ChannelHandlerContext.class)); - spy.channelActive(preparedMockContextWithDifferentIpAddress(hostAddress)); - spy.channelActive(preparedMockContextWithDifferentIpAddress(hostAddress)); - verify(spy, times(1)) - .whenOverLimit(any(ClientConnectionTracker.ConnectionStats.class), any(ChannelHandlerContext.class)); - - // reduce two connections - spy.channelInactive(preparedMockContextWithDifferentIpAddress(hostAddress)); - spy.channelInactive(preparedMockContextWithDifferentIpAddress(hostAddress)); - spy.channelInactive(preparedMockContextWithDifferentIpAddress(hostAddress)); - spy.channelInactive(preparedMockContextWithDifferentIpAddress(hostAddress)); - // add one more and there should not be any warning - spy.channelActive(preparedMockContextWithDifferentIpAddress(hostAddress)); - verify(spy, times(5)).checkConnectionLimit(any(ChannelHandlerContext.class)); - verify(spy, times(1)) - .whenOverLimit(any(ClientConnectionTracker.ConnectionStats.class), any(ChannelHandlerContext.class)); - } -} diff --git a/internal/alpini/netty4/alpini-netty4-base/src/test/java/com/linkedin/alpini/netty4/handlers/TestMemoryPressureIndexMonitor.java b/internal/alpini/netty4/alpini-netty4-base/src/test/java/com/linkedin/alpini/netty4/handlers/TestMemoryPressureIndexMonitor.java deleted file mode 100644 index 25f755304f..0000000000 --- a/internal/alpini/netty4/alpini-netty4-base/src/test/java/com/linkedin/alpini/netty4/handlers/TestMemoryPressureIndexMonitor.java +++ /dev/null @@ -1,194 +0,0 @@ -package com.linkedin.alpini.netty4.handlers; - -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import com.linkedin.alpini.base.misc.HeaderNames; -import com.linkedin.alpini.base.misc.MemoryPressureIndexMonitor; -import com.linkedin.alpini.netty4.misc.BasicHttpRequest; -import com.linkedin.alpini.netty4.misc.BasicHttpResponse; -import com.linkedin.alpini.netty4.misc.MemoryPressureIndexUtils; -import io.netty.channel.ChannelFuture; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.embedded.EmbeddedChannel; -import io.netty.handler.codec.http.HttpHeaders; -import io.netty.handler.codec.http.HttpMethod; -import io.netty.handler.codec.http.HttpRequest; -import io.netty.handler.codec.http.HttpResponse; -import io.netty.handler.codec.http.HttpResponseStatus; -import io.netty.handler.codec.http.HttpVersion; -import java.util.function.Supplier; -import org.testng.Assert; -import org.testng.annotations.Test; - - -public class TestMemoryPressureIndexMonitor { - String URI = "/dontcare/CommentThread2/xxx"; - - private static final Supplier> MONITOR_SUPPLIER = - () -> new MemoryPressureIndexMonitor<>( - MemoryPressureIndexUtils.defaultRequestToKeyFunction(), - new MockStats(), - MockStats::ignore); - - static class MockStats { - public void ignore(Long value) { - } - } - - @Test(groups = "unit") - public void givenARequestCapacityUpStreamHandlerAddedTheBytes() throws Exception { - long bytes = 108L; - HttpRequest request = mockHttpRequestForUpStream(bytes); - ChannelHandlerContext context = mockContext(); - - MemoryPressureIndexMonitor monitor = MONITOR_SUPPLIER.get(); - MemoryPressureIndexHandler handler = - new MemoryPressureIndexHandler<>(monitor, MemoryPressureIndexUtils.defaultResponseToKeyFunction()); - EmbeddedChannel channel = new EmbeddedChannel(handler); - channel.pipeline().fireChannelRead(request); - Assert.assertEquals(monitor.currentMemoryPressureIndex(), bytes); - Assert.assertEquals(monitor.getBytesByReferent((HttpRequest) request), bytes); - - HttpResponse response = mockHttpResponseForDownStream(context, bytes); - channel.writeOneOutbound(response); - Assert.assertEquals(monitor.currentMemoryPressureIndex(), 0); - // The map should have this entry removed. - Assert.assertEquals(monitor.getBytesByReferent(request), 0); - } - - @Test(groups = "unit") - public void givenARequestCapacityUpStreamHandlerAddedTheBytesUsingBasicHttpRequest() throws Exception { - long bytes = 108L; - String requestId = "blah1"; - BasicHttpRequest request = buildBasicHttpRequestForUpStream(bytes, requestId); - - MemoryPressureIndexMonitor monitor = MONITOR_SUPPLIER.get(); - MemoryPressureIndexHandler handler = - new MemoryPressureIndexHandler<>(monitor, MemoryPressureIndexUtils.defaultResponseToKeyFunction()); - EmbeddedChannel channel = new EmbeddedChannel(handler); - channel.pipeline().fireChannelRead(request); - Assert.assertEquals(monitor.currentMemoryPressureIndex(), bytes); - Assert.assertEquals(monitor.getBytesByReferent(request), bytes); - - HttpResponse response = buildBasicHttpResponse(bytes, request); - channel.writeOneOutbound(response); - Assert.assertEquals(monitor.currentMemoryPressureIndex(), 0); - // The map should have this entry removed. - Assert.assertEquals(monitor.getBytesByReferent(request), 0); - } - - @Test(groups = "unit") - public void givenARequestCapacityUpStreamHandlerAddedTheBytesWithPhantomReference() throws Exception { - long requestBytes = 108L; - long responseBytes = 100L; - HttpRequest request = mockHttpRequestForUpStream(requestBytes); - ChannelHandlerContext context = mockContext(); - - MemoryPressureIndexMonitor monitor = MONITOR_SUPPLIER.get(); - MemoryPressureIndexHandler handler = - new MemoryPressureIndexHandler<>(monitor, MemoryPressureIndexUtils.defaultResponseToKeyFunction()) - .phantomMode(true); - EmbeddedChannel channel = new EmbeddedChannel(handler); - channel.pipeline().fireChannelRead(request); - Assert.assertEquals(monitor.currentMemoryPressureIndex(), requestBytes); - Assert.assertEquals(monitor.getBytesByReferent(request), requestBytes); - - HttpResponse response = mockHttpResponseForDownStream(context, responseBytes); - channel.writeOneOutbound(response); - // verify that the bytes is not immediately removed. - Assert.assertEquals(monitor.currentMemoryPressureIndex(), requestBytes + responseBytes); - Assert.assertEquals(monitor.getBytesByReferent(request), requestBytes + responseBytes); - Assert.assertTrue(monitor.isPhantomSetForReferent(request)); - } - - @Test(groups = "unit") - public void givenARequestCapacityUpStreamHandlerAddedTheBytesWithPhantomReferenceUsingBasicHttpRequest() - throws Exception { - long requestBytes = 108L; - long responseBytes = 100L; - String requestId = "blah1"; - BasicHttpRequest request = buildBasicHttpRequestForUpStream(requestBytes, requestId); - - MemoryPressureIndexMonitor monitor = MONITOR_SUPPLIER.get(); - MemoryPressureIndexHandler handler = - new MemoryPressureIndexHandler<>(monitor, MemoryPressureIndexUtils.defaultResponseToKeyFunction()) - .phantomMode(true); - EmbeddedChannel channel = new EmbeddedChannel(handler); - channel.pipeline().fireChannelRead(request); - Assert.assertEquals(monitor.currentMemoryPressureIndex(), requestBytes); - Assert.assertEquals(monitor.getBytesByReferent(request), requestBytes); - - BasicHttpResponse response = buildBasicHttpResponse(responseBytes, request); - channel.writeOneOutbound(response); - // verify that the bytes is not immediately removed. - Assert.assertEquals(monitor.currentMemoryPressureIndex(), requestBytes + responseBytes); - Assert.assertEquals(monitor.getBytesByReferent(request), requestBytes + responseBytes); - Assert.assertTrue(monitor.isPhantomSetForReferent(request)); - } - - @Test(groups = "unit") - public void givenARequestWithZeroLengthTheMinimumIsAdded() throws Exception { - long requestBytes = 0L; - String requestId = "blah1"; - BasicHttpRequest request = buildBasicHttpRequestForUpStream(requestBytes, requestId); - - MemoryPressureIndexMonitor monitor = MONITOR_SUPPLIER.get(); - MemoryPressureIndexHandler handler = - new MemoryPressureIndexHandler<>(monitor, MemoryPressureIndexUtils.defaultResponseToKeyFunction()) - .phantomMode(true); - EmbeddedChannel channel = new EmbeddedChannel(handler); - channel.pipeline().fireChannelRead(request); - - Assert.assertEquals(monitor.currentMemoryPressureIndex(), MemoryPressureIndexUtils.getMinimumBytesToAdd()); - Assert.assertEquals(monitor.getBytesByReferent(request), MemoryPressureIndexUtils.getMinimumBytesToAdd()); - } - - private ChannelHandlerContext mockContext() { - ChannelHandlerContext context = mock(ChannelHandlerContext.class); - when(context.fireChannelRead(any(Object.class))).thenReturn(context); - return context; - } - - private HttpResponse mockHttpResponseForDownStream(ChannelHandlerContext context, long bytes) { - HttpResponse response = mock(HttpResponse.class); - HttpHeaders headers = mockHeaders(bytes); - when(response.headers()).thenReturn(headers); - when(context.write(any(Object.class))).thenReturn(mock(ChannelFuture.class)); - return response; - } - - private HttpHeaders mockHeaders(long bytes) { - HttpHeaders headers = mock(HttpHeaders.class); - when(headers.get(eq(HeaderNames.CONTENT_LENGTH))).thenReturn(String.valueOf(bytes)); - when(headers.get(eq(MemoryPressureIndexUtils.X_ESPRESSO_REQUEST_ID))).thenReturn("blah"); - return headers; - - } - - private BasicHttpRequest buildBasicHttpRequestForUpStream(long bytes, String requestId) { - BasicHttpRequest request = new BasicHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, URI); - request.headers().set(HeaderNames.CONTENT_LENGTH, bytes); - request.headers().set(MemoryPressureIndexUtils.X_ESPRESSO_REQUEST_ID, requestId); - return request; - } - - private BasicHttpResponse buildBasicHttpResponse(long bytes, BasicHttpRequest request) { - BasicHttpResponse response = new BasicHttpResponse(request, HttpResponseStatus.CREATED); - response.headers().set(HeaderNames.CONTENT_LENGTH, bytes); - response.headers() - .set( - MemoryPressureIndexUtils.X_ESPRESSO_REQUEST_ID, - request.headers().get(MemoryPressureIndexUtils.X_ESPRESSO_REQUEST_ID)); - return response; - } - - private HttpRequest mockHttpRequestForUpStream(long bytes) { - HttpRequest request = mock(HttpRequest.class); - HttpHeaders headers = mockHeaders(bytes); - when(request.headers()).thenReturn(headers); - return request; - } -} diff --git a/internal/alpini/netty4/alpini-netty4-base/src/test/java/com/linkedin/alpini/netty4/handlers/TestMultipartResponseHandler.java b/internal/alpini/netty4/alpini-netty4-base/src/test/java/com/linkedin/alpini/netty4/handlers/TestMultipartResponseHandler.java deleted file mode 100644 index 5ec834dd8a..0000000000 --- a/internal/alpini/netty4/alpini-netty4-base/src/test/java/com/linkedin/alpini/netty4/handlers/TestMultipartResponseHandler.java +++ /dev/null @@ -1,224 +0,0 @@ -package com.linkedin.alpini.netty4.handlers; - -import com.linkedin.alpini.netty4.misc.BasicFullHttpMultiPart; -import com.linkedin.alpini.netty4.misc.FullHttpMultiPart; -import io.netty.buffer.ByteBuf; -import io.netty.channel.embedded.EmbeddedChannel; -import io.netty.handler.codec.http.DefaultHttpContent; -import io.netty.handler.codec.http.DefaultHttpResponse; -import io.netty.handler.codec.http.HttpContent; -import io.netty.handler.codec.http.HttpHeaderNames; -import io.netty.handler.codec.http.HttpResponse; -import io.netty.handler.codec.http.HttpResponseEncoder; -import io.netty.handler.codec.http.HttpResponseStatus; -import io.netty.handler.codec.http.HttpUtil; -import io.netty.handler.codec.http.HttpVersion; -import io.netty.handler.codec.http.LastHttpContent; -import io.netty.util.ReferenceCountUtil; -import java.nio.charset.StandardCharsets; -import org.testng.Assert; -import org.testng.annotations.Test; - - -/** - * Created by acurtis on 5/8/17. - */ -@Test(groups = { "unit", "unit-leak", "leak" }, singleThreaded = true) -public final class TestMultipartResponseHandler extends AbstractLeakDetect { - @Test - public void testBasicResponse() { - - EmbeddedChannel channel = new EmbeddedChannel(new HttpResponseEncoder(), new MultipartResponseHandler()); - channel.config().setAllocator(POOLED_ALLOCATOR); - - HttpResponse responseHeader = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK); - - HttpUtil.setContentLength(responseHeader, 0); - - channel.writeOutbound(responseHeader, LastHttpContent.EMPTY_LAST_CONTENT); - - ByteBuf output = channel.readOutbound(); - - Assert.assertEquals( - output.toString(StandardCharsets.US_ASCII), - "HTTP/1.1 200 OK\r\n" + "content-length: 0\r\n" + "\r\n"); - ReferenceCountUtil.release(output); - } - - @Test - public void testBasicResponseWithContent() { - - EmbeddedChannel channel = new EmbeddedChannel(new HttpResponseEncoder(), new MultipartResponseHandler()); - channel.config().setAllocator(POOLED_ALLOCATOR); - - HttpResponse responseHeader = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK); - HttpContent responseContent = new DefaultHttpContent(encodeString("Hello World!\n", StandardCharsets.US_ASCII)); - - HttpUtil.setContentLength(responseHeader, responseContent.content().readableBytes()); - - channel.writeOutbound(responseHeader, responseContent, LastHttpContent.EMPTY_LAST_CONTENT); - - ByteBuf output = channel.readOutbound(); - - Assert.assertEquals( - output.toString(StandardCharsets.US_ASCII), - "HTTP/1.1 200 OK\r\n" + "content-length: 13\r\n" + "\r\n"); - output.release(); - - ByteBuf content = channel.readOutbound(); - Assert.assertEquals(content.toString(StandardCharsets.US_ASCII), "Hello World!\n"); - content.release(); - } - - @Test - public void testMultipartResponse() { - - EmbeddedChannel channel = new EmbeddedChannel(new HttpResponseEncoder(), new MultipartResponseHandler()); - channel.config().setAllocator(POOLED_ALLOCATOR); - - HttpResponse responseHeader = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK); - - responseHeader.headers().set(HttpHeaderNames.CONTENT_TYPE, "multipart/mixed; boundary=--1234567890987654321"); - - FullHttpMultiPart part1 = new BasicFullHttpMultiPart(encodeString("Hello World!\n", StandardCharsets.US_ASCII)); - part1.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain"); - - channel.writeOutbound(responseHeader, part1, LastHttpContent.EMPTY_LAST_CONTENT); - - ByteBuf output = channel.readOutbound(); - - Assert.assertEquals( - output.toString(StandardCharsets.US_ASCII), - "HTTP/1.1 200 OK\r\n" + "content-type: multipart/mixed; boundary=--1234567890987654321\r\n" - + "transfer-encoding: chunked\r\n" + "\r\n"); - output.release(); - - output = channel.readOutbound(); - Assert.assertEquals(output.toString(StandardCharsets.US_ASCII), "3f\r\n"); - ReferenceCountUtil.release(output); - - output = channel.readOutbound(); - Assert.assertEquals( - output.toString(StandardCharsets.US_ASCII), - "content-type: text/plain\r\n" + "content-length: 13\r\n" + "\r\n" + "Hello World!\n" + "\r\n"); - ReferenceCountUtil.release(output); - - output = channel.readOutbound(); - Assert.assertEquals(output.toString(StandardCharsets.US_ASCII), "\r\n"); - ReferenceCountUtil.release(output); - - output = channel.readOutbound(); - Assert.assertEquals(output.toString(StandardCharsets.US_ASCII), "1b\r\n"); - ReferenceCountUtil.release(output); - - output = channel.readOutbound(); - Assert.assertEquals(output.toString(StandardCharsets.US_ASCII), "----1234567890987654321--\r\n"); - ReferenceCountUtil.release(output); - - output = channel.readOutbound(); - Assert.assertEquals(output.toString(StandardCharsets.US_ASCII), "\r\n"); - ReferenceCountUtil.release(output); - - output = channel.readOutbound(); - Assert.assertEquals(output.toString(StandardCharsets.US_ASCII), "0\r\n\r\n"); - ReferenceCountUtil.release(output); - - Assert.assertNull(channel.readOutbound()); - } - - @Test - public void testMultipartResponse2() { - - EmbeddedChannel channel = new EmbeddedChannel(new HttpResponseEncoder(), new MultipartResponseHandler()); - channel.config().setAllocator(POOLED_ALLOCATOR); - - HttpResponse responseHeader = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK); - - responseHeader.headers().set(HttpHeaderNames.CONTENT_TYPE, "multipart/mixed; boundary=--1234567890987654321"); - - FullHttpMultiPart part1 = new BasicFullHttpMultiPart(encodeString("Hello World!\n", StandardCharsets.US_ASCII)); - part1.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain"); - - FullHttpMultiPart part2 = new BasicFullHttpMultiPart(encodeString("This is a test\n", StandardCharsets.US_ASCII)); - part1.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain"); - - channel.writeOutbound(responseHeader, part1, part2, LastHttpContent.EMPTY_LAST_CONTENT); - - ByteBuf output = channel.readOutbound(); - - Assert.assertEquals( - output.toString(StandardCharsets.US_ASCII), - "HTTP/1.1 200 OK\r\n" + "content-type: multipart/mixed; boundary=--1234567890987654321\r\n" - + "transfer-encoding: chunked\r\n" + "\r\n"); - output.release(); - - ByteBuf content = channel.readOutbound(); - Assert.assertEquals(content.toString(StandardCharsets.US_ASCII), "3f\r\n"); - ReferenceCountUtil.release(content); - - content = channel.readOutbound(); - Assert.assertEquals( - content.toString(StandardCharsets.US_ASCII), - "content-type: text/plain\r\n" + "content-length: 13\r\n" + "\r\n" + "Hello World!\n" + "\r\n"); - ReferenceCountUtil.release(content); - - content = channel.readOutbound(); - Assert.assertEquals(content.toString(StandardCharsets.US_ASCII), "\r\n"); - ReferenceCountUtil.release(content); - - content = channel.readOutbound(); - Assert.assertEquals(content.toString(StandardCharsets.US_ASCII), "19\r\n"); - ReferenceCountUtil.release(content); - - content = channel.readOutbound(); - Assert.assertEquals(content.toString(StandardCharsets.US_ASCII), "----1234567890987654321\r\n"); - ReferenceCountUtil.release(content); - - content = channel.readOutbound(); - Assert.assertEquals(content.toString(StandardCharsets.US_ASCII), "\r\n"); - ReferenceCountUtil.release(content); - - content = channel.readOutbound(); - Assert.assertEquals(content.toString(StandardCharsets.US_ASCII), "49\r\n"); - ReferenceCountUtil.release(content); - - content = channel.readOutbound(); - Assert.assertEquals( - content.toString(StandardCharsets.US_ASCII), - "content-type: application/binary\r\n" + "content-length: 15\r\n" + "\r\n" + "This is a test\n" + "\r\n"); - ReferenceCountUtil.release(content); - - content = channel.readOutbound(); - Assert.assertEquals(content.toString(StandardCharsets.US_ASCII), "\r\n"); - ReferenceCountUtil.release(content); - - content = channel.readOutbound(); - Assert.assertEquals(content.toString(StandardCharsets.US_ASCII), "1b\r\n"); - ReferenceCountUtil.release(content); - - content = channel.readOutbound(); - Assert.assertEquals(content.toString(StandardCharsets.US_ASCII), "----1234567890987654321--\r\n"); - ReferenceCountUtil.release(content); - - content = channel.readOutbound(); - Assert.assertEquals(content.toString(StandardCharsets.US_ASCII), "\r\n"); - ReferenceCountUtil.release(content); - - content = channel.readOutbound(); - Assert.assertEquals(content.toString(StandardCharsets.US_ASCII), "0\r\n\r\n"); - ReferenceCountUtil.release(content); - - Assert.assertNull(channel.readOutbound()); - } - - /** - * since TestNG tends to sort by method name, this tries to be the last test - * in the class. We do this because the AfterClass annotated methods may - * execute after other tests classes have run and doesn't execute immediately - * after the methods in this test class. - */ - @Test(alwaysRun = true) - public final void zz9PluralZAlpha() throws InterruptedException { - finallyLeakDetect(); - } -} diff --git a/internal/alpini/netty4/alpini-netty4-base/src/test/java/com/linkedin/alpini/netty4/handlers/TestRequestLogHandler.java b/internal/alpini/netty4/alpini-netty4-base/src/test/java/com/linkedin/alpini/netty4/handlers/TestRequestLogHandler.java deleted file mode 100644 index 498f420365..0000000000 --- a/internal/alpini/netty4/alpini-netty4-base/src/test/java/com/linkedin/alpini/netty4/handlers/TestRequestLogHandler.java +++ /dev/null @@ -1,509 +0,0 @@ -package com.linkedin.alpini.netty4.handlers; - -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.anyString; -import static org.mockito.Mockito.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.same; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; - -import com.linkedin.alpini.base.misc.Msg; -import com.linkedin.alpini.base.misc.Time; -import io.netty.buffer.ByteBufUtil; -import io.netty.buffer.UnpooledByteBufAllocator; -import io.netty.channel.ChannelHandler; -import io.netty.channel.embedded.EmbeddedChannel; -import io.netty.handler.codec.http.DefaultHttpContent; -import io.netty.handler.codec.http.DefaultHttpRequest; -import io.netty.handler.codec.http.DefaultHttpResponse; -import io.netty.handler.codec.http.HttpHeaderNames; -import io.netty.handler.codec.http.HttpMethod; -import io.netty.handler.codec.http.HttpRequest; -import io.netty.handler.codec.http.HttpResponse; -import io.netty.handler.codec.http.HttpResponseStatus; -import io.netty.handler.codec.http.HttpUtil; -import io.netty.handler.codec.http.HttpVersion; -import io.netty.handler.codec.http.LastHttpContent; -import java.net.InetSocketAddress; -import java.net.SocketAddress; -import org.apache.logging.log4j.Logger; -import org.mockito.ArgumentCaptor; -import org.mockito.Mockito; -import org.testng.Assert; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; - - -/** - * @author Jemiah Westerman - * - * @version $Revision$ - */ -@Test(groups = { "unit", "unit-leak", "leak" }, singleThreaded = true) -public final class TestRequestLogHandler extends AbstractLeakDetect { - private static Logger _mockLog = mock(Logger.class); - - @BeforeClass - public void beforeMethod() { - reset(_mockLog); - } - - @AfterClass - public void afterClass() { - Mockito.reset(_mockLog); - } - - @Test - public void testCatchEx() { - class TestException extends Error { - } - - Mockito.reset(_mockLog); - when(_mockLog.isWarnEnabled()).thenReturn(true); - RequestLogHandler handler = new RequestLogHandler(_mockLog, "pipeline"); - - handler.catchException(_mockLog, () -> { - throw new TestException(); - }, "foo"); - - ArgumentCaptor nameCaptor = ArgumentCaptor.forClass(String.class); - ArgumentCaptor exceptionCaptor = ArgumentCaptor.forClass(Throwable.class); - - verify(_mockLog).getName(); - verify(_mockLog).warn(eq("Error in {}"), nameCaptor.capture(), exceptionCaptor.capture()); - - Assert.assertEquals(nameCaptor.getValue(), "foo"); - Assert.assertTrue(exceptionCaptor.getValue() instanceof TestException); - - verifyNoMoreInteractions(_mockLog); - } - - @Test - public void testConnectInfo() { - try { - Time.freeze(); - long ts = Time.currentTimeMillis(); - SocketAddress address = InetSocketAddress.createUnresolved("address", 1234); - RequestLogHandler.ConnectInfo info = new RequestLogHandler.ConnectInfo(address); - - Assert.assertSame(info._remoteAddress, address); - Assert.assertEquals(info._startMillis, ts); - } finally { - Time.restore(); - } - } - - @Test - public void testResponseInfo() { - try { - Time.freeze(); - HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK); - HttpUtil.setContentLength(response, 0); - long ts = Time.currentTimeMillis(); - RequestLogHandler.ResponseInfo info = new RequestLogHandler.ResponseInfo(response, false); - - Assert.assertEquals(info._endMillis, ts); - Assert.assertSame(info._status, HttpResponseStatus.OK); - Assert.assertEquals(info._contentLength, 0); - Assert.assertEquals(info._contentLocation, null); - - HttpUtil.setContentLength(response, 100); - response.headers().set(HttpHeaderNames.CONTENT_LOCATION, "/foo"); - info = new RequestLogHandler.ResponseInfo(response, false); - Assert.assertEquals(info._contentLength, 100); - Assert.assertEquals(info._contentLocation, "/foo"); - } finally { - Time.restore(); - } - } - - @Test - public void testRequestInfo() { - try { - Time.freeze(); - HttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/foo/bar?query=123"); - long ts = Time.currentTimeMillis(); - RequestLogHandler.RequestInfo info = new RequestLogHandler.RequestInfo(request); - - Assert.assertEquals(info._startMillis, ts); - Assert.assertSame(info._method, HttpMethod.GET); - Assert.assertSame(info._protocolVersion, HttpVersion.HTTP_1_1); - Assert.assertEquals(info._uri, "/foo/bar?query=123"); - } finally { - Time.restore(); - } - } - - @Test - public void testHappyPath() { - // Set up our mock request/response and constants - String pipeline = "myPipeline"; - SocketAddress remoteAddress = InetSocketAddress.createUnresolved("remoteAddress", 0); - HttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/foo/bar?query=123"); - HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK); - HttpUtil.setContentLength(response, 100); - response.headers().add(HttpHeaderNames.CONTENT_LOCATION, "/foo"); - - // Set up the mock logger and create the handler - Mockito.reset(_mockLog); - when(_mockLog.isDebugEnabled()).thenReturn(true); - when(_mockLog.isInfoEnabled()).thenReturn(true); - RequestLogHandler handler = new RequestLogHandler(_mockLog, pipeline); - - RequestLogHandler.ConnectInfo connectInfo = new RequestLogHandler.ConnectInfo(remoteAddress); - - // Happy Path: connect, request, response, disconnect - handler.handleConnect(connectInfo); - verify(_mockLog).info(eq("{} {} CONNECTED"), anyString(), any(SocketAddress.class)); // 1 DEBUG messages - connect - handler.handleHttpRequest(connectInfo, request); - handler.handleLastHttpRequestContent(connectInfo, LastHttpContent.EMPTY_LAST_CONTENT, true, true); - handler.handleHttpResponse(connectInfo, response, false); - handler.handleLastHttpResponseContent(connectInfo, LastHttpContent.EMPTY_LAST_CONTENT, true, false); - verify(_mockLog).debug(eq("{}"), any(Object.class)); // 1 INFO message - request+response pair - handler.handleDisconnect(connectInfo); - verify(_mockLog).info(eq("{} {} DISCONNECTED {}"), eq("myPipeline"), any(), any()); // 1 more DEBUG messages - - // disconnect - - // Happy Path: connect, request, response, request, response, request, response, disconnect - reset(_mockLog); - when(_mockLog.isDebugEnabled()).thenReturn(true); - when(_mockLog.isInfoEnabled()).thenReturn(true); - connectInfo = new RequestLogHandler.ConnectInfo(remoteAddress); - handler.handleConnect(connectInfo); - handler.handleHttpRequest(connectInfo, request); - handler.handleLastHttpRequestContent(connectInfo, LastHttpContent.EMPTY_LAST_CONTENT, true, true); - handler.handleHttpResponse(connectInfo, response, false); - handler.handleLastHttpResponseContent(connectInfo, LastHttpContent.EMPTY_LAST_CONTENT, true, false); - handler.handleHttpRequest(connectInfo, request); - handler.handleLastHttpRequestContent(connectInfo, LastHttpContent.EMPTY_LAST_CONTENT, true, true); - handler.handleHttpResponse(connectInfo, response, false); - handler.handleLastHttpResponseContent(connectInfo, LastHttpContent.EMPTY_LAST_CONTENT, true, false); - handler.handleHttpRequest(connectInfo, request); - handler.handleLastHttpRequestContent(connectInfo, LastHttpContent.EMPTY_LAST_CONTENT, true, true); - handler.handleHttpResponse(connectInfo, response, false); - handler.handleLastHttpResponseContent(connectInfo, LastHttpContent.EMPTY_LAST_CONTENT, true, false); - handler.handleDisconnect(connectInfo); - verify(_mockLog).info(eq("{} {} CONNECTED"), anyString(), any(SocketAddress.class)); // 1 DEBUG message - connect - verify(_mockLog).info(eq("{} {} DISCONNECTED {}"), eq("myPipeline"), any(), any()); // 1 DEBUG message - disconnect - verify(_mockLog, times(3)).debug(eq("{}"), any(Object.class)); // 3 INFO messages - request+response pair x 3 - } - - /** - * The way the address is formatted varies by JDK... - */ - private String getLoggedRemoteAddress(String address) { - return System.getProperty("java.version").startsWith("17") ? address + "/" : address; - } - - @Test - public void testHappyPath2() throws InterruptedException { - // Set up our mock request/response and constants - String pipeline = "myPipeline"; - String remoteAddressString = "remoteAddress"; - SocketAddress remoteAddress = InetSocketAddress.createUnresolved(remoteAddressString, 0); - HttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/foo/bar?query=123"); - HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK); - HttpUtil.setContentLength(response, 100); - response.headers().add(HttpHeaderNames.CONTENT_LOCATION, "/foo"); - - Mockito.reset(_mockLog); - // Set up the mock logger and create the handler - when(_mockLog.isDebugEnabled()).thenReturn(true); - when(_mockLog.isInfoEnabled()).thenReturn(true); - RequestLogHandler handler = new RequestLogHandler(_mockLog, pipeline); - - class TestChannel extends EmbeddedChannel { - public TestChannel(ChannelHandler... handler) { - super(handler); - } - - @Override - protected SocketAddress remoteAddress0() { - return remoteAddress; - } - } - - EmbeddedChannel ch = new TestChannel(handler); - - verify(_mockLog).info(eq("{} {} CONNECTED"), anyString(), any(SocketAddress.class)); // 1 DEBUG messages - connect - - ch.writeInbound( - request, - new DefaultHttpContent(ByteBufUtil.writeAscii(UnpooledByteBufAllocator.DEFAULT, "foo")), - LastHttpContent.EMPTY_LAST_CONTENT); - ch.writeOutbound( - response, - new DefaultHttpContent(ByteBufUtil.writeAscii(UnpooledByteBufAllocator.DEFAULT, "foo")), - LastHttpContent.EMPTY_LAST_CONTENT); - - ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(Msg.class); - verify(_mockLog).debug(eq("{}"), messageCaptor.capture()); // 1 INFO message - request+response pair - Assert.assertTrue( - messageCaptor.getValue() - .toString() - .startsWith( - "myPipeline " + getLoggedRemoteAddress(remoteAddressString) - + ":0 HTTP/1.1 GET /foo/bar?query=123 3--> 200 OK 100 /foo "), - messageCaptor.getValue().toString()); - - ch.releaseInbound(); - ch.releaseOutbound(); - - ch.disconnect().sync(); - - verify(_mockLog).info(eq("{} {} DISCONNECTED {}"), eq("myPipeline"), any(), any()); // 1 more DEBUG messages - - // disconnect - - // Happy Path: connect, request, response, request, response, request, response, disconnect - reset(_mockLog); - when(_mockLog.isDebugEnabled()).thenReturn(true); - when(_mockLog.isInfoEnabled()).thenReturn(true); - - ch = new TestChannel(handler); - - ch.writeInbound(request, LastHttpContent.EMPTY_LAST_CONTENT); - ch.writeOutbound(response, LastHttpContent.EMPTY_LAST_CONTENT); - - ch.writeInbound(request, LastHttpContent.EMPTY_LAST_CONTENT); - ch.writeOutbound(response, LastHttpContent.EMPTY_LAST_CONTENT); - - ch.writeInbound(request, LastHttpContent.EMPTY_LAST_CONTENT); - ch.writeOutbound(response, LastHttpContent.EMPTY_LAST_CONTENT); - - ch.disconnect().sync(); - - ch.releaseInbound(); - ch.releaseOutbound(); - - verify(_mockLog).info(eq("{} {} CONNECTED"), anyString(), any(SocketAddress.class)); // 1 DEBUG message - connect - verify(_mockLog).info(eq("{} {} DISCONNECTED {}"), eq("myPipeline"), any(), any()); // 1 DEBUG message - disconnect - verify(_mockLog, times(3)).debug(eq("{}"), any(Object.class)); // 3 INFO messages - request+response pair x 3 - verifyNoMoreInteractions(_mockLog); - } - - @Test - public void testHappyPath3() throws InterruptedException { - // Set up our mock request/response and constants - String pipeline = "myPipeline"; - String remoteAddressString = "remoteAddress"; - SocketAddress remoteAddress = InetSocketAddress.createUnresolved(remoteAddressString, 0); - HttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/foo/bar?query=123"); - HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK); - HttpUtil.setContentLength(response, 100); - response.headers().add(HttpHeaderNames.CONTENT_LOCATION, "/foo"); - - Mockito.reset(_mockLog); - // Set up the mock logger and create the handler - when(_mockLog.isDebugEnabled()).thenReturn(true); - when(_mockLog.isInfoEnabled()).thenReturn(true); - RequestLogHandler handler = new RequestLogHandler(_mockLog, pipeline); - - class TestChannel extends EmbeddedChannel { - public TestChannel(ChannelHandler... handler) { - super(handler); - } - - @Override - protected SocketAddress remoteAddress0() { - return remoteAddress; - } - } - - EmbeddedChannel ch = new TestChannel(handler); - - verify(_mockLog).info(eq("{} {} CONNECTED"), anyString(), any(SocketAddress.class)); // 1 DEBUG messages - connect - - ch.writeOutbound( - request, - new DefaultHttpContent(ByteBufUtil.writeAscii(UnpooledByteBufAllocator.DEFAULT, "foo")), - LastHttpContent.EMPTY_LAST_CONTENT); - ch.writeInbound( - response, - new DefaultHttpContent(ByteBufUtil.writeAscii(UnpooledByteBufAllocator.DEFAULT, "foo")), - LastHttpContent.EMPTY_LAST_CONTENT); - - ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(Msg.class); - verify(_mockLog).debug(eq("{}"), messageCaptor.capture()); // 1 INFO message - request+response pair - Assert.assertTrue( - messageCaptor.getValue() - .toString() - .startsWith( - "myPipeline " + getLoggedRemoteAddress(remoteAddressString) - + ":0 HTTP/1.1 GET /foo/bar?query=123 3--> 200 OK 100 /foo "), - messageCaptor.getValue().toString()); - - ch.disconnect().sync(); - - ch.releaseInbound(); - ch.releaseOutbound(); - - verify(_mockLog).info(eq("{} {} DISCONNECTED {}"), eq("myPipeline"), any(), any()); // 1 more DEBUG messages - - // disconnect - - // Happy Path: connect, request, response, request, response, request, response, disconnect - reset(_mockLog); - when(_mockLog.isDebugEnabled()).thenReturn(true); - when(_mockLog.isInfoEnabled()).thenReturn(true); - - ch = new TestChannel(handler); - - ch.writeOutbound(request, LastHttpContent.EMPTY_LAST_CONTENT); - ch.writeInbound(response, LastHttpContent.EMPTY_LAST_CONTENT); - - ch.writeOutbound(request, LastHttpContent.EMPTY_LAST_CONTENT); - ch.writeInbound(response, LastHttpContent.EMPTY_LAST_CONTENT); - - ch.writeOutbound(request, LastHttpContent.EMPTY_LAST_CONTENT); - ch.writeInbound(response, LastHttpContent.EMPTY_LAST_CONTENT); - - ch.disconnect().sync(); - - ch.releaseInbound(); - ch.releaseOutbound(); - - verify(_mockLog).info(eq("{} {} CONNECTED"), anyString(), any(SocketAddress.class)); // 1 DEBUG message - connect - verify(_mockLog).info(eq("{} {} DISCONNECTED {}"), eq("myPipeline"), any(), any()); // 1 DEBUG message - disconnect - verify(_mockLog, times(3)).debug(eq("{}"), any(Object.class)); // 3 INFO messages - request+response pair x 3 - verifyNoMoreInteractions(_mockLog); - } - - @Test - public void testDisconnect1() throws InterruptedException { - // Set up our mock request/response and constants - String pipeline = "myPipeline"; - SocketAddress remoteAddress = InetSocketAddress.createUnresolved("remoteAddress", 0); - HttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/foo/bar?query=123"); - - Mockito.reset(_mockLog); - // Set up the mock logger and create the handler - when(_mockLog.isDebugEnabled()).thenReturn(true); - when(_mockLog.isInfoEnabled()).thenReturn(true); - RequestLogHandler handler = new RequestLogHandler(_mockLog, pipeline); - - verify(_mockLog).getName(); - - class TestChannel extends EmbeddedChannel { - public TestChannel(ChannelHandler... handler) { - super(handler); - } - - @Override - protected SocketAddress remoteAddress0() { - return remoteAddress; - } - } - - EmbeddedChannel ch = new TestChannel(handler); - - verify(_mockLog).info(eq("{} {} CONNECTED"), anyString(), any(InetSocketAddress.class)); // 1 DEBUG messages - - // connect - - ch.writeOutbound( - request, - new DefaultHttpContent(ByteBufUtil.writeAscii(UnpooledByteBufAllocator.DEFAULT, "foo")), - LastHttpContent.EMPTY_LAST_CONTENT); - - ch.disconnect().sync(); - - ch.releaseInbound(); - ch.releaseOutbound(); - - verify(_mockLog).info(eq("{} {} DISCONNECTED {}"), eq("myPipeline"), any(), any()); // 1 more DEBUG messages - - // disconnect - - verify(_mockLog).info( - eq("{} {} {} {} {} --> CHANNEL-CLOSED {}"), - eq("myPipeline"), - any(), - same(HttpVersion.HTTP_1_1), - same(HttpMethod.GET), - eq("/foo/bar?query=123"), - any()); - - verifyNoMoreInteractions(_mockLog); - } - - @Test - public void testMissingRequestInfo() throws InterruptedException { - // Set up our mock request/response and constants - String pipeline = "myPipeline"; - HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK); - HttpUtil.setContentLength(response, 100); - response.headers().set(HttpHeaderNames.CONTENT_LOCATION, "/foo"); - - // Set up the mock logger and create the handler - reset(_mockLog); - RequestLogHandler handler = new RequestLogHandler(_mockLog, pipeline); - - Mockito.verify(_mockLog).getName(); - Mockito.verifyNoMoreInteractions(_mockLog); - - EmbeddedChannel ch = new EmbeddedChannel(handler); - - verify(_mockLog).info(eq("{} {} CONNECTED"), anyString(), any(SocketAddress.class)); // 1 DEBUG message - connect - Mockito.verifyNoMoreInteractions(_mockLog); - Mockito.reset(_mockLog); - - when(_mockLog.isDebugEnabled()).thenReturn(true); - when(_mockLog.isInfoEnabled()).thenReturn(true); - - // Bad Path: connect, response, disconnect (with no request) - ch.writeOneOutbound(response); - verify(_mockLog).error( - eq("{} {} without corresponding requestInfo or connectInfo. {}"), - anyString(), - eq("handleHttpResponse"), - any()); // 1 ERROR message - response w/o request - Mockito.verifyNoMoreInteractions(_mockLog); - Mockito.reset(_mockLog); - - ch.disconnect().sync(); - verify(_mockLog).info(eq("{} {} DISCONNECTED {}"), eq("myPipeline"), any(), any()); // 1 DEBUG message - disconnect - Mockito.verifyNoMoreInteractions(_mockLog); - } - - /** Test that the infoToString method does not NPE when given null values. */ - @Test - public void testInfoToStringNoNPE() { - Assert.assertNotNull(String.valueOf(Msg.make(() -> RequestLogHandler.infoToString(null, null, null, null)))); - } - - @Test - public void testHandler() throws InterruptedException { - reset(_mockLog); - Mockito.when(_mockLog.getName()).thenReturn("mockLog"); - RequestLogHandler handler = new RequestLogHandler(_mockLog, "testHandler"); - - Mockito.verify(_mockLog).getName(); - Mockito.verifyNoMoreInteractions(_mockLog); - - EmbeddedChannel ch = new EmbeddedChannel(handler); - - verify(_mockLog).info(eq("{} {} CONNECTED"), anyString(), any()); // 1 DEBUG message - connect - - Mockito.verifyNoMoreInteractions(_mockLog); - Mockito.reset(_mockLog); - - ch.disconnect().sync(); - - verify(_mockLog).info(eq("{} {} DISCONNECTED {}"), eq("testHandler"), any(), any()); // 1 DEBUG message - disconnect - - Mockito.verifyNoMoreInteractions(_mockLog); - } - - /** - * since TestNG tends to sort by method name, this tries to be the last test - * in the class. We do this because the AfterClass annotated methods may - * execute after other tests classes have run and doesn't execute immediately - * after the methods in this test class. - */ - @Test(alwaysRun = true) - public final void zz9PluralZAlpha() throws InterruptedException { - finallyLeakDetect(); - } -} diff --git a/internal/alpini/netty4/alpini-netty4-base/src/test/java/com/linkedin/alpini/netty4/misc/TestBalancedEventLoopGroup.java b/internal/alpini/netty4/alpini-netty4-base/src/test/java/com/linkedin/alpini/netty4/misc/TestBalancedEventLoopGroup.java deleted file mode 100644 index f7a09273f2..0000000000 --- a/internal/alpini/netty4/alpini-netty4-base/src/test/java/com/linkedin/alpini/netty4/misc/TestBalancedEventLoopGroup.java +++ /dev/null @@ -1,178 +0,0 @@ -package com.linkedin.alpini.netty4.misc; - -import com.linkedin.alpini.netty4.handlers.AllChannelsHandler; -import io.netty.bootstrap.Bootstrap; -import io.netty.bootstrap.ServerBootstrap; -import io.netty.channel.Channel; -import io.netty.channel.ChannelException; -import io.netty.channel.ChannelInitializer; -import io.netty.channel.DefaultEventLoop; -import io.netty.channel.EventLoop; -import io.netty.channel.EventLoopGroup; -import io.netty.channel.MultithreadEventLoopGroup; -import io.netty.channel.local.LocalAddress; -import io.netty.channel.local.LocalChannel; -import io.netty.channel.local.LocalServerChannel; -import io.netty.util.concurrent.EventExecutor; -import io.netty.util.concurrent.EventExecutorChooserFactory; -import io.netty.util.concurrent.FastThreadLocalThread; -import io.netty.util.concurrent.ThreadPerTaskExecutor; -import java.util.IdentityHashMap; -import java.util.IntSummaryStatistics; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.Executor; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.ThreadLocalRandom; -import java.util.stream.StreamSupport; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.testng.Assert; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; - - -@Test(groups = "unit") -public class TestBalancedEventLoopGroup { - final Logger _log = LogManager.getLogger(getClass()); - final UUID _uuid = UUID.randomUUID(); - - EventLoopGroup _unbalancedEventLoopGroup; - - @BeforeClass - public void beforeClass() { - ThreadFactory threadFactory = FastThreadLocalThread::new; - _unbalancedEventLoopGroup = - new MultithreadEventLoopGroup(32, new ThreadPerTaskExecutor(threadFactory), new UnbalancedChooser()) { - @Override - protected EventLoop newChild(Executor executor, Object... args) throws Exception { - return new DefaultEventLoop(this, executor); - } - }; - } - - @AfterClass - public void afterClass() throws Exception { - if (_unbalancedEventLoopGroup != null) { - _unbalancedEventLoopGroup.shutdownGracefully().sync(); - } - } - - @Test() - public void testBalanceLocal() throws Exception { - testBalanceLocal(new LocalAddress("testBalanceLocal" + _uuid), true, true); - } - - @Test(expectedExceptions = AssertionError.class, expectedExceptionsMessageRegExp = "Server Unbalanced.*") - public void testUnbalancedServer() throws Exception { - testBalanceLocal(new LocalAddress("testUnbalancedServer" + _uuid), false, true); - } - - @Test(expectedExceptions = AssertionError.class, expectedExceptionsMessageRegExp = "Client Unbalanced.*") - public void testUnbalancedClient() throws Exception { - testBalanceLocal(new LocalAddress("testUnbalancedClient" + _uuid), true, false); - } - - private void testBalanceLocal(LocalAddress bindAddress, boolean balanceServer, boolean balanceClient) - throws Exception { - int currentAttempt = 0; - int maxAttempts = 100; - while (currentAttempt++ < maxAttempts) { - try { - AllChannelsHandler allClientConnections = new AllChannelsHandler(); - AllChannelsHandler allServerConnections = new AllChannelsHandler(); - Map clientDist = new IdentityHashMap<>(); - Map serverDist = new IdentityHashMap<>(); - - ServerBootstrap serverBootstrap = new ServerBootstrap().channel(LocalServerChannel.class) - .group( - balanceServer - ? new BalancedEventLoopGroup(_unbalancedEventLoopGroup, allServerConnections) - : _unbalancedEventLoopGroup) - .childHandler(new ChannelInitializer() { - @Override - protected void initChannel(LocalChannel ch) throws Exception { - ch.pipeline().addLast(allServerConnections); - synchronized (serverDist) { - serverDist.compute(ch.eventLoop(), (eventLoop, integer) -> integer != null ? integer + 1 : 1); - - } - } - }); - - Channel server = serverBootstrap.bind(bindAddress).sync().channel(); - - try { - - Bootstrap bootstrap = new Bootstrap().channel(LocalChannel.class) - .group( - balanceClient - ? new BalancedEventLoopGroup(_unbalancedEventLoopGroup, allClientConnections) - : _unbalancedEventLoopGroup) - .handler(new ChannelInitializer() { - @Override - protected void initChannel(LocalChannel ch) throws Exception { - ch.pipeline().addLast(allClientConnections); - } - }); - - final int iterations = 10000; - final int thread = (int) StreamSupport.stream(_unbalancedEventLoopGroup.spliterator(), false).count(); - final int expect = iterations / thread; - - for (int i = iterations; i > 0; i--) { - Channel ch = bootstrap.connect(server.localAddress()).sync().channel(); - - clientDist.compute(ch.eventLoop(), (eventLoop, integer) -> integer != null ? integer + 1 : 1); - } - - IntSummaryStatistics serverStats = - serverDist.values().stream().mapToInt(Integer::intValue).summaryStatistics(); - IntSummaryStatistics clientStats = - clientDist.values().stream().mapToInt(Integer::intValue).summaryStatistics(); - - _log.info("Expected average = {}", expect); - _log.info("Server Stats = {}", serverStats); - _log.info("Client Stats = {}", clientStats); - - // We check that the average is as expected - // Also check that the difference between min and max is less than or equal to 2 - - Assert.assertEquals(serverStats.getAverage(), expect, 1.0, "Server Unbalanced"); - Assert.assertTrue(serverStats.getMax() - serverStats.getMin() <= 2, "Server Unbalanced"); - - Assert.assertEquals(clientStats.getAverage(), expect, 1.0, "Client Unbalanced"); - Assert.assertTrue(clientStats.getMax() - clientStats.getMin() <= 2, "Client Unbalanced"); - - } finally { - server.close().syncUninterruptibly(); - } - return; - } catch (ChannelException e) { - if (e.getMessage().startsWith("address already in use by")) { - _log.warn("Failed attempt {}/{}", currentAttempt, maxAttempts, e); - } else { - throw e; - } - } - } - Assert.fail("Failed to get an address after " + maxAttempts + " attempts."); - } - - private static class UnbalancedChooser implements EventExecutorChooserFactory { - @Override - public EventExecutorChooser newChooser(EventExecutor[] executors) { - return () -> { - ThreadLocalRandom random = ThreadLocalRandom.current(); - // Random gives a pretty even distribution but we want an uneven distribution. - // Six random numbers averaged gives a reasonably good approximation of the bell curve. - int index = (random.nextInt(executors.length) + random.nextInt(executors.length) - + random.nextInt(executors.length) + random.nextInt(executors.length) + random.nextInt(executors.length) - + random.nextInt(executors.length)) / 6; - return executors[index]; - }; - } - } - -} diff --git a/internal/alpini/netty4/alpini-netty4-base/src/test/java/com/linkedin/alpini/netty4/misc/TestBasicHttpRequestSerializer.java b/internal/alpini/netty4/alpini-netty4-base/src/test/java/com/linkedin/alpini/netty4/misc/TestBasicHttpRequestSerializer.java deleted file mode 100644 index 4871d271f1..0000000000 --- a/internal/alpini/netty4/alpini-netty4-base/src/test/java/com/linkedin/alpini/netty4/misc/TestBasicHttpRequestSerializer.java +++ /dev/null @@ -1,245 +0,0 @@ -package com.linkedin.alpini.netty4.misc; - -import com.linkedin.alpini.base.cache.ByteBufHashMap; -import com.linkedin.alpini.base.misc.Time; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufInputStream; -import io.netty.buffer.ByteBufOutputStream; -import io.netty.buffer.PooledByteBufAllocator; -import io.netty.buffer.Unpooled; -import io.netty.handler.codec.http.DefaultHttpHeaders; -import io.netty.handler.codec.http.HttpHeaderNames; -import io.netty.handler.codec.http.HttpHeaders; -import io.netty.handler.codec.http.HttpMethod; -import io.netty.handler.codec.http.HttpVersion; -import io.netty.util.ReferenceCountUtil; -import java.nio.charset.StandardCharsets; -import java.util.LinkedList; -import java.util.List; -import org.testng.Assert; -import org.testng.annotations.Test; - - -/** - * Created by acurtis on 8/16/17. - */ -public class TestBasicHttpRequestSerializer { - private final ByteBufHashMap.SerDes _serDes = - new BasicHttpRequestSerializer(PooledByteBufAllocator.DEFAULT); - - @Test(groups = "unit") - public void testSerializeBasicHttpRequest() { - BasicHttpRequest request = new BasicHttpRequest( - HttpVersion.HTTP_1_1, - HttpMethod.GET, - "/hello/world", - Time.currentTimeMillis(), - Time.nanoTime()); - request.headers().set(HttpHeaderNames.USER_AGENT, "Hello World Agent"); - - ByteBuf buffer = PooledByteBufAllocator.DEFAULT.buffer(); - - Assert.assertTrue(_serDes.serialize(new ByteBufOutputStream(buffer), request)); - - BasicHttpRequest result = _serDes.deserialize(new ByteBufInputStream(buffer)); - - Assert.assertNotSame(result, request); - Assert.assertNotSame(result.headers(), request.headers()); - - Assert.assertEquals(result, request); - Assert.assertEquals(result.hashCode(), request.hashCode()); - - buffer.release(); - } - - @Test(groups = "unit") - public void testSerializeBasicFullHttpRequest() { - BasicFullHttpRequest request = new BasicFullHttpRequest( - HttpVersion.HTTP_1_1, - HttpMethod.GET, - "/hello/world", - Time.currentTimeMillis(), - Time.nanoTime()); - request.headers().set(HttpHeaderNames.USER_AGENT, "Hello World Agent"); - request.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain"); - request.content().writeBytes("Hello world content".getBytes(StandardCharsets.US_ASCII)); - - ByteBuf buffer = PooledByteBufAllocator.DEFAULT.buffer(); - - Assert.assertTrue(_serDes.serialize(new ByteBufOutputStream(buffer), request)); - - BasicHttpRequest result = _serDes.deserialize(new ByteBufInputStream(buffer)); - - Assert.assertNotSame(result, request); - Assert.assertNotSame(result.headers(), request.headers()); - - Assert.assertEquals(result, request); - Assert.assertEquals(result.hashCode(), request.hashCode()); - - Assert.assertTrue(ReferenceCountUtil.release(result)); - - buffer.release(); - } - - @Test(groups = "unit") - public void testSerializeBasicFullHttpMultipartRequest() { - List content = new LinkedList<>(); - - HttpHeaders partHeader = new DefaultHttpHeaders(false); - partHeader.add(HttpHeaderNames.CONTENT_TYPE, "text/plain"); - content.add( - new BasicFullHttpMultiPart( - Unpooled.copiedBuffer("This is test content", StandardCharsets.US_ASCII), - partHeader)); - - BasicFullHttpMultiPartRequest request = new BasicFullHttpMultiPartRequest( - HttpVersion.HTTP_1_1, - HttpMethod.GET, - "/hello/world", - content, - Time.currentTimeMillis(), - Time.nanoTime()); - request.headers().set(HttpHeaderNames.USER_AGENT, "Hello World Agent"); - request.headers().set(HttpHeaderNames.CONTENT_TYPE, "multipart/mixed"); - - ByteBuf buffer = PooledByteBufAllocator.DEFAULT.buffer(); - - Assert.assertTrue(_serDes.serialize(new ByteBufOutputStream(buffer), request)); - - BasicHttpRequest result = _serDes.deserialize(new ByteBufInputStream(buffer)); - - Assert.assertNotSame(result, request); - Assert.assertNotSame(result.headers(), request.headers()); - - Assert.assertEquals(result, request); - Assert.assertEquals(result.hashCode(), request.hashCode()); - - Assert.assertTrue(ReferenceCountUtil.release(result)); - - buffer.release(); - } - - @Test(groups = "unit") - public void testCopyBasicFullHttpMultipartRequest() { - List content = new LinkedList<>(); - - HttpHeaders partHeader = new DefaultHttpHeaders(false); - partHeader.add(HttpHeaderNames.CONTENT_TYPE, "text/plain"); - content.add( - new BasicFullHttpMultiPart( - Unpooled.copiedBuffer("This is test content", StandardCharsets.US_ASCII), - partHeader)); - - BasicFullHttpMultiPartRequest request = new BasicFullHttpMultiPartRequest( - HttpVersion.HTTP_1_1, - HttpMethod.GET, - "/hello/world", - content, - Time.currentTimeMillis(), - Time.nanoTime()); - request.headers().set(HttpHeaderNames.USER_AGENT, "Hello World Agent"); - request.headers().set(HttpHeaderNames.CONTENT_TYPE, "multipart/mixed"); - - BasicHttpRequest result = request.copy(); // doesn't change ref count - - Assert.assertNotSame(result, request); - Assert.assertSame(result.headers(), request.headers()); - - Assert.assertEquals(result, request); - Assert.assertEquals(result.hashCode(), request.hashCode()); - - Assert.assertTrue(ReferenceCountUtil.release(result)); - } - - @Test(groups = "unit") - public void testDuplicateBasicFullHttpMultipartRequest() { - List content = new LinkedList<>(); - - HttpHeaders partHeader = new DefaultHttpHeaders(false); - partHeader.add(HttpHeaderNames.CONTENT_TYPE, "text/plain"); - content.add( - new BasicFullHttpMultiPart( - Unpooled.copiedBuffer("This is test content", StandardCharsets.US_ASCII), - partHeader)); - - BasicFullHttpMultiPartRequest request = new BasicFullHttpMultiPartRequest( - HttpVersion.HTTP_1_1, - HttpMethod.GET, - "/hello/world", - content, - Time.currentTimeMillis(), - Time.nanoTime()); - request.headers().set(HttpHeaderNames.USER_AGENT, "Hello World Agent"); - request.headers().set(HttpHeaderNames.CONTENT_TYPE, "multipart/mixed"); - - BasicHttpRequest result = request.duplicate(); // doesn't change ref count - - Assert.assertNotSame(result, request); - Assert.assertSame(result.headers(), request.headers()); - - Assert.assertEquals(result, request); - Assert.assertEquals(result.hashCode(), request.hashCode()); - - Assert.assertTrue(ReferenceCountUtil.release(result)); - } - - @Test(groups = "unit") - public void testRetainedDuplicateBasicFullHttpMultipartRequest() { - List content = new LinkedList<>(); - - HttpHeaders partHeader = new DefaultHttpHeaders(false); - partHeader.add(HttpHeaderNames.CONTENT_TYPE, "text/plain"); - content.add( - new BasicFullHttpMultiPart( - Unpooled.copiedBuffer("This is test content", StandardCharsets.US_ASCII), - partHeader)); - - BasicFullHttpMultiPartRequest request = new BasicFullHttpMultiPartRequest( - HttpVersion.HTTP_1_1, - HttpMethod.GET, - "/hello/world", - content, - Time.currentTimeMillis(), - Time.nanoTime()); - request.headers().set(HttpHeaderNames.USER_AGENT, "Hello World Agent"); - request.headers().set(HttpHeaderNames.CONTENT_TYPE, "multipart/mixed"); - - BasicHttpRequest result = request.retainedDuplicate(); // increments ref count - - Assert.assertNotSame(result, request); - Assert.assertSame(result.headers(), request.headers()); - - Assert.assertEquals(result, request); - Assert.assertEquals(result.hashCode(), request.hashCode()); - - Assert.assertFalse(ReferenceCountUtil.release(result, 1)); - } - - @Test(groups = "unit") - public void testSerializeEmptyFullHttpMultipartRequest() { - BasicFullHttpMultiPartRequest request = new BasicFullHttpMultiPartRequest( - HttpVersion.HTTP_1_1, - HttpMethod.GET, - "/hello/world", - Time.currentTimeMillis(), - Time.nanoTime()); - request.headers().set(HttpHeaderNames.USER_AGENT, "Hello World Agent"); - request.headers().set(HttpHeaderNames.CONTENT_TYPE, "multipart/mixed"); - - ByteBuf buffer = PooledByteBufAllocator.DEFAULT.buffer(); - - Assert.assertTrue(_serDes.serialize(new ByteBufOutputStream(buffer), request)); - - BasicHttpRequest result = _serDes.deserialize(new ByteBufInputStream(buffer)); - - Assert.assertNotSame(result, request); - Assert.assertNotSame(result.headers(), request.headers()); - - Assert.assertEquals(result, request); - Assert.assertEquals(result.hashCode(), request.hashCode()); - - Assert.assertFalse(ReferenceCountUtil.release(result)); - - buffer.release(); - } -} diff --git a/internal/alpini/netty4/alpini-netty4-base/src/test/java/com/linkedin/alpini/netty4/pool/TestChannelPoolManagerImplHttp2Ping.java b/internal/alpini/netty4/alpini-netty4-base/src/test/java/com/linkedin/alpini/netty4/pool/TestChannelPoolManagerImplHttp2Ping.java index a253c241c8..785690784f 100644 --- a/internal/alpini/netty4/alpini-netty4-base/src/test/java/com/linkedin/alpini/netty4/pool/TestChannelPoolManagerImplHttp2Ping.java +++ b/internal/alpini/netty4/alpini-netty4-base/src/test/java/com/linkedin/alpini/netty4/pool/TestChannelPoolManagerImplHttp2Ping.java @@ -5,8 +5,6 @@ import static org.mockito.Mockito.when; import static org.mockito.Mockito.withSettings; -import com.linkedin.alpini.base.monitoring.CallTrackerImpl; -import com.linkedin.alpini.base.monitoring.NullCallTracker; import com.linkedin.alpini.consts.QOS; import com.linkedin.alpini.netty4.misc.NettyUtils; import com.linkedin.venice.utils.TestUtils; @@ -106,16 +104,12 @@ private void http2PingTest( Assert.assertNotNull(channelFuture.await().getNow()); manager.startPeriodicPing(); - ChannelPoolManager.PoolStats poolStats = ((ChannelPoolManager.PoolStats) manager.getPools().toArray()[0]); - ; if (enableHttp2Ping) { TestUtils.waitForNonDeterministicAssertion(1, TimeUnit.SECONDS, () -> { Assert.assertTrue(manager.enablePeriodicPing()); Assert.assertNotNull(manager.getPeriodicPingScheduledFuture()); Assert.assertEquals(manager.getPools().size(), 1); - Assert.assertTrue(poolStats.http2PingCallTracker() instanceof CallTrackerImpl); - Assert.assertEquals(poolStats.getAvgResponseTimeOfLatestPings(), 0D); Mockito.verify(channelPoolFactory, useGlobalPool ? Mockito.times(1) : Mockito.times(numOfExecutors)) .construct(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()); }); @@ -124,8 +118,6 @@ private void http2PingTest( Assert.assertFalse(manager.enablePeriodicPing()); Assert.assertNull(manager.getPeriodicPingScheduledFuture()); Assert.assertEquals(manager.getPools().size(), 1); - Assert.assertEquals(poolStats.http2PingCallTracker(), NullCallTracker.INSTANCE); - Assert.assertEquals(poolStats.getAvgResponseTimeOfLatestPings(), 0D); Mockito.verify(channelPoolFactory, useGlobalPool ? Mockito.times(1) : Mockito.times(numOfExecutors)) .construct(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()); }); diff --git a/internal/alpini/netty4/alpini-netty4-base/src/test/java/com/linkedin/alpini/netty4/pool/TestChannelPoolResolver.java b/internal/alpini/netty4/alpini-netty4-base/src/test/java/com/linkedin/alpini/netty4/pool/TestChannelPoolResolver.java index 968e076607..30492e15a7 100644 --- a/internal/alpini/netty4/alpini-netty4-base/src/test/java/com/linkedin/alpini/netty4/pool/TestChannelPoolResolver.java +++ b/internal/alpini/netty4/alpini-netty4-base/src/test/java/com/linkedin/alpini/netty4/pool/TestChannelPoolResolver.java @@ -1,10 +1,5 @@ package com.linkedin.alpini.netty4.pool; -import com.linkedin.alpini.util.TestNettyUtil; -import io.netty.channel.epoll.EpollDatagramChannel; -import io.netty.channel.epoll.EpollEventLoopGroup; -import io.netty.channel.nio.NioEventLoopGroup; -import io.netty.channel.socket.nio.NioDatagramChannel; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.ImmediateEventExecutor; import java.net.InetSocketAddress; @@ -46,52 +41,4 @@ public void testBasicDnsResolver() throws InterruptedException { Assert.assertEquals(result.getNow().getHostName(), "unresolved.linkedin.com"); Assert.assertEquals(result.getNow().getPort(), 80); } - - @Test(groups = "unit") - public void testNettyDnsResolverNIO() throws InterruptedException { - NioEventLoopGroup eventLoop = new NioEventLoopGroup(4); - try { - testNettyDnsResolver(new NettyDnsResolver(NioDatagramChannel.class, eventLoop)); - } finally { - eventLoop.shutdownGracefully().sync(); - } - } - - @Test(groups = "unit") - public void testNettyDnsResolverEPOLL() throws InterruptedException { - EpollEventLoopGroup eventLoop = TestNettyUtil.skipEpollIfNotFound(() -> new EpollEventLoopGroup(4)); - try { - testNettyDnsResolver(new NettyDnsResolver(EpollDatagramChannel.class, eventLoop)); - } finally { - eventLoop.shutdownGracefully().sync(); - } - } - - private void testNettyDnsResolver(NettyDnsResolver resolver) throws InterruptedException { - Future result = resolver - .resolve(InetSocketAddress.createUnresolved("localhost", 80), ImmediateEventExecutor.INSTANCE.newPromise()); - - Assert.assertTrue(result.await().isSuccess()); - Assert.assertFalse(result.getNow().isUnresolved()); - Assert.assertEquals(result.getNow().getHostName(), "localhost"); - Assert.assertEquals(result.getNow().getPort(), 80); - Assert.assertEquals(result.getNow().getAddress().getHostAddress(), "127.0.0.1"); - - result = resolver - .resolve(InetSocketAddress.createUnresolved("google.com", 80), ImmediateEventExecutor.INSTANCE.newPromise()); - - Assert.assertTrue(result.await().isSuccess()); - Assert.assertFalse(result.getNow().isUnresolved()); - Assert.assertEquals(result.getNow().getHostName(), "google.com"); - Assert.assertEquals(result.getNow().getPort(), 80); - - result = resolver.resolve( - InetSocketAddress.createUnresolved("unresolved.linkedin.com", 80), - ImmediateEventExecutor.INSTANCE.newPromise()); - - Assert.assertTrue(result.await().isDone()); - Assert.assertFalse(result.isSuccess()); - Assert.assertTrue(result.cause().getMessage().startsWith("Failed to resolve 'unresolved.linkedin.com'")); - } - } diff --git a/internal/alpini/netty4/alpini-netty4-base/src/test/java/com/linkedin/alpini/netty4/pool/TestNettyDnsResolver.java b/internal/alpini/netty4/alpini-netty4-base/src/test/java/com/linkedin/alpini/netty4/pool/TestNettyDnsResolver.java deleted file mode 100644 index 58f2faae41..0000000000 --- a/internal/alpini/netty4/alpini-netty4-base/src/test/java/com/linkedin/alpini/netty4/pool/TestNettyDnsResolver.java +++ /dev/null @@ -1,93 +0,0 @@ -package com.linkedin.alpini.netty4.pool; - -import io.netty.channel.nio.NioEventLoopGroup; -import io.netty.channel.socket.nio.NioDatagramChannel; -import io.netty.util.concurrent.Future; -import io.netty.util.concurrent.ImmediateEventExecutor; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.util.ArrayDeque; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.testng.Assert; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; - - -@Test(groups = "unit") -public class TestNettyDnsResolver { - private static final Logger LOG = LogManager.getLogger(TestNettyDnsResolver.class); - - NioEventLoopGroup _nioEventLoopGroup; - ChannelPoolResolver _resolver; - - @BeforeClass - public void beforeClass() { - _nioEventLoopGroup = new NioEventLoopGroup(4); - _resolver = new NettyDnsResolver(NioDatagramChannel.class, _nioEventLoopGroup); - } - - @AfterClass(alwaysRun = true) - public void afterClass() { - if (_nioEventLoopGroup != null) { - _nioEventLoopGroup.shutdownGracefully(); - } - } - - @Test - public void basicLocalhostResolvedTest() throws Exception { - - InetSocketAddress resolved = new InetSocketAddress("localhost", 1245); - Assert.assertFalse(resolved.isUnresolved()); - - Future future = _resolver.resolve(resolved, ImmediateEventExecutor.INSTANCE.newPromise()); - - InetSocketAddress address = future.get(); - - Assert.assertSame(address, resolved); - } - - private static String getLocalHostName() { - try { - InetAddress localHost = InetAddress.getLocalHost(); - InetAddress address = InetAddress.getByAddress(localHost.getAddress()); - return address.getHostName(); - } catch (Exception ex) { - return "localhost"; - } - } - - @Test - public void basicLocalhostTest() throws Exception { - String localhost = getLocalHostName(); - LOG.error("localhost = {}", localhost); - - Future future = _resolver - .resolve(InetSocketAddress.createUnresolved(localhost, 1234), ImmediateEventExecutor.INSTANCE.newPromise()); - - InetSocketAddress address = future.get(); - - Assert.assertEquals(address.getPort(), 1234); - Assert.assertFalse(address.isUnresolved()); - - ArrayDeque>> futures = new ArrayDeque<>(100); - - for (int i = 100; i > 0; i--) { - - futures.add( - _nioEventLoopGroup.submit( - () -> _resolver.resolve( - InetSocketAddress.createUnresolved(localhost, 1234), - ImmediateEventExecutor.INSTANCE.newPromise()))); - } - - for (Future> f: futures) { - InetSocketAddress addr = f.get().get(); - - Assert.assertEquals(addr.getPort(), 1234); - Assert.assertFalse(addr.isUnresolved()); - - } - } -} diff --git a/internal/alpini/router/alpini-router-api/src/main/java/com/linkedin/alpini/router/api/HostFinder.java b/internal/alpini/router/alpini-router-api/src/main/java/com/linkedin/alpini/router/api/HostFinder.java index d6c9dbb631..4a52c978cc 100644 --- a/internal/alpini/router/alpini-router-api/src/main/java/com/linkedin/alpini/router/api/HostFinder.java +++ b/internal/alpini/router/alpini-router-api/src/main/java/com/linkedin/alpini/router/api/HostFinder.java @@ -1,7 +1,6 @@ package com.linkedin.alpini.router.api; import com.linkedin.alpini.base.concurrency.AsyncFuture; -import java.util.Collection; import java.util.List; import javax.annotation.Nonnull; @@ -34,51 +33,6 @@ List findHosts( @Nonnull HostHealthMonitor hostHealthMonitor, @Nonnull R roles) throws RouterException; - /** - * By accepting the String of initialHost, the host used by initial request, this method would enable its overriding - * method to use the initialHost as part of its host selection algorithm. - * For the sake of backward compatibility, we made this method default and just ignore the initialHost and call - * the findHosts without initialHost. - * - * Find hosts that can serve the given database and partition with one of the roles passed. Roles are - * processed in order with roles at the same level being processed as equivalent, such that a request - * for {{"LEADER"}, {"FOLLOWER"}} would first look for a LEADER, and if none is available then it will - * look for a FOLLOWER, while a request for {{"LEADER","FOLLOWER"}} would pick a random host that is either - * a LEADER or a FOLLOWER. - * - * Note: This call is expected to be non-blocking - */ - @Nonnull - default List findHosts( - @Nonnull String requestMethod, - @Nonnull String resourceName, - @Nonnull String partitionName, - @Nonnull HostHealthMonitor hostHealthMonitor, - @Nonnull R roles, - String initialHost) throws RouterException { - return findHosts(requestMethod, resourceName, partitionName, hostHealthMonitor, roles); - }; - - /** - * Find hosts that can serve the given database with all the roles passed. Roles are - * processed in order, such that a request for {"LEADER", "FOLLOWER"} would first look for a LEADER, and if - * none is available then it will look for a FOLLOWER. - * - * Note: This call is expected to be non-blocking - */ - default @Nonnull Collection findAllHosts(@Nonnull String resourceName, R roles) throws RouterException { - return findAllHosts(roles); - } - - /** - * Find all hosts that can serve with all the roles passed. - * @param roles {@code null} means for all roles. - * - * Note: This call is expected to be non-blocking - */ - @Nonnull - Collection findAllHosts(R roles) throws RouterException; - /** * Returns an async future which is completed when a change in the routing table occurs. */ diff --git a/internal/alpini/router/alpini-router-api/src/main/java/com/linkedin/alpini/router/api/ScatterGatherHelper.java b/internal/alpini/router/alpini-router-api/src/main/java/com/linkedin/alpini/router/api/ScatterGatherHelper.java index 6d258deeca..dc23ffe2d1 100644 --- a/internal/alpini/router/alpini-router-api/src/main/java/com/linkedin/alpini/router/api/ScatterGatherHelper.java +++ b/internal/alpini/router/alpini-router-api/src/main/java/com/linkedin/alpini/router/api/ScatterGatherHelper.java @@ -25,6 +25,7 @@ import java.util.function.Supplier; import java.util.stream.Stream; import javax.annotation.Nonnull; +import javax.annotation.Nullable; /** @@ -39,7 +40,7 @@ public class ScatterGatherHelper, K, R, BASIC_HTTP_ private final @Nonnull ScatterGatherMode _broadcastMode; private final @Nonnull ScatterGatherMode _scatterMode; private final @Nonnull PartitionDispatchHandler _dispatchHandler; - private final @Nonnull Optional> _responseAggregatorFactory; + private final @Nullable ResponseAggregatorFactory _responseAggregatorFactory; private final @Nonnull Function _requestTimeout; private final @Nonnull LongTailRetrySupplier _longTailRetrySupplier; private final @Nonnull Function _metricsProvider; @@ -97,7 +98,7 @@ protected ScatterGatherHelper( _broadcastMode = Objects.requireNonNull(broadcastMode, "broadcastMode"); _scatterMode = Objects.requireNonNull(scatterMode, "scatterMode"); _dispatchHandler = Objects.requireNonNull(dispatchHandler, "dispatchHandler"); - _responseAggregatorFactory = Objects.requireNonNull(responseAggregatorFactory, "responseFactory"); + _responseAggregatorFactory = responseAggregatorFactory.orElse(null); _requestTimeout = Objects.requireNonNull(requestTimeout, "requestTimeout"); _longTailRetrySupplier = Objects.requireNonNull(longTailRetrySupplier, "longTailRetrySupplier"); _metricsProvider = Objects.requireNonNull(metricsProvider, "metricsProvider"); @@ -226,16 +227,7 @@ public Netty dispatcherNettyVersion() { String resourceName = path.getResourceName(); HostFinder hostFinder = _hostFinder.getSnapshot(); ScatterGatherMode mode = path.getPartitionKeys().isEmpty() ? _broadcastMode : _scatterMode; - return mode.scatter( - scatter, - requestMethod, - resourceName, - _partitionFinder, - hostFinder, - hostHealthMonitor, - roles, - metrics, - initialHost); + return mode.scatter(scatter, requestMethod, resourceName, _partitionFinder, hostFinder, hostHealthMonitor, roles); } public @Nonnull HTTP_RESPONSE aggregateResponse( @@ -243,7 +235,10 @@ public Netty dispatcherNettyVersion() { Metrics metrics, @Nonnull List responses, @Nonnull ResponseAggregatorFactory defaultAggregator) { - return _responseAggregatorFactory.orElse(defaultAggregator).buildResponse(request, metrics, responses); + if (_responseAggregatorFactory == null) { + return defaultAggregator.buildResponse(request, metrics, responses); + } + return _responseAggregatorFactory.buildResponse(request, metrics, responses); } public CompletionStage findPartitionName(String resourceName, K key) { diff --git a/internal/alpini/router/alpini-router-api/src/main/java/com/linkedin/alpini/router/api/ScatterGatherMode.java b/internal/alpini/router/alpini-router-api/src/main/java/com/linkedin/alpini/router/api/ScatterGatherMode.java index e97c35cd58..fa0cc0d020 100644 --- a/internal/alpini/router/alpini-router-api/src/main/java/com/linkedin/alpini/router/api/ScatterGatherMode.java +++ b/internal/alpini/router/alpini-router-api/src/main/java/com/linkedin/alpini/router/api/ScatterGatherMode.java @@ -1,17 +1,15 @@ package com.linkedin.alpini.router.api; -import com.linkedin.alpini.base.misc.Metrics; import com.linkedin.alpini.base.misc.Pair; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Optional; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.CompletableFuture; @@ -19,7 +17,6 @@ import java.util.concurrent.CompletionStage; import java.util.function.Consumer; import java.util.function.Function; -import java.util.stream.Collectors; import java.util.stream.Stream; import javax.annotation.Nonnull; import org.apache.logging.log4j.LogManager; @@ -70,8 +67,7 @@ public , K, R> Scatter scatter( @Nonnull PartitionFinder partitionFinder, @Nonnull HostFinder hostFinder, @Nonnull HostHealthMonitor hostHealthMonitor, - @Nonnull R roles, - Metrics metrics) throws RouterException { + @Nonnull R roles) throws RouterException { throw new AbstractMethodError(); } @@ -83,31 +79,7 @@ public , K, R> CompletionStage> sc @Nonnull AsyncPartitionFinder partitionFinder, @Nonnull HostFinder hostFinder, @Nonnull HostHealthMonitor hostHealthMonitor, - @Nonnull R roles, - Metrics metrics, - String initialHost) { - // default method simply ignores the initialHost. - return scatter( - scatter, - requestMethod, - resourceName, - partitionFinder, - hostFinder, - hostHealthMonitor, - roles, - metrics); - } - - @Nonnull - public , K, R> CompletionStage> scatter( - @Nonnull Scatter scatter, - @Nonnull String requestMethod, - @Nonnull String resourceName, - @Nonnull AsyncPartitionFinder partitionFinder, - @Nonnull HostFinder hostFinder, - @Nonnull HostHealthMonitor hostHealthMonitor, - @Nonnull R roles, - Metrics metrics) { + @Nonnull R roles) { try { return CompletableFuture.completedFuture(scatter(scatter, requestMethod, resourceName, new PartitionFinder() { @Nonnull @@ -135,7 +107,7 @@ public int findPartitionNumber(@Nonnull K partitionKey, int numPartitions, Strin throws RouterException { return join(partitionFinder.findPartitionNumber(partitionKey, numPartitions, storeName, versionNumber)); } - }, hostFinder, hostHealthMonitor, roles, metrics)); + }, hostFinder, hostHealthMonitor, roles)); } catch (RouterException ex) { return failedFuture(ex); } @@ -247,41 +219,9 @@ public , K, R> Scatter scatter( @Nonnull PartitionFinder partitionFinder, @Nonnull HostFinder hostFinder, @Nonnull HostHealthMonitor hostHealthMonitor, - @Nonnull R roles, - Metrics metrics) throws RouterException { + @Nonnull R roles) throws RouterException { return join( - scatter( - scatter, - requestMethod, - resourceName, - sync(partitionFinder), - hostFinder, - hostHealthMonitor, - roles, - metrics)); - } - - @Nonnull - @Override - public , K, R> CompletionStage> scatter( - @Nonnull Scatter scatter, - @Nonnull String requestMethod, - @Nonnull String resourceName, - @Nonnull AsyncPartitionFinder partitionFinder, - @Nonnull HostFinder hostFinder, - @Nonnull HostHealthMonitor hostHealthMonitor, - @Nonnull R roles, - Metrics m) { - return scatterBase( - scatter, - requestMethod, - resourceName, - partitionFinder, - hostFinder, - hostHealthMonitor, - roles, - m, - null); + scatter(scatter, requestMethod, resourceName, sync(partitionFinder), hostFinder, hostHealthMonitor, roles)); } @Nonnull @@ -293,19 +233,8 @@ public , K, R> CompletionStage> sc @Nonnull AsyncPartitionFinder partitionFinder, @Nonnull HostFinder hostFinder, @Nonnull HostHealthMonitor hostHealthMonitor, - @Nonnull R roles, - Metrics m, - String initialHost) { - return scatterBase( - scatter, - requestMethod, - resourceName, - partitionFinder, - hostFinder, - hostHealthMonitor, - roles, - m, - initialHost); + @Nonnull R roles) { + return scatterBase(scatter, requestMethod, resourceName, partitionFinder, hostFinder, hostHealthMonitor, roles); } private , K, R> CompletionStage> scatterBase( @@ -315,15 +244,12 @@ private , K, R> CompletionStage> s @Nonnull AsyncPartitionFinder partitionFinder, @Nonnull HostFinder hostFinder, @Nonnull HostHealthMonitor hostHealthMonitor, - @Nonnull R roles, - Metrics m, - String initialHost) { + @Nonnull R roles) { return partitionFinder.getAllPartitionNames(resourceName).thenApply(partitions -> { try { for (String partitionName: partitions) { HostHealthChecked healthChecked = new HostHealthChecked<>(hostHealthMonitor); - List hosts = - hostFinder.findHosts(requestMethod, resourceName, partitionName, healthChecked, roles, initialHost); + List hosts = hostFinder.findHosts(requestMethod, resourceName, partitionName, healthChecked, roles); BroadcastScatterGatherRequest request; if ((hosts = healthChecked.check(hosts, partitionName)).isEmpty()) { // SUPPRESS CHECKSTYLE InnerAssignment request = new BroadcastScatterGatherRequest<>(Collections.emptyList()); @@ -356,8 +282,7 @@ public , K, R> Scatter scatter( @Nonnull PartitionFinder partitionFinder, @Nonnull HostFinder hostFinder, @Nonnull HostHealthMonitor hostHealthMonitor, - @Nonnull R roles, - Metrics metrics) throws RouterException { + @Nonnull R roles) throws RouterException { return join( scatter( scatter, @@ -366,31 +291,7 @@ public , K, R> Scatter scatter( sync(partitionFinder), hostFinder, hostHealthMonitor, - roles, - metrics)); - } - - @Nonnull - @Override - public , K, R> CompletionStage> scatter( - @Nonnull Scatter scatter, - @Nonnull String requestMethod, - @Nonnull String resourceName, - @Nonnull AsyncPartitionFinder partitionFinder, - @Nonnull HostFinder hostFinder, - @Nonnull HostHealthMonitor hostHealthMonitor, - @Nonnull R roles, - Metrics m) { - return scatterBase( - scatter, - requestMethod, - resourceName, - partitionFinder, - hostFinder, - hostHealthMonitor, - roles, - m, - null); + roles)); } @Nonnull @@ -402,9 +303,7 @@ public , K, R> CompletionStage> sc @Nonnull AsyncPartitionFinder partitionFinder, @Nonnull HostFinder hostFinder, @Nonnull HostHealthMonitor hostHealthMonitor, - @Nonnull R roles, - Metrics m, - String initialHost) { + @Nonnull R roles) { return scatterBase( scatter, requestMethod, @@ -412,9 +311,7 @@ public , K, R> CompletionStage> sc partitionFinder, hostFinder, hostHealthMonitor, - roles, - m, - initialHost); + roles); } private , K, R> CompletionStage> scatterBase( @@ -424,29 +321,26 @@ private , K, R> CompletionStage> s @Nonnull AsyncPartitionFinder partitionFinder, @Nonnull HostFinder hostFinder, @Nonnull HostHealthMonitor hostHealthMonitor, - @Nonnull R roles, - Metrics m, - String initialHost) { + @Nonnull R roles) { return partitionFinder.getAllPartitionNames(resourceName).thenApply(partitions -> { try { Map> hostMap = new HashMap<>(); for (String partitionName: partitions) { HostHealthChecked healthChecked = new HostHealthChecked<>(hostHealthMonitor); - Optional host = Optional.empty(); + H host = null; - List hosts = - hostFinder.findHosts(requestMethod, resourceName, partitionName, healthChecked, roles, initialHost); + List hosts = hostFinder.findHosts(requestMethod, resourceName, partitionName, healthChecked, roles); if (hosts != null) { List healthyHosts = healthChecked.check(hosts, partitionName); if (healthyHosts != null && !healthyHosts.isEmpty()) { // First host - host = Optional.of(healthyHosts.get(0)); + host = healthyHosts.get(0); } } BroadcastScatterGatherRequest request; - if (host.isPresent()) { - request = hostMap.computeIfAbsent(host.get(), h -> { + if (host != null) { + request = hostMap.computeIfAbsent(host, h -> { BroadcastScatterGatherRequest r = new BroadcastScatterGatherRequest<>(Collections.singletonList(h)); scatter.addOnlineRequest(r); @@ -479,18 +373,9 @@ public , K, R> Scatter scatter( @Nonnull PartitionFinder partitionFinder, @Nonnull HostFinder hostFinder, @Nonnull HostHealthMonitor hostHealthMonitor, - @Nonnull R roles, - Metrics metrics) throws RouterException { + @Nonnull R roles) throws RouterException { return join( - scatter( - scatter, - requestMethod, - resourceName, - sync(partitionFinder), - hostFinder, - hostHealthMonitor, - roles, - metrics)); + scatter(scatter, requestMethod, resourceName, sync(partitionFinder), hostFinder, hostHealthMonitor, roles)); } @Nonnull @@ -502,42 +387,8 @@ public , K, R> CompletionStage> sc @Nonnull AsyncPartitionFinder partitionFinder, @Nonnull HostFinder hostFinder, @Nonnull HostHealthMonitor hostHealthMonitor, - @Nonnull R roles, - Metrics m) { - return scatterBase( - scatter, - requestMethod, - resourceName, - partitionFinder, - hostFinder, - hostHealthMonitor, - roles, - m, - null); - } - - @Nonnull - @Override - public , K, R> CompletionStage> scatter( - @Nonnull Scatter scatter, - @Nonnull String requestMethod, - @Nonnull String resourceName, - @Nonnull AsyncPartitionFinder partitionFinder, - @Nonnull HostFinder hostFinder, - @Nonnull HostHealthMonitor hostHealthMonitor, - @Nonnull R roles, - Metrics m, - String initialHost) { - return scatterBase( - scatter, - requestMethod, - resourceName, - partitionFinder, - hostFinder, - hostHealthMonitor, - roles, - m, - initialHost); + @Nonnull R roles) { + return scatterBase(scatter, requestMethod, resourceName, partitionFinder, hostFinder, hostHealthMonitor, roles); } private , K, R> CompletionStage> scatterBase( @@ -547,9 +398,7 @@ private , K, R> CompletionStage> s @Nonnull AsyncPartitionFinder partitionFinder, @Nonnull HostFinder hostFinder, @Nonnull HostHealthMonitor hostHealthMonitor, - @Nonnull R roles, - Metrics m, - String initialHost) { + @Nonnull R roles) { CompletionStage>> keyMapStage = CompletableFuture.completedFuture(new HashMap<>()); for (K key: scatter.getPath().getPartitionKeys()) { keyMapStage = @@ -568,8 +417,7 @@ private , K, R> CompletionStage> s try { for (Map.Entry> entry: keyMap.entrySet()) { HostHealthChecked healthChecked = new HostHealthChecked<>(hostHealthMonitor); - List hosts = - hostFinder.findHosts(requestMethod, resourceName, entry.getKey(), healthChecked, roles, initialHost); + List hosts = hostFinder.findHosts(requestMethod, resourceName, entry.getKey(), healthChecked, roles); ScatterGatherRequest request; if ((hosts = healthChecked.check(hosts, entry.getKey())).isEmpty()) { // SUPPRESS CHECKSTYLE InnerAssignment request = new ScatterGatherRequest<>(Collections.emptyList(), entry.getValue()); @@ -657,18 +505,9 @@ public , K, R> Scatter scatter( @Nonnull PartitionFinder partitionFinder, @Nonnull HostFinder hostFinder, @Nonnull HostHealthMonitor hostHealthMonitor, - @Nonnull R roles, - Metrics metrics) throws RouterException { + @Nonnull R roles) throws RouterException { return join( - scatter( - scatter, - requestMethod, - resourceName, - sync(partitionFinder), - hostFinder, - hostHealthMonitor, - roles, - metrics)); + scatter(scatter, requestMethod, resourceName, sync(partitionFinder), hostFinder, hostHealthMonitor, roles)); } @Nonnull @@ -680,42 +519,8 @@ public , K, R> CompletionStage> sc @Nonnull AsyncPartitionFinder partitionFinder, @Nonnull HostFinder hostFinder, @Nonnull HostHealthMonitor hostHealthMonitor, - @Nonnull R roles, - Metrics m) { - return scatterBase( - scatter, - requestMethod, - resourceName, - partitionFinder, - hostFinder, - hostHealthMonitor, - roles, - m, - null); - } - - @Nonnull - @Override - public , K, R> CompletionStage> scatter( - @Nonnull Scatter scatter, - @Nonnull String requestMethod, - @Nonnull String resourceName, - @Nonnull AsyncPartitionFinder partitionFinder, - @Nonnull HostFinder hostFinder, - @Nonnull HostHealthMonitor hostHealthMonitor, - @Nonnull R roles, - Metrics m, - String initialHost) { - return scatterBase( - scatter, - requestMethod, - resourceName, - partitionFinder, - hostFinder, - hostHealthMonitor, - roles, - m, - initialHost); + @Nonnull R roles) { + return scatterBase(scatter, requestMethod, resourceName, partitionFinder, hostFinder, hostHealthMonitor, roles); } @Nonnull @@ -726,9 +531,7 @@ private , K, R> CompletionStage> s @Nonnull AsyncPartitionFinder partitionFinder, @Nonnull HostFinder hostFinder, @Nonnull HostHealthMonitor hostHealthMonitor, - @Nonnull R roles, - Metrics m, - String initialHost) { + @Nonnull R roles) { final class HostInfo implements Comparable { final H host; @@ -790,8 +593,7 @@ public int hashCode() { hostMapAndPartitions.getSecond().computeIfAbsent(partitionName, partition -> { try { HostHealthChecked healthChecked = new HostHealthChecked<>(hostHealthMonitor); - List hosts = - hostFinder.findHosts(requestMethod, resourceName, partition, healthChecked, roles, initialHost); + List hosts = hostFinder.findHosts(requestMethod, resourceName, partition, healthChecked, roles); if ((hosts = healthChecked.check(hosts, partitionName)).isEmpty()) { // SUPPRESS CHECKSTYLE // InnerAssignment hosts = Collections.emptyList(); @@ -822,25 +624,38 @@ public int hashCode() { HostInfo next = Collections.max(hostList); hostList.remove(next); - TreeSet keys = next.partitions.stream() - .flatMap( - partition -> Optional.ofNullable(partitions.remove(partition)) - .map(Pair::getSecond) - .map(Collection::stream) - .orElseGet(Stream::empty)) - .collect(Collectors.toCollection(TreeSet::new)); + TreeSet keys = new TreeSet(); + Pair, List> hostsToKeysPair; + for (String partition: next.partitions) { + hostsToKeysPair = partitions.remove(partition); + if (hostsToKeysPair != null) { + keys.addAll(hostsToKeysPair.getSecond()); + } + } scatter.addOnlineRequest(new ScatterGatherRequest<>(Collections.singletonList(next.host), keys)); - hostList.removeIf(hostInfo -> { - next.partitions.stream().filter(hostInfo.partitions::remove).forEach(partition -> hostInfo.count--); - return hostInfo.count < 1 || hostInfo.partitions.isEmpty(); - }); + Iterator hostListIterator = hostList.iterator(); + HostInfo hostInfo; + while (hostListIterator.hasNext()) { + hostInfo = hostListIterator.next(); + if (hostInfo != null) { + for (String partition: next.partitions) { + if (hostInfo.partitions.remove(partition)) { + hostInfo.count--; + } + } + if (hostInfo.count < 1 || hostInfo.partitions.isEmpty()) { + hostListIterator.remove(); + } + } + } } - partitions.forEach( - (partition, pair) -> scatter.addOfflineRequest( - new ScatterGatherRequest(Collections.emptyList(), new TreeSet<>(pair.getSecond())))); + for (Pair, List> pair: partitions.values()) { + scatter.addOfflineRequest( + new ScatterGatherRequest<>(Collections.emptyList(), new TreeSet<>(pair.getSecond()))); + } return scatter; }); diff --git a/internal/alpini/router/alpini-router-api/src/test/java/com/linkedin/alpini/router/api/TestScatterGatherHelperBuilder.java b/internal/alpini/router/alpini-router-api/src/test/java/com/linkedin/alpini/router/api/TestScatterGatherHelperBuilder.java index c5b3b4cbed..3466217ff9 100644 --- a/internal/alpini/router/alpini-router-api/src/test/java/com/linkedin/alpini/router/api/TestScatterGatherHelperBuilder.java +++ b/internal/alpini/router/alpini-router-api/src/test/java/com/linkedin/alpini/router/api/TestScatterGatherHelperBuilder.java @@ -127,19 +127,6 @@ public List findHosts( return Collections .singletonList(hosts.get((int) ((0xffffffffL & (long) partitionName.hashCode()) % hosts.size()))); } - - @Nonnull - @Override - public Collection findAllHosts(@Nonnull String resourceName, List> roles) - throws RouterException { - return findAllHosts(roles); - } - - @Nonnull - @Override - public Collection findAllHosts(List> roles) throws RouterException { - return Collections.unmodifiableCollection(hosts); - } } Assert.assertNotNull( diff --git a/internal/alpini/router/alpini-router-api/src/test/java/com/linkedin/alpini/router/api/TestScatterGatherMode.java b/internal/alpini/router/alpini-router-api/src/test/java/com/linkedin/alpini/router/api/TestScatterGatherMode.java index 64eb727547..cfc51662f8 100644 --- a/internal/alpini/router/alpini-router-api/src/test/java/com/linkedin/alpini/router/api/TestScatterGatherMode.java +++ b/internal/alpini/router/alpini-router-api/src/test/java/com/linkedin/alpini/router/api/TestScatterGatherMode.java @@ -128,8 +128,7 @@ public

, K> void testBasicBroadcastOfflinePartition(Sc AsyncPartitionFinder.adapt(partitionFinder, Runnable::run), hostFinder, hostHealthMonitor, - roles, - null)) + roles)) .thenCompose(Function.identity()) .join(), scatter); @@ -167,8 +166,7 @@ public

, K> void testBasicBroadcastOnePartition(Scatte Mockito.eq(resourceName), Mockito.eq("OnePartition"), any(), - Mockito.eq(roles), - any()); + Mockito.eq(roles)); Mockito.doReturn(true).when(hostHealthMonitor).isHostHealthy(Mockito.eq(host), Mockito.eq("OnePartition")); @@ -182,8 +180,7 @@ public

, K> void testBasicBroadcastOnePartition(Scatte AsyncPartitionFinder.adapt(partitionFinder, Runnable::run), hostFinder, hostHealthMonitor, - roles, - null)) + roles)) .thenCompose(Function.identity()) .join(), scatter); @@ -222,8 +219,7 @@ public

, K> void testBasicBroadcastTwoPartitions(Scatt Mockito.eq(resourceName), Mockito.eq("OnePartition"), any(), - Mockito.eq(roles), - any()); + Mockito.eq(roles)); Mockito.doReturn(Collections.singletonList(host2)) .when(hostFinder) @@ -232,8 +228,7 @@ public

, K> void testBasicBroadcastTwoPartitions(Scatt Mockito.eq(resourceName), Mockito.eq("TwoPartition"), any(), - Mockito.eq(roles), - any()); + Mockito.eq(roles)); Mockito.doReturn(true).when(hostHealthMonitor).isHostHealthy(Mockito.eq(host1), Mockito.eq("OnePartition")); @@ -249,8 +244,7 @@ public

, K> void testBasicBroadcastTwoPartitions(Scatt AsyncPartitionFinder.adapt(partitionFinder, Runnable::run), hostFinder, hostHealthMonitor, - roles, - null)) + roles)) .thenCompose(Function.identity()) .join(), scatter); @@ -296,8 +290,7 @@ public

, K> void testBasicBroadcastOnlineOfflinePartit Mockito.eq(resourceName), Mockito.eq("OnePartition"), any(), - Mockito.eq(roles), - any()); + Mockito.eq(roles)); Mockito.doReturn(Collections.singletonList(host2)) .when(hostFinder) @@ -306,8 +299,7 @@ public

, K> void testBasicBroadcastOnlineOfflinePartit Mockito.eq(resourceName), Mockito.eq("TwoPartition"), any(), - Mockito.eq(roles), - any()); + Mockito.eq(roles)); Mockito.doReturn(true).when(hostHealthMonitor).isHostHealthy(Mockito.eq(host1), Mockito.eq("OnePartition")); @@ -323,8 +315,7 @@ public

, K> void testBasicBroadcastOnlineOfflinePartit AsyncPartitionFinder.adapt(partitionFinder, Runnable::run), hostFinder, hostHealthMonitor, - roles, - null)) + roles)) .thenCompose(Function.identity()) .join(), scatter); @@ -390,8 +381,7 @@ void testBasicScatterOfflinePartition(ScatterGatherMode mode) throws RouterExcep AsyncPartitionFinder.adapt(partitionFinder, Runnable::run), hostFinder, hostHealthMonitor, - roles, - null)) + roles)) .thenCompose(Function.identity()) .join(), scatter); @@ -432,8 +422,7 @@ void testBasicScatterOnePartition(ScatterGatherMode mode) throws RouterException Mockito.eq(resourceName), Mockito.eq("OnePartition"), any(), - Mockito.eq(roles), - any()); + Mockito.eq(roles)); Mockito.doReturn(true).when(hostHealthMonitor).isHostHealthy(Mockito.eq(host), Mockito.eq("OnePartition")); @@ -447,8 +436,7 @@ void testBasicScatterOnePartition(ScatterGatherMode mode) throws RouterException AsyncPartitionFinder.adapt(partitionFinder, Runnable::run), hostFinder, hostHealthMonitor, - roles, - null)) + roles)) .thenCompose(Function.identity()) .join(), scatter); @@ -500,8 +488,7 @@ void testBasicScatterTwoPartitions(ScatterGatherMode mode) throws RouterExceptio Mockito.eq(resourceName), Mockito.eq("OnePartition"), any(), - Mockito.eq(roles), - any()); + Mockito.eq(roles)); Mockito.doReturn(Collections.singletonList(host2)) .when(hostFinder) @@ -510,8 +497,7 @@ void testBasicScatterTwoPartitions(ScatterGatherMode mode) throws RouterExceptio Mockito.eq(resourceName), Mockito.eq("TwoPartition"), any(), - Mockito.eq(roles), - any()); + Mockito.eq(roles)); Mockito.doReturn(true).when(hostHealthMonitor).isHostHealthy(Mockito.eq(host1), Mockito.eq("OnePartition")); @@ -527,8 +513,7 @@ void testBasicScatterTwoPartitions(ScatterGatherMode mode) throws RouterExceptio AsyncPartitionFinder.adapt(partitionFinder, Runnable::run), hostFinder, hostHealthMonitor, - roles, - null)) + roles)) .thenCompose(Function.identity()) .join(), scatter); @@ -602,8 +587,7 @@ void testGroupByHost() throws RouterException { Mockito.eq(resourceName), Mockito.eq("OnePartition"), any(), - Mockito.eq(roles), - any()); + Mockito.eq(roles)); Mockito.doReturn(CollectionUtil.listOf(host2, host1)) .when(hostFinder) @@ -612,8 +596,7 @@ void testGroupByHost() throws RouterException { Mockito.eq(resourceName), Mockito.eq("TwoPartition"), any(), - Mockito.eq(roles), - any()); + Mockito.eq(roles)); Mockito.doReturn(CollectionUtil.listOf(host1, host2)) .when(hostFinder) @@ -622,8 +605,7 @@ void testGroupByHost() throws RouterException { Mockito.eq(resourceName), Mockito.eq("ThreePartition"), any(), - Mockito.eq(roles), - any()); + Mockito.eq(roles)); Mockito.doReturn(CollectionUtil.listOf(host2, host1)) .when(hostFinder) @@ -632,8 +614,7 @@ void testGroupByHost() throws RouterException { Mockito.eq(resourceName), Mockito.eq("FourPartition"), any(), - Mockito.eq(roles), - any()); + Mockito.eq(roles)); Mockito.doReturn(true).when(hostHealthMonitor).isHostHealthy(Mockito.eq(host1), Mockito.eq("OnePartition")); @@ -653,8 +634,7 @@ void testGroupByHost() throws RouterException { AsyncPartitionFinder.adapt(partitionFinder, Runnable::run), hostFinder, hostHealthMonitor, - roles, - null)) + roles)) .thenCompose(Function.identity()) .join(), scatter); @@ -741,8 +721,7 @@ void testGroupByGreedyHost() throws RouterException { Mockito.eq(resourceName), Mockito.eq("OnePartition"), any(), - Mockito.eq(roles), - any()); + Mockito.eq(roles)); Mockito.doReturn(CollectionUtil.listOf(host2, host3)) .when(hostFinder) @@ -751,8 +730,7 @@ void testGroupByGreedyHost() throws RouterException { Mockito.eq(resourceName), Mockito.eq("TwoPartition"), any(), - Mockito.eq(roles), - any()); + Mockito.eq(roles)); Mockito.doReturn(CollectionUtil.listOf(host1, host2, host3)) .when(hostFinder) @@ -761,8 +739,7 @@ void testGroupByGreedyHost() throws RouterException { Mockito.eq(resourceName), Mockito.eq("ThreePartition"), any(), - Mockito.eq(roles), - any()); + Mockito.eq(roles)); Mockito.doReturn(CollectionUtil.listOf(host2, host3)) .when(hostFinder) @@ -771,8 +748,7 @@ void testGroupByGreedyHost() throws RouterException { Mockito.eq(resourceName), Mockito.eq("FourPartition"), any(), - Mockito.eq(roles), - any()); + Mockito.eq(roles)); Mockito.doReturn(true).when(hostHealthMonitor).isHostHealthy(any(Host.class), Mockito.anyString()); @@ -786,8 +762,7 @@ void testGroupByGreedyHost() throws RouterException { AsyncPartitionFinder.adapt(partitionFinder, Runnable::run), hostFinder, hostHealthMonitor, - roles, - null)) + roles)) .thenCompose(Function.identity()) .join(), scatter); @@ -862,8 +837,7 @@ void testGroupByException() throws Throwable { Mockito.eq(resourceName), Mockito.eq("OnePartition"), any(), - Mockito.eq(roles), - any()); + Mockito.eq(roles)); Mockito.doReturn(CollectionUtil.listOf(host2, host3)) .when(hostFinder) @@ -872,8 +846,7 @@ void testGroupByException() throws Throwable { Mockito.eq(resourceName), Mockito.eq("TwoPartition"), any(), - Mockito.eq(roles), - any()); + Mockito.eq(roles)); Mockito.doThrow(RouterException.class) .when(hostFinder) @@ -882,8 +855,7 @@ void testGroupByException() throws Throwable { Mockito.eq(resourceName), Mockito.eq("ThreePartition"), any(), - Mockito.eq(roles), - any()); + Mockito.eq(roles)); Mockito.doReturn(CollectionUtil.listOf(host2, host3)) .when(hostFinder) @@ -892,8 +864,7 @@ void testGroupByException() throws Throwable { Mockito.eq(resourceName), Mockito.eq("FourPartition"), any(), - Mockito.eq(roles), - any()); + Mockito.eq(roles)); Mockito.doReturn(true).when(hostHealthMonitor).isHostHealthy(any(Host.class), Mockito.anyString()); @@ -907,8 +878,7 @@ void testGroupByException() throws Throwable { AsyncPartitionFinder.adapt(partitionFinder, Runnable::run), hostFinder, hostHealthMonitor, - roles, - null)) + roles)) .thenCompose(Function.identity()) .join(); Assert.fail(); diff --git a/internal/alpini/router/alpini-router-base/src/main/java/com/linkedin/alpini/router/ScatterGatherRequestHandler.java b/internal/alpini/router/alpini-router-base/src/main/java/com/linkedin/alpini/router/ScatterGatherRequestHandler.java index d7ebb8139f..a31b0c58dd 100644 --- a/internal/alpini/router/alpini-router-base/src/main/java/com/linkedin/alpini/router/ScatterGatherRequestHandler.java +++ b/internal/alpini/router/alpini-router-base/src/main/java/com/linkedin/alpini/router/ScatterGatherRequestHandler.java @@ -1,14 +1,11 @@ package com.linkedin.alpini.router; import com.linkedin.alpini.base.concurrency.TimeoutProcessor; -import com.linkedin.alpini.base.misc.Metrics; -import com.linkedin.alpini.base.misc.TimeValue; import com.linkedin.alpini.router.api.ResourcePath; import com.linkedin.alpini.router.api.RouterTimeoutProcessor; import com.linkedin.alpini.router.api.ScatterGatherHelper; import java.util.Objects; import java.util.concurrent.Executor; -import java.util.function.Supplier; import javax.annotation.Nonnull; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -66,14 +63,5 @@ public static , K, R> ScatterGatherRequestHandler getScatterGatherHelper(); - public static > void setMetric( - Metrics metric, - @Nonnull M metricName, - @Nonnull Supplier supplier) { - if (metric != null) { - metric.setMetric(metricName.name(), supplier.get()); - } - } - protected abstract boolean isTooLongFrameException(Throwable cause); } diff --git a/internal/alpini/router/alpini-router-base/src/main/java/com/linkedin/alpini/router/ScatterGatherRequestHandler4.java b/internal/alpini/router/alpini-router-base/src/main/java/com/linkedin/alpini/router/ScatterGatherRequestHandler4.java index 4efc65d5c6..034a0a3493 100644 --- a/internal/alpini/router/alpini-router-base/src/main/java/com/linkedin/alpini/router/ScatterGatherRequestHandler4.java +++ b/internal/alpini/router/alpini-router-base/src/main/java/com/linkedin/alpini/router/ScatterGatherRequestHandler4.java @@ -30,7 +30,6 @@ import io.netty.handler.codec.http.DefaultLastHttpContent; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.FullHttpResponse; -import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpResponseStatus; @@ -48,8 +47,6 @@ import java.util.Collections; import java.util.LinkedList; import java.util.List; -import java.util.Map; -import java.util.Optional; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; @@ -119,11 +116,9 @@ public AsyncFuture handler(@Nonnull ChannelHandlerContext ctx, fullHttpRequest, badRequest(), "HTTP decoder error: " + decoderResult, - Optional.ofNullable(decoderResult) - .map(DecoderResult::cause) - .map(ScatterGatherRequestHandlerImpl::unwrapCompletion) - .orElse(null), - Collections.emptyMap())); + decoderResult == null + ? null + : ScatterGatherRequestHandlerImpl.unwrapCompletion(decoderResult.cause()))); } return handler(ctx, fullHttpRequest); } finally { @@ -252,28 +247,11 @@ protected FullHttpResponse buildResponse( FullHttpResponse message = new BasicFullHttpResponse(request, HttpResponseStatus.NO_CONTENT, Unpooled.EMPTY_BUFFER); - headers.forEach(header -> { - Optional.ofNullable(header.get(HttpHeaderNames.SERVER)) - .ifPresent(server -> message.headers().add(HttpHeaderNames.SERVER, server)); - Optional.ofNullable(header.get(HeaderNames.X_SERVED_BY)) - .ifPresent(server -> message.headers().add(HeaderNames.X_SERVED_BY, server)); - Optional.of(header.getAll(HeaderNames.X_PARTITION)) - .filter(partitions -> !partitions.isEmpty()) - .ifPresent(partitions -> message.headers().add(HeaderNames.X_PARTITION, partitions)); - Optional.ofNullable(metrics) - .ifPresent(m -> m.addSubrequest(getScatterGatherHelper().responseMetrics(new BasicHeaders(header)))); - }); - getScatterGatherHelper().decorateResponse(getResponseHeaders(message), request.getRequestHeaders(), metrics); return message; } - Optional.ofNullable(metrics) - .ifPresent( - m -> headers.forEach( - header -> m.addSubrequest(getScatterGatherHelper().responseMetrics(new BasicHeaders(header))))); - return buildResponse(request, metrics, remaining); } @@ -308,8 +286,6 @@ public void writeChunkedContent(ChannelHandlerContext ctx, Promise m.addSubrequest(getScatterGatherHelper().responseMetrics(getResponseHeaders(r)))); future = ctx.write(part); } } @@ -334,11 +310,6 @@ public void writeChunkedContent(ChannelHandlerContext ctx, Promise> listener = cf -> { if (cf.isSuccess()) { - Optional.ofNullable(metrics) - .ifPresent( - m -> m.addSubrequest( - getScatterGatherHelper().responseMetrics(new BasicHeaders(cf.getNow().trailingHeaders())))); - multipartResponses.stream().map(Pair::getSecond).filter(p -> !p.isDone()).findAny().orElseGet(() -> { promise.setSuccess(); return null; @@ -356,11 +327,6 @@ public void writeChunkedContent(ChannelHandlerContext ctx, Promise> f = new AtomicReference<>(first); chunkedResponses.forEach(p -> f.getAndSet(p.getSecond()).addListener((Future fp) -> { if (fp.isSuccess()) { - Optional.ofNullable(metrics) - .filter(m -> fp.getNow() != null) - .ifPresent( - m -> m.addSubrequest( - getScatterGatherHelper().responseMetrics(new BasicHeaders(fp.getNow().trailingHeaders())))); LOG.debug("writeChunkedContent {}", p.getFirst()); p.getFirst().writeChunkedContent(ctx, p.getSecond()); } else { @@ -380,11 +346,6 @@ public void writeChunkedContent(ChannelHandlerContext ctx, Promise fp) -> { if (fp.isSuccess()) { - Optional.ofNullable(metrics) - .ifPresent( - m -> m.addSubrequest( - getScatterGatherHelper() - .responseMetrics(new BasicHeaders(fp.getNow().trailingHeaders())))); cp.setSuccess(); } else { cp.setFailure(fp.cause()); @@ -446,8 +407,7 @@ protected FullHttpResponse buildErrorResponse( @Nonnull BasicFullHttpRequest request, @Nonnull HttpResponseStatus status, String contentMessage, - Throwable ex, - @Nonnull Map errorHeaders) { + Throwable ex) { boolean returnStackTrace = false; ByteBuf contentByteBuf; if (getScatterGatherHelper().isEnableStackTraceResponseForException() && ex != null) { @@ -481,8 +441,6 @@ protected FullHttpResponse buildErrorResponse( response.headers().set(HeaderNames.X_ERROR_MESSAGE, HeaderUtils.cleanHeaderValue(contentMessage)); } - errorHeaders.forEach((key, value) -> response.headers().set(key, HeaderUtils.cleanHeaderValue(value))); - HttpUtil.setContentLength(response, contentByteBuf.readableBytes()); return response; diff --git a/internal/alpini/router/alpini-router-base/src/main/java/com/linkedin/alpini/router/ScatterGatherRequestHandlerImpl.java b/internal/alpini/router/alpini-router-base/src/main/java/com/linkedin/alpini/router/ScatterGatherRequestHandlerImpl.java index 1f7869cf0a..1e2d432897 100644 --- a/internal/alpini/router/alpini-router-base/src/main/java/com/linkedin/alpini/router/ScatterGatherRequestHandlerImpl.java +++ b/internal/alpini/router/alpini-router-base/src/main/java/com/linkedin/alpini/router/ScatterGatherRequestHandlerImpl.java @@ -10,15 +10,14 @@ import com.linkedin.alpini.base.concurrency.impl.SuccessAsyncFuture; import com.linkedin.alpini.base.misc.BasicRequest; import com.linkedin.alpini.base.misc.ExceptionWithStatus; -import com.linkedin.alpini.base.misc.HeaderNames; import com.linkedin.alpini.base.misc.Headers; import com.linkedin.alpini.base.misc.Http2TooManyStreamsException; +import com.linkedin.alpini.base.misc.MetricNames; import com.linkedin.alpini.base.misc.Metrics; import com.linkedin.alpini.base.misc.Time; import com.linkedin.alpini.base.misc.TimeValue; import com.linkedin.alpini.netty4.misc.Http2Utils; import com.linkedin.alpini.router.api.HostHealthMonitor; -import com.linkedin.alpini.router.api.MetricNames; import com.linkedin.alpini.router.api.ResourcePath; import com.linkedin.alpini.router.api.ResourcePathParser; import com.linkedin.alpini.router.api.RouterException; @@ -32,10 +31,8 @@ import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; import java.util.LinkedList; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; @@ -46,7 +43,6 @@ import java.util.concurrent.TimeUnit; import java.util.function.LongSupplier; import java.util.function.Supplier; -import java.util.stream.Collectors; import javax.annotation.Nonnull; @@ -98,12 +94,9 @@ protected ScatterGatherRequestHandlerImpl( return _scatterGatherHelper; } - public static > void setMetric( - Metrics metric, - @Nonnull M metricName, - @Nonnull Supplier supplier) { + public static void setMetric(Metrics metric, @Nonnull MetricNames metricName, @Nonnull Supplier supplier) { if (metric != null) { - metric.setMetric(metricName.name(), supplier.get()); + metric.setMetric(metricName, supplier.get()); } } @@ -934,47 +927,16 @@ protected CompletionStage appendErrorForEveryKey( CompletionStage complete = COMPLETED; R roles = _scatterGatherHelper.parseRoles(request.getMethodName(), request.getRequestHeaders()); StringBuilder contentMsg = new StringBuilder(contentMessage); - if (part.getPartitionKeys().isEmpty()) { - // Some requests, like table level queries, do not have keys. For these errors, send one message per request with - // the Served-By and Partition header - // if available. We can't send a Content-Location since we don't have a key for the request. - Map errorHeaders = new HashMap<>(); - - if (!part.getPartitionNamesToQuery().isEmpty()) { - String partitions = - part.getPartitionNamesToQuery().stream().map(Object::toString).collect(Collectors.joining(",")); - errorHeaders.put(HeaderNames.X_PARTITION, partitions); - } - List hosts = part.getHosts(); - if (hosts != null && !hosts.isEmpty()) { - errorHeaders.put(HeaderNames.X_SERVED_BY, hosts.get(0).toString()); - } - appendError( - request, - responses, - status, - contentMsg.append(", RoutingPolicy=").append(roles).toString(), - ex, - errorHeaders); + if (part.getPartitionKeys().isEmpty()) { + appendError(request, responses, status, contentMsg.append(", RoutingPolicy=").append(roles).toString(), ex); } else { - // For requests with keys, send an error for each key. The response includes Content-Location, as well as - // Served-By and Partition headers if available. + // For requests with keys, send an error for each key. TODO: Consider if we could rip all of that out? for (K key: part.getPartitionKeys()) { - Map errorHeaders = new HashMap<>(); complete = complete.thenApply(aVoid -> pathParser.substitutePartitionKey(basePath, key)) .thenCompose(pathForThisKey -> { - errorHeaders.put(HeaderNames.CONTENT_LOCATION, pathForThisKey.getLocation()); - return _scatterGatherHelper.findPartitionName(pathForThisKey.getResourceName(), key); - }) - .thenApply(partitionName -> { - errorHeaders.put(HeaderNames.X_PARTITION, partitionName); - contentMsg.append(", PartitionName=").append(partitionName); - - List hosts = part.getHosts(); - if (hosts != null && !hosts.isEmpty()) { - errorHeaders.put(HeaderNames.X_SERVED_BY, hosts.get(0).toString()); - } + contentMsg.append(", PartitionName=") + .append(_scatterGatherHelper.findPartitionName(pathForThisKey.getResourceName(), key)); return null; }) .exceptionally(e -> { @@ -987,8 +949,7 @@ protected CompletionStage appendErrorForEveryKey( responses, status, contentMsg.append(", RoutingPolicy=").append(roles).toString(), - ex, - errorHeaders); + ex); return null; }); } @@ -1001,11 +962,10 @@ protected void appendError( @Nonnull List


responses, @Nonnull HRS status, String contentMessage, - Throwable ex, - @Nonnull Map errorHeaders) { + Throwable ex) { LOG.debug("appendError"); ex = unwrapCompletion(ex); - responses.add(buildErrorResponse(request, status, contentMessage, ex, errorHeaders)); + responses.add(buildErrorResponse(request, status, contentMessage, ex)); } protected abstract boolean isTooLongFrameException(Throwable cause); @@ -1017,18 +977,16 @@ protected void appendError( HR response; boolean closeChannel; - Map errorHeaders = Collections.emptyMap(); - if (cause instanceof ExceptionWithStatus) { ExceptionWithStatus rex = (ExceptionWithStatus) cause; if (rex.code() >= 500) { LOG.warn("RouterException 5XX exception caught", rex); } - response = buildErrorResponse(request, statusOf(rex.code()), rex.getMessage(), rex, errorHeaders); + response = buildErrorResponse(request, statusOf(rex.code()), rex.getMessage(), rex); closeChannel = rex instanceof RouterException && ((RouterException) rex).shouldCloseChannel(); } else if (isTooLongFrameException(cause)) { // Send a 400 error and close the channel - response = buildErrorResponse(request, badRequest(), cause.getMessage(), cause, errorHeaders); + response = buildErrorResponse(request, badRequest(), cause.getMessage(), cause); closeChannel = true; } else if (cause instanceof URISyntaxException) { URISyntaxException uex = (URISyntaxException) cause; @@ -1036,12 +994,10 @@ protected void appendError( request, badRequest(), "Bad request path (" + uex.getInput() + "). " + uex.getMessage(), - uex, - errorHeaders); + uex); closeChannel = false; } else if (Http2Utils.isTooManyActiveStreamsError(cause)) { - response = - buildErrorResponse(request, serviceUnavailable(), null, Http2TooManyStreamsException.INSTANCE, errorHeaders); + response = buildErrorResponse(request, serviceUnavailable(), null, Http2TooManyStreamsException.INSTANCE); // No need to close the client connection closeChannel = false; } else { @@ -1049,7 +1005,7 @@ protected void appendError( LOG.error( "Unexpected exception caught in ScatterGatherRequestHandler.exceptionCaught. Sending error and closing client channel. ", cause); - response = buildErrorResponse(request, internalServerError(), null, cause, errorHeaders); + response = buildErrorResponse(request, internalServerError(), null, cause); closeChannel = true; } @@ -1066,8 +1022,7 @@ protected void appendError( @Nonnull BHS request, @Nonnull HRS status, String contentMessage, - Throwable ex, - @Nonnull Map errorHeaders); + Throwable ex); protected final void dispatch( @Nonnull Scatter scatter, diff --git a/internal/alpini/router/alpini-router-base/src/test/java/com/linkedin/alpini/router/TestScatterGatherRequestHandler4.java b/internal/alpini/router/alpini-router-base/src/test/java/com/linkedin/alpini/router/TestScatterGatherRequestHandler4.java index 35211a2868..7eb69b1c44 100644 --- a/internal/alpini/router/alpini-router-base/src/test/java/com/linkedin/alpini/router/TestScatterGatherRequestHandler4.java +++ b/internal/alpini/router/alpini-router-base/src/test/java/com/linkedin/alpini/router/TestScatterGatherRequestHandler4.java @@ -121,19 +121,6 @@ public List findHosts( @Nonnull List> roles) throws RouterException { throw new IllegalStateException(); } - - @Nonnull - @Override - public Collection findAllHosts(@Nonnull String resourceName, List> roles) - throws RouterException { - throw new IllegalStateException(); - } - - @Nonnull - @Override - public Collection findAllHosts(List> roles) throws RouterException { - throw new IllegalStateException(); - } } ResourceRegistry registry = new ResourceRegistry(); diff --git a/internal/alpini/router/alpini-router-base/src/test/java/com/linkedin/alpini/router/TestScatterGatherRequestHandlerImpl.java b/internal/alpini/router/alpini-router-base/src/test/java/com/linkedin/alpini/router/TestScatterGatherRequestHandlerImpl.java index c0c92c7c78..ef362cf74f 100644 --- a/internal/alpini/router/alpini-router-base/src/test/java/com/linkedin/alpini/router/TestScatterGatherRequestHandlerImpl.java +++ b/internal/alpini/router/alpini-router-base/src/test/java/com/linkedin/alpini/router/TestScatterGatherRequestHandlerImpl.java @@ -35,7 +35,6 @@ import java.util.Iterator; import java.util.LinkedList; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CountDownLatch; @@ -130,12 +129,7 @@ private interface ResponseBuilder { } private interface ErrorBuilder { - Response buildErrorResponse( - @Nonnull Request request, - @Nonnull Status status, - String contentMessage, - Throwable ex, - @Nonnull Map errorHeaders); + Response buildErrorResponse(@Nonnull Request request, @Nonnull Status status, String contentMessage, Throwable ex); } private , K, R, HELPER extends ScatterGatherHelper> ScatterGatherRequestHandlerImpl buildTestHandler( @@ -263,9 +257,8 @@ protected Response buildErrorResponse( @Nonnull Request request, @Nonnull Status status, String contentMessage, - Throwable ex, - @Nonnull Map errorHeaders) { - return errorBuilder.buildErrorResponse(request, status, contentMessage, ex, errorHeaders); + Throwable ex) { + return errorBuilder.buildErrorResponse(request, status, contentMessage, ex); } } @@ -284,7 +277,7 @@ public void testException404InParseResourceUri() throws Exception { throw new UnsupportedOperationException(); }; - ErrorBuilder errorBuilder = (request, status, contentMessage, ex, errorHeaders) -> { + ErrorBuilder errorBuilder = (request, status, contentMessage, ex) -> { Response response = mock(Response.class); Mockito.when(response.status()).thenReturn(status); return response; @@ -426,7 +419,7 @@ public void testDoubleFailure() throws Exception { throw new UnsupportedOperationException(); }; - ErrorBuilder errorBuilder = (request, status, contentMessage, ex, errorHeaders) -> { + ErrorBuilder errorBuilder = (request, status, contentMessage, ex) -> { Response response = mock(Response.class); Mockito.when(response.status()).thenReturn(status); return response; @@ -490,7 +483,7 @@ public void testExceptionInErrorBuilder() throws Exception { NullPointerException npe = new NullPointerException("Foo"); - ErrorBuilder errorBuilder = (request, status, contentMessage, ex, errorHeaders) -> { + ErrorBuilder errorBuilder = (request, status, contentMessage, ex) -> { throw npe; }; @@ -540,7 +533,7 @@ public void testException503InScatter() throws Exception { throw new UnsupportedOperationException(); }; - ErrorBuilder errorBuilder = (request, status, contentMessage, ex, errorHeaders) -> { + ErrorBuilder errorBuilder = (request, status, contentMessage, ex) -> { Response response = mock(Response.class); Mockito.when(response.status()).thenReturn(status); return response; @@ -597,7 +590,7 @@ private ExpectedException(String msg) { } } - ErrorBuilder errorBuilder = (request, status, contentMessage, ex, errorHeaders) -> { + ErrorBuilder errorBuilder = (request, status, contentMessage, ex) -> { Response response = mock(Response.class); Mockito.when(response.status()).thenReturn(status); Assert.assertTrue(ex instanceof ExpectedException); @@ -664,7 +657,7 @@ private ExpectedException(String msg) { } } - ErrorBuilder errorBuilder = (request, status, contentMessage, ex, errorHeaders) -> { + ErrorBuilder errorBuilder = (request, status, contentMessage, ex) -> { Response response = mock(Response.class); Mockito.when(response.status()).thenReturn(status); Assert.assertTrue(ex instanceof ExpectedException); @@ -743,7 +736,7 @@ public void test200InDispatch() throws Exception { throw new UnsupportedOperationException(); }; - ErrorBuilder errorBuilder = (request, status, contentMessage, ex, errorHeaders) -> { + ErrorBuilder errorBuilder = (request, status, contentMessage, ex) -> { Assert.fail("Should not get here"); return mock(Response.class); }; @@ -802,7 +795,7 @@ public void test200LongTailCannotRetry() throws Exception { throw new UnsupportedOperationException(); }; - ErrorBuilder errorBuilder = (request, status, contentMessage, ex, errorHeaders) -> { + ErrorBuilder errorBuilder = (request, status, contentMessage, ex) -> { Assert.fail("Should not get here"); return mock(Response.class); }; @@ -883,7 +876,7 @@ public void test200LongTailDispatch() throws Exception { throw new UnsupportedOperationException(); }; - ErrorBuilder errorBuilder = (request, status, contentMessage, ex, errorHeaders) -> { + ErrorBuilder errorBuilder = (request, status, contentMessage, ex) -> { Assert.fail("Should not get here"); return mock(Response.class); }; @@ -971,7 +964,7 @@ public void test429LongTailRetry() throws Exception { throw new UnsupportedOperationException(); }; - ErrorBuilder errorBuilder = (request, status, contentMessage, ex, errorHeaders) -> { + ErrorBuilder errorBuilder = (request, status, contentMessage, ex) -> { Assert.fail("Should not get here"); return mock(Response.class); }; @@ -1073,7 +1066,7 @@ public void test429NoLongTailRetry() throws Exception { Mockito.when(testResponse1.headers()).thenReturn(mock(Headers.class)); AtomicInteger errorCount = new AtomicInteger(); - ErrorBuilder errorBuilder = (request, status, contentMessage, ex, errorHeaders) -> { + ErrorBuilder errorBuilder = (request, status, contentMessage, ex) -> { errorCount.incrementAndGet(); Assert.assertSame(status, Status.SERVICE_UNAVAILABLE); return testResponse1; @@ -1167,7 +1160,7 @@ public void test429RetryNotAllowed() throws Exception { Mockito.when(testResponse1.headers()).thenReturn(mock(Headers.class)); AtomicInteger errorCount = new AtomicInteger(); - ErrorBuilder errorBuilder = (request, status, contentMessage, ex, errorHeaders) -> { + ErrorBuilder errorBuilder = (request, status, contentMessage, ex) -> { errorCount.incrementAndGet(); Assert.assertSame(status, Status.TOO_BUSY); return testResponse1; @@ -1257,7 +1250,7 @@ public void test503Retry() throws Exception { throw new UnsupportedOperationException(); }; - ErrorBuilder errorBuilder = (request, status, contentMessage, ex, errorHeaders) -> { + ErrorBuilder errorBuilder = (request, status, contentMessage, ex) -> { Assert.fail("Should not get here"); return mock(Response.class); }; @@ -1359,7 +1352,7 @@ public void test503RetryNotAllowed() throws Exception { Mockito.when(testResponse1.headers()).thenReturn(mock(Headers.class)); AtomicInteger errorCount = new AtomicInteger(); - ErrorBuilder errorBuilder = (request, status, contentMessage, ex, errorHeaders) -> { + ErrorBuilder errorBuilder = (request, status, contentMessage, ex) -> { errorCount.incrementAndGet(); Assert.assertSame(status, Status.SERVICE_UNAVAILABLE); return testResponse1; @@ -1449,7 +1442,7 @@ public void testQueryRedirectionAllowed() throws Exception { throw new UnsupportedOperationException(); }; - ErrorBuilder errorBuilder = (request, status, contentMessage, ex, errorHeaders) -> { + ErrorBuilder errorBuilder = (request, status, contentMessage, ex) -> { Assert.fail("Should not get here"); return mock(Response.class); }; @@ -1517,7 +1510,7 @@ public void testQueryRedirectionNotAllowed() throws Exception { throw new UnsupportedOperationException(); }; - ErrorBuilder errorBuilder = (request, status, contentMessage, ex, errorHeaders) -> { + ErrorBuilder errorBuilder = (request, status, contentMessage, ex) -> { Assert.fail("Should not get here"); return mock(Response.class); }; @@ -1586,7 +1579,7 @@ public void test404LongTailTooLateDispatch() throws Exception { throw new UnsupportedOperationException(); }; - ErrorBuilder errorBuilder = (request, status, contentMessage, ex, errorHeaders) -> { + ErrorBuilder errorBuilder = (request, status, contentMessage, ex) -> { Assert.fail("Should not get here"); return mock(Response.class); }; @@ -1679,7 +1672,7 @@ public void testNoHost() throws Exception { AtomicInteger errorCount = new AtomicInteger(); - ErrorBuilder errorBuilder = (request, status, contentMessage, ex, errorHeaders) -> { + ErrorBuilder errorBuilder = (request, status, contentMessage, ex) -> { Assert.assertEquals(status, Status.SERVICE_UNAVAILABLE); Assert.assertNull(ex); errorCount.incrementAndGet(); @@ -1747,7 +1740,7 @@ public void testNoHost2() throws Exception { AtomicInteger errorCount = new AtomicInteger(); - ErrorBuilder errorBuilder = (request, status, contentMessage, ex, errorHeaders) -> { + ErrorBuilder errorBuilder = (request, status, contentMessage, ex) -> { Assert.assertEquals(status, Status.SERVICE_UNAVAILABLE); Assert.assertNull(ex); errorCount.incrementAndGet(); @@ -1908,12 +1901,6 @@ public List findHosts( @Nonnull List> roles) throws RouterException { throw new RouterException(Status.class, Status.SERVICE_UNAVAILABLE, 503, "Foo", false); } - - @Nonnull - @Override - public Collection findAllHosts(List> roles) throws RouterException { - throw new IllegalStateException(); - } } private class Path implements ResourcePath { @@ -2054,19 +2041,6 @@ public List findHosts( return Collections .singletonList(hosts.get((int) ((0xffffffffL & (long) partitionName.hashCode()) % hosts.size()))); } - - @Nonnull - @Override - public Collection findAllHosts(@Nonnull String resourceName, List> roles) - throws RouterException { - return findAllHosts(roles); - } - - @Nonnull - @Override - public Collection findAllHosts(List> roles) throws RouterException { - return Collections.unmodifiableCollection(hosts); - } } private class HostFinder2Impl implements HostFinder>> { @@ -2088,19 +2062,6 @@ public List findHosts( @Nonnull List> roles) throws RouterException { return new ArrayList<>(hosts); } - - @Nonnull - @Override - public Collection findAllHosts(@Nonnull String resourceName, List> roles) - throws RouterException { - return findAllHosts(roles); - } - - @Nonnull - @Override - public Collection findAllHosts(List> roles) throws RouterException { - return Collections.unmodifiableCollection(hosts); - } } private class HostFinder3Impl implements HostFinder>> { @@ -2114,19 +2075,6 @@ public List findHosts( @Nonnull List> roles) throws RouterException { return Collections.emptyList(); } - - @Nonnull - @Override - public Collection findAllHosts(@Nonnull String resourceName, List> roles) - throws RouterException { - return findAllHosts(roles); - } - - @Nonnull - @Override - public Collection findAllHosts(List> roles) throws RouterException { - return Collections.emptySet(); - } } private class HostFinder4Impl implements HostFinder>> { @@ -2146,18 +2094,5 @@ public List findHosts( @Nonnull List> roles) throws RouterException { return new ArrayList<>(hosts); } - - @Nonnull - @Override - public Collection findAllHosts(@Nonnull String resourceName, List> roles) - throws RouterException { - return findAllHosts(roles); - } - - @Nonnull - @Override - public Collection findAllHosts(List> roles) throws RouterException { - return Collections.unmodifiableCollection(hosts); - } } } diff --git a/services/venice-router/src/main/java/com/linkedin/venice/router/api/VeniceDelegateMode.java b/services/venice-router/src/main/java/com/linkedin/venice/router/api/VeniceDelegateMode.java index 0513a6d45b..19455937b1 100644 --- a/services/venice-router/src/main/java/com/linkedin/venice/router/api/VeniceDelegateMode.java +++ b/services/venice-router/src/main/java/com/linkedin/venice/router/api/VeniceDelegateMode.java @@ -6,7 +6,6 @@ import static io.netty.handler.codec.http.HttpResponseStatus.SERVICE_UNAVAILABLE; import static io.netty.handler.codec.http.HttpResponseStatus.TOO_MANY_REQUESTS; -import com.linkedin.alpini.base.misc.Metrics; import com.linkedin.alpini.router.api.HostFinder; import com.linkedin.alpini.router.api.HostHealthMonitor; import com.linkedin.alpini.router.api.PartitionFinder; @@ -143,8 +142,7 @@ public , K, R> Scatter scatter( @Nonnull PartitionFinder partitionFinder, @Nonnull HostFinder hostFinder, @Nonnull HostHealthMonitor hostHealthMonitor, - @Nonnull R roles, - Metrics metrics) throws RouterException { + @Nonnull R roles) throws RouterException { if (readRequestThrottler == null) { throw RouterExceptionAndTrackingUtils.newRouterExceptionAndTracking( Optional.empty(), @@ -206,7 +204,7 @@ public , K, R> Scatter scatter( "Unknown request type: " + venicePath.getRequestType()); } Scatter finalScatter = scatterMode - .scatter(scatter, requestMethod, resourceName, partitionFinder, hostFinder, hostHealthMonitor, roles, metrics); + .scatter(scatter, requestMethod, resourceName, partitionFinder, hostFinder, hostHealthMonitor, roles); int offlineRequestNum = scatter.getOfflineRequestCount(); int onlineRequestNum = scatter.getOnlineRequestCount(); @@ -363,8 +361,7 @@ public , K, R> Scatter scatter( @Nonnull PartitionFinder partitionFinder, @Nonnull HostFinder hostFinder, @Nonnull HostHealthMonitor hostHealthMonitor, - @Nonnull R roles, - Metrics metrics) throws RouterException { + @Nonnull R roles) throws RouterException { P path = scatter.getPath(); VenicePath venicePath; VeniceHostFinder veniceHostFinder; @@ -465,8 +462,7 @@ public , K, R> Scatter scatter( @Nonnull PartitionFinder partitionFinder, @Nonnull HostFinder hostFinder, @Nonnull HostHealthMonitor hostHealthMonitor, - @Nonnull R roles, - Metrics metrics) throws RouterException { + @Nonnull R roles) throws RouterException { P path = scatter.getPath(); Scatter veniceScatter; VenicePath venicePath; diff --git a/services/venice-router/src/main/java/com/linkedin/venice/router/api/VeniceHostFinder.java b/services/venice-router/src/main/java/com/linkedin/venice/router/api/VeniceHostFinder.java index 49a5112694..8e7350cd17 100644 --- a/services/venice-router/src/main/java/com/linkedin/venice/router/api/VeniceHostFinder.java +++ b/services/venice-router/src/main/java/com/linkedin/venice/router/api/VeniceHostFinder.java @@ -5,7 +5,6 @@ import com.linkedin.alpini.router.api.HostFinder; import com.linkedin.alpini.router.api.HostHealthMonitor; -import com.linkedin.alpini.router.api.RouterException; import com.linkedin.venice.meta.Instance; import com.linkedin.venice.meta.OnlineInstanceFinder; import com.linkedin.venice.meta.Version; @@ -13,9 +12,7 @@ import com.linkedin.venice.router.stats.RouterStats; import com.linkedin.venice.router.utils.VeniceRouterUtils; import com.linkedin.venice.utils.HelixUtils; -import io.netty.handler.codec.http.HttpResponseStatus; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.List; import org.apache.logging.log4j.LogManager; @@ -135,14 +132,4 @@ public List findHosts( return newHosts; } - - @Override - public Collection findAllHosts(VeniceRole roles) throws RouterException { - throw new RouterException( - HttpResponseStatus.class, - HttpResponseStatus.BAD_REQUEST, - HttpResponseStatus.BAD_REQUEST.code(), - "Find All Hosts is not a supported operation", - true); - } } diff --git a/services/venice-router/src/main/java/com/linkedin/venice/router/api/VenicePathParserHelper.java b/services/venice-router/src/main/java/com/linkedin/venice/router/api/VenicePathParserHelper.java index 0efd564988..8546d87af1 100644 --- a/services/venice-router/src/main/java/com/linkedin/venice/router/api/VenicePathParserHelper.java +++ b/services/venice-router/src/main/java/com/linkedin/venice/router/api/VenicePathParserHelper.java @@ -3,6 +3,7 @@ import com.linkedin.alpini.netty4.misc.BasicFullHttpRequest; import com.linkedin.venice.router.utils.VeniceRouterUtils; import io.netty.handler.codec.http.HttpRequest; +import io.netty.util.Attribute; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; @@ -27,18 +28,15 @@ public class VenicePathParserHelper { public static VenicePathParserHelper parseRequest(HttpRequest request) { if (request instanceof BasicFullHttpRequest) { BasicFullHttpRequest basicFullHttpRequest = (BasicFullHttpRequest) request; - if (basicFullHttpRequest.hasAttr(VeniceRouterUtils.PATHPARSER_ATTRIBUTE_KEY)) { - return basicFullHttpRequest.attr(VeniceRouterUtils.PATHPARSER_ATTRIBUTE_KEY).get(); + Attribute attr = basicFullHttpRequest.attr(VeniceRouterUtils.PATHPARSER_ATTRIBUTE_KEY); + VenicePathParserHelper helper = attr.get(); + if (helper == null) { + helper = new VenicePathParserHelper(request.uri()); + attr.set(helper); } + return helper; } - - VenicePathParserHelper helper = new VenicePathParserHelper(request.uri()); - - if (request instanceof BasicFullHttpRequest) { - BasicFullHttpRequest basicFullHttpRequest = (BasicFullHttpRequest) request; - basicFullHttpRequest.attr(VeniceRouterUtils.PATHPARSER_ATTRIBUTE_KEY).set(helper); - } - return helper; + return new VenicePathParserHelper(request.uri()); } /** diff --git a/services/venice-router/src/main/java/com/linkedin/venice/router/api/VeniceResponseAggregator.java b/services/venice-router/src/main/java/com/linkedin/venice/router/api/VeniceResponseAggregator.java index 71fa3d690d..7706ae0434 100644 --- a/services/venice-router/src/main/java/com/linkedin/venice/router/api/VeniceResponseAggregator.java +++ b/services/venice-router/src/main/java/com/linkedin/venice/router/api/VeniceResponseAggregator.java @@ -1,9 +1,9 @@ package com.linkedin.venice.router.api; -import static com.linkedin.alpini.router.api.MetricNames.ROUTER_PARSE_URI; -import static com.linkedin.alpini.router.api.MetricNames.ROUTER_RESPONSE_WAIT_TIME; -import static com.linkedin.alpini.router.api.MetricNames.ROUTER_ROUTING_TIME; -import static com.linkedin.alpini.router.api.MetricNames.ROUTER_SERVER_TIME; +import static com.linkedin.alpini.base.misc.MetricNames.ROUTER_PARSE_URI; +import static com.linkedin.alpini.base.misc.MetricNames.ROUTER_RESPONSE_WAIT_TIME; +import static com.linkedin.alpini.base.misc.MetricNames.ROUTER_ROUTING_TIME; +import static com.linkedin.alpini.base.misc.MetricNames.ROUTER_SERVER_TIME; import static com.linkedin.venice.HttpConstants.VENICE_COMPRESSION_STRATEGY; import static com.linkedin.venice.HttpConstants.VENICE_REQUEST_RCU; import static io.netty.handler.codec.http.HttpResponseStatus.BAD_GATEWAY; @@ -15,6 +15,7 @@ import static io.netty.handler.codec.http.HttpResponseStatus.TOO_MANY_REQUESTS; import com.linkedin.alpini.base.misc.HeaderNames; +import com.linkedin.alpini.base.misc.MetricNames; import com.linkedin.alpini.base.misc.Metrics; import com.linkedin.alpini.base.misc.TimeValue; import com.linkedin.alpini.netty4.misc.BasicFullHttpRequest; @@ -230,18 +231,18 @@ public FullHttpResponse buildResponse( } HttpResponseStatus responseStatus = finalResponse.status(); - Map allMetrics = metrics.getMetrics(); + Map allMetrics = metrics.getMetrics(); /** * All the metrics in {@link com.linkedin.ddsstorage.router.api.MetricNames} are supported in {@link Metrics}. * We are not exposing the following metrics inside Venice right now. * 1. {@link ROUTER_PARSE_URI} * 2. {@link ROUTER_ROUTING_TIME} */ - if (allMetrics.containsKey(ROUTER_SERVER_TIME.name())) { + TimeValue timeValue = allMetrics.get(ROUTER_SERVER_TIME); + if (timeValue != null) { // TODO: When a batch get throws a quota exception, the ROUTER_SERVER_TIME is missing, so we can't record anything // here... - double latency = LatencyUtils - .convertLatencyFromNSToMS(allMetrics.get(ROUTER_SERVER_TIME.name()).getRawValue(TimeUnit.NANOSECONDS)); + double latency = LatencyUtils.convertLatencyFromNSToMS(timeValue.getRawValue(TimeUnit.NANOSECONDS)); stats.recordLatency(storeName, latency); if (HEALTHY_STATUSES.contains(responseStatus)) { routerStats.getStatsByType(RequestType.SINGLE_GET) @@ -259,19 +260,19 @@ public FullHttpResponse buildResponse( stats.recordUnhealthyRequest(storeName, latency); } } - if (allMetrics.containsKey(ROUTER_RESPONSE_WAIT_TIME.name())) { - double waitingTime = LatencyUtils - .convertLatencyFromNSToMS(allMetrics.get(ROUTER_RESPONSE_WAIT_TIME.name()).getRawValue(TimeUnit.NANOSECONDS)); + timeValue = allMetrics.get(ROUTER_RESPONSE_WAIT_TIME); + if (timeValue != null) { + double waitingTime = LatencyUtils.convertLatencyFromNSToMS(timeValue.getRawValue(TimeUnit.NANOSECONDS)); stats.recordResponseWaitingTime(storeName, waitingTime); } - if (allMetrics.containsKey(ROUTER_PARSE_URI.name())) { - double parsingTime = LatencyUtils - .convertLatencyFromNSToMS(allMetrics.get(ROUTER_PARSE_URI.name()).getRawValue(TimeUnit.NANOSECONDS)); + timeValue = allMetrics.get(ROUTER_PARSE_URI); + if (timeValue != null) { + double parsingTime = LatencyUtils.convertLatencyFromNSToMS(timeValue.getRawValue(TimeUnit.NANOSECONDS)); stats.recordRequestParsingLatency(storeName, parsingTime); } - if (allMetrics.containsKey(ROUTER_ROUTING_TIME.name())) { - double routingTime = LatencyUtils - .convertLatencyFromNSToMS(allMetrics.get(ROUTER_ROUTING_TIME.name()).getRawValue(TimeUnit.NANOSECONDS)); + timeValue = allMetrics.get(ROUTER_ROUTING_TIME); + if (timeValue != null) { + double routingTime = LatencyUtils.convertLatencyFromNSToMS(timeValue.getRawValue(TimeUnit.NANOSECONDS)); stats.recordRequestRoutingLatency(storeName, routingTime); } if (HEALTHY_STATUSES.contains(responseStatus) && !venicePath.isStreamingRequest()) { diff --git a/services/venice-router/src/test/java/com/linkedin/venice/router/api/TestVeniceDelegateMode.java b/services/venice-router/src/test/java/com/linkedin/venice/router/api/TestVeniceDelegateMode.java index af9d4c3ff3..cba2c5da18 100644 --- a/services/venice-router/src/test/java/com/linkedin/venice/router/api/TestVeniceDelegateMode.java +++ b/services/venice-router/src/test/java/com/linkedin/venice/router/api/TestVeniceDelegateMode.java @@ -15,7 +15,6 @@ import static org.mockito.Mockito.when; import com.linkedin.alpini.base.concurrency.TimeoutProcessor; -import com.linkedin.alpini.base.misc.Metrics; import com.linkedin.alpini.router.api.HostFinder; import com.linkedin.alpini.router.api.HostHealthMonitor; import com.linkedin.alpini.router.api.PartitionFinder; @@ -267,15 +266,8 @@ public void testScatterWithSingleGet() throws RouterException { scatterMode.initReadRequestThrottler(throttler); - Scatter finalScatter = scatterMode.scatter( - scatter, - requestMethod, - resourceName, - partitionFinder, - hostFinder, - monitor, - VeniceRole.REPLICA, - new Metrics()); + Scatter finalScatter = scatterMode + .scatter(scatter, requestMethod, resourceName, partitionFinder, hostFinder, monitor, VeniceRole.REPLICA); // Throttling for single-get request is not happening in VeniceDelegateMode verify(throttler, never()).mayThrottleRead(eq(storeName), eq(1)); @@ -315,15 +307,7 @@ public void testScatterWithSingleGetWithNotAvailablePartition() throws RouterExc new VeniceDelegateMode(config, mock(RouterStats.class), mock(RouteHttpRequestStats.class)); scatterMode.initReadRequestThrottler(throttler); - scatterMode.scatter( - scatter, - requestMethod, - resourceName, - partitionFinder, - hostFinder, - monitor, - VeniceRole.REPLICA, - new Metrics()); + scatterMode.scatter(scatter, requestMethod, resourceName, partitionFinder, hostFinder, monitor, VeniceRole.REPLICA); } @Test(expectedExceptions = RouterException.class, expectedExceptionsMessageRegExp = ".*not available to serve retry request of type: MULTI_GET") @@ -381,15 +365,8 @@ public void testLeastLoadedOnSlowHosts() throws RouterException { mock(RouteHttpRequestStats.class)); scatterMode.initReadRequestThrottler(throttler); - Scatter finalScatter = scatterMode.scatter( - scatter, - requestMethod, - resourceName, - partitionFinder, - hostFinder, - monitor, - VeniceRole.REPLICA, - new Metrics()); + Scatter finalScatter = scatterMode + .scatter(scatter, requestMethod, resourceName, partitionFinder, hostFinder, monitor, VeniceRole.REPLICA); Collection> requests = finalScatter.getOnlineRequests(); Assert.assertEquals(requests.size(), 3); @@ -486,15 +463,8 @@ public void testScatterWithMultiGet() throws RouterException { new VeniceDelegateMode(config, mock(RouterStats.class), mock(RouteHttpRequestStats.class)); scatterMode.initReadRequestThrottler(throttler); - Scatter finalScatter = scatterMode.scatter( - scatter, - requestMethod, - resourceName, - partitionFinder, - hostFinder, - monitor, - VeniceRole.REPLICA, - new Metrics()); + Scatter finalScatter = scatterMode + .scatter(scatter, requestMethod, resourceName, partitionFinder, hostFinder, monitor, VeniceRole.REPLICA); Collection> requests = finalScatter.getOnlineRequests(); Assert.assertEquals(requests.size(), 3); @@ -598,15 +568,8 @@ public void testScatterWithStreamingMultiGet() throws RouterException { new VeniceDelegateMode(config, mock(RouterStats.class), mock(RouteHttpRequestStats.class)); scatterMode.initReadRequestThrottler(throttler); - Scatter finalScatter = scatterMode.scatter( - scatter, - requestMethod, - resourceName, - partitionFinder, - hostFinder, - monitor, - VeniceRole.REPLICA, - new Metrics()); + Scatter finalScatter = scatterMode + .scatter(scatter, requestMethod, resourceName, partitionFinder, hostFinder, monitor, VeniceRole.REPLICA); Collection> requests = finalScatter.getOfflineRequests(); Assert.assertEquals(requests.size(), 1); @@ -710,15 +673,8 @@ public void testScatterForMultiGetWithHelixAssistedRouting() throws RouterExcept mock(TimeoutProcessor.class)); scatterMode.initHelixGroupSelector(helixGroupSelector); - Scatter finalScatter = scatterMode.scatter( - scatter, - requestMethod, - resourceName, - partitionFinder, - hostFinder, - monitor, - VeniceRole.REPLICA, - new Metrics()); + Scatter finalScatter = scatterMode + .scatter(scatter, requestMethod, resourceName, partitionFinder, hostFinder, monitor, VeniceRole.REPLICA); Collection> requests = finalScatter.getOnlineRequests(); Assert.assertEquals(requests.size(), 2); @@ -734,15 +690,8 @@ public void testScatterForMultiGetWithHelixAssistedRouting() throws RouterExcept // The second request should pick up another group scatter = new Scatter(path, getPathParser(), VeniceRole.REPLICA); - finalScatter = scatterMode.scatter( - scatter, - requestMethod, - resourceName, - partitionFinder, - hostFinder, - monitor, - VeniceRole.REPLICA, - new Metrics()); + finalScatter = scatterMode + .scatter(scatter, requestMethod, resourceName, partitionFinder, hostFinder, monitor, VeniceRole.REPLICA); requests = finalScatter.getOnlineRequests(); Assert.assertEquals(requests.size(), 2); @@ -764,15 +713,8 @@ public void testScatterForMultiGetWithHelixAssistedRouting() throws RouterExcept VenicePath pathForAllSlowReplicas = getVenicePath(storeName, version, resourceName, RequestType.MULTI_GET_STREAMING, keys, slowStorageNodeSet); scatter = new Scatter(pathForAllSlowReplicas, getPathParser(), VeniceRole.REPLICA); - finalScatter = scatterMode.scatter( - scatter, - requestMethod, - resourceName, - partitionFinder, - hostFinder, - monitor, - VeniceRole.REPLICA, - new Metrics()); + finalScatter = scatterMode + .scatter(scatter, requestMethod, resourceName, partitionFinder, hostFinder, monitor, VeniceRole.REPLICA); requests = finalScatter.getOnlineRequests(); Assert.assertEquals(requests.size(), 1); diff --git a/services/venice-router/src/test/java/com/linkedin/venice/router/api/TestVeniceResponseAggregator.java b/services/venice-router/src/test/java/com/linkedin/venice/router/api/TestVeniceResponseAggregator.java index 40137be8dc..3075773715 100644 --- a/services/venice-router/src/test/java/com/linkedin/venice/router/api/TestVeniceResponseAggregator.java +++ b/services/venice-router/src/test/java/com/linkedin/venice/router/api/TestVeniceResponseAggregator.java @@ -13,10 +13,10 @@ import static org.mockito.Mockito.when; import com.linkedin.alpini.base.misc.HeaderNames; +import com.linkedin.alpini.base.misc.MetricNames; import com.linkedin.alpini.base.misc.Metrics; import com.linkedin.alpini.base.misc.TimeValue; import com.linkedin.alpini.netty4.misc.BasicFullHttpRequest; -import com.linkedin.alpini.router.api.MetricNames; import com.linkedin.venice.HttpConstants; import com.linkedin.venice.compression.CompressionStrategy; import com.linkedin.venice.compression.CompressorFactory; @@ -217,7 +217,7 @@ public void testBuildResponseForMultiGet() { // test aggregator is able to identify quota exceeded response and // record it properly FullHttpResponse response5 = buildFullHttpResponse(TOO_MANY_REQUESTS, new byte[0], headers); - metrics.setMetric(MetricNames.ROUTER_SERVER_TIME.name(), new TimeValue(1, TimeUnit.MILLISECONDS)); + metrics.setMetric(MetricNames.ROUTER_SERVER_TIME, new TimeValue(1, TimeUnit.MILLISECONDS)); responseAggregator.buildResponse(request, metrics, Collections.singletonList(response5)); verify(mockStatsForMultiGet).recordThrottledRequest(storeName, 1.0); } diff --git a/services/venice-router/src/test/java/com/linkedin/venice/router/throttle/RouterRequestThrottlingTest.java b/services/venice-router/src/test/java/com/linkedin/venice/router/throttle/RouterRequestThrottlingTest.java index ede1390eb4..eae6cfbb36 100644 --- a/services/venice-router/src/test/java/com/linkedin/venice/router/throttle/RouterRequestThrottlingTest.java +++ b/services/venice-router/src/test/java/com/linkedin/venice/router/throttle/RouterRequestThrottlingTest.java @@ -5,7 +5,6 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; -import com.linkedin.alpini.base.misc.Metrics; import com.linkedin.alpini.router.api.HostFinder; import com.linkedin.alpini.router.api.HostHealthMonitor; import com.linkedin.alpini.router.api.PartitionFinder; @@ -124,7 +123,6 @@ public void testMultiKeyThrottling(RequestType requestType) throws Exception { PartitionFinder partitionFinder = mock(VenicePartitionFinder.class); HostFinder hostFinder = mock(VeniceHostFinder.class); HostHealthMonitor hostHealthMonitor = mock(HostHealthMonitor.class); - Metrics metrics = mock(Metrics.class); // The router shouldn't throttle any request if the multi-get QPS is below 10 for (int iter = 0; iter < 3; iter++) { @@ -137,8 +135,7 @@ public void testMultiKeyThrottling(RequestType requestType) throws Exception { partitionFinder, hostFinder, hostHealthMonitor, - VeniceRole.REPLICA, - metrics); + VeniceRole.REPLICA); } catch (Exception e) { Assert.fail("router shouldn't throttle any multi-get requests if the QPS is below " + allowedQPS); } @@ -159,8 +156,7 @@ public void testMultiKeyThrottling(RequestType requestType) throws Exception { partitionFinder, hostFinder, hostHealthMonitor, - VeniceRole.REPLICA, - metrics); + VeniceRole.REPLICA); } catch (Exception e) { multiGetThrottled = true; if (i < allowedQPS) {