morningman opened a new pull request, #68266:
URL: https://github.com/apache/doris/pull/68266

   ### What problem does this PR solve?
   
   Issue Number: #67577
   
   Related PR: #68101 (the one connection pool this builds on), #67966 (the 
Flight session teardown contract this keeps, D-13)
   
   Problem Summary:
   
   Stage 3, item 2 of #67577 (connection governance): the bearer token of an 
Arrow Flight SQL session is the credential of exactly one session and lives 
exactly as long as it.
   
   A bearer token used to be an object of its own next to the session. It was 
issued at the handshake into a Guava cache — LRU, sized to the Flight 
sub-quota, expiring 24 hours after write 
(`arrow_flight_token_alive_time_second`) — plus a per-user LRU of 
`max_user_connections / 2` tokens, while the session (`ConnectContext`) was 
created on the token's first request and registered in the connection pool. The 
two coupled only one way: a token leaving the cache unregistered its session, a 
session leaving the pool left its token behind. What that produced:
   
   - A full Flight sub-quota showed as the eviction of the least recently used 
token — someone else's live session with it, its transaction rolled back, its 
deferred query finalized — rather than as the pool's refusal, which was in 
practice unreachable for the sub-quota (the cache is sized to it, and Guava 
evicts per segment before the total is reached). A second, undocumented 
per-user limit of `max_user_connections / 2` tokens evicted the same way.
   - An active session was cut off 24 hours after its handshake, however busy 
it was: the expiry counted from the write, not from use.
   - After `KILL CONNECTION` or `wait_timeout` the token stayed in the cache, 
so the session's next request first tripped the created-session guard 
(`INTERNAL: UserSession expire after access`) and only the one after that was 
`UNAUTHENTICATED`.
   - Session creation needed a frontend-wide lock and a re-validation of the 
