1919import java .util .concurrent .CountDownLatch ;
2020import java .util .concurrent .ExecutionException ;
2121import java .util .concurrent .TimeUnit ;
22+ import java .util .concurrent .atomic .AtomicInteger ;
2223import java .util .concurrent .atomic .AtomicReference ;
2324
2425import org .eclipse .jetty .client .transport .HttpClientTransportOverHTTP ;
2526import org .eclipse .jetty .client .transport .HttpDestination ;
2627import org .eclipse .jetty .client .transport .internal .HttpConnectionOverHTTP ;
28+ import org .eclipse .jetty .http .HttpMethod ;
29+ import org .eclipse .jetty .http .HttpStatus ;
2730import org .eclipse .jetty .io .EndPoint ;
2831import org .eclipse .jetty .server .Handler ;
2932import org .eclipse .jetty .server .Server ;
3033import org .eclipse .jetty .server .ServerConnector ;
3134import org .eclipse .jetty .util .Callback ;
35+ import org .eclipse .jetty .util .component .LifeCycle ;
3236import org .eclipse .jetty .util .thread .QueuedThreadPool ;
3337import org .junit .jupiter .api .AfterEach ;
3438import org .junit .jupiter .api .Test ;
3539
40+ import static org .awaitility .Awaitility .await ;
41+ import static org .hamcrest .MatcherAssert .assertThat ;
42+ import static org .hamcrest .Matchers .is ;
43+ import static org .hamcrest .Matchers .notNullValue ;
44+ import static org .hamcrest .Matchers .nullValue ;
3645import static org .junit .jupiter .api .Assertions .assertEquals ;
3746import static org .junit .jupiter .api .Assertions .assertThrows ;
3847import static org .junit .jupiter .api .Assertions .assertTrue ;
@@ -41,7 +50,6 @@ public class HttpClientFailureTest
4150{
4251 private Server server ;
4352 private ServerConnector connector ;
44- private HttpClient client ;
4553
4654 private void startServer (Handler handler ) throws Exception
4755 {
@@ -57,30 +65,29 @@ private void startServer(Handler handler) throws Exception
5765 @ AfterEach
5866 public void dispose () throws Exception
5967 {
60- if (server != null )
61- server .stop ();
62- if (client != null )
63- client .stop ();
68+ LifeCycle .stop (server );
6469 }
6570
6671 @ Test
6772 public void testFailureBeforeRequestCommit () throws Exception
6873 {
6974 startServer (new EmptyServerHandler ());
7075
71- client = new HttpClient (new HttpClientTransportOverHTTP (1 ));
72- client .start ();
73-
74- Request request = client .newRequest ("localhost" , connector .getLocalPort ())
75- .onRequestHeaders (r -> r .getConnection ().close ())
76- .timeout (5 , TimeUnit .SECONDS );
77- assertThrows (ExecutionException .class , request ::send );
78-
79- HttpDestination destination = (HttpDestination )client .resolveDestination (request );
80- DuplexConnectionPool connectionPool = (DuplexConnectionPool )destination .getConnectionPool ();
81- assertEquals (0 , connectionPool .getConnectionCount ());
82- assertEquals (0 , connectionPool .getActiveConnections ().size ());
83- assertEquals (0 , connectionPool .getIdleConnections ().size ());
76+ try (HttpClient client = new HttpClient (new HttpClientTransportOverHTTP (1 )))
77+ {
78+ client .start ();
79+
80+ Request request = client .newRequest ("localhost" , connector .getLocalPort ())
81+ .onRequestHeaders (r -> r .getConnection ().close ())
82+ .timeout (5 , TimeUnit .SECONDS );
83+ assertThrows (ExecutionException .class , request ::send );
84+
85+ HttpDestination destination = (HttpDestination )client .resolveDestination (request );
86+ DuplexConnectionPool connectionPool = (DuplexConnectionPool )destination .getConnectionPool ();
87+ assertEquals (0 , connectionPool .getConnectionCount ());
88+ assertEquals (0 , connectionPool .getActiveConnections ().size ());
89+ assertEquals (0 , connectionPool .getIdleConnections ().size ());
90+ }
8491 }
8592
8693 @ Test
@@ -89,7 +96,7 @@ public void testFailureAfterRequestCommit() throws Exception
8996 startServer (new EmptyServerHandler ());
9097
9198 AtomicReference <HttpConnectionOverHTTP > connectionRef = new AtomicReference <>();
92- client = new HttpClient (new HttpClientTransportOverHTTP (1 )
99+ try ( HttpClient client = new HttpClient (new HttpClientTransportOverHTTP (1 )
93100 {
94101 @ Override
95102 public org .eclipse .jetty .io .Connection newConnection (EndPoint endPoint , Map <String , Object > context ) throws IOException
@@ -98,48 +105,83 @@ public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<Stri
98105 connectionRef .set (connection );
99106 return connection ;
100107 }
101- });
102- client .start ();
103-
104- CountDownLatch commitLatch = new CountDownLatch (1 );
105- CountDownLatch completeLatch = new CountDownLatch (1 );
106- AsyncRequestContent content = new AsyncRequestContent ();
107- client .newRequest ("localhost" , connector .getLocalPort ())
108- .onRequestCommit (request ->
109- {
110- connectionRef .get ().getEndPoint ().close ();
111- commitLatch .countDown ();
112- })
113- .body (content )
114- .idleTimeout (2 , TimeUnit .SECONDS )
115- .send (result ->
108+ }))
109+ {
110+ client .start ();
111+
112+ CountDownLatch commitLatch = new CountDownLatch (1 );
113+ CountDownLatch completeLatch = new CountDownLatch (1 );
114+ AsyncRequestContent content = new AsyncRequestContent ();
115+ client .newRequest ("localhost" , connector .getLocalPort ())
116+ .onRequestCommit (request ->
117+ {
118+ connectionRef .get ().getEndPoint ().close ();
119+ commitLatch .countDown ();
120+ })
121+ .body (content )
122+ .idleTimeout (2 , TimeUnit .SECONDS )
123+ .send (result ->
124+ {
125+ if (result .isFailed ())
126+ completeLatch .countDown ();
127+ });
128+
129+ assertTrue (commitLatch .await (5 , TimeUnit .SECONDS ));
130+
131+ // The first chunk will be read but its write will fail.
132+ content .write (ByteBuffer .allocate (1024 ), Callback .NOOP );
133+
134+ // The second chunk is failed because the content is failed.
135+ CountDownLatch contentLatch = new CountDownLatch (1 );
136+ content .write (ByteBuffer .allocate (1024 ), new Callback ()
116137 {
117- if (result .isFailed ())
118- completeLatch .countDown ();
138+ @ Override
139+ public void failed (Throwable x )
140+ {
141+ contentLatch .countDown ();
142+ }
119143 });
120144
121- assertTrue (commitLatch .await (5 , TimeUnit .SECONDS ));
122-
123- // The first chunk will be read but its write will fail.
124- content .write (ByteBuffer .allocate (1024 ), Callback .NOOP );
145+ assertTrue (contentLatch .await (5 , TimeUnit .SECONDS ));
146+ assertTrue (completeLatch .await (5 , TimeUnit .SECONDS ));
125147
126- // The second chunk is failed because the content is failed.
127- CountDownLatch contentLatch = new CountDownLatch (1 );
128- content .write (ByteBuffer .allocate (1024 ), new Callback ()
129- {
130- @ Override
131- public void failed (Throwable x )
132- {
133- contentLatch .countDown ();
134- }
135- });
148+ DuplexConnectionPool connectionPool = (DuplexConnectionPool )connectionRef .get ().getHttpDestination ().getConnectionPool ();
149+ assertEquals (0 , connectionPool .getConnectionCount ());
150+ assertEquals (0 , connectionPool .getActiveConnections ().size ());
151+ assertEquals (0 , connectionPool .getIdleConnections ().size ());
152+ }
153+ }
136154
137- assertTrue (contentLatch .await (5 , TimeUnit .SECONDS ));
138- assertTrue (completeLatch .await (5 , TimeUnit .SECONDS ));
155+ @ Test
156+ public void testPendingRequestContentThenTotalTimeout () throws Exception
157+ {
158+ startServer (new EmptyServerHandler ());
139159
140- DuplexConnectionPool connectionPool = (DuplexConnectionPool )connectionRef .get ().getHttpDestination ().getConnectionPool ();
141- assertEquals (0 , connectionPool .getConnectionCount ());
142- assertEquals (0 , connectionPool .getActiveConnections ().size ());
143- assertEquals (0 , connectionPool .getIdleConnections ().size ());
160+ try (HttpClient client = new HttpClient (new HttpClientTransportOverHTTP (1 )))
161+ {
162+ client .start ();
163+
164+ long timeout = 1000 ;
165+ AsyncRequestContent content = new AsyncRequestContent ();
166+ AtomicInteger completed = new AtomicInteger ();
167+ CountDownLatch resultLatch = new CountDownLatch (1 );
168+ client .newRequest ("localhost" , connector .getLocalPort ())
169+ .method (HttpMethod .POST )
170+ .body (content )
171+ .timeout (timeout , TimeUnit .MILLISECONDS )
172+ .send (result ->
173+ {
174+ // This is invoked only when the total timeout elapses.
175+ completed .incrementAndGet ();
176+ assertThat (result .getRequestFailure (), notNullValue ());
177+ assertThat (result .getResponseFailure (), nullValue ());
178+ assertThat (result .getResponse ().getStatus (), is (HttpStatus .OK_200 ));
179+ resultLatch .countDown ();
180+ });
181+
182+ assertTrue (resultLatch .await (2 * timeout , TimeUnit .MILLISECONDS ));
183+ // Verify that the CompleteListener is invoked only once.
184+ await ().during (1 , TimeUnit .SECONDS ).atMost (5 , TimeUnit .SECONDS ).until (completed ::get , is (1 ));
185+ }
144186 }
145187}
0 commit comments