Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

max usage count fixes #5743

Merged
merged 2 commits into from
Dec 1, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 52 additions & 8 deletions jetty-util/src/main/java/org/eclipse/jetty/util/Pool.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.component.DumpableCollection;
Expand Down Expand Up @@ -166,16 +167,42 @@ public final void setMaxMultiplex(int maxMultiplex)
this.maxMultiplex = maxMultiplex;
}

/**
* Get the maximum number of times the entries of the pool
* can be acquired.
* @return the max usage count.
*/
public int getMaxUsageCount()
{
return maxUsageCount;
}

/**
* Change the max usage count of the pool's entries. All existing
* idle entries over this new max usage are removed and closed.
* @param maxUsageCount the max usage count.
*/
public final void setMaxUsageCount(int maxUsageCount)
{
if (maxUsageCount == 0)
throw new IllegalArgumentException("Max usage count must be != 0");
this.maxUsageCount = maxUsageCount;

// Iterate the entries, remove overused ones and collect a list of the closeable removed ones.
List<Closeable> copy;
try (Locker.Lock l = locker.lock())
{
if (closed)
return;

copy = entries.stream()
.filter(entry -> entry.isIdleAndOverUsed() && remove(entry) && entry.pooled instanceof Closeable)
.map(entry -> (Closeable)entry.pooled)
.collect(Collectors.toList());
}

// Iterate the copy and close the collected entries.
copy.forEach(IO::close);
}

/**
Expand Down Expand Up @@ -449,6 +476,12 @@ public class Entry
this.state = new AtomicBiInteger(Integer.MIN_VALUE, 0);
}

// for testing only
void setUsageCount(int usageCount)
{
this.state.getAndSetHi(usageCount);
}

/** Enable a reserved entry {@link Entry}.
* An entry returned from the {@link #reserve(int)} method must be enabled with this method,
* once and only once, before it is usable by the pool.
Expand Down Expand Up @@ -527,7 +560,9 @@ boolean tryAcquire()
if (closed || multiplexingCount >= maxMultiplex || (currentMaxUsageCount > 0 && usageCount >= currentMaxUsageCount))
return false;

if (state.compareAndSet(encoded, usageCount + 1, multiplexingCount + 1))
// Prevent overflowing the usage counter by capping it at Integer.MAX_VALUE.
int newUsageCount = usageCount == Integer.MAX_VALUE ? Integer.MAX_VALUE : usageCount + 1;
if (state.compareAndSet(encoded, newUsageCount, multiplexingCount + 1))
return true;
}
}
Expand Down Expand Up @@ -563,13 +598,6 @@ boolean tryRelease()
return !(overUsed && newMultiplexingCount == 0);
}

public boolean isOverUsed()
{
int currentMaxUsageCount = maxUsageCount;
int usageCount = state.getHi();
return currentMaxUsageCount > 0 && usageCount >= currentMaxUsageCount;
}

/**
* Try to mark the entry as removed.
* @return true if the entry has to be removed from the containing pool, false otherwise.
Expand Down Expand Up @@ -610,6 +638,22 @@ public boolean isInUse()
return AtomicBiInteger.getHi(encoded) >= 0 && AtomicBiInteger.getLo(encoded) > 0;
}

public boolean isOverUsed()
{
int currentMaxUsageCount = maxUsageCount;
int usageCount = state.getHi();
return currentMaxUsageCount > 0 && usageCount >= currentMaxUsageCount;
}

boolean isIdleAndOverUsed()
{
int currentMaxUsageCount = maxUsageCount;
long encoded = state.get();
int usageCount = AtomicBiInteger.getHi(encoded);
int multiplexCount = AtomicBiInteger.getLo(encoded);
return currentMaxUsageCount > 0 && usageCount >= currentMaxUsageCount && multiplexCount == 0;
}

public int getUsageCount()
{
return Math.max(state.getHi(), 0);
Expand Down
36 changes: 36 additions & 0 deletions jetty-util/src/test/java/org/eclipse/jetty/util/PoolTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,42 @@ public void testUsageCountAfterReachingMaxMultiplexLimit(Factory factory)
assertThat(e1.getUsageCount(), is(2));
}

@ParameterizedTest
@MethodSource(value = "strategy")
public void testDynamicMaxUsageCountChangeOverflowMaxInt(Factory factory)
{
Pool<String> pool = factory.getPool(1);
Pool<String>.Entry entry = pool.reserve(-1);
entry.enable("aaa", false);
entry.setUsageCount(Integer.MAX_VALUE);

Pool<String>.Entry acquired1 = pool.acquire();
assertThat(acquired1, notNullValue());
assertThat(pool.release(acquired1), is(true));

pool.setMaxUsageCount(1);
Pool<String>.Entry acquired2 = pool.acquire();
assertThat(acquired2, nullValue());
}

@ParameterizedTest
@MethodSource(value = "strategy")
public void testDynamicMaxUsageCountChangeSweep(Factory factory)
{
Pool<String> pool = factory.getPool(2);
Pool<String>.Entry entry1 = pool.reserve(-1);
entry1.enable("aaa", false);
Pool<String>.Entry entry2 = pool.reserve(-1);
entry2.enable("bbb", false);

Pool<String>.Entry acquired1 = pool.acquire();
assertThat(acquired1, notNullValue());
assertThat(pool.release(acquired1), is(true));

pool.setMaxUsageCount(1);
assertThat(pool.size(), is(1));
}

@Test
public void testConfigLimits()
{
Expand Down