Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions connection.c
Original file line number Diff line number Diff line change
Expand Up @@ -2033,6 +2033,8 @@ CC_send_query_append(ConnectionClass *self, const char *query, QueryInfo *qi, UD
char *errmsg = PQerrorMessage(self->pqconn);
QLOG(0, "\nCommunication Error: %s\n", SAFE_STR(errmsg));
CC_set_error(self, CONNECTION_COMMUNICATION_ERROR, errmsg, func);
if (PQstatus(self->pqconn) == CONNECTION_BAD)
CC_on_abort(self, CONN_DEAD); /* give up the connection */
goto cleanup;
}
PQsetSingleRowMode(self->pqconn);
Expand Down
8 changes: 8 additions & 0 deletions connection.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,14 @@ typedef enum

/* not connected yet || already disconnected */
#define CC_not_connected(x) (!(x) || CONN_DOWN == (x)->status || CONN_NOT_CONNECTED == (x)->status)
/*
* Answer for SQL_ATTR_CONNECTION_DEAD. libpq's view is consulted too, so
* a socket that libpq has already given up on is reported dead even if no
* code path has called CC_on_abort() yet; applications such as
* System.Data.Odbc only reconnect when this reports SQL_CD_TRUE.
* PQstatus(NULL) is CONNECTION_BAD.
*/
#define CC_connection_dead(x) (CC_not_connected(x) || CONNECTION_BAD == PQstatus((x)->pqconn))

