Skip to content

Commit d500dd7

Browse files
author
oleksiys
committed
+ make sure we reuse the connection only after request is fully sent
1 parent 9dbb72c commit d500dd7

File tree

5 files changed

+130
-31
lines changed

5 files changed

+130
-31
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,4 @@ test-output
1818
/META-INF/MANIFEST.MF
1919
work
2020
atlassian-ide-plugin.xml
21+
/nb-configuration.xml

src/main/java/com/ning/http/client/providers/grizzly/AhcEventFilter.java

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2012-2015 Sonatype, Inc. All rights reserved.
2+
* Copyright (c) 2012-2016 Sonatype, Inc. All rights reserved.
33
*
44
* This program is licensed to you under the Apache License Version 2.0,
55
* and you may not use this file except in compliance with the Apache License Version 2.0.
@@ -57,6 +57,7 @@
5757

5858
import static com.ning.http.util.AsyncHttpProviderUtils.*;
5959
import static com.ning.http.util.MiscUtils.isNonEmpty;
60+
import org.glassfish.grizzly.EmptyCompletionHandler;
6061
/**
6162
* AHC {@link HttpClientFilter} implementation.
6263
*
@@ -442,22 +443,25 @@ private static boolean isRedirectAllowed(final HttpTransactionContext ctx) {
442443
}
443444

444445
private static void cleanup(final HttpContext httpContext) {
445-
final HttpTransactionContext context =
446-
HttpTransactionContext.cleanupTransaction(httpContext);
447-
448-
if (!context.isReuseConnection()) {
449-
final Connection c = (Connection) httpContext.getCloseable();
450-
final ConnectionManager cm = context.provider.getConnectionManager();
451-
if (!httpContext.getRequest().getProcessingState().isStayAlive()) {
452-
if (notKeepAliveReason == null) {
453-
notKeepAliveReason =
454-
new IOException("HTTP keep-alive was disabled for this connection");
446+
HttpTransactionContext.cleanupTransaction(httpContext,
447+
new EmptyCompletionHandler<HttpTransactionContext>() {
448+
@Override
449+
public void completed(HttpTransactionContext context) {
450+
if (!context.isReuseConnection()) {
451+
final Connection c = (Connection) httpContext.getCloseable();
452+
if (!httpContext.getRequest().getProcessingState().isStayAlive()) {
453+
if (notKeepAliveReason == null) {
454+
notKeepAliveReason
455+
= new IOException("HTTP keep-alive was disabled for this connection");
456+
}
457+
c.closeWithReason(notKeepAliveReason);
458+
} else {
459+
final ConnectionManager cm = context.provider.getConnectionManager();
460+
cm.returnConnection(c);
461+
}
455462
}
456-
c.closeWithReason(notKeepAliveReason);
457-
} else {
458-
cm.returnConnection(c);
459463
}
460-
}
464+
});
461465
}
462466

463467
private static boolean redirectCountExceeded(final HttpTransactionContext context) {

src/main/java/com/ning/http/client/providers/grizzly/AsyncHttpClientFilter.java

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2012-2015 Sonatype, Inc. All rights reserved.
2+
* Copyright (c) 2012-2016 Sonatype, Inc. All rights reserved.
33
*
44
* This program is licensed to you under the Apache License Version 2.0,
55
* and you may not use this file except in compliance with the Apache License Version 2.0.
@@ -151,7 +151,10 @@ private boolean sendAsGrizzlyRequest(final HttpTransactionContext httpTxCtx,
151151
}
152152

153153
HttpRequestPacket requestPacket;
154-
final PayloadGenerator payloadGenerator = isPayloadAllowed(method) ? PayloadGenFactory.getPayloadGenerator(ahcRequest) : null;
154+
final PayloadGenerator payloadGenerator = isPayloadAllowed(method)
155+
? PayloadGenFactory.getPayloadGenerator(ahcRequest)
156+
: null;
157+
155158
if (payloadGenerator != null) {
156159
final long contentLength = ahcRequest.getContentLength();
157160
if (contentLength >= 0) {
@@ -196,7 +199,13 @@ private boolean sendAsGrizzlyRequest(final HttpTransactionContext httpTxCtx,
196199
ctx.notifyDownstream(new SSLSwitchingEvent(connection, secure,
197200
uri.getHost(), uri.getPort()));
198201

199-
return sendRequest(httpTxCtx, ctx, requestPacket, wrapWithExpectHandlerIfNeeded(payloadGenerator, requestPacket));
202+
final boolean isFullySent = sendRequest(httpTxCtx, ctx, requestPacket,
203+
wrapWithExpectHandlerIfNeeded(payloadGenerator, requestPacket));
204+
if (isFullySent) {
205+
httpTxCtx.onRequestFullySent();
206+
}
207+
208+
return isFullySent;
200209
}
201210

202211
private boolean establishConnectTunnel(final ProxyServer proxy,
@@ -284,7 +293,9 @@ private PayloadGenerator wrapWithExpectHandlerIfNeeded(final PayloadGenerator pa
284293
// check if we need to wrap the PayloadGenerator with ExpectWrapper
285294
final MimeHeaders headers = requestPacket.getHeaders();
286295
final int expectHeaderIdx = headers.indexOf(Header.Expect, 0);
287-
return expectHeaderIdx != -1 && headers.getValue(expectHeaderIdx).equalsIgnoreCase("100-Continue") ? PayloadGenFactory.wrapWithExpect(payloadGenerator) : payloadGenerator;
296+
return expectHeaderIdx != -1 && headers.getValue(expectHeaderIdx).equalsIgnoreCase("100-Continue")
297+
? PayloadGenFactory.wrapWithExpect(payloadGenerator)
298+
: payloadGenerator;
288299
}
289300

290301
private boolean isPayloadAllowed(final Method method) {

src/main/java/com/ning/http/client/providers/grizzly/FeedableBodyGenerator.java

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2012-2015 Sonatype, Inc. All rights reserved.
2+
* Copyright (c) 2012-2016 Sonatype, Inc. All rights reserved.
33
*
44
* This program is licensed to you under the Apache License Version 2.0,
55
* and you may not use this file except in compliance with the Apache License Version 2.0.
@@ -306,10 +306,10 @@ public interface Feeder {
306306
* an implementation for the contract defined by the {@link #feed} method.
307307
*/
308308
public static abstract class BaseFeeder implements Feeder {
309-
309+
310310
protected final FeedableBodyGenerator feedableBodyGenerator;
311-
312-
311+
312+
private boolean wasLastSent;
313313
// -------------------------------------------------------- Constructors
314314

315315

@@ -329,15 +329,35 @@ public final synchronized void feed(final Buffer buffer, final boolean last)
329329
throw new IllegalArgumentException(
330330
"Buffer argument cannot be null.");
331331
}
332+
332333
if (!feedableBodyGenerator.asyncTransferInitiated) {
333334
throw new IllegalStateException("Asynchronous transfer has not been initiated.");
334335
}
336+
337+
if (wasLastSent) {
338+
if (buffer.hasRemaining()) {
339+
throw new IOException("Last chunk was alredy written");
340+
}
341+
342+
return;
343+
}
344+
335345
blockUntilQueueFree(feedableBodyGenerator.context.getConnection());
336346
final HttpContent content =
337347
feedableBodyGenerator.contentBuilder.content(buffer).last(last).build();
338348
final CompletionHandler<WriteResult> handler =
339349
((last) ? new LastPacketCompletionHandler() : null);
340350
feedableBodyGenerator.context.write(content, handler);
351+
352+
if (last) {
353+
wasLastSent = true;
354+
final HttpTransactionContext currentTransaction =
355+
HttpTransactionContext.currentTransaction(
356+
feedableBodyGenerator.requestPacket);
357+
if (currentTransaction != null) {
358+
currentTransaction.onRequestFullySent();
359+
}
360+
}
341361
}
342362

