-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Description
Version
Which version(s) did you encounter this bug ?
4.2.6
Context
I encountered a state in my Vertx Java application where the websocket connection requests are not reaching to the server (confirmed by TCPDump). On digging deeper, i noticed that each Vertx context has a HttpClient. Each HttpClient has 2 ConnectionManager one for Http and other for Websocket. WebsocketConnectionManager contains Endpoint map storing a WebsocketEndpoint object against a key - server. The class WebSocketEndpoint maintains 2 important properties - Waiters queue and inflightRequest. For each connection request, if inflightRequest count is less than maxPool, then the request is appended to Waiter queue otherwise inflightRequest is incremented. The inflightRequest count is only decremented after the connection is made(see second code block). Once number of inflight request crosses for a server crosses maxPool than all further requests are queued. We noticed when the maxPool number of connection request fails, the inflightRequest count is not decremented causing all further request to land onto Waiter queue. If this queue is unbounded its a never ending state. All these details are confirmed with heapdump.
public void requestConnection2(ContextInternal ctx, long timeout, Handler<AsyncResult<HttpClientConnection>> handler) {
synchronized(this) {
if (this.inflightConnections >= this.maxPoolSize) {
this.waiters.add(new Waiter(handler, ctx));
return;
}
++this.inflightConnections;
}
this.tryConnect(ctx, handler);
}
private void tryConnect(ContextInternal ctx, final Handler<AsyncResult<HttpClientConnection>> handler) {
EventLoopContext eventLoopContext;
if (ctx instanceof EventLoopContext) {
eventLoopContext = (EventLoopContext)ctx;
} else {
eventLoopContext = ctx.owner().createEventLoopContext(ctx.nettyEventLoop(), ctx.workerPool(), ctx.classLoader());
}
class Listener implements Handler<AsyncResult<HttpClientConnection>> {
Listener() {
}
private void onEvict() {
WebSocketEndpoint.this.decRefCount();
Waiter h;
synchronized(WebSocketEndpoint.this) {
if (--WebSocketEndpoint.this.inflightConnections > WebSocketEndpoint.this.maxPoolSize || WebSocketEndpoint.this.waiters.isEmpty()) {
return;
}
h = (Waiter)WebSocketEndpoint.this.waiters.poll();
}
WebSocketEndpoint.this.tryConnect(h.context, h.handler);
}
public void handle(AsyncResult<HttpClientConnection> ar) {
if (ar.succeeded()) {
HttpClientConnection c = (HttpClientConnection)ar.result();
if (WebSocketEndpoint.this.incRefCount()) {
c.evictionHandler((v) -> {
this.onEvict();
});
handler.handle(Future.succeededFuture(c));
} else {
c.close();
handler.handle(Future.failedFuture("Connection closed"));
}
} else {
handler.handle(Future.failedFuture(ar.cause()));
}
}
}
this.connector.httpConnect(eventLoopContext, new Listener());
}
Do you have a reproducer?
Sorry, this code is part of my repository for which i have not created a reproducer yet. I would like to take guidance on current problem.
Extra
- JDK17