Skip to content

Commit

Permalink
Cookiejar optimization, close #1580 (#1708)
Browse files Browse the repository at this point in the history
Changes:

Segmented map based on domain names. So instead of traversing all the domains it traverses the domains that are of interest.
Use NettyTimer to clean up the expired cookies asynchronously. The timer task that provides this functionality is CookieEvictionTask.
  • Loading branch information
sivaalli authored Apr 3, 2020
1 parent 7a1a190 commit d4f1e58
Show file tree
Hide file tree
Showing 12 changed files with 326 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,13 @@ public interface AsyncHttpClientConfig {
*/
CookieStore getCookieStore();

/**
* Return the delay in milliseconds to evict expired cookies from {@linkplain CookieStore}
*
* @return the delay in milliseconds to evict expired cookies from {@linkplain CookieStore}
*/
int expiredCookieEvictionDelay();

/**
* Return the number of time the library will retry when an {@link java.io.IOException} is throw by the remote server
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import io.netty.util.Timer;
import io.netty.util.concurrent.DefaultThreadFactory;
import org.asynchttpclient.channel.ChannelPool;
import org.asynchttpclient.cookie.CookieEvictionTask;
import org.asynchttpclient.filter.FilterContext;
import org.asynchttpclient.filter.FilterException;
import org.asynchttpclient.filter.RequestFilter;
Expand Down Expand Up @@ -90,6 +91,22 @@ public DefaultAsyncHttpClient(AsyncHttpClientConfig config) {
channelManager = new ChannelManager(config, nettyTimer);
requestSender = new NettyRequestSender(config, channelManager, nettyTimer, new AsyncHttpClientState(closed));
channelManager.configureBootstraps(requestSender);
boolean scheduleCookieEviction = false;

final int cookieStoreCount = config.getCookieStore().incrementAndGet();
if (!allowStopNettyTimer) {
if (cookieStoreCount == 1) {
// If this is the first AHC instance for the shared (user-provided) netty timer.
scheduleCookieEviction = true;
}
} else {
// If Timer is not shared.
scheduleCookieEviction = true;
}
if (scheduleCookieEviction) {
nettyTimer.newTimeout(new CookieEvictionTask(config.expiredCookieEvictionDelay(), config.getCookieStore()),
config.expiredCookieEvictionDelay(), TimeUnit.MILLISECONDS);
}
}

private Timer newNettyTimer(AsyncHttpClientConfig config) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ public class DefaultAsyncHttpClientConfig implements AsyncHttpClientConfig {

// cookie store
private final CookieStore cookieStore;
private final int expiredCookieEvictionDelay;

// internals
private final String threadPoolName;
Expand Down Expand Up @@ -192,6 +193,7 @@ private DefaultAsyncHttpClientConfig(// http

// cookie store
CookieStore cookieStore,
int expiredCookieEvictionDelay,

// tuning
boolean tcpNoDelay,
Expand Down Expand Up @@ -283,6 +285,7 @@ private DefaultAsyncHttpClientConfig(// http

// cookie store
this.cookieStore = cookieStore;
this.expiredCookieEvictionDelay = expiredCookieEvictionDelay;

// tuning
this.tcpNoDelay = tcpNoDelay;
Expand Down Expand Up @@ -558,6 +561,11 @@ public CookieStore getCookieStore() {
return cookieStore;
}

@Override
public int expiredCookieEvictionDelay() {
return expiredCookieEvictionDelay;
}

// tuning
@Override
public boolean isTcpNoDelay() {
Expand Down Expand Up @@ -746,6 +754,7 @@ public static class Builder {

// cookie store
private CookieStore cookieStore = new ThreadSafeCookieStore();
private int expiredCookieEvictionDelay = defaultExpiredCookieEvictionDelay();

// tuning
private boolean tcpNoDelay = defaultTcpNoDelay();
Expand Down Expand Up @@ -1146,6 +1155,11 @@ public Builder setCookieStore(CookieStore cookieStore) {
return this;
}

public Builder setExpiredCookieEvictionDelay(int expiredCookieEvictionDelay) {
this.expiredCookieEvictionDelay = expiredCookieEvictionDelay;
return this;
}

// tuning
public Builder setTcpNoDelay(boolean tcpNoDelay) {
this.tcpNoDelay = tcpNoDelay;
Expand Down Expand Up @@ -1330,6 +1344,7 @@ public DefaultAsyncHttpClientConfig build() {
responseFilters.isEmpty() ? Collections.emptyList() : Collections.unmodifiableList(responseFilters),
ioExceptionFilters.isEmpty() ? Collections.emptyList() : Collections.unmodifiableList(ioExceptionFilters),
cookieStore,
expiredCookieEvictionDelay,
tcpNoDelay,
soReuseAddress,
soKeepAlive,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ public final class AsyncHttpClientConfigDefaults {
public static final String IO_THREADS_COUNT_CONFIG = "ioThreadsCount";
public static final String HASHED_WHEEL_TIMER_TICK_DURATION = "hashedWheelTimerTickDuration";
public static final String HASHED_WHEEL_TIMER_SIZE = "hashedWheelTimerSize";
public static final String EXPIRED_COOKIE_EVICTION_DELAY = "expiredCookieEvictionDelay";

public static final String AHC_VERSION;

Expand Down Expand Up @@ -304,4 +305,8 @@ public static int defaultHashedWheelTimerTickDuration() {
public static int defaultHashedWheelTimerSize() {
return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInt(ASYNC_CLIENT_CONFIG_ROOT + HASHED_WHEEL_TIMER_SIZE);
}

public static int defaultExpiredCookieEvictionDelay() {
return AsyncHttpClientConfigHelper.getAsyncHttpClientConfig().getInt(ASYNC_CLIENT_CONFIG_ROOT + EXPIRED_COOKIE_EVICTION_DELAY);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.asynchttpclient.cookie;

import java.util.concurrent.TimeUnit;

import org.asynchttpclient.AsyncHttpClientConfig;

import io.netty.util.Timeout;
import io.netty.util.TimerTask;

/**
* Evicts expired cookies from the {@linkplain CookieStore} periodically.
* The default delay is 30 seconds. You may override the default using
* {@linkplain AsyncHttpClientConfig#expiredCookieEvictionDelay()}.
*/
public class CookieEvictionTask implements TimerTask {

private final long evictDelayInMs;
private final CookieStore cookieStore;

public CookieEvictionTask(long evictDelayInMs, CookieStore cookieStore) {
this.evictDelayInMs = evictDelayInMs;
this.cookieStore = cookieStore;
}

@Override
public void run(Timeout timeout) throws Exception {
cookieStore.evictExpired();
timeout.timer().newTimeout(this, evictDelayInMs, TimeUnit.MILLISECONDS);
}
}
10 changes: 8 additions & 2 deletions client/src/main/java/org/asynchttpclient/cookie/CookieStore.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import io.netty.handler.codec.http.cookie.Cookie;
import org.asynchttpclient.uri.Uri;
import org.asynchttpclient.util.Counted;

import java.net.CookieManager;
import java.util.List;
Expand All @@ -31,10 +32,10 @@
*
* @since 2.1
*/
public interface CookieStore {
public interface CookieStore extends Counted {
/**
* Adds one {@link Cookie} to the store. This is called for every incoming HTTP response.
* If the given cookie has already expired it will not be added, but existing values will still be removed.
* If the given cookie has already expired it will not be added.
*
* <p>A cookie to store may or may not be associated with an URI. If it
* is not associated with an URI, the cookie's domain and path attribute
Expand Down Expand Up @@ -82,4 +83,9 @@ public interface CookieStore {
* @return true if any cookies were purged.
*/
boolean clear();

/**
* Evicts all the cookies that expired as of the time this method is run.
*/
void evictExpired();
}
Loading

0 comments on commit d4f1e58

Please sign in to comment.