343363
/**

src/main/java/com/ning/http/client/providers/grizzly/HttpTransactionContext.java

Lines changed: 71 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2012-2015 Sonatype, Inc. All rights reserved.
2+
* Copyright (c) 2012-2016 Sonatype, Inc. All rights reserved.
33
*
44
* This program is licensed to you under the Apache License Version 2.0,
55
* and you may not use this file except in compliance with the Apache License Version 2.0.
@@ -21,9 +21,12 @@
2121
import com.ning.http.util.AsyncHttpProviderUtils;
2222
import com.ning.http.util.ProxyUtils;
2323
import java.io.IOException;
24+
import java.util.HashSet;
25+
import java.util.Set;
2426
import org.glassfish.grizzly.CloseListener;
2527
import org.glassfish.grizzly.CloseType;
2628
import org.glassfish.grizzly.Closeable;
29+
import org.glassfish.grizzly.CompletionHandler;
2730
import org.glassfish.grizzly.Connection;
2831
import org.glassfish.grizzly.Grizzly;
2932
import org.glassfish.grizzly.attributes.Attribute;
@@ -80,6 +83,11 @@ public final class HttpTransactionContext {
8083
// the pool
8184
boolean isReuseConnection;
8285

86+
/**
87+
* <tt>true</tt> if the request is fully sent, or <tt>false</tt>otherwise.
88+
*/
89+
volatile boolean isRequestFullySent;
90+
Set<CompletionHandler<HttpTransactionContext>> reqFullySentHandlers;
8391

