github-actions[bot] commented on code in PR #68101:
URL: https://github.com/apache/doris/pull/68101#discussion_r4046064594
##########
fe/fe-core/src/main/java/org/apache/doris/qe/ConnectPoolMgr.java:
##########
@@ -66,23 +109,59 @@ public void timeoutChecker(long now) {
// Return -1 means register OK
// Return >=0 means register failed, and return value is current
connection num.
public int registerConnection(ConnectContext ctx) {
+ boolean flight = isFlight(ctx);
if (numberConnection.incrementAndGet() > maxConnections) {
numberConnection.decrementAndGet();
return numberConnection.get();
}
+ if (flight && numberFlightConnection.incrementAndGet() >
flightMaxConnections) {
Review Comment:
[P1] Make shared-quota admission one atomic transition
With `maxConnections=2`, `flightMaxConnections=1`, and one Flight session, a
doomed second Flight attempt can reserve the last global slot here. A
concurrent valid MySQL attempt then increments to 3 and is refused; afterward
Flight fails its sub-quota and rolls the global count back to 1. Both calls
fail although MySQL fits in every serial ordering, so saturated Flight traffic
can consume the capacity this PR intends to reserve for MySQL. Check/reserve
the global, Flight, and user quotas in one short critical transition and add a
barrier-based test.
##########
fe/fe-core/src/main/java/org/apache/doris/qe/ConnectPoolMgr.java:
##########
@@ -66,23 +109,59 @@ public void timeoutChecker(long now) {
// Return -1 means register OK
// Return >=0 means register failed, and return value is current
connection num.
public int registerConnection(ConnectContext ctx) {
+ boolean flight = isFlight(ctx);
if (numberConnection.incrementAndGet() > maxConnections) {
numberConnection.decrementAndGet();
return numberConnection.get();
}
+ if (flight && numberFlightConnection.incrementAndGet() >
flightMaxConnections) {
+ numberFlightConnection.decrementAndGet();
+ numberConnection.decrementAndGet();
+ return numberConnection.get();
+ }
// Check user
connByUser.putIfAbsent(ctx.getQualifiedUser(), new AtomicInteger(0));
AtomicInteger conns = connByUser.get(ctx.getQualifiedUser());
if (conns.incrementAndGet() >
ctx.getEnv().getAuth().getMaxConn(ctx.getQualifiedUser())) {
conns.decrementAndGet();
+ if (flight) {
+ numberFlightConnection.decrementAndGet();
+ }
numberConnection.decrementAndGet();
return numberConnection.get();
}
connectionMap.put(ctx.getConnectionId(), ctx);
+ if (flight) {
+ peerIdentity2ConnectionId.put(ctx.getPeerIdentity(),
ctx.getConnectionId());
Review Comment:
[P1] Make token/session publication atomic
Two first RPCs can both pass the unlocked lookup/`createdSession` check, and
this put overwrites one connection id. Token eviction or CloseSession then
unregisters only the last context, leaving the other in `connectionMap` and all
three quota counters until `wait_timeout`; invalidation between `validateToken`
and this put creates the same orphan with only one request. Since those slots
now also block MySQL, commit exactly one live token-to-context owner atomically
(and roll back the loser/removal race), with latch-based tests for both
interleavings.
##########
fe/fe-core/src/main/java/org/apache/doris/arrowflight/DorisFlightSqlService.java:
##########
@@ -52,8 +53,24 @@ public DorisFlightSqlService(int port) {
BufferAllocator allocator = new RootAllocator();
// arrow flight sql is a stateless protocol, connection is usually not
actively disconnected.
// bearer token is evict from the cache will unregister ConnectContext.
- this.flightTokenManager = new FlightTokenManagerImpl(
- Math.min(Config.arrow_flight_max_connections,
Config.arrow_flight_token_cache_size),
+ int flightMaxConnections =
ConnectPoolMgr.effectiveFlightMaxConnections(
+ Config.qe_max_connection, Config.arrow_flight_max_connections);
+ if (Config.arrow_flight_max_connections > Config.qe_max_connection) {
+ // A fe.conf from before the pools were merged may still carry the
old default, 4096: capped
+ // to the whole pool, that is exactly the sharing the default of
half is there to prevent.
+ LOG.warn("arrow_flight_max_connections={} exceeds
qe_max_connection={}: Arrow Flight SQL sessions are"
+ + " connections of the one pool, so the sub-quota
is capped at {}, the whole pool."
+ + " Flight sessions, which their clients mostly
never close, can then hold every"
+ + " connection until wait_timeout and refuse MySQL
logins. On an FE that serves both"
+ + " protocols, remove the setting (the default is
half of qe_max_connection; 4096 was"
+ + " the default before the pools were merged) or
set it below qe_max_connection",
+ Config.arrow_flight_max_connections,
Config.qe_max_connection, flightMaxConnections);
+ }
+ // The token cache holds as many tokens as the sub-quota allows,
capped by
+ // arrow_flight_token_cache_size: a session opens on a token, so this
is what bounds Flight
+ // sessions in practice (the oldest token, and its session, is evicted
first).
+ int tokenCacheSize = Math.min(flightMaxConnections,
Config.arrow_flight_token_cache_size);
Review Comment:
[P2] Define zero-quota authentication before building the cache
With `qe_max_connection=1` and the new `-1` default, the effective Flight
quota is 0 (the new pool test asserts this), so this constructs a Guava
`maximumSize(0)` token cache. Every newly issued token is evicted immediately;
basic auth can return it, but the first RPC is rejected as `UNAUTHENTICATED`
before `registerConnection` can return the documented quota error, and the
removal callback runs before that user's cache is created. Handle quota 0
explicitly (or retain a token long enough to produce `RESOURCE_EXHAUSTED`) and
add an end-to-end boundary test.
##########
fe/fe-core/src/test/java/org/apache/doris/arrowflight/protocol/FlightProtocolAdapterTest.java:
##########
@@ -138,24 +145,78 @@ public void
testSessionRegistersItsTraceIdInTheFlightPool() {
ctx.setQueryId(queryId);
- Assertions.assertEquals(DebugUtil.printId(queryId),
-
scheduler.getFlightSqlConnectPoolMgr().getQueryIdByTraceId("trace-1"));
- Assertions.assertEquals("",
scheduler.getConnectPoolMgr().getQueryIdByTraceId("trace-1"));
+ Assertions.assertEquals(DebugUtil.printId(queryId),
scheduler.getQueryIdByTraceId("trace-1"));
}
@Test
- public void testKillUnregistersTheSessionFromTheFlightPool() {
+ public void testKillUnregistersTheSessionFromThePool() {
ConnectScheduler scheduler = new ConnectScheduler(10, 10);
ConnectContext ctx = flightSession();
ctx.setConnectScheduler(scheduler);
scheduler.submit(ctx);
- Assertions.assertEquals(-1,
scheduler.getFlightSqlConnectPoolMgr().registerConnection(ctx));
+ Assertions.assertEquals(-1,
scheduler.getConnectPoolMgr().registerConnection(ctx));
Assertions.assertSame(ctx,
scheduler.getContext(ctx.getConnectionId()));
+ Assertions.assertSame(ctx,
scheduler.getContextWithPeerIdentity(ctx.getPeerIdentity()));
ctx.kill(true);
Assertions.assertTrue(ctx.isKilled());
Assertions.assertNull(scheduler.getContext(ctx.getConnectionId()));
+
Assertions.assertNull(scheduler.getContextWithPeerIdentity(ctx.getPeerIdentity()));
+ }
+
+ // Every Flight session teardown path meets in the pool's
unregisterConnection, which asks the
+ // protocol to release what it holds: the channel-cached results first,
then the deferred
+ // executors (tearDown), whether or not the session was ever registered.
+ @Test
+ public void testReleaseSessionClosesTheChannelAndTearsTheSessionDown() {
+ ConnectContext ctx = flightSession();
+ FlightProtocolAdapter adapter = FlightProtocolAdapter.of(ctx);
+ StmtExecutor deferred = Mockito.mock(StmtExecutor.class);
+ ctx.addFlightSqlDeferredExecutor(deferred);
+ // A result the client never pulled: off-heap Arrow buffers the
channel holds.
+ adapter.getChannel().addOKResult("query-1", "SELECT 1");
+ Assertions.assertEquals(1, adapter.getChannel().resultNum());
+ Assertions.assertTrue(adapter.getChannel().getAllocatedMemory() > 0);
+
+ ctx.releaseProtocolSession();
+
+ // The channel's results are released with their buffers, and the
deferred query finalized.
+ Assertions.assertEquals(0, adapter.getChannel().resultNum());
+ Assertions.assertEquals(0, adapter.getChannel().getAllocatedMemory());
+ Assertions.assertTrue(ctx.getFlightSqlDeferredExecutors().isEmpty());
+ // Idempotent: the second release finds nothing to do, throws nothing,
and finalizes nothing twice.
+ Assertions.assertDoesNotThrow(ctx::releaseProtocolSession);
+ Mockito.verify(deferred, Mockito.times(1)).finalizeArrowFlightQuery();
+ }
+
+ // The channel close can fail - the allocator refuses to close over a
buffer still referenced
+ // elsewhere - and the session is torn down all the same: the deferred
query is finalized and
+ // the session takes no further command.
+ @Test
+ public void
testReleaseSessionTearsTheSessionDownEvenWhenTheChannelCloseFails() {
+ ConnectContext ctx = flightSession();
+ FlightProtocolAdapter adapter = FlightProtocolAdapter.of(ctx);
+ StmtExecutor deferred = Mockito.mock(StmtExecutor.class);
+ ctx.addFlightSqlDeferredExecutor(deferred);
+ adapter.getChannel().addOKResult("query-1", "SELECT 1");
+ // A buffer of the cached result held by someone else, as a DoGet
still streaming it would:
+ // closing the result does not free it, and the allocator's close
throws over it. The
+ // reference is never given back - the failure is the point of the
test.
+ ArrowBuf held =
adapter.getChannel().getResult("query-1").getVectorSchemaRoot().getVector(0)
+ .getDataBuffer();
+ held.getReferenceManager().retain();
Review Comment:
[P2] Release the retained Arrow buffer after this assertion
This `retain()` creates the extra native reference needed to make
`allocator.close()` fail, but the test never closes `held`. After the cached
`VectorSchemaRoot` is invalidated, that retained reference is the remaining
owner, and `releaseSession` intentionally swallows the allocator-close failure,
so the native allocation survives the test. Keep the failure assertions, then
release `held` in a `finally` block so the rest of the FE test JVM is not
contaminated.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]