Skip to content

Commit f580b2a

Browse files
committed
Rework SQLCancel logistics to avoid possible races.
* Split most of its logic into a new _SQLCancel; have SQLCancel itself merely set a flag indicating that an _SQLCancel call is in order. * Automatically check that flag from ODBC_EXIT(_), ODBC_RETURN(_), and a new odbc_int_handler (which _SQLAllocEnv registers), by way of a new ODBC_CHECK_CANCEL macro. * Check for cancellation on function entry too, bailing only when potentially seeking more results from a freshly canceled query; to that end, introduce ODBC_ENTER_HSTMT_OR_BAIL (and a helper ODBC_ENTER_HSTMT_EX). * In _SQLCancel, rely on having the statement's mutex held and add HY008 (Operation was cancelled) after clearing other errors. Signed-off-by: Aaron M. Ucko <[email protected]>
1 parent e6b1f0f commit f580b2a

File tree

3 files changed

+76
-32
lines changed

3 files changed

+76
-32
lines changed

include/freetds/odbc.h

+15
Original file line numberDiff line numberDiff line change
@@ -118,21 +118,31 @@ void odbc_check_struct_extra(void *p);
118118
static inline void odbc_check_struct_extra(void *p TDS_UNUSED) {}
119119
#endif
120120

121+
#define ODBC_CHECK_CANCEL(handle) \
122+
if (IS_HSTMT(handle)) { \
123+
TDS_STMT * _stmt = (TDS_STMT *) handle; \
124+
if (_stmt->cancel_requested) _SQLCancel(_stmt); \
125+
}
126+
121127
#define ODBC_RETURN(handle, rc) \
122128
do { odbc_check_struct_extra(handle); \
129+
ODBC_CHECK_CANCEL(handle) \
123130
return handle->errs.lastrc = (rc); } while(0)
124131
#define ODBC_RETURN_(handle) \
125132
do { odbc_check_struct_extra(handle); \
133+
ODBC_CHECK_CANCEL(handle) \
126134
return handle->errs.lastrc; } while(0)
127135

128136
#define ODBC_EXIT(handle, rc) \
129137
do { SQLRETURN _odbc_rc = handle->errs.lastrc = (rc); \
130138
odbc_check_struct_extra(handle); \
139+
ODBC_CHECK_CANCEL(handle) \
131140
tds_mutex_unlock(&handle->mtx); \
132141
return _odbc_rc; } while(0)
133142
#define ODBC_EXIT_(handle) \
134143
do { SQLRETURN _odbc_rc = handle->errs.lastrc; \
135144
odbc_check_struct_extra(handle); \
145+
ODBC_CHECK_CANCEL(handle) \
136146
tds_mutex_unlock(&handle->mtx); \
137147
return _odbc_rc; } while(0)
138148

@@ -441,6 +451,9 @@ struct _hstmt
441451
TDS_ODBC_SPECIAL_ROWS special_row;
442452
/* do NOT free cursor, free from socket or attach to connection */
443453
TDSCURSOR *cursor;
454+
455+
/* unsafe to combine with prepared_query flags */
456+
volatile bool cancel_requested;
444457
};
445458

446459
typedef struct _henv TDS_ENV;
@@ -587,6 +600,7 @@ void tvp_free(SQLTVP *tvp);
587600
* odbc.c
588601
*/
589602

603+
SQLRETURN _SQLCancel(TDS_STMT * stmt);
590604
SQLRETURN _SQLRowCount(SQLHSTMT hstmt, SQLLEN FAR * pcrow);
591605

