Linton Miller created HTTPCORE-589:
--------------------------------------
Summary: LaxConnPool may not allocate available connection
Key: HTTPCORE-589
URL: https://issues.apache.org/jira/browse/HTTPCORE-589
Project: HttpComponents HttpCore
Issue Type: Bug
Components: HttpCore
Affects Versions: 5.0-beta8
Reporter: Linton Miller
The LaxConnPool can fail to allocate available connections from the pool in
rare circumstances.
The issue happens when 2 threads execute concurrently; one requesting a lease
while another releases a pool entry.
Thread 1
future = lease
getAvailableEntry = null
allocatePoolEntry = false
<about to add a LeaseRequest to pending queue>
Thread 2
release
removeLeased
available.add
servicePendingRequest
while (pending.poll() != null) does nothing because pending queue is empty
<release call completed, with an entry now in the available list>
Thread 1
pending.add(new LeaseRequest())
<lease call completed, with an unsatisfied future>
future.get()
And now Thread 1 is blocked until there is another release call, even though
there is actually an available connection in the pool.
The problem arises because there is a window between checking for an available
pool entry and registering on the pending request queue. In that window, the
statte of the pool can change.
Recreating this problem is test is extremely difficult, because the window for
failure is really very small. I've only be able to observe the error in running
code by introducing synchronization or delay (as small as 3ms, but still
needed) into the pool code just before the lease pending.add, so as to ensure
the necessary timing condition between threads exist. My test class:
{code:java}
import org.apache.hc.core5.io.ModalCloseable;
import org.apache.hc.core5.io.CloseMode;
import org.apache.hc.core5.pool.PoolEntry;
import org.apache.hc.core5.pool.LaxConnPool;
import org.apache.hc.core5.pool.PoolEntry;
import org.apache.hc.core5.util.TimeValue;
import java.util.concurrent.Future;
public class PoolPendTest {
private static class TestConn implements ModalCloseable {
public void close() {
}
public void close(CloseMode mode) {
}
}
private static LaxConnPool<String,TestConn> pool;
private static void dbg(String msg) {
System.out.println(Thread.currentThread().getName()+":
"+System.currentTimeMillis()+": "+msg);
}
public static void main(String[] args) {
final int poolSize = 1;
pool = new FixPendPool<>(poolSize);
// Create a pool entry purely to prime class loading
new PoolEntry<>("", TimeValue.NEG_ONE_MILLISECONDS);
for (int i=0;i<poolSize*2;i++) {
new Thread(String.format("req-%04d", i)) {
@Override
public void run() {
dbg("Started");
Future<PoolEntry<String,TestConn>> req = pool.lease("somehost",null);
PoolEntry<String,TestConn> entry = null;
if (req.isDone()) {
try {
entry = req.get();
if (!entry.hasConnection()) {
entry.assignConnection(new TestConn());
pool.release(entry, true);
dbg("Released pool entry "+System.identityHashCode(entry));
}
}
catch (Exception shouldNotHappen) {
dbg("Should not happen!");
shouldNotHappen.printStackTrace();
}
}
try {
dbg("Assigned pool entry "+System.identityHashCode(req.get()));
}
catch (Exception fail) {
dbg("Getting pool entry failed");
fail.printStackTrace();
}
}
}.start();
}
}
}
{code}
When I modify the LaxConnPool code to add a 3ms delay just before adding to the
pend queue:
{code:java}
try { Thread.sleep(3); } catch (InterruptedException onward) {}
pending.add(new LeaseRequest<>(state, requestTimeout, future));
{code}
then the test class blocks with an infinite wait where the pool contains a
returned connection, but it's never assigned to the second thread.
--
This message was sent by Atlassian JIRA
(v7.6.14#76016)
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]