token after registration to close the race between the two owners (#68101 left 
a note that the token's lifecycle would move under one owner in this PR).
   
   Now the token is nothing but the peer identity a session is registered in 
the pool under, and that index is the only place a token exists.
   
   - `FlightSessionsManager` has three operations: `openSession` — at the 
handshake that authenticated the password (`FlightCredentialValidator`): mint 
the token, build the `ConnectContext`, register it in the pool; the pool's 
refusal is the handshake's `RESOURCE_EXHAUSTED`, in the words a MySQL client is 
refused in, and nothing is left of a refused session, no token either — 
`getConnectContext` — the pool's lookup by peer identity; none → 
`UNAUTHENTICATED`, used by the header authenticator for every call and by the 
producer to resolve a call's session — and `closeConnectContext` — 
`CloseSession`: what `COM_QUIT` does for a MySQL connection 
(`ConnectContext.cleanup`: the channel's cached results, the deferred 
executors, the transaction, the temporary tables, the pool's slots), plus 
cancelling a statement still running on another thread, since a Flight 
session's commands need not run on the thread that closes it.
   - `FlightSessionsInConnectPool` is the one implementation, serving both the 
header authenticator and the producer. The `tokens` package 
(`FlightTokenManager`, `FlightTokenManagerImpl`, `FlightTokenDetails`), both 
caches, the cleanup thread, the created-session flag and the creation lock with 
its re-validation are deleted. `FlightTokenDetails.expiresAt`, computed with 
minutes for seconds so that the in-code expiry check never fired, goes with the 
class.
   - A session ends with `CloseSession`, `KILL CONNECTION` or `wait_timeout` 
(or the frontend restarting), and from that moment the very next call under its 
token is `UNAUTHENTICATED`, whatever kind of call it is. Nothing else ends a 
session: no session that fits is evicted for one that does not (the pool's 
quotas are the only admission control, as decided for #67577 D-3), and there is 
no idle timeout of Flight's own — `wait_timeout`, which a Flight client can set 
through its session options since #67966, governs both protocols.
   - `arrow_flight_token_cache_size` and `arrow_flight_token_alive_time_second` 
are deprecated no-ops (`@Deprecated`, the way `enable_query_hive_views` is), 
kept for one release so a fe.conf that sets them still parses; a value other 
than the default is reported at startup by name.
   - `arrow_flight_max_connections`' description is rewritten around how a 
session now ends. `ConnectPoolMgr`, `FlightProtocolAdapter` and the producer's 
comments no longer name token expiry or eviction as teardown paths.
   
   Two things that did not change, stated so nobody looks for them: 
`getStreamStatement` still resolves the executing session from the ticket's 
peer identity rather than the call's token (a client that opens a second 
connection for an endpoint may authenticate again on it), and the 
deferred-query contract of #67966 is untouched — teardown still does not wait 
for a running command.
   
   One known edge: a client that authenticates again for each connection it 
opens to fetch a result opens a session each time, which stays until 
`wait_timeout` (before this PR it left a token in the cache instead). The 
Flight SQL JDBC driver did that before 15.0.0; 15.0.0 and later reuse the 
bearer token for the endpoint connections they open, and the ADBC drivers pass 
the token along too. The config description says so.
   
   ### Release note
   
   The bearer token of an Arrow Flight SQL session is now valid exactly as long 
as the session it was issued with: the session opens, and the token is issued, 
at the handshake that authenticates the user (`authenticateBasicToken`), and 
the session ends with `CloseSession`, a `KILL CONNECTION` from another 
connection, or `wait_timeout`. From that moment every call made with the token 
is refused as `UNAUTHENTICATED`, with a message naming the token by its masked 
id and the ways the session may have ended. A session is no longer ended by 
anything else: not by a 24-hour token expiry (an active session is never cut 
off any more), and not by the eviction of a token from a cache when another 
session is opened — a Flight session that does not fit `qe_max_connection`, 
`arrow_flight_max_connections` or the user's `max_user_connections` is refused 
at its handshake, with the same `Reach limit of connections ...` message as a 
MySQL connection, and no session already open is evicted for it. The 
 per-user cap of `max_user_connections / 2` bearer tokens is gone with the 
cache: a user may hold up to `max_user_connections` connections over both 
protocols, as documented.
   
   `arrow_flight_token_cache_size` and `arrow_flight_token_alive_time_second` 
are deprecated and no longer read; a fe.conf that sets them still starts, with 
a warning naming the setting, and they will be removed in a later release. 
Sessions whose clients have gone without `CloseSession` (the ADBC drivers send 
it; the Flight SQL JDBC driver only for a connection opened with a `catalog`) 
stay in the pool until `wait_timeout` (8 hours by default), which is the one 
knob for reclaiming them sooner: lower it globally, per user, or for the 
session (Flight clients can set it through their session options). 
`CloseSession` now also drops the session's temporary tables and cancels a 
statement of the session that is still running, as a MySQL client's disconnect 
does.
   
   ### Check List (For Author)
   
   - Test
       - [x] Regression test
       - [x] Unit Test
       - [ ] Manual test (add detailed scripts or steps below)
   
     **Unit tests.** `FlightSessionsInConnectPoolTest` (new): opening a session 
registers it under the token it issues (one token per session, each naming only 
its own); a token no session is registered under is `UNAUTHENTICATED`, named by 
its masked id and never by the token itself; `CloseSession`, `KILL CONNECTION` 
and `wait_timeout` (through the pool's timeout checker) each end the session 
and with it the token, `CloseSession` cancelling a statement still running and 
being harmless on a session already gone; a session that does not fit is 
refused with the pool's own words and leaves nothing, the session already open 
untouched; tokens are 130 random bits in base 32. `ConnectionExceedTest` 
reworked around `openSession` (the refusal is the handshake's 
`RESOURCE_EXHAUSTED` with the MySQL wording; the lazy-creation race test is 
gone with lazy creation). `DorisFlightSqlServiceTest`: the startup report names 
only the deprecated settings that differ from their defaults, which are the conf
 ig fields' own. `DorisFlightSqlProducerTest`, `FlightProtocolAdapterTest`, 
`FlightSessionClientIpTest`, `FlightSqlDeferredQueryIdleTimeoutTest`, 
`FlightSessionOptionsTest`, `ConnectPoolMgrTest`, `TokenMaskerTest` green. 
Protocol goldens `MysqlPacketGoldenTest` (33 cases) / `FlightResultGoldenTest` 
(9 statements) unchanged.
   
     **Regression.** `arrow_flight_sql_p0/test_connection_quota`: 
`max_user_connections` back to 1 (it was 4 to dodge the per-user token cap): 
the first Flight session is the user's one connection from its handshake on and 
answers a statement; the second is refused at its handshake in MySQL's words, 
leaving the first untouched; a MySQL connection is refused in the same words; 
`CloseSession` frees the slot (the closed session's token is `UNAUTHENTICATED` 
at once), a MySQL connection takes it and a Flight session is refused for it, 
and once that connection is closed a Flight session opens again. 
`arrow_flight_sql_p0/test_bearer_token_lifecycle` (new): `KILL CONNECTION` from 
the control connection, `wait_timeout` (the session sets its own to 3 seconds) 
and `CloseSession` each make the very next call under the token 
`UNAUTHENTICATED` — a statement, a metadata request and a session action alike, 
with the new message — and a later session of the same user does not revive an 
ended token
 . Whole `arrow_flight_sql_p0` green locally: 12 suites, 0 failed, against an 
FE built from this branch. With the Python ADBC driver against that FE: one 
connection is one session from the handshake on across FE-side (`SHOW`, `SET`) 
and BE-side (`SELECT`) statements, `KILL CONNECTION` makes the driver's next 
statement fail `UNAUTHENTICATED`, and `Connection.close()` ends the session.
   
   - Behavior changed:
       - [x] Yes. See the release note: refusal at the handshake instead of at 
the first request; no token expiry and no eviction of a session for another; no 
per-user cap of `max_user_connections / 2` tokens; `UNAUTHENTICATED` 
immediately after `KILL CONNECTION` / `wait_timeout` (it was `INTERNAL` once, 
then `UNAUTHENTICATED`); the two settings deprecated; `CloseSession` drops 
temporary tables and cancels a running statement.
   
   - Does this need documentation?
       - [x] Yes. Docs PR to apache/doris-website to follow (together with 
#68101's, not opened yet): the Arrow Flight SQL page and the FE config page — 
how a session ends and what the token's validity is, the refusal at the 
handshake, the two deprecated settings, `wait_timeout` as the knob for 
abandoned sessions.
   
   ### Check List (For Reviewer who merge this PR)
   
   - [ ] Confirm the release note
   - [ ] Confirm test cases
   - [ ] Confirm document
   - [ ] Add branch pick label <!-- Add branch pick label that this PR should 
merge into -->
   


-- 
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]

Reply via email to