592606
/*
@@ -681,6 +695,7 @@ SQLRETURN odbc_set_concise_c_type(SQLSMALLINT concise_type, struct _drecord *dre
681695

682696
SQLLEN odbc_get_octet_len(int c_type, const struct _drecord *drec);
683697
void odbc_convert_err_set(struct _sql_errors *errs, TDS_INT err);
698+
int odbc_int_handler(void *handle);
684699

685700
/*
686701
* prepare_query.c

src/odbc/odbc.c

+45-32
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,15 @@ static void odbc_ird_check(TDS_STMT * stmt);
105105
CHECK_##t##_EXTRA(n); \
106106
odbc_errs_reset(&n->errs);
107107

108-
#define ODBC_ENTER_HSTMT INIT_HANDLE(STMT, stmt)
108+
#define ODBC_ENTER_HSTMT_EX(post_cancel) \
109+
INIT_HANDLE(STMT, stmt) \
110+
if (stmt->cancel_requested) { \
111+
_SQLCancel(stmt); \
112+
post_cancel; \
113+
}
114+
115+
#define ODBC_ENTER_HSTMT ODBC_ENTER_HSTMT_EX(odbc_errs_reset(&stmt->errs))
116+
#define ODBC_ENTER_HSTMT_OR_BAIL ODBC_ENTER_HSTMT_EX(return SQL_ERROR)
109117
#define ODBC_ENTER_HDBC INIT_HANDLE(DBC, dbc)
110118
#define ODBC_ENTER_HENV INIT_HANDLE(ENV, env)
111119
#define ODBC_ENTER_HDESC INIT_HANDLE(DESC, desc)
@@ -733,7 +741,7 @@ SQLExtendedFetch(SQLHSTMT hstmt, SQLUSMALLINT fFetchType, SQLROWOFFSET irow, SQL
733741
SQLULEN bookmark;
734742
SQLULEN out_len = 0;
735743

736-
ODBC_ENTER_HSTMT;
744+
ODBC_ENTER_HSTMT_OR_BAIL;
737745

738746
tdsdump_log(TDS_DBG_FUNC, "SQLExtendedFetch(%p, %d, %d, %p, %p)\n",
739747
hstmt, fFetchType, (int)irow, pcrow, rgfRowStatus);
@@ -901,7 +909,7 @@ SQLMoreResults(SQLHSTMT hstmt)
901909
SQLUSMALLINT param_status;
902910
int token_flags;
903911

904-
ODBC_ENTER_HSTMT;
912+
ODBC_ENTER_HSTMT_OR_BAIL;
905913

906914
tdsdump_log(TDS_DBG_FUNC, "SQLMoreResults(%p)\n", hstmt);
907915

@@ -1233,7 +1241,7 @@ SQLSetPos(SQLHSTMT hstmt, SQLSETPOSIROW irow, SQLUSMALLINT fOption, SQLUSMALLINT
12331241
TDSSOCKET *tds;
12341242
TDS_CURSOR_OPERATION op;
12351243
TDSPARAMINFO *params = NULL;
1236-
ODBC_ENTER_HSTMT;
1244+
ODBC_ENTER_HSTMT_OR_BAIL;
12371245

12381246
tdsdump_log(TDS_DBG_FUNC, "SQLSetPos(%p, %ld, %d, %d)\n",
12391247
hstmt, (long) irow, fOption, fLock);
@@ -1748,6 +1756,7 @@ _SQLAllocEnv(SQLHENV FAR * phenv, SQLINTEGER odbc_version)
17481756
env->tds_ctx = ctx;
17491757
ctx->msg_handler = odbc_errmsg_handler;
17501758
ctx->err_handler = odbc_errmsg_handler;
1759+
ctx->int_handler = odbc_int_handler;
17511760

17521761
/* ODBC has its own format */
17531762
free(ctx->locale->datetime_fmt);
@@ -1983,55 +1992,59 @@ SQLBindCol(SQLHSTMT hstmt, SQLUSMALLINT icol, SQLSMALLINT fCType, SQLPOINTER rgb
19831992
SQLRETURN ODBC_PUBLIC ODBC_API
19841993
SQLCancel(SQLHSTMT hstmt)
19851994
{
1986-
TDSSOCKET *tds;
1987-
1988-
/*
1989-
* FIXME this function can be called from other thread, do not free
1990-
* errors for this function
1991-
* If function is called from another thread errors are not touched
1992-
*/
1993-
/* TODO some tests required */
19941995
TDS_STMT *stmt = (TDS_STMT*)hstmt;
19951996
if (SQL_NULL_HSTMT == hstmt || !IS_HSTMT(hstmt))
19961997
return SQL_INVALID_HANDLE;
1998+
/* tdsdump_log(TDS_DBG_FUNC, "SQLCancel(%p)\n", hstmt); */
1999+
stmt->cancel_requested = true;
2000+
return SQL_SUCCESS;
2001+
}
2002+
2003+
SQLRETURN
2004+
_SQLCancel(TDS_STMT * stmt)
2005+
{
2006+
TDSSOCKET *tds;
19972007

1998-
tdsdump_log(TDS_DBG_FUNC, "SQLCancel(%p)\n", hstmt);
2008+
/* TODO some tests required */
19992009

2010+
tds_mutex_lock(&stmt->dbc->mtx);
2011+
if (!stmt->cancel_requested) {
2012+
tds_mutex_unlock(&stmt->dbc->mtx);
2013+
return SQL_SUCCESS;
2014+
}
20002015
tds = stmt->tds;
2016+
tds_mutex_unlock(&stmt->dbc->mtx);
2017+
2018+
tdsdump_log(TDS_DBG_FUNC, "_SQLCancel(%p)\n", stmt);
20012019

20022020
/* cancelling an inactive statement ?? */
20032021
if (!tds) {
2004-
ODBC_SAFE_ERROR(stmt);
2005-
ODBC_EXIT_(stmt);
2006-
}
2007-
if (tds_mutex_trylock(&stmt->mtx) == 0) {
2022+
stmt->cancel_requested = false;
2023+
ODBC_RETURN_(stmt);
2024+
} else {
20082025
CHECK_STMT_EXTRA(stmt);
20092026
odbc_errs_reset(&stmt->errs);
2027+
odbc_errs_add(&stmt->errs, "HY008", "Operation was cancelled");
20102028

20112029
/* FIXME test current statement */
2012-
/* FIXME here we are unlocked */
20132030

20142031
if (TDS_FAILED(tds_send_cancel(tds))) {
2015-
ODBC_SAFE_ERROR(stmt);
2016-
ODBC_EXIT_(stmt);
2032+
stmt->cancel_requested = false;
2033+
ODBC_RETURN_(stmt);
20172034
}
20182035

20192036
if (TDS_FAILED(tds_process_cancel(tds))) {
2020-
ODBC_SAFE_ERROR(stmt);
2021-
ODBC_EXIT_(stmt);
2037+
stmt->cancel_requested = false;
2038+
ODBC_RETURN_(stmt);
20222039
}
20232040

20242041
/* only if we processed cancel reset statement */
20252042
if (tds->state == TDS_IDLE)
20262043
odbc_unlock_statement(stmt);
20272044

2028-
ODBC_EXIT_(stmt);
2045+
stmt->cancel_requested = false;
2046+
ODBC_RETURN_(stmt);
20292047
}
2030-
2031-
/* don't access error here, just return error */
2032-
if (TDS_FAILED(tds_send_cancel(tds)))
2033-
return SQL_ERROR;
2034-
return SQL_SUCCESS;
20352048
}
20362049

20372050
ODBC_FUNC(SQLConnect, (P(SQLHDBC,hdbc), PCHARIN(DSN,SQLSMALLINT), PCHARIN(UID,SQLSMALLINT),
@@ -4166,7 +4179,7 @@ SQLFetch(SQLHSTMT hstmt)
41664179
SQLUSMALLINT *array_status_ptr;
41674180
} keep;
41684181

4169-
ODBC_ENTER_HSTMT;
4182+
ODBC_ENTER_HSTMT_OR_BAIL;
41704183

41714184
tdsdump_log(TDS_DBG_FUNC, "SQLFetch(%p)\n", hstmt);
41724185

@@ -4195,7 +4208,7 @@ SQLFetch(SQLHSTMT hstmt)
41954208
SQLRETURN ODBC_PUBLIC ODBC_API
41964209
SQLFetchScroll(SQLHSTMT hstmt, SQLSMALLINT FetchOrientation, SQLLEN FetchOffset)
41974210
{
4198-
ODBC_ENTER_HSTMT;
4211+
ODBC_ENTER_HSTMT_OR_BAIL;
41994212

42004213
tdsdump_log(TDS_DBG_FUNC, "SQLFetchScroll(%p, %d, %d)\n", hstmt, FetchOrientation, (int)FetchOffset);
42014214

@@ -5060,7 +5073,7 @@ SQLGetData(SQLHSTMT hstmt, SQLUSMALLINT icol, SQLSMALLINT fCType, SQLPOINTER rgb
50605073
TDSRESULTINFO *resinfo;
50615074
SQLLEN dummy_cb;
50625075

5063-
ODBC_ENTER_HSTMT;
5076+
ODBC_ENTER_HSTMT_OR_BAIL;
50645077

50655078
tdsdump_log(TDS_DBG_FUNC, "SQLGetData(%p, %u, %d, %p, %d, %p)\n",
50665079
hstmt, icol, fCType, rgbValue, (int)cbValueMax, pcbValue);
@@ -6303,7 +6316,7 @@ SQLGetTypeInfoW(SQLHSTMT hstmt, SQLSMALLINT fSqlType)
63036316
static SQLRETURN
63046317
_SQLParamData(SQLHSTMT hstmt, SQLPOINTER FAR * prgbValue)
63056318
{
6306-
ODBC_ENTER_HSTMT;
6319+
ODBC_ENTER_HSTMT_OR_BAIL;
63076320

63086321
tdsdump_log(TDS_DBG_FUNC, "SQLParamData(%p, %p) [param_num %d, param_data_called = %d]\n",
63096322
hstmt, prgbValue, stmt->param_num, stmt->param_data_called);

src/odbc/odbc_util.c

+16
Original file line numberDiff line numberDiff line change
@@ -1208,4 +1208,20 @@ odbc_convert_err_set(struct _sql_errors *errs, TDS_INT err)
12081208
}
12091209
}
12101210

1211+
1212+
int
1213+
odbc_int_handler(void *handle)
1214+
{
1215+
TDS_STMT *stmt = (TDS_STMT*)handle;
1216+
if (SQL_NULL_HSTMT != handle)
1217+
ODBC_CHECK_CANCEL(handle);
1218+
/*
1219+
* Alternatively, this handler could conditionally return
1220+
* TDS_INT_CANCEL. NB: Combining those approaches yields SQL
1221+
* error 08S01 (Communication link failure) rather than the
1222+
* desired HY008 (Operation was cancelled).
1223+
*/
1224+
return TDS_INT_CONTINUE;
1225+
}
1226+
12111227
/** @} */

0 commit comments

Comments
 (0)