You've already forked Arcturus-Morningstar-Extended
mirror of
https://github.com/duckietm/Arcturus-Morningstar-Extended.git
synced 2026-06-19 15:06:19 +00:00
perf: run auth HTTP endpoints off the Netty event loop (P1)
The /api/auth/* handlers ran inline on the Netty worker event loop, so their blocking work — BCrypt (cost 12 ~tens of ms), JDBC, the Turnstile HTTPS round-trip and SMTP — stalled every other client multiplexed on that thread; a burst of logins/registers could freeze game traffic. Dispatch each auth request to a dedicated bounded pool (4..16 daemon threads, bounded queue, 503 on saturation) instead. It is deliberately SEPARATE from the shared game ThreadPooling so auth load can't starve room cycles either. Netty writes are thread-safe, so the endpoints' sendJson calls work unchanged from the worker; the FullHttpRequest is released when the task finishes. Caveat: this allows concurrent handling of pipelined requests on a single keep-alive connection (out-of-order responses) — not a concern for the Nitro client which is strictly request/response, but worth load-testing before prod.
This commit is contained in:
+49
-3
@@ -9,8 +9,15 @@ import io.netty.handler.codec.http.HttpMethod;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import io.netty.handler.codec.http.QueryStringDecoder;
|
||||
import io.netty.util.ReferenceCountUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static com.eu.habbo.networking.gameserver.auth.AuthHttpUtil.MAX_BODY_BYTES;
|
||||
import static com.eu.habbo.networking.gameserver.auth.AuthHttpUtil.errorPayload;
|
||||
@@ -21,6 +28,25 @@ import static com.eu.habbo.networking.gameserver.auth.AuthHttpUtil.sendJson;
|
||||
|
||||
public class AuthHttpHandler extends ChannelInboundHandlerAdapter {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(AuthHttpHandler.class);
|
||||
|
||||
// Dedicated, bounded pool for the auth endpoints. Their work blocks on
|
||||
// BCrypt, JDBC, the Turnstile HTTPS round-trip and SMTP — running that on the
|
||||
// Netty event loop stalls every client on the same worker. A SEPARATE pool
|
||||
// (not the shared game ThreadPooling) also keeps it from starving room cycles.
|
||||
private static final ThreadPoolExecutor AUTH_EXECUTOR = new ThreadPoolExecutor(
|
||||
4, 16, 60L, TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<>(512),
|
||||
new java.util.concurrent.ThreadFactory() {
|
||||
private final AtomicInteger counter = new AtomicInteger(1);
|
||||
@Override
|
||||
public Thread newThread(Runnable r) {
|
||||
Thread t = new Thread(r, "auth-http-worker-" + counter.getAndIncrement());
|
||||
t.setDaemon(true);
|
||||
return t;
|
||||
}
|
||||
});
|
||||
|
||||
static final String LOGIN_PATH = "/api/auth/login";
|
||||
static final String REGISTER_PATH = "/api/auth/register";
|
||||
static final String FORGOT_PATH = "/api/auth/forgot-password";
|
||||
@@ -52,10 +78,30 @@ public class AuthHttpHandler extends ChannelInboundHandlerAdapter {
|
||||
return;
|
||||
}
|
||||
|
||||
// Offload the (potentially blocking) auth work off the event loop. Netty
|
||||
// writes are thread-safe, so the endpoints' sendJson/writeAndFlush calls
|
||||
// are fine from the worker; the request is released once the work ends.
|
||||
try {
|
||||
handle(ctx, req, path);
|
||||
} finally {
|
||||
ReferenceCountUtil.release(req);
|
||||
AUTH_EXECUTOR.execute(() -> {
|
||||
try {
|
||||
handle(ctx, req, path);
|
||||
} catch (Throwable t) {
|
||||
LOGGER.error("Auth handler failed for {}", path, t);
|
||||
try {
|
||||
sendJson(ctx, req, HttpResponseStatus.INTERNAL_SERVER_ERROR, errorPayload("Internal error."));
|
||||
} catch (Throwable ignored) {
|
||||
// response may already be partially written — nothing else to do
|
||||
}
|
||||
} finally {
|
||||
ReferenceCountUtil.release(req);
|
||||
}
|
||||
});
|
||||
} catch (RejectedExecutionException rejected) {
|
||||
try {
|
||||
sendJson(ctx, req, HttpResponseStatus.SERVICE_UNAVAILABLE, errorPayload("Server busy, try again shortly."));
|
||||
} finally {
|
||||
ReferenceCountUtil.release(req);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user