From bd8c1ca3bcfaa78b4315fd4888c02b55c3fc88a2 Mon Sep 17 00:00:00 2001 From: "Aaron M. Ucko" Date: Thu, 19 Dec 2024 14:02:11 -0500 Subject: [PATCH] Introduce CS_INTERRUPT_CB and corresponding return values: CS_INT_*. Define CS_INTERRUPT_CB's numeric value alongside other CS_*_CBs'; start a new 94xx range to avoid confusion with properties. (Sybase/SAP ctlib has no such callback.) Allow specification on either the connection or the context level as usual, with the help of a shim installed on demand to minimize interference with tds_select's optimization for the no-handler case. For CS_INT_*, copy TDS_INT_* and confirm their equality at compile time as with CS_NULLTERM et al. Add a test roughly modeled on dblib's timeout.c unit test, which (alone) exercises the corresponding db_setinterrupt API. Signed-off-by: Aaron M. Ucko --- include/cspublic.h | 7 ++ include/ctlib.h | 3 + include/freetds/tds.h | 1 + src/ctlib/ct.c | 15 ++++ src/ctlib/ctutil.c | 18 +++++ src/ctlib/unittests/.gitignore | 1 + src/ctlib/unittests/CMakeLists.txt | 2 +- src/ctlib/unittests/Makefile.am | 2 + src/ctlib/unittests/timeout.c | 107 +++++++++++++++++++++++++++++ 9 files changed, 155 insertions(+), 1 deletion(-) create mode 100644 src/ctlib/unittests/timeout.c diff --git a/include/cspublic.h b/include/cspublic.h index 1143d1956..0bc503976 100644 --- a/include/cspublic.h +++ b/include/cspublic.h @@ -69,6 +69,7 @@ extern "C" typedef CS_RETCODE(*CS_CSLIBMSG_FUNC) (CS_CONTEXT *, CS_CLIENTMSG *); typedef CS_RETCODE(*CS_CLIENTMSG_FUNC) (CS_CONTEXT *, CS_CONNECTION *, CS_CLIENTMSG *); typedef CS_RETCODE(*CS_SERVERMSG_FUNC) (CS_CONTEXT *, CS_CONNECTION *, CS_SERVERMSG *); +typedef CS_RETCODE(*CS_INTERRUPT_FUNC) (CS_CONNECTION *); #define CS_IODATA TDS_STATIC_CAST(CS_INT, 1600) @@ -453,6 +454,7 @@ enum #define CS_SECSESSION_CB 8 #define CS_SIGNAL_CB 100 #define CS_MESSAGE_CB 9119 +#define CS_INTERRUPT_CB 9400 /* string types */ #define CS_NULLTERM -9 @@ -789,6 +791,11 @@ enum #define CS_SERVERMSG_TYPE 4701 #define CS_ALLMSG_TYPE 4702 +/* CS_INTERRUPT_CB return values */ +#define CS_INT_CONTINUE 1 +#define CS_INT_CANCEL 2 +#define CS_INT_TIMEOUT 3 + CS_RETCODE cs_convert(CS_CONTEXT * ctx, CS_DATAFMT * srcfmt, CS_VOID * srcdata, CS_DATAFMT * destfmt, CS_VOID * destdata, CS_INT * resultlen); CS_RETCODE cs_ctx_alloc(CS_INT version, CS_CONTEXT ** ctx); diff --git a/include/ctlib.h b/include/ctlib.h index 080cb2458..58b788efe 100644 --- a/include/ctlib.h +++ b/include/ctlib.h @@ -152,6 +152,7 @@ struct _cs_context CS_CSLIBMSG_FUNC _cslibmsg_cb; CS_CLIENTMSG_FUNC _clientmsg_cb; CS_SERVERMSG_FUNC _servermsg_cb; + CS_INTERRUPT_FUNC _interrupt_cb; /* code changes start here - CS_CONFIG - 01*/ void *userdata; int userdata_len; @@ -190,6 +191,7 @@ struct _cs_connection TDSSOCKET *tds_socket; CS_CLIENTMSG_FUNC _clientmsg_cb; CS_SERVERMSG_FUNC _servermsg_cb; + CS_INTERRUPT_FUNC _interrupt_cb; void *userdata; int userdata_len; CS_LOCALE *locale; @@ -380,6 +382,7 @@ typedef union */ TDSRET _ct_handle_server_message(const TDSCONTEXT * ctxptr, TDSSOCKET * tdsptr, TDSMESSAGE * msgptr); int _ct_handle_client_message(const TDSCONTEXT * ctxptr, TDSSOCKET * tdsptr, TDSMESSAGE * msgptr); +int _ct_handle_interrupt(void * ptr); TDS_SERVER_TYPE _ct_get_server_type(TDSSOCKET *tds, int datatype); int _ct_bind_data(CS_CONTEXT *ctx, TDSRESULTINFO * resinfo, TDSRESULTINFO *bindinfo, CS_INT offset); int _ct_get_client_type(const TDSCOLUMN *col, bool describe); diff --git a/include/freetds/tds.h b/include/freetds/tds.h index e65b888d9..a74634fd3 100644 --- a/include/freetds/tds.h +++ b/include/freetds/tds.h @@ -1009,6 +1009,7 @@ typedef struct tds_multiple /* forward declaration */ typedef struct tds_context TDSCONTEXT; typedef int (*err_handler_t) (const TDSCONTEXT *, TDSSOCKET *, TDSMESSAGE *); +typedef int (*int_handler_t) (void *); struct tds_context { diff --git a/src/ctlib/ct.c b/src/ctlib/ct.c index 61bc1b2a5..d9889da9d 100644 --- a/src/ctlib/ct.c +++ b/src/ctlib/ct.c @@ -362,6 +362,9 @@ ct_callback(CS_CONTEXT * ctx, CS_CONNECTION * con, CS_INT action, CS_INT type, C case CS_SERVERMSG_CB: out_func = (CS_VOID *) (con ? con->_servermsg_cb : ctx->_servermsg_cb); break; + case CS_INTERRUPT_CB: + out_func = (CS_VOID *) (con ? con->_interrupt_cb : ctx->_interrupt_cb); + break; default: _ctclient_msg(ctx, con, "ct_callback()", 1, 1, 1, 5, "%d, %s", type, "type"); return CS_FAIL; @@ -389,6 +392,18 @@ ct_callback(CS_CONTEXT * ctx, CS_CONNECTION * con, CS_INT action, CS_INT type, C else ctx->_servermsg_cb = (CS_SERVERMSG_FUNC) funcptr; break; + case CS_INTERRUPT_CB: + if (funcptr) { + if (con) + ctx = con->ctx; + /* install shim on demand */ + ctx->tds_ctx->int_handler = _ct_handle_interrupt; + } + if (con) + con->_interrupt_cb = (CS_INTERRUPT_FUNC) funcptr; + else + ctx->_interrupt_cb = (CS_INTERRUPT_FUNC) funcptr; + break; default: _ctclient_msg(ctx, con, "ct_callback()", 1, 1, 1, 5, "%d, %s", type, "type"); return CS_FAIL; diff --git a/src/ctlib/ctutil.c b/src/ctlib/ctutil.c index b03ecfa7e..5964eec0d 100644 --- a/src/ctlib/ctutil.c +++ b/src/ctlib/ctutil.c @@ -54,6 +54,9 @@ TEST_EQUAL(t12,CS_COMPUTEFMT_RESULT,TDS_COMPUTEFMT_RESULT); TEST_EQUAL(t13,CS_ROWFMT_RESULT,TDS_ROWFMT_RESULT); TEST_EQUAL(t14,CS_MSG_RESULT,TDS_MSG_RESULT); TEST_EQUAL(t15,CS_DESCRIBE_RESULT,TDS_DESCRIBE_RESULT); +TEST_EQUAL(t16,CS_INT_CONTINUE,TDS_INT_CONTINUE); +TEST_EQUAL(t17,CS_INT_CANCEL,TDS_INT_CANCEL); +TEST_EQUAL(t18,CS_INT_TIMEOUT,TDS_INT_TIMEOUT); #define TEST_ATTRIBUTE(t,sa,fa,sb,fb) \ TDS_COMPILE_CHECK(t,sizeof(((sa*)0)->fa) == sizeof(((sb*)0)->fb) && TDS_OFFSET(sa,fa) == TDS_OFFSET(sb,fb)) @@ -161,6 +164,21 @@ _ct_handle_client_message(const TDSCONTEXT * ctx_tds, TDSSOCKET * tds, TDSMESSAG return TDS_INT_CANCEL; } +/* + * interrupt handler, installed on demand to avoid gratuitously interfering + * with tds_select's optimization for the no-handler case */ +int +_ct_handle_interrupt(void * ptr) +{ + CS_CONNECTION *con = (CS_CONNECTION *) ptr; + if (con->_interrupt_cb) + return (*con->_interrupt_cb)(con); + else if (con->ctx->_interrupt_cb) + return (*con->ctx->_interrupt_cb)(con); + else + return TDS_INT_CONTINUE; +} + /* message handler */ TDSRET _ct_handle_server_message(const TDSCONTEXT * ctx_tds, TDSSOCKET * tds, TDSMESSAGE * msg) diff --git a/src/ctlib/unittests/.gitignore b/src/ctlib/unittests/.gitignore index 5ca1f9467..01a70d5b8 100644 --- a/src/ctlib/unittests/.gitignore +++ b/src/ctlib/unittests/.gitignore @@ -37,4 +37,5 @@ /variant /errors /ct_command +/timeout /libcommon.a diff --git a/src/ctlib/unittests/CMakeLists.txt b/src/ctlib/unittests/CMakeLists.txt index 4010a5a70..787d71631 100644 --- a/src/ctlib/unittests/CMakeLists.txt +++ b/src/ctlib/unittests/CMakeLists.txt @@ -10,7 +10,7 @@ foreach(target t0001 t0002 t0003 t0004 blk_out ct_cursor ct_cursors ct_dynamic blk_in2 data datafmt rpc_fail row_count all_types long_binary will_convert - variant errors ct_command) + variant errors ct_command timeout) add_executable(c_${target} EXCLUDE_FROM_ALL ${target}.c) set_target_properties(c_${target} PROPERTIES OUTPUT_NAME ${target}) if (target STREQUAL "all_types") diff --git a/src/ctlib/unittests/Makefile.am b/src/ctlib/unittests/Makefile.am index 426d3e4b1..10ff6b64c 100644 --- a/src/ctlib/unittests/Makefile.am +++ b/src/ctlib/unittests/Makefile.am @@ -38,6 +38,7 @@ TESTS = \ variant$(EXEEXT) \ errors$(EXEEXT) \ ct_command$(EXEEXT) \ + timeout$(EXEEXT) \ $(NULL) check_PROGRAMS = $(TESTS) @@ -81,6 +82,7 @@ will_convert_SOURCES = will_convert.c variant_SOURCES = variant.c errors_SOURCES = errors.c ct_command_SOURCES = ct_command.c +timeout_SOURCES = timeout.c noinst_LIBRARIES = libcommon.a libcommon_a_SOURCES = common.c common.h diff --git a/src/ctlib/unittests/timeout.c b/src/ctlib/unittests/timeout.c new file mode 100644 index 000000000..736037ef1 --- /dev/null +++ b/src/ctlib/unittests/timeout.c @@ -0,0 +1,107 @@ +/* + * Purpose: Test handling of timeouts with callbacks + */ + +#include "common.h" +#include +#include + +static int ntimeouts = 0, ncancels = 0; +static int max_timeouts = 2, timeout_seconds = 2, cancel_timeout = 10; +static time_t start_time; +static const char sql[] = + "select getdate() as 'begintime'\n" + "waitfor delay '00:00:30'\n" + "select getdate() as 'endtime'"; + +static int +on_interrupt(CS_CONNECTION *con TDS_UNUSED) +{ + printf("In on_interrupt, %ld seconds elapsed.\n", (long int) (time(NULL) - start_time)); + return CS_INT_CONTINUE; +} + +static int +on_client_msg(CS_CONTEXT *ctx, CS_CONNECTION *con, CS_CLIENTMSG *errmsg) +{ + if (errmsg->msgnumber == 20003) { /* TDSETIME */ + fprintf(stderr, "%d timeout(s) received in %ld seconds; ", ++ntimeouts, (long int) (time(NULL) - start_time)); + if (ntimeouts > max_timeouts) { + if (++ncancels > 1) { + fputs("could not time out cleanly;" + " breaking connection.\n", stderr); + ncancels = 0; + return CS_FAIL; + } + fputs("lost patience;" + " cancelling (allowing 10 seconds)\n", stderr); + if (CS_FAIL == ct_con_props(con, CS_SET, CS_TIMEOUT, &cancel_timeout, CS_UNUSED, NULL)) + fputs("... but ct_con_props() failed" + " in error handler.\n", stderr); + return CS_FAIL; + } + fputs("continuing to wait.\n", stderr); + return CS_SUCCEED; + } + return clientmsg_cb(ctx, con, errmsg); +} + +static void +test(CS_CONNECTION *con, CS_COMMAND *cmd) +{ + CS_INT result_type = 0; + CS_RETCODE ret; + + ntimeouts = 0; + ncancels = 0; + + printf("Using %d-second query timeouts.\n", timeout_seconds); + + if (CS_FAIL == ct_con_props(con, CS_SET, CS_TIMEOUT, &timeout_seconds, CS_UNUSED, NULL)) { + fputs("Failed: ct_con_props(..., CS_SET, CS_TIMEOUT, ...).", stderr); + exit(1); + } + + /* Send something that will take a while to execute. */ + printf("Issuing a query that will take 30 seconds.\n"); + if (CS_SUCCEED != ct_command(cmd, CS_LANG_CMD, (void *) sql, sizeof(sql) - 1, CS_UNUSED)) { + fputs("Failed: ct_command.\n", stderr); + exit(1); + } + + start_time = time(NULL); /* Track for reporting purposes. */ + ntimeouts = 0; + if (CS_FAIL == ct_callback(NULL, con, CS_SET, CS_CLIENTMSG_CB, &on_client_msg) + || CS_FAIL == ct_callback(NULL, con, CS_SET, CS_INTERRUPT_CB, &on_interrupt)) { + fputs("Failed: ct_callback.\n", stderr); + exit(1); + } + + if (CS_SUCCEED != ct_send(cmd)) { + fputs("Failed: ct_send.\n", stderr); + exit(1); + } + + ret = ct_results(cmd, &result_type); + if (ret == CS_SUCCEED) { + fprintf(stderr, "Query unexpectedly succeeded, with result type %d.\n", result_type); + } else { + printf("Query failed as expected, with return code %d.\n", ret); + } +} + +int +main(int argc, char **argv) +{ + CS_CONTEXT *ctx; + CS_CONNECTION *con; + CS_COMMAND *cmd; + + if (CS_SUCCEED != try_ctlogin_with_options(argc, argv, &ctx, &con, &cmd, false)) { + fputs("Customary setup failed.\n", stderr); + return 1; + } + test(con, cmd); + try_ctlogout(ctx, con, cmd, false); + return 0; +}