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

   ### What problem does this PR solve?
   
   Issue Number: None
   
   Related PR: #66331, #68353
   
   Problem Summary:
   
   **In short:** dropping or altering an ADBC catalog can crash the FE process. 
Closing the catalog frees the native ADBC objects at once, including the 
connections and results that other threads are still reading, such as the 
column-statistics loads that a query on the catalog has just started. This 
kills the FE in about one External regression run in 75, whatever the PR under 
test changes. With this PR, closing a client only refuses new calls, and the 
last call still in flight releases the driver as it leaves. DROP and ALTER 
CATALOG still return without waiting.
   
   This commit was first pushed as the last commit of #68353, which is still in 
review. It shares nothing with the rest of that PR, so it is carved out here to 
land on its own.
   
   #### Background
   
   An ADBC catalog (#66331) reads its metadata through `AdbcClient`, which owns 
one native `AdbcDatabase` and one Arrow allocator per catalog. Every metadata 
call borrows a fresh connection through `AdbcClient.withConnection` and closes 
it afterwards. FE talks to the C driver through adbc-driver-jni 0.24. In that 
bridge, closing a parent closes everything still open under it, on the closing 
thread: `JniDatabase.close()` closes every connection still open on the 
database, and `JniConnection.close()` closes every result (`getObjects` and 
similar readers) still open on the connection. The bridge only checks a handle 
before a call starts, and the C driver does not check at all, so freeing an 
object that another thread is still using is a use-after-free in native code, 
which takes the whole JVM down rather than failing one call.
   
   Column statistics reach the driver too. When a query plans against an ADBC 
table, the STATS_FETCH pool loads column statistics in the background. A load 
that finds nothing in the statistics table asks the table itself: 
`PluginDrivenExternalTable.getColumnStatistic` → 
`AdbcConnectorMetadata.getTableHandle` → `withConnection` → `getObjects`. With 
`"meta.cache.adbc.metadata.enable" = "false"`, every such load calls into the 
driver.
   
   #### The problem, and what it cost
   
   - `AdbcClient.close()` closed the database and then the allocator 
immediately, whatever was still in flight. DROP CATALOG and ALTER CATALOG ... 
SET PROPERTIES close the catalog's connector this way, on their own thread 
(since #69665, on the `ExternalResourceReclaimer` pool; before that, 
synchronously on the statement thread, with the same race).
   - A statistics load that was reading a `getObjects` result, or was inside 
the driver on its connection, then read memory that had just been freed. Where 
it dies depends on where that thread is at that moment.
   - In CI, the External regression pipeline lost its FE in 11 of the 834 runs 
between 2026-10-22 and 2026-10-30. Each time, 54 to 56 suites then failed with 
`Communications link failure`, so the PR under test showed a red run it did not 
cause. All 11 died at the same moment: `test_adbc_sqlite_catalog_scan` drops 
its catalog with the metadata cache turned off right after querying it. In the 
same millisecond, fe.log shows the allocator closing with `Memory was leaked by 
query` and a statistics load failing with `Failed to read the ADBC getObjects 
result: ArrowArrayStream is already closed`. That is the same race, caught by a 
thread that came back only after the close had finished.
   
   | FE crash in `fe.out` | Runs |
   | --- | --- |
   | `SIGSEGV` in 
`nanoarrow::ArrayStreamFactory<VectorArrayStream>::get_next_wrapper+0x4` | 6 |
   | `SIGSEGV` in `PrivateArrowSchemaDeepCopy+0x46` | 2 |
   | glibc `double free or corruption (fasttop)` | 2 |
   | no message | 1 |
   | `SIGSEGV` in `sqlite3FindTable` (an earlier run, 2026-10-06) | 1 |
   
   - Users can hit this too, not only CI: DROP or ALTER an ADBC catalog while a 
statement or a statistics load is still reading it.
   
   #### How this PR fixes it
   
   - `AdbcClient` counts the calls inside `withConnection` (`enter` / `leave`, 
under the client's monitor, which also opens the database on first use).
   - `close()` marks the client closed, so new calls are refused with 
`AdbcClient has been closed`. It releases the database and the allocator itself 
only when no call is in flight. Otherwise it returns at once, and the last call 
to leave releases them.
   - A deferred release that fails is logged with the driver's path, as the 
catalog already logs a failed connector close. It is not thrown at that last 
caller, whose own work succeeded and which did not close anything.
   - `close()` never waits for the calls in flight, so DROP and ALTER CATALOG 
are not held up by a slow remote source.
   
   #### Results
   
   Unit tests, with the real JNI bridge and the SQLite driver (0 skipped):
   
   | `AdbcClientTest` case | `close()` releasing at once, as before this PR | 
This PR |
   | --- | --- | --- |
   | a call still on its connection when the client is closed keeps using it | 
fails: `Native connection handle is closed` | passes |
   | a call still reading a `getObjects` result when the client is closed reads 
it to the end | fails: `Attempting operation on allocator when allocator is 
closed` | passes |
   | the last call to leave a closed client releases the database exactly once, 
after it leaves | fails: `the database was released under an open connection` | 
passes |
   | a release that fails as the last call leaves does not fail that call | 
fails: `the database was released under an open connection` | passes |
   | an idle client releases at once | passes | passes |
   
   A stress program (not committed) closes a client 200 times while four 
threads loop over `getObjects` and `getTableSchema` through `withConnection` 
against the SQLite driver:
   
   | | `AdbcClient` of master | This PR |
   | --- | --- | --- |
   | Runs | 5 | 5 |
   | JVM crashed | 5 of 5, 1.1 to 1.3 s after the JVM started: `SIGSEGV` in 
`PrivateArrowSchemaDeepCopy+0x40`, under `ArrowArrayStreamReader.readSchema` ← 
`AdbcObjectsReader.readNamespaces` ← `AdbcClient.withConnection` | 0 of 5 |
   | Closes completed per run | crashed before finishing | 200 |
   | Metadata calls per run | crashed before reporting | 6,019 to 7,238 |
   | Calls the close reached while they were using the driver | | 0 |
   | Failed closes, failed deferred releases | | 0, 0 (checked with WARN 
logging on, where the unit test's deliberate release failure shows up as one 
warning) |
   
   The stress runs used the macOS arm64 build of the same SQLite driver, so the 
offset differs from the Linux one in CI.
   
   #### Classes, and how they call each other
   
   - `AdbcClient`: `withConnection` brackets the body with `enter` / `leave`; 
`close` refuses new calls and releases only when idle; `release` closes the 
database, then the allocator; `openDatabase` is the one place the database is 
created, so a test can stand a recording database in for the driver.
   - `AdbcClientTest`: five new cases, two against the real driver and three 
against a recording database.
   
   ```
   STATS_FETCH: ColumnStatisticsCacheLoader.doLoad
     -> PluginDrivenExternalTable.getColumnStatistic
        -> AdbcConnectorMetadata.getTableHandle (namespaces, table names)
           -> AdbcClient.withConnection
                enter()   refused once closed; callsInFlight++
                -> AdbcConnection.getObjects -> 
AdbcObjectsReader.readNamespaces / readTableNames
                leave()   callsInFlight--; the last one out of a closed client 
-> release()
   
   DROP CATALOG / ALTER CATALOG ... SET PROPERTIES
     -> ExternalCatalog.onClose / resetToUninitialized -> 
ExternalResourceReclaimer
        -> AdbcConnector.close -> AdbcClient.close()
             closed = true; release() now only if no call is in flight
             release(): AdbcDatabase.close (closes its connections and their 
results) -> allocator.close
   ```
   
   Not changed: `AdbcConnector`, the catalog lifecycle and 
`ExternalResourceReclaimer`, BE, and the regression suites; 
`test_adbc_sqlite_catalog_scan` stays as it is, since what it exposes is this 
bug.
   
   ### Release note
   
   Fix an FE crash (SIGSEGV in the ADBC driver) when an ADBC catalog is dropped 
or altered while a statement or a statistics load is still reading it.
   
   ### Check List (For Author)
   
   - Test <!-- At least one of them must be included. -->
       - [ ] Regression test
       - [x] Unit Test
       - [x] Manual test (add detailed scripts or steps below)
       - [ ] No need to test or manual test. Explain why:
           - [ ] This is a refactor/code format and no logic has been changed.
           - [ ] Previous test can cover this change.
           - [ ] No code files have been changed.
           - [ ] Other reason <!-- Add your reason?  -->
   
       Unit test: `AdbcClientTest`, 12 cases including the five new ones, and 
all 197 tests of `fe-connector-adbc` pass with the real JNI bridge and SQLite 
driver. The four new cases above fail with `close()` put back to releasing at 
once. Manual: the stress program in the table above, run against the 
`AdbcClient` of master and against this PR.
   
   - Behavior changed:
       - [x] No.
       - [ ] Yes. <!-- Explain the behavior change -->
   
       DROP and ALTER CATALOG still return without waiting. The native driver 
of an ADBC catalog is now freed when its last in-flight call finishes, instead 
of under it.
   
   - Does this need documentation?
       - [x] No.
       - [ ] Yes. <!-- Add document PR link here. eg: 
https://github.com/apache/doris-website/pull/1214 -->
   
   ### 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