Skip to content

Commit a44aac8

Browse files
committed
Fix regression not allowing disabling CookieStore, close AsyncHttpClient#1714
Motivation: Setting CookieStore to null in conf should be supported to disable it. Latest commit introduced a NPE regression. Modification: * Protect against null CookieStore * Make sure to decrement CookieStore ref count when AHC instance is closed. Result: No more NPE when CookieStore is null.
1 parent 45b4561 commit a44aac8

File tree

2 files changed

+85
-59
lines changed

2 files changed

+85
-59
lines changed

client/src/main/java/org/asynchttpclient/DefaultAsyncHttpClient.java

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import io.netty.util.concurrent.DefaultThreadFactory;
2424
import org.asynchttpclient.channel.ChannelPool;
2525
import org.asynchttpclient.cookie.CookieEvictionTask;
26+
import org.asynchttpclient.cookie.CookieStore;
2627
import org.asynchttpclient.filter.FilterContext;
2728
import org.asynchttpclient.filter.FilterException;
2829
import org.asynchttpclient.filter.RequestFilter;
@@ -91,21 +92,17 @@ public DefaultAsyncHttpClient(AsyncHttpClientConfig config) {
9192
channelManager = new ChannelManager(config, nettyTimer);
9293
requestSender = new NettyRequestSender(config, channelManager, nettyTimer, new AsyncHttpClientState(closed));
9394
channelManager.configureBootstraps(requestSender);
94-
boolean scheduleCookieEviction = false;
9595

96-
final int cookieStoreCount = config.getCookieStore().incrementAndGet();
97-
if (!allowStopNettyTimer) {
98-
if (cookieStoreCount == 1) {
99-
// If this is the first AHC instance for the shared (user-provided) netty timer.
100-
scheduleCookieEviction = true;
96+
CookieStore cookieStore = config.getCookieStore();
97+
if (cookieStore != null) {
98+
int cookieStoreCount = config.getCookieStore().incrementAndGet();
99+
if (
100+
allowStopNettyTimer // timer is not shared
101+
|| cookieStoreCount == 1 // this is the first AHC instance for the shared (user-provided) timer
102+
) {
103+
nettyTimer.newTimeout(new CookieEvictionTask(config.expiredCookieEvictionDelay(), cookieStore),
104+
config.expiredCookieEvictionDelay(), TimeUnit.MILLISECONDS);
101105
}
102-
} else {
103-
// If Timer is not shared.
104-
scheduleCookieEviction = true;
105-
}
106-
if (scheduleCookieEviction) {
107-
nettyTimer.newTimeout(new CookieEvictionTask(config.expiredCookieEvictionDelay(), config.getCookieStore()),
108-
config.expiredCookieEvictionDelay(), TimeUnit.MILLISECONDS);
109106
}
110107
}
111108

@@ -124,6 +121,10 @@ public void close() {
124121
} catch (Throwable t) {
125122
LOGGER.warn("Unexpected error on ChannelManager close", t);
126123
}
124+
CookieStore cookieStore = config.getCookieStore();
125+
if (cookieStore != null) {
126+
cookieStore.decrementAndGet();
127+
}
127128
if (allowStopNettyTimer) {
128129
try {
129130
nettyTimer.stop();
Lines changed: 71 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
package org.asynchttpclient;
22

33
import io.netty.util.Timer;
4-
import org.asynchttpclient.DefaultAsyncHttpClientConfig.Builder;
54
import org.asynchttpclient.cookie.CookieEvictionTask;
65
import org.asynchttpclient.cookie.CookieStore;
76
import org.asynchttpclient.cookie.ThreadSafeCookieStore;
87
import org.testng.annotations.Test;
98

10-
import java.io.Closeable;
119
import java.io.IOException;
1210
import java.util.concurrent.TimeUnit;
1311

12+
import static org.asynchttpclient.Dsl.*;
1413
import static org.mockito.ArgumentMatchers.any;
1514
import static org.mockito.ArgumentMatchers.anyLong;
1615
import static org.mockito.Mockito.*;
@@ -20,62 +19,88 @@ public class DefaultAsyncHttpClientTest {
2019

2120
@Test
2221
public void testWithSharedNettyTimerShouldScheduleCookieEvictionOnlyOnce() throws IOException {
23-
final Timer nettyTimerMock = mock(Timer.class);
24-
final CookieStore cookieStore = new ThreadSafeCookieStore();
25-
final DefaultAsyncHttpClientConfig config = new Builder().setNettyTimer(nettyTimerMock).setCookieStore(cookieStore).build();
26-
final AsyncHttpClient client1 = new DefaultAsyncHttpClient(config);
27-
final AsyncHttpClient client2 = new DefaultAsyncHttpClient(config);
28-
29-
assertEquals(cookieStore.count(), 2);
30-
verify(nettyTimerMock, times(1)).newTimeout(any(CookieEvictionTask.class), anyLong(), any(TimeUnit.class));
31-
32-
closeSilently(client1);
33-
closeSilently(client2);
22+
Timer nettyTimerMock = mock(Timer.class);
23+
CookieStore cookieStore = new ThreadSafeCookieStore();
24+
AsyncHttpClientConfig config = config().setNettyTimer(nettyTimerMock).setCookieStore(cookieStore).build();
25+
26+
try (AsyncHttpClient client1 = asyncHttpClient(config)) {
27+
try (AsyncHttpClient client2 = asyncHttpClient(config)) {
28+
assertEquals(cookieStore.count(), 2);
29+
verify(nettyTimerMock, times(1)).newTimeout(any(CookieEvictionTask.class), anyLong(), any(TimeUnit.class));
30+
}
31+
}
3432
}
3533

3634
@Test
3735
public void testWitDefaultConfigShouldScheduleCookieEvictionForEachAHC() throws IOException {
38-
final AsyncHttpClientConfig config1 = new DefaultAsyncHttpClientConfig.Builder().build();
39-
final DefaultAsyncHttpClient client1 = new DefaultAsyncHttpClient(config1);
36+
AsyncHttpClientConfig config1 = config().build();
37+
try (AsyncHttpClient client1 = asyncHttpClient(config1)) {
38+
AsyncHttpClientConfig config2 = config().build();
39+
try (AsyncHttpClient client2 = asyncHttpClient(config2)) {
40+
assertEquals(config1.getCookieStore().count(), 1);
41+
assertEquals(config2.getCookieStore().count(), 1);
42+
}
43+
}
44+
}
4045

41-
final AsyncHttpClientConfig config2 = new DefaultAsyncHttpClientConfig.Builder().build();
42-
final DefaultAsyncHttpClient client2 = new DefaultAsyncHttpClient(config2);
46+
@Test
47+
public void testWithSharedCookieStoreButNonSharedTimerShouldScheduleCookieEvictionForFirstAHC() throws IOException {
48+
CookieStore cookieStore = new ThreadSafeCookieStore();
49+
Timer nettyTimerMock1 = mock(Timer.class);
50+
AsyncHttpClientConfig config1 = config()
51+
.setCookieStore(cookieStore).setNettyTimer(nettyTimerMock1).build();
52+
53+
try (AsyncHttpClient client1 = asyncHttpClient(config1)) {
54+
Timer nettyTimerMock2 = mock(Timer.class);
55+
AsyncHttpClientConfig config2 = config()
56+
.setCookieStore(cookieStore).setNettyTimer(nettyTimerMock2).build();
57+
try (AsyncHttpClient client2 = asyncHttpClient(config2)) {
58+
assertEquals(config1.getCookieStore().count(), 2);
59+
verify(nettyTimerMock1, times(1)).newTimeout(any(CookieEvictionTask.class), anyLong(), any(TimeUnit.class));
60+
verify(nettyTimerMock2, never()).newTimeout(any(CookieEvictionTask.class), anyLong(), any(TimeUnit.class));
61+
}
62+
}
4363

44-
assertEquals(config1.getCookieStore().count(), 1);
45-
assertEquals(config2.getCookieStore().count(), 1);
64+
Timer nettyTimerMock3 = mock(Timer.class);
65+
AsyncHttpClientConfig config3 = config()
66+
.setCookieStore(cookieStore).setNettyTimer(nettyTimerMock3).build();
4667

47-
closeSilently(client1);
48-
closeSilently(client2);
68+
try (AsyncHttpClient client2 = asyncHttpClient(config3)) {
69+
assertEquals(config1.getCookieStore().count(), 1);
70+
verify(nettyTimerMock3, times(1)).newTimeout(any(CookieEvictionTask.class), anyLong(), any(TimeUnit.class));
71+
}
4972
}
5073

5174
@Test
52-
public void testWithSharedCookieStoreButNonSharedTimerShouldScheduleCookieEvictionForFirstAHC() throws IOException {
53-
final CookieStore cookieStore = new ThreadSafeCookieStore();
54-
final Timer nettyTimerMock1 = mock(Timer.class);
55-
final AsyncHttpClientConfig config1 = new DefaultAsyncHttpClientConfig.Builder()
56-
.setCookieStore(cookieStore).setNettyTimer(nettyTimerMock1).build();
57-
final DefaultAsyncHttpClient client1 = new DefaultAsyncHttpClient(config1);
58-
59-
final Timer nettyTimerMock2 = mock(Timer.class);
60-
final AsyncHttpClientConfig config2 = new DefaultAsyncHttpClientConfig.Builder()
61-
.setCookieStore(cookieStore).setNettyTimer(nettyTimerMock2).build();
62-
final DefaultAsyncHttpClient client2 = new DefaultAsyncHttpClient(config2);
63-
64-
assertEquals(config1.getCookieStore().count(), 2);
65-
verify(nettyTimerMock1, times(1)).newTimeout(any(CookieEvictionTask.class), anyLong(), any(TimeUnit.class));
66-
verify(nettyTimerMock2, never()).newTimeout(any(CookieEvictionTask.class), anyLong(), any(TimeUnit.class));
67-
68-
closeSilently(client1);
69-
closeSilently(client2);
75+
public void testWithSharedCookieStoreButNonSharedTimerShouldReScheduleCookieEvictionWhenFirstInstanceGetClosed() throws IOException {
76+
CookieStore cookieStore = new ThreadSafeCookieStore();
77+
Timer nettyTimerMock1 = mock(Timer.class);
78+
AsyncHttpClientConfig config1 = config()
79+
.setCookieStore(cookieStore).setNettyTimer(nettyTimerMock1).build();
80+
81+
try (AsyncHttpClient client1 = asyncHttpClient(config1)) {
82+
assertEquals(config1.getCookieStore().count(), 1);
83+
verify(nettyTimerMock1, times(1)).newTimeout(any(CookieEvictionTask.class), anyLong(), any(TimeUnit.class));
84+
}
85+
86+
assertEquals(config1.getCookieStore().count(), 0);
87+
88+
Timer nettyTimerMock2 = mock(Timer.class);
89+
AsyncHttpClientConfig config2 = config()
90+
.setCookieStore(cookieStore).setNettyTimer(nettyTimerMock2).build();
91+
92+
try (AsyncHttpClient client2 = asyncHttpClient(config2)) {
93+
assertEquals(config1.getCookieStore().count(), 1);
94+
verify(nettyTimerMock2, times(1)).newTimeout(any(CookieEvictionTask.class), anyLong(), any(TimeUnit.class));
95+
}
7096
}
7197

72-
private static void closeSilently(Closeable closeable) {
73-
if (closeable != null) {
74-
try {
75-
closeable.close();
76-
} catch (IOException e) {
77-
// swallow
78-
}
98+
@Test
99+
public void testDisablingCookieStore() throws IOException {
100+
AsyncHttpClientConfig config = config()
101+
.setCookieStore(null).build();
102+
try (AsyncHttpClient client = asyncHttpClient(config)) {
103+
//
79104
}
80105
}
81106
}

0 commit comments

Comments
 (0)