8492
private final CloseListener listener = new CloseListener<Closeable, CloseType>() {
8593
@Override
@@ -111,16 +119,30 @@ static void bind(final HttpContext httpCtx,
111119
REQUEST_STATE_ATTR.set(httpCtx, httpTxContext);
112120
}
113121

114-
static HttpTransactionContext cleanupTransaction(final HttpContext httpCtx) {
122+
static void cleanupTransaction(final HttpContext httpCtx,
123+
final CompletionHandler<HttpTransactionContext> completionHandler) {
115124
final HttpTransactionContext httpTxContext = currentTransaction(httpCtx);
116-
if (httpTxContext != null) {
117-
httpCtx.getCloseable().removeCloseListener(httpTxContext.listener);
118-
REQUEST_STATE_ATTR.remove(httpCtx);
119-
}
120125

121-
return httpTxContext;
126+
assert httpTxContext != null;
127+
128+
if (httpTxContext.isRequestFullySent) {
129+
cleanupTransaction(httpCtx, httpTxContext);
130+
completionHandler.completed(httpTxContext);
131+
} else {
132+
httpTxContext.addRequestSentCompletionHandler(completionHandler);
133+
if (httpTxContext.isRequestFullySent &&
134+
httpTxContext.removeRequestSentCompletionHandler(completionHandler)) {
135+
completionHandler.completed(httpTxContext);
136+
}
137+
}
122138
}
123139

140+
static void cleanupTransaction(final HttpContext httpCtx,
141+
final HttpTransactionContext httpTxContext) {
142+
httpCtx.getCloseable().removeCloseListener(httpTxContext.listener);
143+
REQUEST_STATE_ATTR.remove(httpCtx);
144+
}
145+
124146
static HttpTransactionContext currentTransaction(
125147
final HttpHeader httpHeader) {
126148
return currentTransaction(httpHeader.getProcessingState().getHttpContext());
@@ -171,7 +193,7 @@ Request getAhcRequest() {
171193

172194
ProxyServer getProxyServer() {
173195
return proxyServer;
174-
}
196+
}
175197

176198
// ----------------------------------------------------- Private Methods
177199

@@ -246,4 +268,45 @@ void touchConnection() {
246268
void closeConnection() {
247269
connection.closeSilently();
248270
}
271+
272+
private synchronized void addRequestSentCompletionHandler(
273+
final CompletionHandler<HttpTransactionContext> completionHandler) {
274+
if (reqFullySentHandlers == null) {
275+
reqFullySentHandlers = new HashSet<>();
276+
}
277+
reqFullySentHandlers.add(completionHandler);
278+
}
279+
280+
private synchronized boolean removeRequestSentCompletionHandler(
281+
final CompletionHandler<HttpTransactionContext> completionHandler) {
282+
return reqFullySentHandlers != null
283+
? reqFullySentHandlers.remove(completionHandler)
284+
: false;
285+
}
286+
287+
boolean isRequestFullySent() {
288+
return isRequestFullySent;
289+
}
290+
291+
@SuppressWarnings("unchecked")
292+
void onRequestFullySent() {
293+
this.isRequestFullySent = true;
294+
295+
Object[] handlers = null;
296+
synchronized (this) {
297+
if (reqFullySentHandlers != null) {
298+
handlers = reqFullySentHandlers.toArray();
299+
reqFullySentHandlers = null;
300+
}
301+
}
302+
303+
if (handlers != null) {
304+
for (Object o : handlers) {
305+
try {
306+
((CompletionHandler<HttpTransactionContext>) o).completed(this);
307+
} catch (Exception e) {
308+
}
309+
}
310+
}
311+
}
249312
} // END HttpTransactionContext

0 commit comments

Comments
 (0)