feat: add connection.cancel() and statement.cancel() via SQLCancel#481
feat: add connection.cancel() and statement.cancel() via SQLCancel#481rendonvelez wants to merge 4 commits into
Conversation
Closes IBM#434. Add the ability to cancel in-flight operations: - connection.cancel(): ODBCConnection keeps a registry of statement handles for in-flight operations (std::set<SQLHSTMT> guarded by a uv_mutex_t). QueryAsyncWorker and CallProcedureAsyncWorker register their handle right after allocating it and deregister it in their destructors. cancel() walks the registry and calls SQLCancel on each handle. - statement.cancel(): calls SQLCancel directly on the statement's handle, which lives for the whole lifetime of the Statement object. The cancelled operations return with SQLSTATE HY008 ("Operation canceled"), so their promises reject (or their callbacks receive an error) and callers can distinguish cancellation from other failures. SQLCancel is invoked synchronously on the main thread rather than through an AsyncWorker: cancellation is most needed precisely when the libuv thread pool is saturated by the operations being cancelled, so queueing the cancel behind them could delay it indefinitely. Calling SQLCancel from a different thread than the one running the statement is the documented multithreaded-cancel use of the function. StatementData.hstmt is now initialized to SQL_NULL_HANDLE so that cleanup paths that run before the handle is allocated read a defined value. Signed-off-by: Gustavo Rendón Vélez <22383219+rendonvelez@users.noreply.github.com>
Signed-off-by: Gustavo Rendón Vélez <22383219+rendonvelez@users.noreply.github.com>
The data pointer is always valid: the only constructors of QueryAsyncWorker and CallProcedureAsyncWorker receive it as a required argument, so checking it for NULL before unregistering while dereferencing it unconditionally right after was inconsistent. Addresses review feedback. Signed-off-by: Gustavo Rendón Vélez <22383219+rendonvelez@users.noreply.github.com>
|
End-to-end validation done. Environment: Cloudera Impala ODBC driver (64-bit) on Windows x64, Node 20, against a production Impala cluster. Real workload (scan over a large partitioned table):
One caveat worth documenting: with From my side the PR is complete — validation done, review feedback applied in 061d352. |
Yeah, we have the same thing in Db2 (at least on IBM i, not sure other variants) - calls to stored procedures are not cancelable (which makes it hard to test!). This is probably worth calling out in the README. |
SQLCancel only requests cancellation; when the operation actually aborts depends on the engine reaching a cancellation checkpoint. Note the known cases: Impala's sleep() completes its full wait, and stored procedure calls on Db2 for IBM i are not cancelable. Signed-off-by: Gustavo Rendón Vélez <22383219+rendonvelez@users.noreply.github.com>
|
README note added in 25e61bd, covering both cases: the general "SQLCancel only requests cancellation — the abort happens at the engine's next cancellation checkpoint" behavior, with Impala's |
Closes #434.
As discussed in the issue, this adds the ability to cancel in-flight operations. Opening as a draft to gather feedback on the API shape and implementation approach.
API
Both
connection.cancel()andstatement.cancel()are provided, with the usual dual callback/promise interface.Implementation
connection.cancel():ODBCConnectionkeeps a registry of statement handles for in-flight operations (std::set<SQLHSTMT>guarded by auv_mutex_t, following the existingODBC::g_odbcMutexpattern).QueryAsyncWorkerandCallProcedureAsyncWorkerregister their handle right afterSQLAllocHandlesucceeds and deregister it in their destructors.cancel()walks the registry and callsSQLCancelon each handle.statement.cancel(): callsSQLCanceldirectly on the statement's handle, which lives for the whole lifetime of theStatementobject.odbcErrorsshape, letting callers distinguish cancellation from other failures.Why SQLCancel runs synchronously on the main thread
Cancellation is most needed precisely when the libuv thread pool is saturated by the very operations being cancelled — queueing the cancel behind them in an AsyncWorker could delay it indefinitely. Calling
SQLCancelfrom a different thread than the one running the statement is the documented multithreaded-cancel use of the function, and it returns quickly (it only signals the driver).Scope notes
fetch()calls are not registered in the connection registry in this first pass, soconnection.cancel()cancels queries and procedure calls but not an in-flight cursor fetch. Happy to extend if you'd like it covered here.StatementData.hstmtis now initialized toSQL_NULL_HANDLEso cleanup paths that run before the handle is allocated read a defined value.Testing
All translation units compile cleanly. I still need to run this against a live datasource (Cloudera Impala ODBC driver) to validate end-to-end cancellation behavior — will report results here before marking the PR ready for review. Guidance welcome on how you'd like automated tests structured, given that exercising a real cancel requires a long-running query against a live DSN.