org.asynchttpclient.netty.NettyResponseFuture Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of async-http-client Show documentation
Show all versions of async-http-client Show documentation
The Async Http Client (AHC) classes.
/*
* Copyright (c) 2014 AsyncHttpClient Project. All rights reserved.
*
* This program is licensed to you under the Apache License Version 2.0,
* and you may not use this file except in compliance with the Apache License Version 2.0.
* You may obtain a copy of the Apache License Version 2.0 at
* http://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the Apache License Version 2.0 is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the Apache License Version 2.0 for the specific language governing permissions and limitations there under.
*/
package org.asynchttpclient.netty;
import io.netty.channel.Channel;
import org.asynchttpclient.AsyncHandler;
import org.asynchttpclient.ListenableFuture;
import org.asynchttpclient.Realm;
import org.asynchttpclient.Request;
import org.asynchttpclient.channel.ChannelPoolPartitioning;
import org.asynchttpclient.netty.channel.ChannelState;
import org.asynchttpclient.netty.channel.Channels;
import org.asynchttpclient.netty.channel.ConnectionSemaphore;
import org.asynchttpclient.netty.request.NettyRequest;
import org.asynchttpclient.netty.timeout.TimeoutsHolder;
import org.asynchttpclient.proxy.ProxyServer;
import org.asynchttpclient.uri.Uri;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import static org.asynchttpclient.util.DateUtils.unpreciseMillisTime;
/**
* A {@link Future} that can be used to track when an asynchronous HTTP request
* has been fully processed.
*
* @param the result type
*/
public final class NettyResponseFuture implements ListenableFuture {
private static final Logger LOGGER = LoggerFactory.getLogger(NettyResponseFuture.class);
@SuppressWarnings("rawtypes")
private static final AtomicIntegerFieldUpdater REDIRECT_COUNT_UPDATER = AtomicIntegerFieldUpdater
.newUpdater(NettyResponseFuture.class, "redirectCount");
@SuppressWarnings("rawtypes")
private static final AtomicIntegerFieldUpdater CURRENT_RETRY_UPDATER = AtomicIntegerFieldUpdater
.newUpdater(NettyResponseFuture.class, "currentRetry");
@SuppressWarnings("rawtypes")
private static final AtomicIntegerFieldUpdater IS_DONE_FIELD = AtomicIntegerFieldUpdater
.newUpdater(NettyResponseFuture.class, "isDone");
@SuppressWarnings("rawtypes")
private static final AtomicIntegerFieldUpdater IS_CANCELLED_FIELD = AtomicIntegerFieldUpdater
.newUpdater(NettyResponseFuture.class, "isCancelled");
@SuppressWarnings("rawtypes")
private static final AtomicIntegerFieldUpdater IN_AUTH_FIELD = AtomicIntegerFieldUpdater
.newUpdater(NettyResponseFuture.class, "inAuth");
@SuppressWarnings("rawtypes")
private static final AtomicIntegerFieldUpdater IN_PROXY_AUTH_FIELD = AtomicIntegerFieldUpdater
.newUpdater(NettyResponseFuture.class, "inProxyAuth");
@SuppressWarnings("rawtypes")
private static final AtomicIntegerFieldUpdater CONTENT_PROCESSED_FIELD = AtomicIntegerFieldUpdater
.newUpdater(NettyResponseFuture.class, "contentProcessed");
@SuppressWarnings("rawtypes")
private static final AtomicIntegerFieldUpdater ON_THROWABLE_CALLED_FIELD = AtomicIntegerFieldUpdater
.newUpdater(NettyResponseFuture.class, "onThrowableCalled");
@SuppressWarnings("rawtypes")
private static final AtomicReferenceFieldUpdater TIMEOUTS_HOLDER_FIELD = AtomicReferenceFieldUpdater
.newUpdater(NettyResponseFuture.class, TimeoutsHolder.class, "timeoutsHolder");
@SuppressWarnings("rawtypes")
private static final AtomicReferenceFieldUpdater PARTITION_KEY_LOCK_FIELD = AtomicReferenceFieldUpdater
.newUpdater(NettyResponseFuture.class, Object.class, "partitionKeyLock");
private final long start = unpreciseMillisTime();
private final ChannelPoolPartitioning connectionPoolPartitioning;
private final ConnectionSemaphore connectionSemaphore;
private final ProxyServer proxyServer;
private final int maxRetry;
private final CompletableFuture future = new CompletableFuture<>();
public Throwable pendingException;
// state mutated from outside the event loop
// TODO check if they are indeed mutated outside the event loop
private volatile int isDone = 0;
private volatile int isCancelled = 0;
private volatile int inAuth = 0;
private volatile int inProxyAuth = 0;
private volatile int statusReceived = 0;
@SuppressWarnings("unused")
private volatile int contentProcessed = 0;
@SuppressWarnings("unused")
private volatile int onThrowableCalled = 0;
@SuppressWarnings("unused")
private volatile TimeoutsHolder timeoutsHolder;
// partition key, when != null used to release lock in ChannelManager
private volatile Object partitionKeyLock;
// volatile where we need CAS ops
private volatile int redirectCount = 0;
private volatile int currentRetry = 0;
// volatile where we don't need CAS ops
private volatile long touch = unpreciseMillisTime();
private volatile ChannelState channelState = ChannelState.NEW;
// state mutated only inside the event loop
private Channel channel;
private boolean keepAlive = true;
private Request targetRequest;
private Request currentRequest;
private NettyRequest nettyRequest;
private AsyncHandler asyncHandler;
private boolean streamAlreadyConsumed;
private boolean reuseChannel;
private boolean headersAlreadyWrittenOnContinue;
private boolean dontWriteBodyBecauseExpectContinue;
private boolean allowConnect;
private Realm realm;
private Realm proxyRealm;
public NettyResponseFuture(Request originalRequest,
AsyncHandler asyncHandler,
NettyRequest nettyRequest,
int maxRetry,
ChannelPoolPartitioning connectionPoolPartitioning,
ConnectionSemaphore connectionSemaphore,
ProxyServer proxyServer) {
this.asyncHandler = asyncHandler;
this.targetRequest = currentRequest = originalRequest;
this.nettyRequest = nettyRequest;
this.connectionPoolPartitioning = connectionPoolPartitioning;
this.connectionSemaphore = connectionSemaphore;
this.proxyServer = proxyServer;
this.maxRetry = maxRetry;
}
private void releasePartitionKeyLock() {
if (connectionSemaphore == null) {
return;
}
Object partitionKey = takePartitionKeyLock();
if (partitionKey != null) {
connectionSemaphore.releaseChannelLock(partitionKey);
}
}
// Take partition key lock object,
// but do not release channel lock.
public Object takePartitionKeyLock() {
// shortcut, much faster than getAndSet
if (partitionKeyLock == null) {
return null;
}
return PARTITION_KEY_LOCK_FIELD.getAndSet(this, null);
}
// java.util.concurrent.Future
@Override
public boolean isDone() {
return isDone != 0 || isCancelled();
}
@Override
public boolean isCancelled() {
return isCancelled != 0;
}
@Override
public boolean cancel(boolean force) {
releasePartitionKeyLock();
cancelTimeouts();
if (IS_CANCELLED_FIELD.getAndSet(this, 1) != 0)
return false;
// cancel could happen before channel was attached
if (channel != null) {
Channels.setDiscard(channel);
Channels.silentlyCloseChannel(channel);
}
if (ON_THROWABLE_CALLED_FIELD.getAndSet(this, 1) == 0) {
try {
asyncHandler.onThrowable(new CancellationException());
} catch (Throwable t) {
LOGGER.warn("cancel", t);
}
}
future.cancel(false);
return true;
}
@Override
public V get() throws InterruptedException, ExecutionException {
return future.get();
}
@Override
public V get(long l, TimeUnit tu) throws InterruptedException, TimeoutException, ExecutionException {
return future.get(l, tu);
}
private void loadContent() throws ExecutionException {
if (future.isDone()) {
try {
future.get();
} catch (InterruptedException e) {
throw new RuntimeException("unreachable", e);
}
}
// No more retry
CURRENT_RETRY_UPDATER.set(this, maxRetry);
if (CONTENT_PROCESSED_FIELD.getAndSet(this, 1) == 0) {
try {
future.complete(asyncHandler.onCompleted());
} catch (Throwable ex) {
if (ON_THROWABLE_CALLED_FIELD.getAndSet(this, 1) == 0) {
try {
try {
asyncHandler.onThrowable(ex);
} catch (Throwable t) {
LOGGER.debug("asyncHandler.onThrowable", t);
}
} finally {
cancelTimeouts();
}
}
future.completeExceptionally(ex);
}
}
future.getNow(null);
}
// org.asynchttpclient.ListenableFuture
private boolean terminateAndExit() {
releasePartitionKeyLock();
cancelTimeouts();
this.channel = null;
this.reuseChannel = false;
return IS_DONE_FIELD.getAndSet(this, 1) != 0 || isCancelled != 0;
}
public final void done() {
if (terminateAndExit())
return;
try {
loadContent();
} catch (ExecutionException ignored) {
} catch (RuntimeException t) {
future.completeExceptionally(t);
} catch (Throwable t) {
future.completeExceptionally(t);
throw t;
}
}
public final void abort(final Throwable t) {
if (terminateAndExit())
return;
future.completeExceptionally(t);
if (ON_THROWABLE_CALLED_FIELD.compareAndSet(this, 0, 1)) {
try {
asyncHandler.onThrowable(t);
} catch (Throwable te) {
LOGGER.debug("asyncHandler.onThrowable", te);
}
}
}
@Override
public void touch() {
touch = unpreciseMillisTime();
}
@Override
public ListenableFuture addListener(Runnable listener, Executor exec) {
if (exec == null) {
exec = Runnable::run;
}
future.whenCompleteAsync((r, v) -> listener.run(), exec);
return this;
}
@Override
public CompletableFuture toCompletableFuture() {
return future;
}
// INTERNAL
public Uri getUri() {
return targetRequest.getUri();
}
public ProxyServer getProxyServer() {
return proxyServer;
}
public void cancelTimeouts() {
TimeoutsHolder ref = TIMEOUTS_HOLDER_FIELD.getAndSet(this, null);
if (ref != null) {
ref.cancel();
}
}
public final Request getTargetRequest() {
return targetRequest;
}
public void setTargetRequest(Request targetRequest) {
this.targetRequest = targetRequest;
}
public final Request getCurrentRequest() {
return currentRequest;
}
public void setCurrentRequest(Request currentRequest) {
this.currentRequest = currentRequest;
}
public final NettyRequest getNettyRequest() {
return nettyRequest;
}
public final void setNettyRequest(NettyRequest nettyRequest) {
this.nettyRequest = nettyRequest;
}
public final AsyncHandler getAsyncHandler() {
return asyncHandler;
}
public void setAsyncHandler(AsyncHandler asyncHandler) {
this.asyncHandler = asyncHandler;
}
public final boolean isKeepAlive() {
return keepAlive;
}
public final void setKeepAlive(final boolean keepAlive) {
this.keepAlive = keepAlive;
}
public int incrementAndGetCurrentRedirectCount() {
return REDIRECT_COUNT_UPDATER.incrementAndGet(this);
}
public TimeoutsHolder getTimeoutsHolder() {
return TIMEOUTS_HOLDER_FIELD.get(this);
}
public void setTimeoutsHolder(TimeoutsHolder timeoutsHolder) {
TIMEOUTS_HOLDER_FIELD.set(this, timeoutsHolder);
}
public boolean isInAuth() {
return inAuth != 0;
}
public void setInAuth(boolean inAuth) {
this.inAuth = inAuth ? 1 : 0;
}
public boolean isAndSetInAuth(boolean set) {
return IN_AUTH_FIELD.getAndSet(this, set ? 1 : 0) != 0;
}
public boolean isInProxyAuth() {
return inProxyAuth != 0;
}
public void setInProxyAuth(boolean inProxyAuth) {
this.inProxyAuth = inProxyAuth ? 1 : 0;
}
public boolean isAndSetInProxyAuth(boolean inProxyAuth) {
return IN_PROXY_AUTH_FIELD.getAndSet(this, inProxyAuth ? 1 : 0) != 0;
}
public ChannelState getChannelState() {
return channelState;
}
public void setChannelState(ChannelState channelState) {
this.channelState = channelState;
}
public boolean isStreamConsumed() {
return streamAlreadyConsumed;
}
public void setStreamConsumed(boolean streamConsumed) {
this.streamAlreadyConsumed = streamConsumed;
}
public long getLastTouch() {
return touch;
}
public boolean isHeadersAlreadyWrittenOnContinue() {
return headersAlreadyWrittenOnContinue;
}
public void setHeadersAlreadyWrittenOnContinue(boolean headersAlreadyWrittenOnContinue) {
this.headersAlreadyWrittenOnContinue = headersAlreadyWrittenOnContinue;
}
public boolean isDontWriteBodyBecauseExpectContinue() {
return dontWriteBodyBecauseExpectContinue;
}
public void setDontWriteBodyBecauseExpectContinue(boolean dontWriteBodyBecauseExpectContinue) {
this.dontWriteBodyBecauseExpectContinue = dontWriteBodyBecauseExpectContinue;
}
public boolean isConnectAllowed() {
return allowConnect;
}
public void setConnectAllowed(boolean allowConnect) {
this.allowConnect = allowConnect;
}
public void attachChannel(Channel channel, boolean reuseChannel) {
// future could have been cancelled first
if (isDone()) {
Channels.silentlyCloseChannel(channel);
}
this.channel = channel;
this.reuseChannel = reuseChannel;
}
public Channel channel() {
return channel;
}
public boolean isReuseChannel() {
return reuseChannel;
}
public void setReuseChannel(boolean reuseChannel) {
this.reuseChannel = reuseChannel;
}
public boolean incrementRetryAndCheck() {
return maxRetry > 0 && CURRENT_RETRY_UPDATER.incrementAndGet(this) <= maxRetry;
}
/**
* Return true if the {@link Future} can be recovered. There is some scenario
* where a connection can be closed by an unexpected IOException, and in some
* situation we can recover from that exception.
*
* @return true if that {@link Future} cannot be recovered.
*/
public boolean isReplayPossible() {
return !isDone() && !(Channels.isChannelActive(channel) && !getUri().getScheme().equalsIgnoreCase("https"))
&& inAuth == 0 && inProxyAuth == 0;
}
public long getStart() {
return start;
}
public Object getPartitionKey() {
return connectionPoolPartitioning.getPartitionKey(targetRequest.getUri(), targetRequest.getVirtualHost(),
proxyServer);
}
public void acquirePartitionLockLazily() throws IOException {
if (connectionSemaphore == null || partitionKeyLock != null) {
return;
}
Object partitionKey = getPartitionKey();
connectionSemaphore.acquireChannelLock(partitionKey);
Object prevKey = PARTITION_KEY_LOCK_FIELD.getAndSet(this, partitionKey);
if (prevKey != null) {
// self-check
connectionSemaphore.releaseChannelLock(prevKey);
releasePartitionKeyLock();
throw new IllegalStateException("Trying to acquire partition lock concurrently. Please report.");
}
if (isDone()) {
// may be cancelled while we acquired a lock
releasePartitionKeyLock();
}
}
public Realm getRealm() {
return realm;
}
public void setRealm(Realm realm) {
this.realm = realm;
}
public Realm getProxyRealm() {
return proxyRealm;
}
public void setProxyRealm(Realm proxyRealm) {
this.proxyRealm = proxyRealm;
}
@Override
public String toString() {
return "NettyResponseFuture{" + //
"currentRetry=" + currentRetry + //
",\n\tisDone=" + isDone + //
",\n\tisCancelled=" + isCancelled + //
",\n\tasyncHandler=" + asyncHandler + //
",\n\tnettyRequest=" + nettyRequest + //
",\n\tfuture=" + future + //
",\n\turi=" + getUri() + //
",\n\tkeepAlive=" + keepAlive + //
",\n\tredirectCount=" + redirectCount + //
",\n\ttimeoutsHolder=" + TIMEOUTS_HOLDER_FIELD.get(this) + //
",\n\tinAuth=" + inAuth + //
",\n\tstatusReceived=" + statusReceived + //
",\n\ttouch=" + touch + //
'}';
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy