mirror of https://github.com/citusdata/citus.git
Merge pull request #1473 from citusdata/fix/cancellation
Fix issues around statement cancellation and connection closure.pull/1487/head
commit
74a6bac8cc
|
@ -658,6 +658,7 @@ MasterPartitionMethod(RangeVar *relation)
|
||||||
{
|
{
|
||||||
char partitionMethod = '\0';
|
char partitionMethod = '\0';
|
||||||
PGresult *queryResult = NULL;
|
PGresult *queryResult = NULL;
|
||||||
|
bool raiseInterrupts = true;
|
||||||
|
|
||||||
char *relationName = relation->relname;
|
char *relationName = relation->relname;
|
||||||
char *schemaName = relation->schemaname;
|
char *schemaName = relation->schemaname;
|
||||||
|
@ -666,7 +667,11 @@ MasterPartitionMethod(RangeVar *relation)
|
||||||
StringInfo partitionMethodCommand = makeStringInfo();
|
StringInfo partitionMethodCommand = makeStringInfo();
|
||||||
appendStringInfo(partitionMethodCommand, PARTITION_METHOD_QUERY, qualifiedName);
|
appendStringInfo(partitionMethodCommand, PARTITION_METHOD_QUERY, qualifiedName);
|
||||||
|
|
||||||
queryResult = PQexec(masterConnection->pgConn, partitionMethodCommand->data);
|
if (!SendRemoteCommand(masterConnection, partitionMethodCommand->data))
|
||||||
|
{
|
||||||
|
ReportConnectionError(masterConnection, ERROR);
|
||||||
|
}
|
||||||
|
queryResult = GetRemoteCommandResult(masterConnection, raiseInterrupts);
|
||||||
if (PQresultStatus(queryResult) == PGRES_TUPLES_OK)
|
if (PQresultStatus(queryResult) == PGRES_TUPLES_OK)
|
||||||
{
|
{
|
||||||
char *partitionMethodString = PQgetvalue((PGresult *) queryResult, 0, 0);
|
char *partitionMethodString = PQgetvalue((PGresult *) queryResult, 0, 0);
|
||||||
|
@ -687,6 +692,9 @@ MasterPartitionMethod(RangeVar *relation)
|
||||||
|
|
||||||
PQclear(queryResult);
|
PQclear(queryResult);
|
||||||
|
|
||||||
|
queryResult = GetRemoteCommandResult(masterConnection, raiseInterrupts);
|
||||||
|
Assert(!queryResult);
|
||||||
|
|
||||||
return partitionMethod;
|
return partitionMethod;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -735,6 +743,7 @@ OpenCopyConnections(CopyStmt *copyStatement, ShardConnections *shardConnections,
|
||||||
ListCell *placementCell = NULL;
|
ListCell *placementCell = NULL;
|
||||||
List *connectionList = NULL;
|
List *connectionList = NULL;
|
||||||
int64 shardId = shardConnections->shardId;
|
int64 shardId = shardConnections->shardId;
|
||||||
|
bool raiseInterrupts = true;
|
||||||
|
|
||||||
MemoryContext localContext = AllocSetContextCreate(CurrentMemoryContext,
|
MemoryContext localContext = AllocSetContextCreate(CurrentMemoryContext,
|
||||||
"OpenCopyConnections",
|
"OpenCopyConnections",
|
||||||
|
@ -784,15 +793,19 @@ OpenCopyConnections(CopyStmt *copyStatement, ShardConnections *shardConnections,
|
||||||
MarkRemoteTransactionCritical(connection);
|
MarkRemoteTransactionCritical(connection);
|
||||||
ClaimConnectionExclusively(connection);
|
ClaimConnectionExclusively(connection);
|
||||||
RemoteTransactionBeginIfNecessary(connection);
|
RemoteTransactionBeginIfNecessary(connection);
|
||||||
|
|
||||||
copyCommand = ConstructCopyStatement(copyStatement, shardConnections->shardId,
|
copyCommand = ConstructCopyStatement(copyStatement, shardConnections->shardId,
|
||||||
useBinaryCopyFormat);
|
useBinaryCopyFormat);
|
||||||
result = PQexec(connection->pgConn, copyCommand->data);
|
|
||||||
|
|
||||||
|
if (!SendRemoteCommand(connection, copyCommand->data))
|
||||||
|
{
|
||||||
|
ReportConnectionError(connection, ERROR);
|
||||||
|
}
|
||||||
|
result = GetRemoteCommandResult(connection, raiseInterrupts);
|
||||||
if (PQresultStatus(result) != PGRES_COPY_IN)
|
if (PQresultStatus(result) != PGRES_COPY_IN)
|
||||||
{
|
{
|
||||||
ReportResultError(connection, result, ERROR);
|
ReportResultError(connection, result, ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
PQclear(result);
|
PQclear(result);
|
||||||
connectionList = lappend(connectionList, connection);
|
connectionList = lappend(connectionList, connection);
|
||||||
}
|
}
|
||||||
|
@ -926,11 +939,16 @@ RemoteFinalizedShardPlacementList(uint64 shardId)
|
||||||
{
|
{
|
||||||
List *finalizedPlacementList = NIL;
|
List *finalizedPlacementList = NIL;
|
||||||
PGresult *queryResult = NULL;
|
PGresult *queryResult = NULL;
|
||||||
|
bool raiseInterrupts = true;
|
||||||
|
|
||||||
StringInfo shardPlacementsCommand = makeStringInfo();
|
StringInfo shardPlacementsCommand = makeStringInfo();
|
||||||
appendStringInfo(shardPlacementsCommand, FINALIZED_SHARD_PLACEMENTS_QUERY, shardId);
|
appendStringInfo(shardPlacementsCommand, FINALIZED_SHARD_PLACEMENTS_QUERY, shardId);
|
||||||
|
|
||||||
queryResult = PQexec(masterConnection->pgConn, shardPlacementsCommand->data);
|
if (!SendRemoteCommand(masterConnection, shardPlacementsCommand->data))
|
||||||
|
{
|
||||||
|
ReportConnectionError(masterConnection, ERROR);
|
||||||
|
}
|
||||||
|
queryResult = GetRemoteCommandResult(masterConnection, raiseInterrupts);
|
||||||
if (PQresultStatus(queryResult) == PGRES_TUPLES_OK)
|
if (PQresultStatus(queryResult) == PGRES_TUPLES_OK)
|
||||||
{
|
{
|
||||||
int rowCount = PQntuples(queryResult);
|
int rowCount = PQntuples(queryResult);
|
||||||
|
@ -939,8 +957,8 @@ RemoteFinalizedShardPlacementList(uint64 shardId)
|
||||||
for (rowIndex = 0; rowIndex < rowCount; rowIndex++)
|
for (rowIndex = 0; rowIndex < rowCount; rowIndex++)
|
||||||
{
|
{
|
||||||
char *placementIdString = PQgetvalue(queryResult, rowIndex, 0);
|
char *placementIdString = PQgetvalue(queryResult, rowIndex, 0);
|
||||||
char *nodeName = PQgetvalue(queryResult, rowIndex, 1);
|
char *nodeName = pstrdup(PQgetvalue(queryResult, rowIndex, 1));
|
||||||
char *nodePortString = PQgetvalue(queryResult, rowIndex, 2);
|
char *nodePortString = pstrdup(PQgetvalue(queryResult, rowIndex, 2));
|
||||||
uint32 nodePort = atoi(nodePortString);
|
uint32 nodePort = atoi(nodePortString);
|
||||||
uint64 placementId = atoll(placementIdString);
|
uint64 placementId = atoll(placementIdString);
|
||||||
|
|
||||||
|
@ -959,6 +977,10 @@ RemoteFinalizedShardPlacementList(uint64 shardId)
|
||||||
ereport(ERROR, (errmsg("could not get shard placements from the master node")));
|
ereport(ERROR, (errmsg("could not get shard placements from the master node")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PQclear(queryResult);
|
||||||
|
queryResult = GetRemoteCommandResult(masterConnection, raiseInterrupts);
|
||||||
|
Assert(!queryResult);
|
||||||
|
|
||||||
return finalizedPlacementList;
|
return finalizedPlacementList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1064,8 +1086,7 @@ SendCopyDataToAll(StringInfo dataBuffer, int64 shardId, List *connectionList)
|
||||||
static void
|
static void
|
||||||
SendCopyDataToPlacement(StringInfo dataBuffer, int64 shardId, MultiConnection *connection)
|
SendCopyDataToPlacement(StringInfo dataBuffer, int64 shardId, MultiConnection *connection)
|
||||||
{
|
{
|
||||||
int copyResult = PQputCopyData(connection->pgConn, dataBuffer->data, dataBuffer->len);
|
if (!PutRemoteCopyData(connection, dataBuffer->data, dataBuffer->len))
|
||||||
if (copyResult != 1)
|
|
||||||
{
|
{
|
||||||
ereport(ERROR, (errcode(ERRCODE_IO_ERROR),
|
ereport(ERROR, (errcode(ERRCODE_IO_ERROR),
|
||||||
errmsg("failed to COPY to shard %ld on %s:%d",
|
errmsg("failed to COPY to shard %ld on %s:%d",
|
||||||
|
@ -1089,13 +1110,11 @@ EndRemoteCopy(int64 shardId, List *connectionList, bool stopOnFailure)
|
||||||
foreach(connectionCell, connectionList)
|
foreach(connectionCell, connectionList)
|
||||||
{
|
{
|
||||||
MultiConnection *connection = (MultiConnection *) lfirst(connectionCell);
|
MultiConnection *connection = (MultiConnection *) lfirst(connectionCell);
|
||||||
int copyEndResult = 0;
|
|
||||||
PGresult *result = NULL;
|
PGresult *result = NULL;
|
||||||
|
bool raiseInterrupts = true;
|
||||||
|
|
||||||
/* end the COPY input */
|
/* end the COPY input */
|
||||||
copyEndResult = PQputCopyEnd(connection->pgConn, NULL);
|
if (!PutRemoteCopyEnd(connection, NULL))
|
||||||
|
|
||||||
if (copyEndResult != 1)
|
|
||||||
{
|
{
|
||||||
if (stopOnFailure)
|
if (stopOnFailure)
|
||||||
{
|
{
|
||||||
|
@ -1108,7 +1127,7 @@ EndRemoteCopy(int64 shardId, List *connectionList, bool stopOnFailure)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* check whether there were any COPY errors */
|
/* check whether there were any COPY errors */
|
||||||
result = PQgetResult(connection->pgConn);
|
result = GetRemoteCommandResult(connection, raiseInterrupts);
|
||||||
if (PQresultStatus(result) != PGRES_COMMAND_OK && stopOnFailure)
|
if (PQresultStatus(result) != PGRES_COMMAND_OK && stopOnFailure)
|
||||||
{
|
{
|
||||||
ReportCopyError(connection, result);
|
ReportCopyError(connection, result);
|
||||||
|
@ -1427,11 +1446,16 @@ RemoteCreateEmptyShard(char *relationName)
|
||||||
{
|
{
|
||||||
int64 shardId = 0;
|
int64 shardId = 0;
|
||||||
PGresult *queryResult = NULL;
|
PGresult *queryResult = NULL;
|
||||||
|
bool raiseInterrupts = true;
|
||||||
|
|
||||||
StringInfo createEmptyShardCommand = makeStringInfo();
|
StringInfo createEmptyShardCommand = makeStringInfo();
|
||||||
appendStringInfo(createEmptyShardCommand, CREATE_EMPTY_SHARD_QUERY, relationName);
|
appendStringInfo(createEmptyShardCommand, CREATE_EMPTY_SHARD_QUERY, relationName);
|
||||||
|
|
||||||
queryResult = PQexec(masterConnection->pgConn, createEmptyShardCommand->data);
|
if (!SendRemoteCommand(masterConnection, createEmptyShardCommand->data))
|
||||||
|
{
|
||||||
|
ReportConnectionError(masterConnection, ERROR);
|
||||||
|
}
|
||||||
|
queryResult = GetRemoteCommandResult(masterConnection, raiseInterrupts);
|
||||||
if (PQresultStatus(queryResult) == PGRES_TUPLES_OK)
|
if (PQresultStatus(queryResult) == PGRES_TUPLES_OK)
|
||||||
{
|
{
|
||||||
char *shardIdString = PQgetvalue((PGresult *) queryResult, 0, 0);
|
char *shardIdString = PQgetvalue((PGresult *) queryResult, 0, 0);
|
||||||
|
@ -1445,6 +1469,8 @@ RemoteCreateEmptyShard(char *relationName)
|
||||||
}
|
}
|
||||||
|
|
||||||
PQclear(queryResult);
|
PQclear(queryResult);
|
||||||
|
queryResult = GetRemoteCommandResult(masterConnection, raiseInterrupts);
|
||||||
|
Assert(!queryResult);
|
||||||
|
|
||||||
return shardId;
|
return shardId;
|
||||||
}
|
}
|
||||||
|
@ -1475,18 +1501,25 @@ static void
|
||||||
RemoteUpdateShardStatistics(uint64 shardId)
|
RemoteUpdateShardStatistics(uint64 shardId)
|
||||||
{
|
{
|
||||||
PGresult *queryResult = NULL;
|
PGresult *queryResult = NULL;
|
||||||
|
bool raiseInterrupts = true;
|
||||||
|
|
||||||
StringInfo updateShardStatisticsCommand = makeStringInfo();
|
StringInfo updateShardStatisticsCommand = makeStringInfo();
|
||||||
appendStringInfo(updateShardStatisticsCommand, UPDATE_SHARD_STATISTICS_QUERY,
|
appendStringInfo(updateShardStatisticsCommand, UPDATE_SHARD_STATISTICS_QUERY,
|
||||||
shardId);
|
shardId);
|
||||||
|
|
||||||
queryResult = PQexec(masterConnection->pgConn, updateShardStatisticsCommand->data);
|
if (!SendRemoteCommand(masterConnection, updateShardStatisticsCommand->data))
|
||||||
|
{
|
||||||
|
ReportConnectionError(masterConnection, ERROR);
|
||||||
|
}
|
||||||
|
queryResult = GetRemoteCommandResult(masterConnection, raiseInterrupts);
|
||||||
if (PQresultStatus(queryResult) != PGRES_TUPLES_OK)
|
if (PQresultStatus(queryResult) != PGRES_TUPLES_OK)
|
||||||
{
|
{
|
||||||
ereport(ERROR, (errmsg("could not update shard statistics")));
|
ereport(ERROR, (errmsg("could not update shard statistics")));
|
||||||
}
|
}
|
||||||
|
|
||||||
PQclear(queryResult);
|
PQclear(queryResult);
|
||||||
|
queryResult = GetRemoteCommandResult(masterConnection, raiseInterrupts);
|
||||||
|
Assert(!queryResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -401,6 +401,40 @@ CloseConnectionByPGconn(PGconn *pqConn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ShutdownConnection, if necessary cancels the currently running statement,
|
||||||
|
* and then closes the underlying libpq connection. The MultiConnection
|
||||||
|
* itself is left intact.
|
||||||
|
*
|
||||||
|
* NB: Cancelling a statement requires network IO, and currently is not
|
||||||
|
* interruptible. Unfortunately libpq does not provide a non-blocking
|
||||||
|
* implementation of PQcancel(), so we don't have much choice for now.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
ShutdownConnection(MultiConnection *connection)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Only cancel statement if there's currently one running, and the
|
||||||
|
* connection is in an OK state.
|
||||||
|
*/
|
||||||
|
if (PQstatus(connection->pgConn) == CONNECTION_OK &&
|
||||||
|
PQtransactionStatus(connection->pgConn) == PQTRANS_ACTIVE)
|
||||||
|
{
|
||||||
|
char errorMessage[256] = { 0 };
|
||||||
|
PGcancel *cancel = PQgetCancel(connection->pgConn);
|
||||||
|
|
||||||
|
if (!PQcancel(cancel, errorMessage, sizeof(errorMessage)))
|
||||||
|
{
|
||||||
|
ereport(WARNING, (errmsg("could not cancel connection: %s",
|
||||||
|
errorMessage)));
|
||||||
|
}
|
||||||
|
PQfreeCancel(cancel);
|
||||||
|
}
|
||||||
|
PQfinish(connection->pgConn);
|
||||||
|
connection->pgConn = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* FinishConnectionListEstablishment is a wrapper around FinishConnectionEstablishment.
|
* FinishConnectionListEstablishment is a wrapper around FinishConnectionEstablishment.
|
||||||
* The function iterates over the multiConnectionList and finishes the connection
|
* The function iterates over the multiConnectionList and finishes the connection
|
||||||
|
@ -640,6 +674,13 @@ StartConnectionEstablishment(ConnectionHashKey *key)
|
||||||
connection->pgConn = PQconnectStartParams(keywords, values, false);
|
connection->pgConn = PQconnectStartParams(keywords, values, false);
|
||||||
connection->connectionStart = GetCurrentTimestamp();
|
connection->connectionStart = GetCurrentTimestamp();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* To avoid issues with interrupts not getting caught all our connections
|
||||||
|
* are managed in a non-blocking manner. remote_commands.c provides
|
||||||
|
* wrappers emulating blocking behaviour.
|
||||||
|
*/
|
||||||
|
PQsetnonblocking(connection->pgConn, true);
|
||||||
|
|
||||||
return connection;
|
return connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -677,8 +718,7 @@ AfterXactHostConnectionHandling(ConnectionHashEntry *entry, bool isCommit)
|
||||||
PQstatus(connection->pgConn) != CONNECTION_OK ||
|
PQstatus(connection->pgConn) != CONNECTION_OK ||
|
||||||
PQtransactionStatus(connection->pgConn) != PQTRANS_IDLE)
|
PQtransactionStatus(connection->pgConn) != PQTRANS_IDLE)
|
||||||
{
|
{
|
||||||
PQfinish(connection->pgConn);
|
ShutdownConnection(connection);
|
||||||
connection->pgConn = NULL;
|
|
||||||
|
|
||||||
/* unlink from list */
|
/* unlink from list */
|
||||||
dlist_delete(iter.cur);
|
dlist_delete(iter.cur);
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
/* GUC, determining whether statements sent to remote nodes are logged */
|
/* GUC, determining whether statements sent to remote nodes are logged */
|
||||||
bool LogRemoteCommands = false;
|
bool LogRemoteCommands = false;
|
||||||
|
|
||||||
|
static bool FinishConnectionIO(MultiConnection *connection, bool raiseInterrupts);
|
||||||
|
|
||||||
/* simple helpers */
|
/* simple helpers */
|
||||||
|
|
||||||
|
@ -47,8 +48,8 @@ IsResponseOK(PGresult *result)
|
||||||
/*
|
/*
|
||||||
* ForgetResults clears a connection from pending activity.
|
* ForgetResults clears a connection from pending activity.
|
||||||
*
|
*
|
||||||
* XXX: In the future it might be a good idea to use use PQcancel() if results
|
* Note that this might require network IO. If that's not acceptable, use
|
||||||
* would require network IO.
|
* NonblockingForgetResults().
|
||||||
*/
|
*/
|
||||||
void
|
void
|
||||||
ForgetResults(MultiConnection *connection)
|
ForgetResults(MultiConnection *connection)
|
||||||
|
@ -74,6 +75,74 @@ ForgetResults(MultiConnection *connection)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* NonblockingForgetResults clears a connection from pending activity if doing
|
||||||
|
* so does not require network IO. Returns true if successful, false
|
||||||
|
* otherwise.
|
||||||
|
*/
|
||||||
|
bool
|
||||||
|
NonblockingForgetResults(MultiConnection *connection)
|
||||||
|
{
|
||||||
|
PGconn *pgConn = connection->pgConn;
|
||||||
|
|
||||||
|
if (PQstatus(pgConn) != CONNECTION_OK)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert(PQisnonblocking(pgConn));
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
PGresult *result = NULL;
|
||||||
|
|
||||||
|
/* just in case there's a lot of results */
|
||||||
|
CHECK_FOR_INTERRUPTS();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If busy, there might still be results already received and buffered
|
||||||
|
* by the OS. As connection is in non-blocking mode, we can check for
|
||||||
|
* that without blocking.
|
||||||
|
*/
|
||||||
|
if (PQisBusy(pgConn))
|
||||||
|
{
|
||||||
|
if (PQflush(pgConn) == -1)
|
||||||
|
{
|
||||||
|
/* write failed */
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (PQconsumeInput(pgConn) == 0)
|
||||||
|
{
|
||||||
|
/* some low-level failure */
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* clearing would require blocking IO, return */
|
||||||
|
if (PQisBusy(pgConn))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = PQgetResult(pgConn);
|
||||||
|
if (PQresultStatus(result) == PGRES_COPY_IN)
|
||||||
|
{
|
||||||
|
/* in copy, can't reliably recover without blocking */
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result == NULL)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
PQclear(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
pg_unreachable();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* SqlStateMatchesCategory returns true if the given sql state (which may be
|
* SqlStateMatchesCategory returns true if the given sql state (which may be
|
||||||
* NULL if unknown) is in the given error category. Note that we use
|
* NULL if unknown) is in the given error category. Note that we use
|
||||||
|
@ -279,7 +348,6 @@ SendRemoteCommandParams(MultiConnection *connection, const char *command,
|
||||||
const char *const *parameterValues)
|
const char *const *parameterValues)
|
||||||
{
|
{
|
||||||
PGconn *pgConn = connection->pgConn;
|
PGconn *pgConn = connection->pgConn;
|
||||||
bool wasNonblocking = false;
|
|
||||||
int rc = 0;
|
int rc = 0;
|
||||||
|
|
||||||
LogRemoteCommand(connection, command);
|
LogRemoteCommand(connection, command);
|
||||||
|
@ -293,23 +361,11 @@ SendRemoteCommandParams(MultiConnection *connection, const char *command,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
wasNonblocking = PQisnonblocking(pgConn);
|
Assert(PQisnonblocking(pgConn));
|
||||||
|
|
||||||
/* make sure not to block anywhere */
|
|
||||||
if (!wasNonblocking)
|
|
||||||
{
|
|
||||||
PQsetnonblocking(pgConn, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
rc = PQsendQueryParams(pgConn, command, parameterCount, parameterTypes,
|
rc = PQsendQueryParams(pgConn, command, parameterCount, parameterTypes,
|
||||||
parameterValues, NULL, NULL, 0);
|
parameterValues, NULL, NULL, 0);
|
||||||
|
|
||||||
/* reset nonblocking connection to its original state */
|
|
||||||
if (!wasNonblocking)
|
|
||||||
{
|
|
||||||
PQsetnonblocking(pgConn, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -367,7 +423,7 @@ ReadFirstColumnAsText(PGresult *queryResult)
|
||||||
* an error.
|
* an error.
|
||||||
*
|
*
|
||||||
* If raiseInterrupts is false and an interrupt arrives that'd otherwise raise
|
* If raiseInterrupts is false and an interrupt arrives that'd otherwise raise
|
||||||
* an error, GetRemotecommandResult returns NULL, and the transaction is
|
* an error, GetRemoteCommandResult returns NULL, and the transaction is
|
||||||
* marked as having failed. While that's not a perfect way to signal failure,
|
* marked as having failed. While that's not a perfect way to signal failure,
|
||||||
* callers will usually treat that as an error, and it's easy to use.
|
* callers will usually treat that as an error, and it's easy to use.
|
||||||
*
|
*
|
||||||
|
@ -379,11 +435,7 @@ PGresult *
|
||||||
GetRemoteCommandResult(MultiConnection *connection, bool raiseInterrupts)
|
GetRemoteCommandResult(MultiConnection *connection, bool raiseInterrupts)
|
||||||
{
|
{
|
||||||
PGconn *pgConn = connection->pgConn;
|
PGconn *pgConn = connection->pgConn;
|
||||||
int socket = 0;
|
|
||||||
int waitFlags = WL_POSTMASTER_DEATH | WL_LATCH_SET;
|
|
||||||
bool wasNonblocking = false;
|
|
||||||
PGresult *result = NULL;
|
PGresult *result = NULL;
|
||||||
bool failed = false;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Short circuit tests around the more expensive parts of this
|
* Short circuit tests around the more expensive parts of this
|
||||||
|
@ -395,106 +447,159 @@ GetRemoteCommandResult(MultiConnection *connection, bool raiseInterrupts)
|
||||||
return PQgetResult(connection->pgConn);
|
return PQgetResult(connection->pgConn);
|
||||||
}
|
}
|
||||||
|
|
||||||
socket = PQsocket(pgConn);
|
if (!FinishConnectionIO(connection, raiseInterrupts))
|
||||||
wasNonblocking = PQisnonblocking(pgConn);
|
|
||||||
|
|
||||||
/* make sure not to block anywhere */
|
|
||||||
if (!wasNonblocking)
|
|
||||||
{
|
{
|
||||||
PQsetnonblocking(pgConn, true);
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (raiseInterrupts)
|
/* no IO should be necessary to get result */
|
||||||
{
|
Assert(!PQisBusy(pgConn));
|
||||||
CHECK_FOR_INTERRUPTS();
|
|
||||||
|
result = PQgetResult(connection->pgConn);
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* make sure command has been sent out */
|
|
||||||
while (!failed)
|
|
||||||
{
|
|
||||||
int rc = 0;
|
|
||||||
|
|
||||||
ResetLatch(MyLatch);
|
|
||||||
|
|
||||||
/* try to send all the data */
|
|
||||||
rc = PQflush(pgConn);
|
|
||||||
|
|
||||||
/* stop writing if all data has been sent, or there was none to send */
|
|
||||||
if (rc == 0)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* if sending failed, there's nothing more we can do */
|
|
||||||
if (rc == -1)
|
|
||||||
{
|
|
||||||
failed = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* this means we have to wait for data to go out */
|
|
||||||
Assert(rc == 1);
|
|
||||||
|
|
||||||
#if (PG_VERSION_NUM >= 100000)
|
|
||||||
rc = WaitLatchOrSocket(MyLatch, waitFlags | WL_SOCKET_WRITEABLE, socket, 0,
|
|
||||||
PG_WAIT_EXTENSION);
|
|
||||||
#else
|
|
||||||
rc = WaitLatchOrSocket(MyLatch, waitFlags | WL_SOCKET_WRITEABLE, socket, 0);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (rc & WL_POSTMASTER_DEATH)
|
|
||||||
{
|
|
||||||
ereport(ERROR, (errmsg("postmaster was shut down, exiting")));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rc & WL_LATCH_SET)
|
|
||||||
{
|
|
||||||
/* if allowed raise errors */
|
|
||||||
if (raiseInterrupts)
|
|
||||||
{
|
|
||||||
CHECK_FOR_INTERRUPTS();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If raising errors allowed, or called within in a section with
|
* PutRemoteCopyData is a wrapper around PQputCopyData() that handles
|
||||||
* interrupts held, return NULL instead, and mark the transaction
|
* interrupts.
|
||||||
* as failed.
|
*
|
||||||
|
* Returns false if PQputCopyData() failed, true otherwise.
|
||||||
*/
|
*/
|
||||||
if (InterruptHoldoffCount > 0 && (QueryCancelPending || ProcDiePending))
|
bool
|
||||||
|
PutRemoteCopyData(MultiConnection *connection, const char *buffer, int nbytes)
|
||||||
{
|
{
|
||||||
connection->remoteTransaction.transactionFailed = true;
|
PGconn *pgConn = connection->pgConn;
|
||||||
failed = true;
|
int copyState = 0;
|
||||||
break;
|
|
||||||
|
if (PQstatus(pgConn) != CONNECTION_OK)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Assert(PQisnonblocking(pgConn));
|
||||||
|
|
||||||
|
copyState = PQputCopyData(pgConn, buffer, nbytes);
|
||||||
|
|
||||||
|
if (copyState == 1)
|
||||||
|
{
|
||||||
|
/* successful */
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (copyState == -1)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bool allowInterrupts = true;
|
||||||
|
return FinishConnectionIO(connection, allowInterrupts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* wait for the result of the command to come in */
|
|
||||||
while (!failed)
|
/*
|
||||||
|
* PutRemoteCopyEnd is a wrapper around PQputCopyEnd() that handles
|
||||||
|
* interrupts.
|
||||||
|
*
|
||||||
|
* Returns false if PQputCopyEnd() failed, true otherwise.
|
||||||
|
*/
|
||||||
|
bool
|
||||||
|
PutRemoteCopyEnd(MultiConnection *connection, const char *errormsg)
|
||||||
{
|
{
|
||||||
|
PGconn *pgConn = connection->pgConn;
|
||||||
|
int copyState = 0;
|
||||||
|
|
||||||
|
if (PQstatus(pgConn) != CONNECTION_OK)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert(PQisnonblocking(pgConn));
|
||||||
|
|
||||||
|
copyState = PQputCopyEnd(pgConn, errormsg);
|
||||||
|
|
||||||
|
if (copyState == 1)
|
||||||
|
{
|
||||||
|
/* successful */
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (copyState == -1)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bool allowInterrupts = true;
|
||||||
|
return FinishConnectionIO(connection, allowInterrupts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* FinishConnectionIO performs pending IO for the connection, while accepting
|
||||||
|
* interrupts.
|
||||||
|
*
|
||||||
|
* See GetRemoteCommandResult() for documentation of interrupt handling
|
||||||
|
* behaviour.
|
||||||
|
*
|
||||||
|
* Returns true if IO was successfully completed, false otherwise.
|
||||||
|
*/
|
||||||
|
static bool
|
||||||
|
FinishConnectionIO(MultiConnection *connection, bool raiseInterrupts)
|
||||||
|
{
|
||||||
|
PGconn *pgConn = connection->pgConn;
|
||||||
|
int socket = PQsocket(pgConn);
|
||||||
|
|
||||||
|
Assert(pgConn);
|
||||||
|
Assert(PQisnonblocking(pgConn));
|
||||||
|
|
||||||
|
if (raiseInterrupts)
|
||||||
|
{
|
||||||
|
CHECK_FOR_INTERRUPTS();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* perform the necessary IO */
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
int sendStatus = 0;
|
||||||
int rc = 0;
|
int rc = 0;
|
||||||
|
int waitFlags = WL_POSTMASTER_DEATH | WL_LATCH_SET;
|
||||||
|
|
||||||
ResetLatch(MyLatch);
|
/* try to send all pending data */
|
||||||
|
sendStatus = PQflush(pgConn);
|
||||||
|
|
||||||
|
/* if sending failed, there's nothing more we can do */
|
||||||
|
if (sendStatus == -1)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (sendStatus == 1)
|
||||||
|
{
|
||||||
|
waitFlags |= WL_SOCKET_WRITEABLE;
|
||||||
|
}
|
||||||
|
|
||||||
/* if reading fails, there's not much we can do */
|
/* if reading fails, there's not much we can do */
|
||||||
if (PQconsumeInput(pgConn) == 0)
|
if (PQconsumeInput(pgConn) == 0)
|
||||||
{
|
{
|
||||||
failed = true;
|
return false;
|
||||||
break;
|
}
|
||||||
|
if (PQisBusy(pgConn))
|
||||||
|
{
|
||||||
|
waitFlags |= WL_SOCKET_READABLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* check if all the necessary data is now available */
|
if ((waitFlags & (WL_SOCKET_READABLE | WL_SOCKET_WRITEABLE)) == 0)
|
||||||
if (!PQisBusy(pgConn))
|
|
||||||
{
|
{
|
||||||
result = PQgetResult(connection->pgConn);
|
/* no IO necessary anymore, we're done */
|
||||||
break;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if (PG_VERSION_NUM >= 100000)
|
#if (PG_VERSION_NUM >= 100000)
|
||||||
rc = WaitLatchOrSocket(MyLatch, waitFlags | WL_SOCKET_READABLE, socket, 0,
|
rc = WaitLatchOrSocket(MyLatch, waitFlags, socket, 0, PG_WAIT_EXTENSION);
|
||||||
PG_WAIT_EXTENSION);
|
|
||||||
#else
|
#else
|
||||||
rc = WaitLatchOrSocket(MyLatch, waitFlags | WL_SOCKET_READABLE, socket, 0);
|
rc = WaitLatchOrSocket(MyLatch, waitFlags, socket, 0);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (rc & WL_POSTMASTER_DEATH)
|
if (rc & WL_POSTMASTER_DEATH)
|
||||||
|
@ -504,6 +609,8 @@ GetRemoteCommandResult(MultiConnection *connection, bool raiseInterrupts)
|
||||||
|
|
||||||
if (rc & WL_LATCH_SET)
|
if (rc & WL_LATCH_SET)
|
||||||
{
|
{
|
||||||
|
ResetLatch(MyLatch);
|
||||||
|
|
||||||
/* if allowed raise errors */
|
/* if allowed raise errors */
|
||||||
if (raiseInterrupts)
|
if (raiseInterrupts)
|
||||||
{
|
{
|
||||||
|
@ -512,22 +619,16 @@ GetRemoteCommandResult(MultiConnection *connection, bool raiseInterrupts)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If raising errors allowed, or called within in a section with
|
* If raising errors allowed, or called within in a section with
|
||||||
* interrupts held, return NULL instead, and mark the transaction
|
* interrupts held, return instead, and mark the transaction as
|
||||||
* as failed.
|
* failed.
|
||||||
*/
|
*/
|
||||||
if (InterruptHoldoffCount > 0 && (QueryCancelPending || ProcDiePending))
|
if (InterruptHoldoffCount > 0 && (QueryCancelPending || ProcDiePending))
|
||||||
{
|
{
|
||||||
connection->remoteTransaction.transactionFailed = true;
|
connection->remoteTransaction.transactionFailed = true;
|
||||||
failed = true;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!wasNonblocking)
|
return false;
|
||||||
{
|
|
||||||
PQsetnonblocking(pgConn, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,6 @@ static PostgresPollingStatusType ClientPollingStatusArray[MAX_CONNECTION_COUNT];
|
||||||
|
|
||||||
|
|
||||||
/* Local functions forward declarations */
|
/* Local functions forward declarations */
|
||||||
static void ClearRemainingResults(MultiConnection *connection);
|
|
||||||
static bool ClientConnectionReady(MultiConnection *connection,
|
static bool ClientConnectionReady(MultiConnection *connection,
|
||||||
PostgresPollingStatusType pollingStatus);
|
PostgresPollingStatusType pollingStatus);
|
||||||
|
|
||||||
|
@ -401,6 +400,7 @@ MultiClientQueryResult(int32 connectionId, void **queryResult, int *rowCount,
|
||||||
PGresult *result = NULL;
|
PGresult *result = NULL;
|
||||||
ConnStatusType connStatusType = CONNECTION_OK;
|
ConnStatusType connStatusType = CONNECTION_OK;
|
||||||
ExecStatusType resultStatus = PGRES_COMMAND_OK;
|
ExecStatusType resultStatus = PGRES_COMMAND_OK;
|
||||||
|
bool raiseInterrupts = true;
|
||||||
|
|
||||||
Assert(connectionId != INVALID_CONNECTION_ID);
|
Assert(connectionId != INVALID_CONNECTION_ID);
|
||||||
connection = ClientConnectionArray[connectionId];
|
connection = ClientConnectionArray[connectionId];
|
||||||
|
@ -413,7 +413,7 @@ MultiClientQueryResult(int32 connectionId, void **queryResult, int *rowCount,
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
result = PQgetResult(connection->pgConn);
|
result = GetRemoteCommandResult(connection, raiseInterrupts);
|
||||||
resultStatus = PQresultStatus(result);
|
resultStatus = PQresultStatus(result);
|
||||||
if (resultStatus == PGRES_TUPLES_OK)
|
if (resultStatus == PGRES_TUPLES_OK)
|
||||||
{
|
{
|
||||||
|
@ -430,7 +430,7 @@ MultiClientQueryResult(int32 connectionId, void **queryResult, int *rowCount,
|
||||||
}
|
}
|
||||||
|
|
||||||
/* clear extra result objects */
|
/* clear extra result objects */
|
||||||
ClearRemainingResults(connection);
|
ForgetResults(connection);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -454,6 +454,7 @@ MultiClientBatchResult(int32 connectionId, void **queryResult, int *rowCount,
|
||||||
ConnStatusType connStatusType = CONNECTION_OK;
|
ConnStatusType connStatusType = CONNECTION_OK;
|
||||||
ExecStatusType resultStatus = PGRES_COMMAND_OK;
|
ExecStatusType resultStatus = PGRES_COMMAND_OK;
|
||||||
BatchQueryStatus queryStatus = CLIENT_INVALID_BATCH_QUERY;
|
BatchQueryStatus queryStatus = CLIENT_INVALID_BATCH_QUERY;
|
||||||
|
bool raiseInterrupts = true;
|
||||||
|
|
||||||
Assert(connectionId != INVALID_CONNECTION_ID);
|
Assert(connectionId != INVALID_CONNECTION_ID);
|
||||||
connection = ClientConnectionArray[connectionId];
|
connection = ClientConnectionArray[connectionId];
|
||||||
|
@ -471,7 +472,7 @@ MultiClientBatchResult(int32 connectionId, void **queryResult, int *rowCount,
|
||||||
return CLIENT_BATCH_QUERY_FAILED;
|
return CLIENT_BATCH_QUERY_FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
result = PQgetResult(connection->pgConn);
|
result = GetRemoteCommandResult(connection, raiseInterrupts);
|
||||||
if (result == NULL)
|
if (result == NULL)
|
||||||
{
|
{
|
||||||
return CLIENT_BATCH_QUERY_DONE;
|
return CLIENT_BATCH_QUERY_DONE;
|
||||||
|
@ -538,6 +539,7 @@ MultiClientQueryStatus(int32 connectionId)
|
||||||
ConnStatusType connStatusType = CONNECTION_OK;
|
ConnStatusType connStatusType = CONNECTION_OK;
|
||||||
ExecStatusType resultStatus = PGRES_COMMAND_OK;
|
ExecStatusType resultStatus = PGRES_COMMAND_OK;
|
||||||
QueryStatus queryStatus = CLIENT_INVALID_QUERY;
|
QueryStatus queryStatus = CLIENT_INVALID_QUERY;
|
||||||
|
bool raiseInterrupts = true;
|
||||||
|
|
||||||
Assert(connectionId != INVALID_CONNECTION_ID);
|
Assert(connectionId != INVALID_CONNECTION_ID);
|
||||||
connection = ClientConnectionArray[connectionId];
|
connection = ClientConnectionArray[connectionId];
|
||||||
|
@ -555,7 +557,7 @@ MultiClientQueryStatus(int32 connectionId)
|
||||||
* isn't ready yet (the caller didn't wait for the connection to be ready),
|
* isn't ready yet (the caller didn't wait for the connection to be ready),
|
||||||
* we will block on this call.
|
* we will block on this call.
|
||||||
*/
|
*/
|
||||||
result = PQgetResult(connection->pgConn);
|
result = GetRemoteCommandResult(connection, raiseInterrupts);
|
||||||
resultStatus = PQresultStatus(result);
|
resultStatus = PQresultStatus(result);
|
||||||
|
|
||||||
if (resultStatus == PGRES_COMMAND_OK)
|
if (resultStatus == PGRES_COMMAND_OK)
|
||||||
|
@ -598,7 +600,7 @@ MultiClientQueryStatus(int32 connectionId)
|
||||||
*/
|
*/
|
||||||
if (!copyResults)
|
if (!copyResults)
|
||||||
{
|
{
|
||||||
ClearRemainingResults(connection);
|
ForgetResults(connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
return queryStatus;
|
return queryStatus;
|
||||||
|
@ -665,7 +667,8 @@ MultiClientCopyData(int32 connectionId, int32 fileDescriptor)
|
||||||
else if (receiveLength == -1)
|
else if (receiveLength == -1)
|
||||||
{
|
{
|
||||||
/* received copy done message */
|
/* received copy done message */
|
||||||
PGresult *result = PQgetResult(connection->pgConn);
|
bool raiseInterrupts = true;
|
||||||
|
PGresult *result = GetRemoteCommandResult(connection, raiseInterrupts);
|
||||||
ExecStatusType resultStatus = PQresultStatus(result);
|
ExecStatusType resultStatus = PQresultStatus(result);
|
||||||
|
|
||||||
if (resultStatus == PGRES_COMMAND_OK)
|
if (resultStatus == PGRES_COMMAND_OK)
|
||||||
|
@ -692,7 +695,7 @@ MultiClientCopyData(int32 connectionId, int32 fileDescriptor)
|
||||||
/* if copy out completed, make sure we drain all results from libpq */
|
/* if copy out completed, make sure we drain all results from libpq */
|
||||||
if (receiveLength < 0)
|
if (receiveLength < 0)
|
||||||
{
|
{
|
||||||
ClearRemainingResults(connection);
|
ForgetResults(connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
return copyStatus;
|
return copyStatus;
|
||||||
|
@ -853,23 +856,6 @@ MultiClientWait(WaitInfo *waitInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* ClearRemainingResults reads result objects from the connection until we get
|
|
||||||
* null, and clears these results. This is the last step in completing an async
|
|
||||||
* query.
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
ClearRemainingResults(MultiConnection *connection)
|
|
||||||
{
|
|
||||||
PGresult *result = PQgetResult(connection->pgConn);
|
|
||||||
while (result != NULL)
|
|
||||||
{
|
|
||||||
PQclear(result);
|
|
||||||
result = PQgetResult(connection->pgConn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ClientConnectionReady checks if the given connection is ready for non-blocking
|
* ClientConnectionReady checks if the given connection is ready for non-blocking
|
||||||
* reads or writes. This function is loosely based on pqSocketCheck() at fe-misc.c
|
* reads or writes. This function is loosely based on pqSocketCheck() at fe-misc.c
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
#include "distributed/connection_management.h"
|
#include "distributed/connection_management.h"
|
||||||
#include "distributed/metadata_cache.h"
|
#include "distributed/metadata_cache.h"
|
||||||
#include "distributed/multi_server_executor.h"
|
#include "distributed/multi_server_executor.h"
|
||||||
|
#include "distributed/remote_commands.h"
|
||||||
#include "distributed/worker_protocol.h"
|
#include "distributed/worker_protocol.h"
|
||||||
#include "lib/stringinfo.h"
|
#include "lib/stringinfo.h"
|
||||||
#include "utils/builtins.h"
|
#include "utils/builtins.h"
|
||||||
|
@ -39,11 +40,11 @@ static void ExecuteCommandsInParallelAndStoreResults(StringInfo *nodeNameArray,
|
||||||
bool *statusArray,
|
bool *statusArray,
|
||||||
StringInfo *resultStringArray,
|
StringInfo *resultStringArray,
|
||||||
int commmandCount);
|
int commmandCount);
|
||||||
static bool GetConnectionStatusAndResult(PGconn *connection, bool *resultStatus,
|
static bool GetConnectionStatusAndResult(MultiConnection *connection, bool *resultStatus,
|
||||||
StringInfo queryResultString);
|
StringInfo queryResultString);
|
||||||
static bool EvaluateQueryResult(PGconn *connection, PGresult *queryResult, StringInfo
|
static bool EvaluateQueryResult(MultiConnection *connection, PGresult *queryResult,
|
||||||
queryResultString);
|
StringInfo queryResultString);
|
||||||
static void StoreErrorMessage(PGconn *connection, StringInfo queryResultString);
|
static void StoreErrorMessage(MultiConnection *connection, StringInfo queryResultString);
|
||||||
static void ExecuteCommandsAndStoreResults(StringInfo *nodeNameArray,
|
static void ExecuteCommandsAndStoreResults(StringInfo *nodeNameArray,
|
||||||
int *nodePortArray,
|
int *nodePortArray,
|
||||||
StringInfo *commandStringArray,
|
StringInfo *commandStringArray,
|
||||||
|
@ -223,7 +224,8 @@ ExecuteCommandsInParallelAndStoreResults(StringInfo *nodeNameArray, int *nodePor
|
||||||
int commmandCount)
|
int commmandCount)
|
||||||
{
|
{
|
||||||
int commandIndex = 0;
|
int commandIndex = 0;
|
||||||
PGconn **connectionArray = palloc0(commmandCount * sizeof(PGconn *));
|
MultiConnection **connectionArray =
|
||||||
|
palloc0(commmandCount * sizeof(MultiConnection *));
|
||||||
int finishedCount = 0;
|
int finishedCount = 0;
|
||||||
|
|
||||||
/* establish connections */
|
/* establish connections */
|
||||||
|
@ -232,14 +234,13 @@ ExecuteCommandsInParallelAndStoreResults(StringInfo *nodeNameArray, int *nodePor
|
||||||
char *nodeName = nodeNameArray[commandIndex]->data;
|
char *nodeName = nodeNameArray[commandIndex]->data;
|
||||||
int nodePort = nodePortArray[commandIndex];
|
int nodePort = nodePortArray[commandIndex];
|
||||||
int connectionFlags = FORCE_NEW_CONNECTION;
|
int connectionFlags = FORCE_NEW_CONNECTION;
|
||||||
MultiConnection *multiConnection =
|
MultiConnection *connection =
|
||||||
GetNodeConnection(connectionFlags, nodeName, nodePort);
|
GetNodeConnection(connectionFlags, nodeName, nodePort);
|
||||||
PGconn *connection = multiConnection->pgConn;
|
|
||||||
StringInfo queryResultString = resultStringArray[commandIndex];
|
StringInfo queryResultString = resultStringArray[commandIndex];
|
||||||
|
|
||||||
statusArray[commandIndex] = true;
|
statusArray[commandIndex] = true;
|
||||||
|
|
||||||
if (PQstatus(multiConnection->pgConn) != CONNECTION_OK)
|
if (PQstatus(connection->pgConn) != CONNECTION_OK)
|
||||||
{
|
{
|
||||||
appendStringInfo(queryResultString, "failed to connect to %s:%d", nodeName,
|
appendStringInfo(queryResultString, "failed to connect to %s:%d", nodeName,
|
||||||
(int) nodePort);
|
(int) nodePort);
|
||||||
|
@ -256,7 +257,7 @@ ExecuteCommandsInParallelAndStoreResults(StringInfo *nodeNameArray, int *nodePor
|
||||||
for (commandIndex = 0; commandIndex < commmandCount; commandIndex++)
|
for (commandIndex = 0; commandIndex < commmandCount; commandIndex++)
|
||||||
{
|
{
|
||||||
int querySent = 0;
|
int querySent = 0;
|
||||||
PGconn *connection = connectionArray[commandIndex];
|
MultiConnection *connection = connectionArray[commandIndex];
|
||||||
char *queryString = commandStringArray[commandIndex]->data;
|
char *queryString = commandStringArray[commandIndex]->data;
|
||||||
StringInfo queryResultString = resultStringArray[commandIndex];
|
StringInfo queryResultString = resultStringArray[commandIndex];
|
||||||
|
|
||||||
|
@ -269,13 +270,17 @@ ExecuteCommandsInParallelAndStoreResults(StringInfo *nodeNameArray, int *nodePor
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
querySent = PQsendQuery(connection, queryString);
|
/*
|
||||||
|
* NB: this intentionally uses PQsendQuery rather than
|
||||||
|
* SendRemoteCommand as multiple commands are allowed.
|
||||||
|
*/
|
||||||
|
querySent = PQsendQuery(connection->pgConn, queryString);
|
||||||
|
|
||||||
if (querySent == 0)
|
if (querySent == 0)
|
||||||
{
|
{
|
||||||
StoreErrorMessage(connection, queryResultString);
|
StoreErrorMessage(connection, queryResultString);
|
||||||
statusArray[commandIndex] = false;
|
statusArray[commandIndex] = false;
|
||||||
CloseConnectionByPGconn(connection);
|
CloseConnection(connection);
|
||||||
connectionArray[commandIndex] = NULL;
|
connectionArray[commandIndex] = NULL;
|
||||||
finishedCount++;
|
finishedCount++;
|
||||||
}
|
}
|
||||||
|
@ -286,7 +291,7 @@ ExecuteCommandsInParallelAndStoreResults(StringInfo *nodeNameArray, int *nodePor
|
||||||
{
|
{
|
||||||
for (commandIndex = 0; commandIndex < commmandCount; commandIndex++)
|
for (commandIndex = 0; commandIndex < commmandCount; commandIndex++)
|
||||||
{
|
{
|
||||||
PGconn *connection = connectionArray[commandIndex];
|
MultiConnection *connection = connectionArray[commandIndex];
|
||||||
StringInfo queryResultString = resultStringArray[commandIndex];
|
StringInfo queryResultString = resultStringArray[commandIndex];
|
||||||
bool success = false;
|
bool success = false;
|
||||||
bool queryFinished = false;
|
bool queryFinished = false;
|
||||||
|
@ -304,7 +309,7 @@ ExecuteCommandsInParallelAndStoreResults(StringInfo *nodeNameArray, int *nodePor
|
||||||
finishedCount++;
|
finishedCount++;
|
||||||
statusArray[commandIndex] = success;
|
statusArray[commandIndex] = success;
|
||||||
connectionArray[commandIndex] = NULL;
|
connectionArray[commandIndex] = NULL;
|
||||||
CloseConnectionByPGconn(connection);
|
CloseConnection(connection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -328,11 +333,11 @@ ExecuteCommandsInParallelAndStoreResults(StringInfo *nodeNameArray, int *nodePor
|
||||||
* reported upon completion of the query.
|
* reported upon completion of the query.
|
||||||
*/
|
*/
|
||||||
static bool
|
static bool
|
||||||
GetConnectionStatusAndResult(PGconn *connection, bool *resultStatus,
|
GetConnectionStatusAndResult(MultiConnection *connection, bool *resultStatus,
|
||||||
StringInfo queryResultString)
|
StringInfo queryResultString)
|
||||||
{
|
{
|
||||||
bool finished = true;
|
bool finished = true;
|
||||||
ConnStatusType connectionStatus = PQstatus(connection);
|
ConnStatusType connectionStatus = PQstatus(connection->pgConn);
|
||||||
int consumeInput = 0;
|
int consumeInput = 0;
|
||||||
PGresult *queryResult = NULL;
|
PGresult *queryResult = NULL;
|
||||||
bool success = false;
|
bool success = false;
|
||||||
|
@ -346,7 +351,7 @@ GetConnectionStatusAndResult(PGconn *connection, bool *resultStatus,
|
||||||
return finished;
|
return finished;
|
||||||
}
|
}
|
||||||
|
|
||||||
consumeInput = PQconsumeInput(connection);
|
consumeInput = PQconsumeInput(connection->pgConn);
|
||||||
if (consumeInput == 0)
|
if (consumeInput == 0)
|
||||||
{
|
{
|
||||||
appendStringInfo(queryResultString, "query result unavailable");
|
appendStringInfo(queryResultString, "query result unavailable");
|
||||||
|
@ -354,14 +359,14 @@ GetConnectionStatusAndResult(PGconn *connection, bool *resultStatus,
|
||||||
}
|
}
|
||||||
|
|
||||||
/* check later if busy */
|
/* check later if busy */
|
||||||
if (PQisBusy(connection) != 0)
|
if (PQisBusy(connection->pgConn) != 0)
|
||||||
{
|
{
|
||||||
finished = false;
|
finished = false;
|
||||||
return finished;
|
return finished;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* query result is available at this point */
|
/* query result is available at this point */
|
||||||
queryResult = PQgetResult(connection);
|
queryResult = PQgetResult(connection->pgConn);
|
||||||
success = EvaluateQueryResult(connection, queryResult, queryResultString);
|
success = EvaluateQueryResult(connection, queryResult, queryResultString);
|
||||||
PQclear(queryResult);
|
PQclear(queryResult);
|
||||||
|
|
||||||
|
@ -379,7 +384,7 @@ GetConnectionStatusAndResult(PGconn *connection, bool *resultStatus,
|
||||||
* error otherwise.
|
* error otherwise.
|
||||||
*/
|
*/
|
||||||
static bool
|
static bool
|
||||||
EvaluateQueryResult(PGconn *connection, PGresult *queryResult,
|
EvaluateQueryResult(MultiConnection *connection, PGresult *queryResult,
|
||||||
StringInfo queryResultString)
|
StringInfo queryResultString)
|
||||||
{
|
{
|
||||||
bool success = false;
|
bool success = false;
|
||||||
|
@ -434,9 +439,9 @@ EvaluateQueryResult(PGconn *connection, PGresult *queryResult,
|
||||||
* otherwise it would return a default error message.
|
* otherwise it would return a default error message.
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
StoreErrorMessage(PGconn *connection, StringInfo queryResultString)
|
StoreErrorMessage(MultiConnection *connection, StringInfo queryResultString)
|
||||||
{
|
{
|
||||||
char *errorMessage = PQerrorMessage(connection);
|
char *errorMessage = PQerrorMessage(connection->pgConn);
|
||||||
if (errorMessage != NULL)
|
if (errorMessage != NULL)
|
||||||
{
|
{
|
||||||
char *firstNewlineIndex = strchr(errorMessage, '\n');
|
char *firstNewlineIndex = strchr(errorMessage, '\n');
|
||||||
|
@ -499,26 +504,27 @@ ExecuteRemoteQueryOrCommand(char *nodeName, uint32 nodePort, char *queryString,
|
||||||
StringInfo queryResultString)
|
StringInfo queryResultString)
|
||||||
{
|
{
|
||||||
int connectionFlags = FORCE_NEW_CONNECTION;
|
int connectionFlags = FORCE_NEW_CONNECTION;
|
||||||
MultiConnection *multiConnection =
|
MultiConnection *connection =
|
||||||
GetNodeConnection(connectionFlags, nodeName, nodePort);
|
GetNodeConnection(connectionFlags, nodeName, nodePort);
|
||||||
PGconn *nodeConnection = multiConnection->pgConn;
|
|
||||||
bool success = false;
|
bool success = false;
|
||||||
PGresult *queryResult = NULL;
|
PGresult *queryResult = NULL;
|
||||||
|
bool raiseInterrupts = true;
|
||||||
|
|
||||||
if (PQstatus(multiConnection->pgConn) != CONNECTION_OK)
|
if (PQstatus(connection->pgConn) != CONNECTION_OK)
|
||||||
{
|
{
|
||||||
appendStringInfo(queryResultString, "failed to connect to %s:%d", nodeName,
|
appendStringInfo(queryResultString, "failed to connect to %s:%d", nodeName,
|
||||||
(int) nodePort);
|
(int) nodePort);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
queryResult = PQexec(nodeConnection, queryString);
|
SendRemoteCommand(connection, queryString);
|
||||||
success = EvaluateQueryResult(nodeConnection, queryResult, queryResultString);
|
queryResult = GetRemoteCommandResult(connection, raiseInterrupts);
|
||||||
|
success = EvaluateQueryResult(connection, queryResult, queryResultString);
|
||||||
|
|
||||||
PQclear(queryResult);
|
PQclear(queryResult);
|
||||||
|
|
||||||
/* close the connection */
|
/* close the connection */
|
||||||
CloseConnection(multiConnection);
|
CloseConnection(connection);
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
|
@ -276,16 +276,21 @@ StartRemoteTransactionAbort(MultiConnection *connection)
|
||||||
Assert(transaction->transactionState != REMOTE_TRANS_INVALID);
|
Assert(transaction->transactionState != REMOTE_TRANS_INVALID);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Clear previous results, so we have a better chance to send
|
* Clear previous results, so we have a better chance to send ROLLBACK
|
||||||
* ROLLBACK [PREPARED];
|
* [PREPARED]. If we've previously sent a PREPARE TRANSACTION, we always
|
||||||
|
* want to wait for that result, as that shouldn't take long and will
|
||||||
|
* reserve resources. But if there's another query running, we don't want
|
||||||
|
* to wait, because a longrunning statement may be running, force it to be
|
||||||
|
* killed in that case.
|
||||||
*/
|
*/
|
||||||
ForgetResults(connection);
|
|
||||||
|
|
||||||
if (transaction->transactionState == REMOTE_TRANS_PREPARING ||
|
if (transaction->transactionState == REMOTE_TRANS_PREPARING ||
|
||||||
transaction->transactionState == REMOTE_TRANS_PREPARED)
|
transaction->transactionState == REMOTE_TRANS_PREPARED)
|
||||||
{
|
{
|
||||||
StringInfoData command;
|
StringInfoData command;
|
||||||
|
|
||||||
|
/* await PREPARE TRANSACTION results, closing the connection would leave it dangling */
|
||||||
|
ForgetResults(connection);
|
||||||
|
|
||||||
initStringInfo(&command);
|
initStringInfo(&command);
|
||||||
appendStringInfo(&command, "ROLLBACK PREPARED '%s'",
|
appendStringInfo(&command, "ROLLBACK PREPARED '%s'",
|
||||||
transaction->preparedName);
|
transaction->preparedName);
|
||||||
|
@ -304,6 +309,14 @@ StartRemoteTransactionAbort(MultiConnection *connection)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
if (!NonblockingForgetResults(connection))
|
||||||
|
{
|
||||||
|
ShutdownConnection(connection);
|
||||||
|
|
||||||
|
/* FinishRemoteTransactionAbort will emit warning */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!SendRemoteCommand(connection, "ROLLBACK"))
|
if (!SendRemoteCommand(connection, "ROLLBACK"))
|
||||||
{
|
{
|
||||||
/* no point in reporting a likely redundant message */
|
/* no point in reporting a likely redundant message */
|
||||||
|
@ -336,16 +349,16 @@ FinishRemoteTransactionAbort(MultiConnection *connection)
|
||||||
ReportResultError(connection, result, WARNING);
|
ReportResultError(connection, result, WARNING);
|
||||||
MarkRemoteTransactionFailed(connection, dontRaiseErrors);
|
MarkRemoteTransactionFailed(connection, dontRaiseErrors);
|
||||||
|
|
||||||
if (transaction->transactionState == REMOTE_TRANS_1PC_ABORTING)
|
if (transaction->transactionState == REMOTE_TRANS_2PC_ABORTING)
|
||||||
{
|
{
|
||||||
ereport(WARNING,
|
WarnAboutLeakedPreparedTransaction(connection, isNotCommit);
|
||||||
(errmsg("failed to abort 2PC transaction \"%s\" on %s:%d",
|
|
||||||
transaction->preparedName, connection->hostname,
|
|
||||||
connection->port)));
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
WarnAboutLeakedPreparedTransaction(connection, isNotCommit);
|
ereport(WARNING,
|
||||||
|
(errmsg("failed to abort 1PC transaction \"%s\" on %s:%d",
|
||||||
|
transaction->preparedName, connection->hostname,
|
||||||
|
connection->port)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -134,6 +134,7 @@ extern MultiConnection * GetConnectionFromPGconn(struct pg_conn *pqConn);
|
||||||
extern void CloseNodeConnectionsAfterTransaction(char *nodeName, int nodePort);
|
extern void CloseNodeConnectionsAfterTransaction(char *nodeName, int nodePort);
|
||||||
extern void CloseConnection(MultiConnection *connection);
|
extern void CloseConnection(MultiConnection *connection);
|
||||||
extern void CloseConnectionByPGconn(struct pg_conn *pqConn);
|
extern void CloseConnectionByPGconn(struct pg_conn *pqConn);
|
||||||
|
extern void ShutdownConnection(MultiConnection *connection);
|
||||||
|
|
||||||
/* dealing with a connection */
|
/* dealing with a connection */
|
||||||
extern void FinishConnectionListEstablishment(List *multiConnectionList);
|
extern void FinishConnectionListEstablishment(List *multiConnectionList);
|
||||||
|
|
|
@ -26,6 +26,7 @@ extern bool LogRemoteCommands;
|
||||||
/* simple helpers */
|
/* simple helpers */
|
||||||
extern bool IsResponseOK(struct pg_result *result);
|
extern bool IsResponseOK(struct pg_result *result);
|
||||||
extern void ForgetResults(MultiConnection *connection);
|
extern void ForgetResults(MultiConnection *connection);
|
||||||
|
extern bool NonblockingForgetResults(MultiConnection *connection);
|
||||||
extern bool SqlStateMatchesCategory(char *sqlStateString, int category);
|
extern bool SqlStateMatchesCategory(char *sqlStateString, int category);
|
||||||
|
|
||||||
/* report errors & warnings */
|
/* report errors & warnings */
|
||||||
|
@ -47,6 +48,9 @@ extern int SendRemoteCommandParams(MultiConnection *connection, const char *comm
|
||||||
extern List * ReadFirstColumnAsText(struct pg_result *queryResult);
|
extern List * ReadFirstColumnAsText(struct pg_result *queryResult);
|
||||||
extern struct pg_result * GetRemoteCommandResult(MultiConnection *connection,
|
extern struct pg_result * GetRemoteCommandResult(MultiConnection *connection,
|
||||||
bool raiseInterrupts);
|
bool raiseInterrupts);
|
||||||
|
extern bool PutRemoteCopyData(MultiConnection *connection, const char *buffer,
|
||||||
|
int nbytes);
|
||||||
|
extern bool PutRemoteCopyEnd(MultiConnection *connection, const char *errormsg);
|
||||||
|
|
||||||
|
|
||||||
#endif /* REMOTE_COMMAND_H */
|
#endif /* REMOTE_COMMAND_H */
|
||||||
|
|
|
@ -0,0 +1,127 @@
|
||||||
|
Parsed test spec with 2 sessions
|
||||||
|
|
||||||
|
starting permutation: s1-timeout s1-sleep10000 s1-reset s1-drop
|
||||||
|
step s1-timeout:
|
||||||
|
SET statement_timeout = '100ms';
|
||||||
|
|
||||||
|
step s1-sleep10000:
|
||||||
|
SELECT pg_sleep(10000) FROM cancel_table WHERE test_id = 1;
|
||||||
|
|
||||||
|
ERROR: canceling statement due to statement timeout
|
||||||
|
step s1-reset:
|
||||||
|
RESET ALL;
|
||||||
|
|
||||||
|
step s1-drop:
|
||||||
|
|
||||||
|
DROP TABLE cancel_table;
|
||||||
|
|
||||||
|
|
||||||
|
starting permutation: s1-timeout s1-sleep10000 s1-reset s2-drop
|
||||||
|
step s1-timeout:
|
||||||
|
SET statement_timeout = '100ms';
|
||||||
|
|
||||||
|
step s1-sleep10000:
|
||||||
|
SELECT pg_sleep(10000) FROM cancel_table WHERE test_id = 1;
|
||||||
|
|
||||||
|
ERROR: canceling statement due to statement timeout
|
||||||
|
step s1-reset:
|
||||||
|
RESET ALL;
|
||||||
|
|
||||||
|
step s2-drop:
|
||||||
|
|
||||||
|
DROP TABLE cancel_table;
|
||||||
|
|
||||||
|
|
||||||
|
starting permutation: s1-timeout s1-begin s1-sleep10000 s1-rollback s1-reset s1-drop
|
||||||
|
step s1-timeout:
|
||||||
|
SET statement_timeout = '100ms';
|
||||||
|
|
||||||
|
step s1-begin:
|
||||||
|
BEGIN;
|
||||||
|
|
||||||
|
step s1-sleep10000:
|
||||||
|
SELECT pg_sleep(10000) FROM cancel_table WHERE test_id = 1;
|
||||||
|
|
||||||
|
ERROR: canceling statement due to statement timeout
|
||||||
|
step s1-rollback:
|
||||||
|
ROLLBACK;
|
||||||
|
|
||||||
|
step s1-reset:
|
||||||
|
RESET ALL;
|
||||||
|
|
||||||
|
step s1-drop:
|
||||||
|
|
||||||
|
DROP TABLE cancel_table;
|
||||||
|
|
||||||
|
|
||||||
|
starting permutation: s1-timeout s1-begin s1-sleep10000 s1-rollback s1-reset s2-drop
|
||||||
|
step s1-timeout:
|
||||||
|
SET statement_timeout = '100ms';
|
||||||
|
|
||||||
|
step s1-begin:
|
||||||
|
BEGIN;
|
||||||
|
|
||||||
|
step s1-sleep10000:
|
||||||
|
SELECT pg_sleep(10000) FROM cancel_table WHERE test_id = 1;
|
||||||
|
|
||||||
|
ERROR: canceling statement due to statement timeout
|
||||||
|
step s1-rollback:
|
||||||
|
ROLLBACK;
|
||||||
|
|
||||||
|
step s1-reset:
|
||||||
|
RESET ALL;
|
||||||
|
|
||||||
|
step s2-drop:
|
||||||
|
|
||||||
|
DROP TABLE cancel_table;
|
||||||
|
|
||||||
|
|
||||||
|
starting permutation: s1-timeout s1-begin s1-update1 s1-sleep10000 s1-rollback s1-reset s1-drop
|
||||||
|
step s1-timeout:
|
||||||
|
SET statement_timeout = '100ms';
|
||||||
|
|
||||||
|
step s1-begin:
|
||||||
|
BEGIN;
|
||||||
|
|
||||||
|
step s1-update1:
|
||||||
|
UPDATE cancel_table SET data = '' WHERE test_id = 1;
|
||||||
|
|
||||||
|
step s1-sleep10000:
|
||||||
|
SELECT pg_sleep(10000) FROM cancel_table WHERE test_id = 1;
|
||||||
|
|
||||||
|
ERROR: canceling statement due to statement timeout
|
||||||
|
step s1-rollback:
|
||||||
|
ROLLBACK;
|
||||||
|
|
||||||
|
step s1-reset:
|
||||||
|
RESET ALL;
|
||||||
|
|
||||||
|
step s1-drop:
|
||||||
|
|
||||||
|
DROP TABLE cancel_table;
|
||||||
|
|
||||||
|
|
||||||
|
starting permutation: s1-timeout s1-begin s1-update1 s1-sleep10000 s1-rollback s1-reset s2-drop
|
||||||
|
step s1-timeout:
|
||||||
|
SET statement_timeout = '100ms';
|
||||||
|
|
||||||
|
step s1-begin:
|
||||||
|
BEGIN;
|
||||||
|
|
||||||
|
step s1-update1:
|
||||||
|
UPDATE cancel_table SET data = '' WHERE test_id = 1;
|
||||||
|
|
||||||
|
step s1-sleep10000:
|
||||||
|
SELECT pg_sleep(10000) FROM cancel_table WHERE test_id = 1;
|
||||||
|
|
||||||
|
ERROR: canceling statement due to statement timeout
|
||||||
|
step s1-rollback:
|
||||||
|
ROLLBACK;
|
||||||
|
|
||||||
|
step s1-reset:
|
||||||
|
RESET ALL;
|
||||||
|
|
||||||
|
step s2-drop:
|
||||||
|
|
||||||
|
DROP TABLE cancel_table;
|
||||||
|
|
|
@ -5,6 +5,6 @@ test: isolation_add_node_vs_reference_table_operations
|
||||||
# that come later can be parallelized
|
# that come later can be parallelized
|
||||||
test: isolation_cluster_management
|
test: isolation_cluster_management
|
||||||
|
|
||||||
test: isolation_dml_vs_repair isolation_copy_placement_vs_copy_placement
|
test: isolation_dml_vs_repair isolation_copy_placement_vs_copy_placement isolation_cancellation
|
||||||
test: isolation_concurrent_dml isolation_data_migration
|
test: isolation_concurrent_dml isolation_data_migration
|
||||||
test: isolation_drop_shards isolation_copy_placement_vs_modification
|
test: isolation_drop_shards isolation_copy_placement_vs_modification
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
# Tests around cancelling statements. As we can't trigger cancel
|
||||||
|
# interrupts directly, we use statement_timeout instead, which largely
|
||||||
|
# behaves the same as proper cancellation.
|
||||||
|
|
||||||
|
setup
|
||||||
|
{
|
||||||
|
CREATE TABLE cancel_table (test_id integer NOT NULL, data text);
|
||||||
|
SELECT create_distributed_table('cancel_table', 'test_id', 'hash');
|
||||||
|
INSERT INTO cancel_table VALUES(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
teardown
|
||||||
|
{
|
||||||
|
DROP TABLE IF EXISTS cancel_table;
|
||||||
|
}
|
||||||
|
|
||||||
|
session "s1"
|
||||||
|
|
||||||
|
step "s1-begin"
|
||||||
|
{
|
||||||
|
BEGIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
step "s1-commit"
|
||||||
|
{
|
||||||
|
COMMIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
step "s1-rollback"
|
||||||
|
{
|
||||||
|
ROLLBACK;
|
||||||
|
}
|
||||||
|
|
||||||
|
step "s1-sleep10000"
|
||||||
|
{
|
||||||
|
SELECT pg_sleep(10000) FROM cancel_table WHERE test_id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
step "s1-timeout"
|
||||||
|
{
|
||||||
|
SET statement_timeout = '100ms';
|
||||||
|
}
|
||||||
|
|
||||||
|
step "s1-update1"
|
||||||
|
{
|
||||||
|
UPDATE cancel_table SET data = '' WHERE test_id = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
step "s1-reset"
|
||||||
|
{
|
||||||
|
RESET ALL;
|
||||||
|
}
|
||||||
|
|
||||||
|
step "s1-drop"
|
||||||
|
{
|
||||||
|
|
||||||
|
DROP TABLE cancel_table;
|
||||||
|
}
|
||||||
|
|
||||||
|
session "s2"
|
||||||
|
|
||||||
|
step "s2-drop"
|
||||||
|
{
|
||||||
|
|
||||||
|
DROP TABLE cancel_table;
|
||||||
|
}
|
||||||
|
|
||||||
|
# check that statement cancel works for plain selects, drop table
|
||||||
|
# afterwards to make sure sleep on workers is cancelled (thereby not
|
||||||
|
# preventing drop via locks)
|
||||||
|
permutation "s1-timeout" "s1-sleep10000" "s1-reset" "s1-drop"
|
||||||
|
permutation "s1-timeout" "s1-sleep10000" "s1-reset" "s2-drop"
|
||||||
|
|
||||||
|
# check that statement cancel works for selects in transaction
|
||||||
|
permutation "s1-timeout" "s1-begin" "s1-sleep10000" "s1-rollback" "s1-reset" "s1-drop"
|
||||||
|
permutation "s1-timeout" "s1-begin" "s1-sleep10000" "s1-rollback" "s1-reset" "s2-drop"
|
||||||
|
|
||||||
|
# check that statement cancel works for selects in transaction, that previously wrote
|
||||||
|
permutation "s1-timeout" "s1-begin" "s1-update1" "s1-sleep10000" "s1-rollback" "s1-reset" "s1-drop"
|
||||||
|
permutation "s1-timeout" "s1-begin" "s1-update1" "s1-sleep10000" "s1-rollback" "s1-reset" "s2-drop"
|
Loading…
Reference in New Issue