HTTPCLIENT-2284: internal cache storage improvements (#478)

This commit is contained in:
Oleg Kalnichevski 2023-08-27 13:53:58 +02:00
parent 8466b19861
commit 893574e092
5 changed files with 197 additions and 54 deletions

View File

@ -52,13 +52,13 @@ import org.apache.hc.core5.util.Args;
@Contract(threading = ThreadingBehavior.SAFE)
public class BasicHttpCacheStorage implements HttpCacheStorage {
private final CacheMap entries;
private final InternalCacheStorage entries;
private final ReentrantLock lock;
public BasicHttpCacheStorage(final CacheConfig config) {
super();
this.entries = new CacheMap(config.getMaxCacheEntries());
this.entries = new InternalCacheStorage(config.getMaxCacheEntries(), null);
this.lock = new ReentrantLock();
}

View File

@ -1,50 +0,0 @@
/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package org.apache.hc.client5.http.impl.cache;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.hc.client5.http.cache.HttpCacheEntry;
final class CacheMap extends LinkedHashMap<String, HttpCacheEntry> {
private static final long serialVersionUID = -7750025207539768511L;
private final int maxEntries;
CacheMap(final int maxEntries) {
super(20, 0.75f, true);
this.maxEntries = maxEntries;
}
@Override
protected boolean removeEldestEntry(final Map.Entry<String, HttpCacheEntry> eldest) {
return size() > this.maxEntries;
}
}

View File

@ -0,0 +1,96 @@
/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package org.apache.hc.client5.http.impl.cache;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.function.Consumer;
import org.apache.hc.client5.http.cache.HttpCacheEntry;
import org.apache.hc.client5.http.cache.HttpCacheStorageEntry;
import org.apache.hc.core5.annotation.Internal;
@Internal
final public class InternalCacheStorage {
private final Map<String, HttpCacheEntry> map;
private final Queue<HttpCacheStorageEntry> evictionQueue;
private final Consumer<HttpCacheStorageEntry> evictionCallback;
public InternalCacheStorage(final int maxEntries, final Consumer<HttpCacheStorageEntry> evictionCallback) {
this.evictionCallback = evictionCallback;
this.map = new LinkedHashMap<String, HttpCacheEntry>(20, 0.75f, true) {
@Override
protected boolean removeEldestEntry(final Map.Entry<String, HttpCacheEntry> eldest) {
if (size() > maxEntries) {
if (evictionCallback != null) {
evictionQueue.add(new HttpCacheStorageEntry(eldest.getKey(), eldest.getValue()));
}
return true;
} else {
return false;
}
}
};
this.evictionQueue = new LinkedList<>();
}
public InternalCacheStorage(final int maxEntries) {
this(maxEntries, null);
}
public InternalCacheStorage() {
this(Integer.MAX_VALUE, null);
}
public void put(final String key, final HttpCacheEntry entry) {
map.put(key, entry);
HttpCacheStorageEntry evicted;
while ((evicted = evictionQueue.poll()) != null) {
if (evictionCallback != null) {
evictionCallback.accept(evicted);
}
}
}
public HttpCacheEntry get(final String key) {
return map.get(key);
}
public HttpCacheEntry remove(final String key) {
return map.remove(key);
}
public void clear() {
map.clear();
}
}

View File

@ -78,7 +78,7 @@ import org.apache.hc.core5.util.Args;
@Contract(threading = ThreadingBehavior.SAFE)
public class ManagedHttpCacheStorage implements HttpCacheStorage, Closeable {
private final CacheMap entries;
private final InternalCacheStorage entries;
private final ReferenceQueue<HttpCacheEntry> morque;
private final Set<ResourceReference> resources;
private final AtomicBoolean active;
@ -87,7 +87,7 @@ public class ManagedHttpCacheStorage implements HttpCacheStorage, Closeable {
public ManagedHttpCacheStorage(final CacheConfig config) {
super();
this.entries = new CacheMap(config.getMaxCacheEntries());
this.entries = new InternalCacheStorage(config.getMaxCacheEntries(), null);
this.morque = new ReferenceQueue<>();
this.resources = new HashSet<>();
this.active = new AtomicBoolean(true);

View File

@ -0,0 +1,97 @@
/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package org.apache.hc.client5.http.impl.cache;
import java.util.LinkedList;
import java.util.Queue;
import org.apache.hc.client5.http.cache.HttpCacheEntry;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
public class TestInternalCacheStorage {
@Test
public void testCacheBasics() {
final InternalCacheStorage storage = new InternalCacheStorage();
final String key1 = "some-key-1";
Assertions.assertNull(storage.get(key1));
Assertions.assertNull(storage.remove(key1));
final HttpCacheEntry entry1 = HttpTestUtils.makeCacheEntry();
storage.put(key1, entry1);
Assertions.assertSame(entry1, storage.get(key1));
Assertions.assertSame(entry1, storage.remove(key1));
Assertions.assertNull(storage.get(key1));
Assertions.assertNull(storage.remove(key1));
final String key2 = "some-key-2";
final HttpCacheEntry entry2 = HttpTestUtils.makeCacheEntry();
final String key3 = "some-key-3";
final HttpCacheEntry entry3 = HttpTestUtils.makeCacheEntry();
storage.put(key2, entry2);
storage.put(key3, entry3);
Assertions.assertSame(entry2, storage.get(key2));
Assertions.assertSame(entry3, storage.get(key3));
storage.clear();
Assertions.assertNull(storage.get(key2));
Assertions.assertNull(storage.get(key3));
}
@Test
public void testCacheEviction() {
final Queue<HttpCacheEntry> evictedEntries = new LinkedList<>();
final InternalCacheStorage storage = new InternalCacheStorage(2, e -> evictedEntries.add(e.getContent()));
final String key1 = "some-key-1";
final String key2 = "some-key-2";
final String key3 = "some-key-3";
final String key4 = "some-key-4";
final HttpCacheEntry entry1 = HttpTestUtils.makeCacheEntry();
final HttpCacheEntry entry2 = HttpTestUtils.makeCacheEntry();
final HttpCacheEntry entry3 = HttpTestUtils.makeCacheEntry();
final HttpCacheEntry entry4 = HttpTestUtils.makeCacheEntry();
storage.put(key1, entry1);
storage.put(key2, entry2);
storage.put(key3, entry3);
storage.put(key4, entry4);
Assertions.assertSame(entry1, evictedEntries.poll());
Assertions.assertSame(entry2, evictedEntries.poll());
Assertions.assertNull(evictedEntries.poll());
storage.clear();
storage.put(key1, entry1);
storage.put(key2, entry2);
storage.get(key1);
storage.put(key3, entry3);
storage.get(key1);
storage.put(key4, entry4);
Assertions.assertSame(entry2, evictedEntries.poll());
Assertions.assertSame(entry3, evictedEntries.poll());
Assertions.assertNull(evictedEntries.poll());
}
}