/* AutoCommit functions */
#define CC_is_in_autocommit(x) (x->transact_status & CONN_IN_AUTOCOMMIT)
Expand Down
2 changes: 1 addition & 1 deletion options.c
Original file line number Diff line number Diff line change
Expand Up @@ -552,7 +552,7 @@ PGAPI_GetConnectOption(HDBC hdbc,
case 1209:
#endif /* SQL_ATTR_CONNECTION_DEAD */
MYLOG(0, "CONNECTION_DEAD status=%d", conn->status);
*((SQLUINTEGER *) pvParam) = CC_not_connected(conn);
*((SQLUINTEGER *) pvParam) = CC_connection_dead(conn);
MYPRINTF(0, " val=" FORMAT_UINTEGER "\n", *((SQLUINTEGER *) pvParam));
break;

Expand Down
2 changes: 1 addition & 1 deletion pgapi30.c
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,7 @@ PGAPI_GetConnectAttr(HDBC ConnectionHandle,
*((SQLINTEGER *) Value) = SQL_FALSE;
break;
case SQL_ATTR_CONNECTION_DEAD:
*((SQLUINTEGER *) Value) = CC_not_connected(conn);
*((SQLUINTEGER *) Value) = CC_connection_dead(conn);
break;
case SQL_ATTR_CONNECTION_TIMEOUT:
*((SQLUINTEGER *) Value) = 0;
Expand Down
7 changes: 7 additions & 0 deletions test/expected/connection-dead.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
connected
connection dead before termination: 0
first query failed as expected
connection dead after first failure: 1
second query failed as expected
connection dead after second failure: 1
disconnecting
126 changes: 126 additions & 0 deletions test/src/connection-dead-test.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/*
* Test that SQL_ATTR_CONNECTION_DEAD reports SQL_CD_TRUE once the server
* has dropped the connection, and keeps doing so on later calls.
* System.Data.Odbc only closes an OdbcConnection when this attribute
* says the connection is dead (issue #224).
*
* The backend of the test connection is terminated from a second
* connection, so the driver only learns about it on the next query.
*/
#include <stdio.h>
#include <stdlib.h>

#include "common.h"

static SQLUINTEGER
get_connection_dead(void)
{
SQLRETURN rc;
SQLUINTEGER dead = 99;

rc = SQLGetConnectAttr(conn, SQL_ATTR_CONNECTION_DEAD, &dead, 0, NULL);
CHECK_CONN_RESULT(rc, "SQLGetConnectAttr(SQL_ATTR_CONNECTION_DEAD) failed", conn);
return dead;
}

static void
run_query_expect_failure(const char *label)
{
SQLRETURN rc;
HSTMT hstmt = SQL_NULL_HSTMT;

rc = SQLAllocHandle(SQL_HANDLE_STMT, conn, &hstmt);
CHECK_CONN_RESULT(rc, "failed to allocate stmt handle", conn);
rc = SQLExecDirect(hstmt, (SQLCHAR *) "SELECT 1", SQL_NTS);
if (SQL_SUCCEEDED(rc))
{
printf("FAIL: %s query succeeded on a terminated connection\n", label);
exit(1);
}
printf("%s query failed as expected\n", label);
SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
}

int main(int argc, char **argv)
{
SQLRETURN rc;
HSTMT hstmt = SQL_NULL_HSTMT;
SQLHENV env2;
SQLHDBC conn2;
HSTMT hstmt2 = SQL_NULL_HSTMT;
SQLINTEGER pid;
SQLINTEGER remaining;
char dsn[1024];
char *envvar;
char sql[128];
int i;

test_connect();

rc = SQLAllocHandle(SQL_HANDLE_STMT, conn, &hstmt);
CHECK_CONN_RESULT(rc, "failed to allocate stmt handle", conn);
rc = SQLExecDirect(hstmt, (SQLCHAR *) "SELECT pg_backend_pid()", SQL_NTS);
CHECK_STMT_RESULT(rc, "SELECT pg_backend_pid() failed", hstmt);
rc = SQLFetch(hstmt);
CHECK_STMT_RESULT(rc, "SQLFetch failed", hstmt);
rc = SQLGetData(hstmt, 1, SQL_C_SLONG, &pid, 0, NULL);
CHECK_STMT_RESULT(rc, "SQLGetData failed", hstmt);
SQLFreeHandle(SQL_HANDLE_STMT, hstmt);

printf("connection dead before termination: %u\n", (unsigned int) get_connection_dead());

/* Terminate the first connection's backend from a second connection */
envvar = getenv("COMMON_CONNECTION_STRING_FOR_REGRESSION_TEST");
snprintf(dsn, sizeof(dsn), "DSN=%s;%s", get_test_dsn(), envvar ? envvar : "");
SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env2);
SQLSetEnvAttr(env2, SQL_ATTR_ODBC_VERSION, (void *) SQL_OV_ODBC3, 0);
SQLAllocHandle(SQL_HANDLE_DBC, env2, &conn2);
rc = SQLDriverConnect(conn2, NULL, (SQLCHAR *) dsn, SQL_NTS,
NULL, 0, NULL, SQL_DRIVER_NOPROMPT);
CHECK_CONN_RESULT(rc, "second SQLDriverConnect failed", conn2);
rc = SQLAllocHandle(SQL_HANDLE_STMT, conn2, &hstmt2);
CHECK_CONN_RESULT(rc, "failed to allocate stmt handle", conn2);

snprintf(sql, sizeof(sql), "SELECT pg_terminate_backend(%d)", (int) pid);
rc = SQLExecDirect(hstmt2, (SQLCHAR *) sql, SQL_NTS);
CHECK_STMT_RESULT(rc, "pg_terminate_backend failed", hstmt2);
SQLFreeStmt(hstmt2, SQL_CLOSE);

/* pg_terminate_backend() only signals; wait until the backend is gone */
snprintf(sql, sizeof(sql), "SELECT count(*) FROM pg_stat_activity WHERE pid = %d", (int) pid);
for (i = 0; i < 100; i++)
{
rc = SQLExecDirect(hstmt2, (SQLCHAR *) sql, SQL_NTS);
CHECK_STMT_RESULT(rc, "pg_stat_activity query failed", hstmt2);
rc = SQLFetch(hstmt2);
CHECK_STMT_RESULT(rc, "SQLFetch failed", hstmt2);
rc = SQLGetData(hstmt2, 1, SQL_C_SLONG, &remaining, 0, NULL);
CHECK_STMT_RESULT(rc, "SQLGetData failed", hstmt2);
SQLFreeStmt(hstmt2, SQL_CLOSE);
if (0 == remaining)
break;
rc = SQLExecDirect(hstmt2, (SQLCHAR *) "SELECT pg_sleep(0.1)", SQL_NTS);
CHECK_STMT_RESULT(rc, "pg_sleep failed", hstmt2);
SQLFreeStmt(hstmt2, SQL_CLOSE);
}
if (0 != remaining)
{
printf("FAIL: backend %d was not terminated\n", (int) pid);
exit(1);
}

SQLFreeHandle(SQL_HANDLE_STMT, hstmt2);
SQLDisconnect(conn2);
SQLFreeHandle(SQL_HANDLE_DBC, conn2);
SQLFreeHandle(SQL_HANDLE_ENV, env2);

run_query_expect_failure("first");
printf("connection dead after first failure: %u\n", (unsigned int) get_connection_dead());

run_query_expect_failure("second");
printf("connection dead after second failure: %u\n", (unsigned int) get_connection_dead());

test_disconnect();

return 0;
}
3 changes: 2 additions & 1 deletion test/tests
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,5 @@ TESTBINS = exe/connect-test \
exe/connstring-escape-test \
exe/dbms-version-test \
exe/surrogate-pair-test \
exe/many-params-test
exe/many-params-test \
exe/connection-dead-test
Loading