8
0
mirror of https://github.com/FirebirdSQL/firebird.git synced 2025-01-22 20:43:02 +01:00

New feature CORE-5488 : Timeouts for running SQL statements and idle connections

This commit is contained in:
hvlad 2017-02-22 14:30:57 +02:00
parent 4deeaac7c7
commit 2c49e6fcf2
60 changed files with 2078 additions and 50 deletions

View File

@ -526,6 +526,30 @@
#DeadlockTimeout = 10
# ----------------------------
#
# Set number of seconds after which statement execution will be automatically
# cancelled by the engine. Zero means no timeout is set.
#
# Per-database configurable.
#
# Type: integer
#
#StatementTimeout = 0
# ----------------------------
#
# Set number of minutes after which idle attachment will be disconnected by the
# engine. Zero means no timeout is set.
#
# Per-database configurable.
#
# Type: integer
#
#ConnectionIdleTimeout = 0
# ----------------------------
#
# How often the pages are flushed on disk

View File

@ -222,6 +222,7 @@ EXPORTS
isc_dsql_release @199
isc_dsql_set_cursor_name @200
isc_dsql_sql_info @201
fb_dsql_set_timeout
; ESQL functions

View File

@ -0,0 +1,113 @@
Timeouts for idle database sessions.
Author:
Vlad Khorsun <hvlad@users.sf.net>
Description:
The feature allows to automatically close user connection after period of inactivity.
It could be used by database administrators to forcibly close old inactive connections
and free resources it occupies. Application and tools developers also could find it as
easy replacement of self-made control for the connection life time.
It is recommended (but not required) to set idle timeout to reasonable big value, such
as few hours. By default it is not enabled.
The feature works as below
- when user API call leaves engine, special idle timer assotiated with current connection
is started
- when user API call enters engine, idle timer is stopped
- when idle time is fired engine immediately closes the connection in the same way as
with asyncronous connection cancellation:
- all active statements and cursors are closed
- all active transactions are rolled back
- network connection is not closed at this moment. It allows client application to get
exact error code on next API call. Network connection will be closed by the server
side after error is reported or due to network timeout if client side disconnects.
- idle session timeout could be set:
- at database level, by setting value in firebird.conf (or databases.conf) by database
administrator
scope - all user connections, except of system connections (garbage collector, cache
writer, etc)
units - minutes
- at connection level, using API and\or new SQL statement (see below)
scope - given connection
units - up to seconds
- effective value of idle timeout is evaluated every time user API call leaves the engine
as:
- if not set at connection level, look at database level
- in any case can't be greater than value set at database level
i.e. value of idle timeout could be overriden by application developer at given
connection but it can't relax limit set by DBA (in config)
- zero timeout means no timeout, i.e. idle timer will not start
- while idle timeout is set in seconds at API level, we can't promise absolute precision.
With high load it could be less precise. The only guarantee is that timeout will not
fire before specified moment.
- if connection was cancelled, next user API call returns error isc_att_shutdown with
secondary error code specifying exact reason:
isc_att_shut_killed: Killed by database administrator
isc_att_shut_idle: Idle timeout expired
isc_att_shut_db_down: Database is shutdown
isc_att_shut_engine: Engine is shutdown
Support at configuration level (firebird.conf and\or databases.conf)
New setting "ConnectionIdleTimeout": set number of minutes after which idle connection
will be disconnected by the engine. Zero means no timeout is set.
Per-database configurable. Type: integer. Default value is 0.
Support at API level
- get\set idle connection timeout, seconds
interface Attachment
uint getIdleTimeout(Status status);
void setIdleTimeout(Status status, uint timeOut);
- get idle connection timeout at config and\or connection level is possible
using isc_database_info() API with new info tags:
- fb_info_ses_idle_timeout_db value set at config level
- fb_info_ses_idle_timeout_att value set at given connection level
- fb_info_ses_idle_timeout_run actual timeout value for given connection
evaluated considering values set at config and
connection levels, see "effective value of idle
timeout" above
Remote client implementation notes:
- Attachment::setIdleTimeout() issued "SET SESSION IDLE TIMEOUT" SQL statement
- Attachment::getIdleTimeout() calls isc_database_info() with
fb_info_ses_idle_timeout_att tag
If remote server doesn't support idle connection timeouts (protocol version less than 15):
- Attachment::setIdleTimeout() will return isc_wish_list error
- Attachment::getIdleTimeout() will return zero and set isc_wish_list error
- isc_database_info() will return isc_info_error tag in info buffer (as usual).
Support in SQL
- New SQL statement allows to set idle connection timeout at connection level:
SET SESSION IDLE TIMEOUT <value> [HOUR | MINUTE | SECOND]
if timepart is not set, default is MINUTE.
This statement could run outside of transaction control and immediately effective.
- Context variables
Context 'SYSTEM' have new variable: 'SESSION_IDLE_TIMEOUT'. It contains current value
of idle connection timeout that was set at connection level, or zero, if timeout was not
set.
- Monitoring tables
MON$ATTACHMENTS
MON$IDLE_TIMEOUT Connection level idle timeout
MON$IDLE_TIMER Idle timer expiration time
MON$IDLE_TIMEOUT contains timeout value set at connection level, in seconds. Zero, if
timeout is not set.
MON$IDLE_TIMER contains NULL value if idle timeout was not set or if timer is not
running.

View File

@ -0,0 +1,159 @@
Timeouts for running SQL statements.
Author:
Vlad Khorsun <hvlad@users.sf.net>
Description:
The feature allows to set timeout for SQL statement, i.e. it allows to automatically
stop execution of SQL statement when it running longer than given timeout value.
The feature could be useful for:
- database administrators get instrument to limit heavy queries from consuming too
much resources
- application developers could use statement timeout when creating\debugging complex
queries with unknown in advance execution time
- testers could use statement timeout to detect long running queries and ensure finite
run time of the test suites
- and so on
From the end user point of view feature have following details:
- when statement starts execution (or cursor is opened), engine starts special timer
- fetch doesn't reset timer
- timer is stopped when statement execution finished (or last record is fetched)
- when timer is fired
- if statement execution is active, it stops at closest possible moment
- if statement is not active currently (between fetches, for example), it is marked
as cancelled and next fetch will actually break execution and returns with error
- timeout value could be set:
- at database level, by setting value in firebird.conf (or databases.conf) by database
administrator
scope - all statements in all connections
units - seconds
- at connection level, using API and\or new SQL statement (see below)
scope - all statements at given connection
units - up to milliseconds
- at statement level, using API
scope - given statement
units - milliseconds
- effective value of timeout is evaluated every time statement starts execution
(or cursor is opened) as:
- if not set at statement level, look at connection level
- if not set at connection level, look at database level
- in any case can't be greater than value set at database level
i.e. value of statement timeout could be overriden by application developer at lower
scope but it can't relax limit set by DBA (in config)
- zero timeout means no timeout, i.e. timer will not start
- while statement timeout is set in milliseconds at API level, we can't promise
absolute precision. With big load it could be less precise. The only guarantee
is that timeout will not fire before specified moment.
- if statement execution is cancelled due to timeout, then API call returns error
isc_cancelled with secondary error code specifying exact reason:
- isc_cfg_stmt_timeout: Config level timeout expired
- isc_att_stmt_timeout: Attachment level timeout expired
- isc_req_stmt_timeout: Statement level timeout expired
- statement timeout is ignored for all internal queries issued by engine itself
- statement timeout is ignored for DDL statements
- client application could wait more time than set by timeout value if engine
need to undo many actions due to statement cancellation
- when engine run EXECUTE STATEMENT statement, it pass rest of currently active timeout
to the new statement. If external (remote) engine doesn't support statement timeouts,
local engine silently ignores corresponding error
- when engine acquires some lock of lock manager, it could lower value of lock timeout
using rest of the currently active statement timeout, if possible. Due to lock manager
internals rest of statement timeout will be rounded up to the whole seconds.
Support at configuration level (firebird.conf and\or databases.conf)
New setting "StatementTimeout": set number of seconds after which statement execution
will be automatically cancelled by the engine. Zero means no timeout is set.
Per-database configurable. Type: integer. Default value is 0.
Support at API level
- get\set statement execution timeout at connection level, milliseconds:
interface Attachment
uint getStatementTimeout(Status status);
void setStatementTimeout(Status status, uint timeOut);
- get\set statement execution timeout at statement level, milliseconds:
interface Statement
uint getTimeout(Status status);
void setTimeout(Status status, uint timeOut);
- set statement execution timeout at statement level using ISC API, milliseconds:
ISC_STATUS ISC_EXPORT fb_dsql_set_timeout(ISC_STATUS*, isc_stmt_handle*, ISC_ULONG);
- get statement execution timeout at config and\or connection level is possible
using isc_database_info() API with new info tags:
- fb_info_statement_timeout_db
- fb_info_statement_timeout_att
- get statement execution timeout at statement level is possible using isc_dsql_info()
API with new info tags:
- isc_info_sql_stmt_timeout_user timeout value of given statement
- isc_info_sql_stmt_timeout_run actual timeout value of given statement
evaluated considering values set at config, connection and statement levels, see
"effective value of timeout" above. Valid only when timeout timer is running, i.e.
for currently executed statements.
Remote client implementation notes:
- Attachment::setStatementTimeout() issued "SET STATEMENT TIMEOUT" SQL statement
- Attachment::getStatementTimeout() calls isc_database_info() with
fb_info_statement_timeout_att tag
- Statement::setTimeout() save timeout value given and pass it with op_execute
and op_execute2 packets
- Statement::getTimeout() returns saved timeout value
- fb_dsql_set_timeout() is a wrapper over Statement::setTimeout()
If remote server doesn't support statement timeouts (protocol version less than 15):
- "set" functions will return isc_wish_list error
- "get" functions will return zero and set isc_wish_list error
- "info" functions will return isc_info_error tag in info buffer (as usual).
Support in SQL
- New SQL statement allows to set set statement execution timeout at connection level:
SET STATEMENT TIMEOUT <value> [HOUR | MINUTE | SECOND | MILLISECOND]
if timepart is not set, default is SECOND.
This statement could run outside of transaction control and immediately effective.
- Context variables
Context 'SYSTEM' have new variable: 'STATEMENT_TIMEOUT'. It contains current value of
statement execution timeout that was set at connection level, or zero, if timeout was
not set.
- Monitoring tables
MON$ATTACHMENTS
MON$STATEMENT_TIMEOUT Connection level statement timeout
MON$STATEMENTS
MON$STATEMENT_TIMEOUT Statement level statement timeout
MON$STATEMENT_TIMER Timeout timer expiration time
MON$STATEMENT_TIMEOUT contains timeout values set at connection\statement level,
in milliseconds. Zero, if timeout is not set.
MON$STATEMENT_TIMER contains NULL value if timeout was not set or if timer is not
running.
Support in ISQL tool
New ISQL command is introduced:
SET LOCAL_TIMEOUT <int>
It allows to set statement execution timeout (in milliseconds) for the next statement.
After statement execution it automatically reset to zero.

View File

@ -1664,6 +1664,20 @@ C --
PARAMETER (GDS__dsql_window_duplicate = 335545125)
INTEGER*4 GDS__sql_too_long
PARAMETER (GDS__sql_too_long = 335545126)
INTEGER*4 GDS__cfg_stmt_timeout
PARAMETER (GDS__cfg_stmt_timeout = 335545127)
INTEGER*4 GDS__att_stmt_timeout
PARAMETER (GDS__att_stmt_timeout = 335545128)
INTEGER*4 GDS__req_stmt_timeout
PARAMETER (GDS__req_stmt_timeout = 335545129)
INTEGER*4 GDS__att_shut_killed
PARAMETER (GDS__att_shut_killed = 335545130)
INTEGER*4 GDS__att_shut_idle
PARAMETER (GDS__att_shut_idle = 335545131)
INTEGER*4 GDS__att_shut_db_down
PARAMETER (GDS__att_shut_db_down = 335545132)
INTEGER*4 GDS__att_shut_engine
PARAMETER (GDS__att_shut_engine = 335545133)
INTEGER*4 GDS__gfix_db_name
PARAMETER (GDS__gfix_db_name = 335740929)
INTEGER*4 GDS__gfix_invalid_sw

View File

@ -1659,6 +1659,20 @@ const
gds_dsql_window_duplicate = 335545125;
isc_sql_too_long = 335545126;
gds_sql_too_long = 335545126;
isc_cfg_stmt_timeout = 335545127;
gds_cfg_stmt_timeout = 335545127;
isc_att_stmt_timeout = 335545128;
gds_att_stmt_timeout = 335545128;
isc_req_stmt_timeout = 335545129;
gds_req_stmt_timeout = 335545129;
isc_att_shut_killed = 335545130;
gds_att_shut_killed = 335545130;
isc_att_shut_idle = 335545131;
gds_att_shut_idle = 335545131;
isc_att_shut_db_down = 335545132;
gds_att_shut_db_down = 335545132;
isc_att_shut_engine = 335545133;
gds_att_shut_engine = 335545133;
isc_gfix_db_name = 335740929;
gds_gfix_db_name = 335740929;
isc_gfix_invalid_sw = 335740930;

View File

@ -197,7 +197,9 @@ const Config::ConfigEntry Config::entries[MAX_CONFIG_KEY] =
{TYPE_BOOLEAN, "WireCompression", (ConfigValue) false},
{TYPE_INTEGER, "MaxIdentifierByteLength", (ConfigValue) -1},
{TYPE_INTEGER, "MaxIdentifierCharLength", (ConfigValue) -1},
{TYPE_BOOLEAN, "CryptSecurityDatabase", (ConfigValue) false}
{TYPE_BOOLEAN, "CryptSecurityDatabase", (ConfigValue) false},
{TYPE_INTEGER, "StatementTimeout", (ConfigValue) 0},
{TYPE_INTEGER, "ConnectionIdleTimeout", (ConfigValue) 0}
};
/******************************************************************************
@ -818,3 +820,13 @@ bool Config::getCryptSecurityDatabase() const
{
return get<bool>(KEY_ENCRYPT_SECURITY_DATABASE);
}
unsigned int Config::getStatementTimeout() const
{
return get<unsigned int>(KEY_STMT_TIMEOUT);
}
unsigned int Config::getConnIdleTimeout() const
{
return get<unsigned int>(KEY_CONN_IDLE_TIMEOUT);
}

View File

@ -143,6 +143,8 @@ public:
KEY_MAX_IDENTIFIER_BYTE_LENGTH,
KEY_MAX_IDENTIFIER_CHAR_LENGTH,
KEY_ENCRYPT_SECURITY_DATABASE,
KEY_STMT_TIMEOUT,
KEY_CONN_IDLE_TIMEOUT,
MAX_CONFIG_KEY // keep it last
};
@ -352,6 +354,11 @@ public:
int getMaxIdentifierCharLength() const;
bool getCryptSecurityDatabase() const;
// set in seconds
unsigned int getStatementTimeout() const;
// set in minutes
unsigned int getConnIdleTimeout() const;
};
// Implementation of interface to access master configuration file

View File

@ -281,6 +281,24 @@ public:
};
class SetSessionNode : public Node
{
public:
enum Type {TYPE_IDLE_TIMEOUT, TYPE_STMT_TIMEOUT};
SetSessionNode(MemoryPool& pool, Type aType, ULONG aVal, UCHAR blr_timepart);
public:
virtual Firebird::string internalPrint(NodePrinter& printer) const;
virtual SetSessionNode* dsqlPass(DsqlCompilerScratch* dsqlScratch);
virtual void execute(thread_db* tdbb, dsql_req* request) const;
private:
Type m_type;
ULONG m_value;
};
class DmlNode : public Node
{
public:

View File

@ -3448,6 +3448,10 @@ const StmtNode* ExecStatementNode::execute(thread_db* tdbb, jrd_req* request, Ex
const MetaName* const* inpNames = inputNames ? inputNames->begin() : NULL;
stmt->prepare(tdbb, tran, sSql, inputNames != NULL);
const TimeoutTimer* timer = tdbb->getTimeoutTimer();
if (timer)
stmt->setTimeout(tdbb, timer->timeToExpire());
if (stmt->isSelectable())
stmt->open(tdbb, tran, inpNames, inputs, !innerStmt);
else
@ -7973,6 +7977,78 @@ void SetRoleNode::execute(thread_db* tdbb, dsql_req* request, jrd_tra** transact
}
//--------------------
SetSessionNode::SetSessionNode(MemoryPool& pool, Type aType, ULONG aVal, UCHAR blr_timepart)
: Node(pool),
m_type(aType),
m_value(0)
{
// TYPE_IDLE_TIMEOUT should be set in seconds
// TYPE_STMT_TIMEOUT should be set in milliseconds
ULONG mult = 1;
switch (blr_timepart)
{
case blr_extract_hour:
mult = (aType == TYPE_IDLE_TIMEOUT) ? 3660 : 3660000;
break;
case blr_extract_minute:
mult = (aType == TYPE_IDLE_TIMEOUT) ? 60 : 60000;
break;
case blr_extract_second:
mult = (aType == TYPE_IDLE_TIMEOUT) ? 1 : 1000;
break;
case blr_extract_millisecond:
if (aType == TYPE_IDLE_TIMEOUT)
Arg::Gds(isc_invalid_extractpart_time).raise();
mult = 1;
break;
default:
Arg::Gds(isc_invalid_extractpart_time).raise();
break;
}
m_value = aVal * mult;
}
string SetSessionNode::internalPrint(NodePrinter& printer) const
{
Node::internalPrint(printer);
NODE_PRINT(printer, m_type);
NODE_PRINT(printer, m_value);
return "SetSessionNode";
}
SetSessionNode* SetSessionNode::dsqlPass(DsqlCompilerScratch* dsqlScratch)
{
dsqlScratch->getStatement()->setType(DsqlCompiledStatement::TYPE_SET_SESSION);
return this;
}
void SetSessionNode::execute(thread_db* tdbb, dsql_req* request) const
{
Attachment* att = tdbb->getAttachment();
switch (m_type)
{
case TYPE_IDLE_TIMEOUT:
att->setIdleTimeout(m_value);
break;
case TYPE_STMT_TIMEOUT:
att->setStatementTimeout(m_value);
break;
}
}
//--------------------

View File

@ -147,9 +147,10 @@ void DSQL_execute(thread_db* tdbb,
Arg::Gds(isc_bad_req_handle));
}
// Only allow NULL trans_handle if we're starting a transaction
// Only allow NULL trans_handle if we're starting a transaction or set session properties
if (!*tra_handle && statement->getType() != DsqlCompiledStatement::TYPE_START_TRANS)
if (!*tra_handle && statement->getType() != DsqlCompiledStatement::TYPE_START_TRANS &&
statement->getType() != DsqlCompiledStatement::TYPE_SET_SESSION)
{
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-901) <<
Arg::Gds(isc_bad_trans_handle));
@ -274,6 +275,10 @@ bool DsqlDmlRequest::fetch(thread_db* tdbb, UCHAR* msgBuffer)
Jrd::Attachment* att = req_dbb->dbb_attachment;
TraceDSQLFetch trace(att, this);
thread_db::TimerGuard timerGuard(tdbb, req_timer, false);
if (req_timer && req_timer->expired())
tdbb->checkCancelState(true);
UCHAR* dsqlMsgBuffer = req_msg_buffers[message->msg_buffer_number];
JRD_receive(tdbb, req_request, message->msg_number, message->msg_length, dsqlMsgBuffer);
@ -283,6 +288,9 @@ bool DsqlDmlRequest::fetch(thread_db* tdbb, UCHAR* msgBuffer)
if (eofReached)
{
if (req_timer)
req_timer->stop();
delayedFormat = NULL;
trace.fetch(true, ITracePlugin::RESULT_SUCCESS);
return false;
@ -535,9 +543,10 @@ void DSQL_execute_immediate(thread_db* tdbb, Jrd::Attachment* attachment, jrd_tr
const DsqlCompiledStatement* statement = request->getStatement();
// Only allow NULL trans_handle if we're starting a transaction
// Only allow NULL trans_handle if we're starting a transaction or set session properties
if (!*tra_handle && statement->getType() != DsqlCompiledStatement::TYPE_START_TRANS)
if (!*tra_handle && statement->getType() != DsqlCompiledStatement::TYPE_START_TRANS &&
statement->getType() != DsqlCompiledStatement::TYPE_SET_SESSION)
{
ERRD_post(Arg::Gds(isc_sqlerr) << Arg::Num(-901) <<
Arg::Gds(isc_bad_trans_handle));
@ -676,6 +685,12 @@ void DsqlDmlRequest::execute(thread_db* tdbb, jrd_tra** traHandle,
// manager know statement parameters values
TraceDSQLExecute trace(req_dbb->dbb_attachment, this);
// Setup and start timeout timer
const bool have_cursor = reqTypeWithCursor(statement->getType()) && !singleton;
setupTimer(tdbb);
thread_db::TimerGuard timerGuard(tdbb, req_timer, !have_cursor);
if (!message)
JRD_start(tdbb, req_request, req_transaction);
else
@ -798,7 +813,6 @@ void DsqlDmlRequest::execute(thread_db* tdbb, jrd_tra** traHandle,
break;
}
const bool have_cursor = reqTypeWithCursor(statement->getType()) && !singleton;
trace.finish(have_cursor, ITracePlugin::RESULT_SUCCESS);
}
@ -896,16 +910,36 @@ void DsqlTransactionRequest::dsqlPass(thread_db* tdbb, DsqlCompilerScratch* scra
req_traced = false;
}
// Execute a dynamic SQL statement.
void DsqlTransactionRequest::execute(thread_db* tdbb, jrd_tra** traHandle,
Firebird::IMessageMetadata* inMetadata, const UCHAR* inMsg,
Firebird::IMessageMetadata* outMetadata, UCHAR* outMsg,
bool singleton)
IMessageMetadata* /*inMetadata*/, const UCHAR* /*inMsg*/,
IMessageMetadata* /*outMetadata*/, UCHAR* /*outMsg*/,
bool /*singleton*/)
{
node->execute(tdbb, this, traHandle);
}
void SetSessionRequest::execute(thread_db* tdbb, jrd_tra** /*traHandle*/,
IMessageMetadata* /*inMetadata*/, const UCHAR* /*inMsg*/,
IMessageMetadata* /*outMetadata*/, UCHAR* /*outMsg*/,
bool /*singleton*/)
{
node->execute(tdbb, this);
}
void SetSessionRequest::dsqlPass(thread_db* tdbb, DsqlCompilerScratch* scratch,
ntrace_result_t* /*traceResult*/)
{
node = Node::doDsqlPass(scratch, node);
// Don't trace pseudo-statements (without requests associated).
req_traced = false;
}
/**
get_request_info
@ -1530,7 +1564,12 @@ dsql_req::dsql_req(MemoryPool& pool)
req_cursor_name(req_pool),
req_cursor(NULL),
req_user_descs(req_pool),
req_traced(false)
req_traced(false),
req_timeout(0)
{
}
dsql_req::~dsql_req()
{
}
@ -1560,11 +1599,93 @@ bool dsql_req::fetch(thread_db* /*tdbb*/, UCHAR* /*msgBuffer*/)
return false; // avoid warning
}
unsigned int dsql_req::getTimeout()
{
return req_timeout;
}
unsigned int dsql_req::getActualTimeout()
{
if (req_timer)
return req_timer->getValue();
return 0;
}
void dsql_req::setTimeout(unsigned int timeOut)
{
req_timeout = timeOut;
}
void dsql_req::setupTimer(thread_db* tdbb)
{
if (statement->getFlags() & JrdStatement::FLAG_INTERNAL)
return;
if (req_request)
{
req_request->req_timeout = this->req_timeout;
fb_assert(!req_request->req_caller);
if (req_request->req_caller)
{
if (req_timer)
req_timer->setup(0, 0);
return;
}
}
Database* dbb = tdbb->getDatabase();
Attachment* att = tdbb->getAttachment();
ISC_STATUS toutErr = isc_cfg_stmt_timeout;
unsigned int timeOut = dbb->dbb_config->getStatementTimeout() * 1000;
if (req_timeout)
{
if (!timeOut || req_timeout < timeOut)
{
timeOut = req_timeout;
toutErr = isc_req_stmt_timeout;
}
}
else
{
const unsigned int attTout = att->getStatementTimeout();
if (!timeOut || attTout && attTout < timeOut)
{
timeOut = attTout;
toutErr = isc_att_stmt_timeout;
}
}
if (!req_timer && timeOut)
{
req_timer = FB_NEW TimeoutTimer();
req_request->req_timer = this->req_timer;
}
if (req_timer)
{
req_timer->setup(timeOut, toutErr);
req_timer->start();
}
}
// Release a dynamic request.
void dsql_req::destroy(thread_db* tdbb, dsql_req* request, bool drop)
{
SET_TDBB(tdbb);
if (request->req_timer)
{
request->req_timer->stop();
request->req_timer = NULL;
}
// If request is parent, orphan the children and release a portion of their requests
for (FB_SIZE_T i = 0; i < request->cursors.getCount(); ++i)
@ -1757,6 +1878,7 @@ static void sql_info(thread_db* tdbb,
case DsqlCompiledStatement::TYPE_CREATE_DB:
case DsqlCompiledStatement::TYPE_DDL:
case DsqlCompiledStatement::TYPE_SET_ROLE:
case DsqlCompiledStatement::TYPE_SET_SESSION:
number = isc_info_sql_stmt_ddl;
break;
case DsqlCompiledStatement::TYPE_COMMIT:
@ -1828,6 +1950,16 @@ static void sql_info(thread_db* tdbb,
return;
break;
case isc_info_sql_stmt_timeout_user:
case isc_info_sql_stmt_timeout_run:
value = (item == isc_info_sql_stmt_timeout_user) ?
request->getTimeout() : request->getActualTimeout();
length = put_vax_long(buffer, value);
if (!(info = put_item(item, length, buffer, info, end_info)))
return;
break;
case isc_info_sql_get_plan:
case isc_info_sql_explain_plan:
{

View File

@ -80,6 +80,7 @@ namespace Jrd
class RseNode;
class StmtNode;
class TransactionNode;
class SetSessionNode;
class ValueExprNode;
class ValueListNode;
class WindowClause;
@ -93,6 +94,7 @@ namespace Jrd
class dsql_par;
class dsql_map;
class dsql_intlsym;
class TimeoutTimer;
typedef Firebird::Stack<dsql_ctx*> DsqlContextStack;
@ -431,7 +433,7 @@ public:
TYPE_SELECT, TYPE_SELECT_UPD, TYPE_INSERT, TYPE_DELETE, TYPE_UPDATE, TYPE_UPDATE_CURSOR,
TYPE_DELETE_CURSOR, TYPE_COMMIT, TYPE_ROLLBACK, TYPE_CREATE_DB, TYPE_DDL, TYPE_START_TRANS,
TYPE_EXEC_PROCEDURE, TYPE_COMMIT_RETAIN, TYPE_ROLLBACK_RETAIN, TYPE_SET_GENERATOR,
TYPE_SAVEPOINT, TYPE_EXEC_BLOCK, TYPE_SELECT_BLOCK, TYPE_SET_ROLE
TYPE_SAVEPOINT, TYPE_EXEC_BLOCK, TYPE_SELECT_BLOCK, TYPE_SET_ROLE, TYPE_SET_SESSION
};
// Statement flags.
@ -556,6 +558,18 @@ public:
virtual void setDelayedFormat(thread_db* tdbb, Firebird::IMessageMetadata* metadata);
// Get session-level timeout, milliseconds
unsigned int getTimeout();
// Set session-level timeout, milliseconds
void setTimeout(unsigned int timeOut);
// Get actual timeout, milliseconds
unsigned int getActualTimeout();
// Evaluate actual timeout value, consider config- and session-level timeout values,
// setup and start timer
void setupTimer(thread_db* tdbb);
static void destroy(thread_db* tdbb, dsql_req* request, bool drop);
private:
@ -580,11 +594,12 @@ public:
bool req_traced; // request is traced via TraceAPI
protected:
unsigned int req_timeout; // query timeout in milliseconds, set by the user
Firebird::RefPtr<TimeoutTimer> req_timer; // timeout timer
// Request should never be destroyed using delete.
// It dies together with it's pool in release_request().
~dsql_req()
{
}
~dsql_req();
// To avoid posix warning about missing public destructor declare
// MemoryPool as friend class. In fact IT releases request memory!
@ -670,6 +685,28 @@ private:
NestConst<TransactionNode> node;
};
class SetSessionRequest : public dsql_req
{
public:
explicit SetSessionRequest(MemoryPool& pool, SetSessionNode* aNode)
: dsql_req(pool),
node(aNode)
{
req_traced = false;
}
virtual void dsqlPass(thread_db* tdbb, DsqlCompilerScratch* scratch,
ntrace_result_t* traceResult);
virtual void execute(thread_db* tdbb, jrd_tra** traHandle,
Firebird::IMessageMetadata* inMetadata, const UCHAR* inMsg,
Firebird::IMessageMetadata* outMetadata, UCHAR* outMsg,
bool singleton);
private:
NestConst<SetSessionNode> node;
};
//! Implicit (NATURAL and USING) joins
class ImplicitJoin : public pool_alloc<dsql_type_imp_join>
{

View File

@ -614,6 +614,8 @@ using namespace Firebird;
%token <metaNamePtr> UNBOUNDED
%token <metaNamePtr> VARBINARY
%token <metaNamePtr> WINDOW
%token <metaNamePtr> IDLE
%token <metaNamePtr> SESSION
// precedence declarations for expression evaluation
@ -754,6 +756,7 @@ using namespace Firebird;
Jrd::MappingNode* mappingNode;
Jrd::MappingNode::OP mappingOp;
Jrd::SetRoleNode* setRoleNode;
Jrd::SetSessionNode* setSessionNode;
Jrd::CreateAlterRoleNode* createAlterRoleNode;
}
@ -773,6 +776,7 @@ statement
: dml_statement { $$ = newNode<DsqlDmlRequest>($1); }
| ddl_statement { $$ = newNode<DsqlDdlRequest>($1); }
| tra_statement { $$ = newNode<DsqlTransactionRequest>($1); }
| session_statement { $$ = newNode<SetSessionRequest>($1); }
;
%type <stmtNode> dml_statement
@ -4985,6 +4989,31 @@ set_role
{ $$ = newNode<SetRoleNode>(); }
;
%type <setSessionNode> session_statement
session_statement
: SET SESSION IDLE TIMEOUT long_integer timepart_sesion_idle_tout
{ $$ = newNode<SetSessionNode>(SetSessionNode::TYPE_IDLE_TIMEOUT, $5, $6); }
| SET STATEMENT TIMEOUT long_integer timepart_ses_stmt_tout
{ $$ = newNode<SetSessionNode>(SetSessionNode::TYPE_STMT_TIMEOUT, $4, $5); }
;
%type <blrOp> timepart_sesion_idle_tout
timepart_sesion_idle_tout
: { $$ = blr_extract_minute; }
| HOUR { $$ = blr_extract_hour; }
| MINUTE { $$ = blr_extract_minute; }
| SECOND { $$ = blr_extract_second; }
;
%type <blrOp> timepart_ses_stmt_tout
timepart_ses_stmt_tout
: { $$ = blr_extract_second; }
| HOUR { $$ = blr_extract_hour; }
| MINUTE { $$ = blr_extract_minute; }
| SECOND { $$ = blr_extract_second; }
| MILLISECOND { $$ = blr_extract_millisecond; }
;
%type tran_option_list_opt(<setTransactionNode>)
tran_option_list_opt($setTransactionNode)
: // nothing
@ -8227,6 +8256,8 @@ non_reserved_word
| SQL
| SYSTEM
| TIES
| SESSION
| IDLE
;
%%

View File

@ -442,6 +442,11 @@ typedef ISC_STATUS API_ROUTINE prototype_fb_cancel_operation(ISC_STATUS *,
typedef ISC_STATUS API_ROUTINE prototype_fb_database_crypt_callback(ISC_STATUS *,
void *);
typedef ISC_STATUS API_ROUTINE prototype_fb_dsql_set_timeout(ISC_STATUS*,
isc_stmt_handle*,
ULONG);
struct FirebirdApiPointers
{
prototype_isc_attach_database *isc_attach_database;
@ -523,6 +528,7 @@ struct FirebirdApiPointers
prototype_isc_service_start *isc_service_start;
prototype_fb_cancel_operation *fb_cancel_operation;
prototype_fb_database_crypt_callback *fb_database_crypt_callback;
prototype_fb_dsql_set_timeout* fb_dsql_set_timeout;
};
#endif

View File

@ -439,6 +439,11 @@ interface Statement : ReferenceCounted
void setCursorName(Status status, const string name);
void free(Status status);
uint getFlags(Status status);
version: // 3.0 => 4.0
// Statement execution timeout, milliseconds
uint getTimeout(Status status);
void setTimeout(Status status, uint timeOut);
}
interface Request : ReferenceCounted
@ -516,6 +521,15 @@ interface Attachment : ReferenceCounted
void ping(Status status);
void detach(Status status);
void dropDatabase(Status status);
version: // 3.0 => 4.0
// Idle attachment timeout, seconds
uint getIdleTimeout(Status status);
void setIdleTimeout(Status status, uint timeOut);
// Statement execution timeout, milliseconds
uint getStatementTimeout(Status status);
void setStatementTimeout(Status status, uint timeOut);
}
interface Service : ReferenceCounted

View File

@ -1556,6 +1556,8 @@ namespace Firebird
void (CLOOP_CARG *setCursorName)(IStatement* self, IStatus* status, const char* name) throw();
void (CLOOP_CARG *free)(IStatement* self, IStatus* status) throw();
unsigned (CLOOP_CARG *getFlags)(IStatement* self, IStatus* status) throw();
unsigned (CLOOP_CARG *getTimeout)(IStatement* self, IStatus* status) throw();
void (CLOOP_CARG *setTimeout)(IStatement* self, IStatus* status, unsigned timeOut) throw();
};
protected:
@ -1569,7 +1571,7 @@ namespace Firebird
}
public:
static const unsigned VERSION = 3;
static const unsigned VERSION = 4;
static const unsigned PREPARE_PREFETCH_NONE = 0;
static const unsigned PREPARE_PREFETCH_TYPE = 1;
@ -1669,6 +1671,33 @@ namespace Firebird
StatusType::checkException(status);
return ret;
}
template <typename StatusType> unsigned getTimeout(StatusType* status)
{
if (cloopVTable->version < 4)
{
StatusType::setVersionError(status, "IStatement", cloopVTable->version, 4);
StatusType::checkException(status);
return 0;
}
StatusType::clearException(status);
unsigned ret = static_cast<VTable*>(this->cloopVTable)->getTimeout(this, status);
StatusType::checkException(status);
return ret;
}
template <typename StatusType> void setTimeout(StatusType* status, unsigned timeOut)
{
if (cloopVTable->version < 4)
{
StatusType::setVersionError(status, "IStatement", cloopVTable->version, 4);
StatusType::checkException(status);
return;
}
StatusType::clearException(status);
static_cast<VTable*>(this->cloopVTable)->setTimeout(this, status, timeOut);
StatusType::checkException(status);
}
};
class IRequest : public IReferenceCounted
@ -1862,6 +1891,10 @@ namespace Firebird
void (CLOOP_CARG *ping)(IAttachment* self, IStatus* status) throw();
void (CLOOP_CARG *detach)(IAttachment* self, IStatus* status) throw();
void (CLOOP_CARG *dropDatabase)(IAttachment* self, IStatus* status) throw();
unsigned (CLOOP_CARG *getIdleTimeout)(IAttachment* self, IStatus* status) throw();
void (CLOOP_CARG *setIdleTimeout)(IAttachment* self, IStatus* status, unsigned timeOut) throw();
unsigned (CLOOP_CARG *getStatementTimeout)(IAttachment* self, IStatus* status) throw();
void (CLOOP_CARG *setStatementTimeout)(IAttachment* self, IStatus* status, unsigned timeOut) throw();
};
protected:
@ -1875,7 +1908,7 @@ namespace Firebird
}
public:
static const unsigned VERSION = 3;
static const unsigned VERSION = 4;
template <typename StatusType> void getInfo(StatusType* status, unsigned itemsLength, const unsigned char* items, unsigned bufferLength, unsigned char* buffer)
{
@ -2012,6 +2045,60 @@ namespace Firebird
static_cast<VTable*>(this->cloopVTable)->dropDatabase(this, status);
StatusType::checkException(status);
}
template <typename StatusType> unsigned getIdleTimeout(StatusType* status)
{
if (cloopVTable->version < 4)
{
StatusType::setVersionError(status, "IAttachment", cloopVTable->version, 4);
StatusType::checkException(status);
return 0;
}
StatusType::clearException(status);
unsigned ret = static_cast<VTable*>(this->cloopVTable)->getIdleTimeout(this, status);
StatusType::checkException(status);
return ret;
}
template <typename StatusType> void setIdleTimeout(StatusType* status, unsigned timeOut)
{
if (cloopVTable->version < 4)
{
StatusType::setVersionError(status, "IAttachment", cloopVTable->version, 4);
StatusType::checkException(status);
return;
}
StatusType::clearException(status);
static_cast<VTable*>(this->cloopVTable)->setIdleTimeout(this, status, timeOut);
StatusType::checkException(status);
}
template <typename StatusType> unsigned getStatementTimeout(StatusType* status)
{
if (cloopVTable->version < 4)
{
StatusType::setVersionError(status, "IAttachment", cloopVTable->version, 4);
StatusType::checkException(status);
return 0;
}
StatusType::clearException(status);
unsigned ret = static_cast<VTable*>(this->cloopVTable)->getStatementTimeout(this, status);
StatusType::checkException(status);
return ret;
}
template <typename StatusType> void setStatementTimeout(StatusType* status, unsigned timeOut)
{
if (cloopVTable->version < 4)
{
StatusType::setVersionError(status, "IAttachment", cloopVTable->version, 4);
StatusType::checkException(status);
return;
}
StatusType::clearException(status);
static_cast<VTable*>(this->cloopVTable)->setStatementTimeout(this, status, timeOut);
StatusType::checkException(status);
}
};
class IService : public IReferenceCounted
@ -8183,6 +8270,8 @@ namespace Firebird
this->setCursorName = &Name::cloopsetCursorNameDispatcher;
this->free = &Name::cloopfreeDispatcher;
this->getFlags = &Name::cloopgetFlagsDispatcher;
this->getTimeout = &Name::cloopgetTimeoutDispatcher;
this->setTimeout = &Name::cloopsetTimeoutDispatcher;
}
} vTable;
@ -8351,6 +8440,35 @@ namespace Firebird
}
}
static unsigned CLOOP_CARG cloopgetTimeoutDispatcher(IStatement* self, IStatus* status) throw()
{
StatusType status2(status);
try
{
return static_cast<Name*>(self)->Name::getTimeout(&status2);
}
catch (...)
{
StatusType::catchException(&status2);
return static_cast<unsigned>(0);
}
}
static void CLOOP_CARG cloopsetTimeoutDispatcher(IStatement* self, IStatus* status, unsigned timeOut) throw()
{
StatusType status2(status);
try
{
static_cast<Name*>(self)->Name::setTimeout(&status2, timeOut);
}
catch (...)
{
StatusType::catchException(&status2);
}
}
static void CLOOP_CARG cloopaddRefDispatcher(IReferenceCounted* self) throw()
{
try
@ -8401,6 +8519,8 @@ namespace Firebird
virtual void setCursorName(StatusType* status, const char* name) = 0;
virtual void free(StatusType* status) = 0;
virtual unsigned getFlags(StatusType* status) = 0;
virtual unsigned getTimeout(StatusType* status) = 0;
virtual void setTimeout(StatusType* status, unsigned timeOut) = 0;
};
template <typename Name, typename StatusType, typename Base>
@ -8825,6 +8945,10 @@ namespace Firebird
this->ping = &Name::clooppingDispatcher;
this->detach = &Name::cloopdetachDispatcher;
this->dropDatabase = &Name::cloopdropDatabaseDispatcher;
this->getIdleTimeout = &Name::cloopgetIdleTimeoutDispatcher;
this->setIdleTimeout = &Name::cloopsetIdleTimeoutDispatcher;
this->getStatementTimeout = &Name::cloopgetStatementTimeoutDispatcher;
this->setStatementTimeout = &Name::cloopsetStatementTimeoutDispatcher;
}
} vTable;
@ -9093,6 +9217,64 @@ namespace Firebird
}
}
static unsigned CLOOP_CARG cloopgetIdleTimeoutDispatcher(IAttachment* self, IStatus* status) throw()
{
StatusType status2(status);
try
{
return static_cast<Name*>(self)->Name::getIdleTimeout(&status2);
}
catch (...)
{
StatusType::catchException(&status2);
return static_cast<unsigned>(0);
}
}
static void CLOOP_CARG cloopsetIdleTimeoutDispatcher(IAttachment* self, IStatus* status, unsigned timeOut) throw()
{
StatusType status2(status);
try
{
static_cast<Name*>(self)->Name::setIdleTimeout(&status2, timeOut);
}
catch (...)
{
StatusType::catchException(&status2);
}
}
static unsigned CLOOP_CARG cloopgetStatementTimeoutDispatcher(IAttachment* self, IStatus* status) throw()
{
StatusType status2(status);
try
{
return static_cast<Name*>(self)->Name::getStatementTimeout(&status2);
}
catch (...)
{
StatusType::catchException(&status2);
return static_cast<unsigned>(0);
}
}
static void CLOOP_CARG cloopsetStatementTimeoutDispatcher(IAttachment* self, IStatus* status, unsigned timeOut) throw()
{
StatusType status2(status);
try
{
static_cast<Name*>(self)->Name::setStatementTimeout(&status2, timeOut);
}
catch (...)
{
StatusType::catchException(&status2);
}
}
static void CLOOP_CARG cloopaddRefDispatcher(IReferenceCounted* self) throw()
{
try
@ -9150,6 +9332,10 @@ namespace Firebird
virtual void ping(StatusType* status) = 0;
virtual void detach(StatusType* status) = 0;
virtual void dropDatabase(StatusType* status) = 0;
virtual unsigned getIdleTimeout(StatusType* status) = 0;
virtual void setIdleTimeout(StatusType* status, unsigned timeOut) = 0;
virtual unsigned getStatementTimeout(StatusType* status) = 0;
virtual void setStatementTimeout(StatusType* status, unsigned timeOut) = 0;
};
template <typename Name, typename StatusType, typename Base>

View File

@ -828,6 +828,13 @@ static const struct {
{"dsql_window_cant_overr_frame", 335545124},
{"dsql_window_duplicate", 335545125},
{"sql_too_long", 335545126},
{"cfg_stmt_timeout", 335545127},
{"att_stmt_timeout", 335545128},
{"req_stmt_timeout", 335545129},
{"att_shut_killed", 335545130},
{"att_shut_idle", 335545131},
{"att_shut_db_down", 335545132},
{"att_shut_engine", 335545133},
{"gfix_db_name", 335740929},
{"gfix_invalid_sw", 335740930},
{"gfix_incmp_sw", 335740932},

View File

@ -862,6 +862,13 @@ const ISC_STATUS isc_dsql_window_cant_overr_order = 335545123L;
const ISC_STATUS isc_dsql_window_cant_overr_frame = 335545124L;
const ISC_STATUS isc_dsql_window_duplicate = 335545125L;
const ISC_STATUS isc_sql_too_long = 335545126L;
const ISC_STATUS isc_cfg_stmt_timeout = 335545127L;
const ISC_STATUS isc_att_stmt_timeout = 335545128L;
const ISC_STATUS isc_req_stmt_timeout = 335545129L;
const ISC_STATUS isc_att_shut_killed = 335545130L;
const ISC_STATUS isc_att_shut_idle = 335545131L;
const ISC_STATUS isc_att_shut_db_down = 335545132L;
const ISC_STATUS isc_att_shut_engine = 335545133L;
const ISC_STATUS isc_gfix_db_name = 335740929L;
const ISC_STATUS isc_gfix_invalid_sw = 335740930L;
const ISC_STATUS isc_gfix_incmp_sw = 335740932L;
@ -1336,7 +1343,7 @@ const ISC_STATUS isc_trace_switch_user_only = 337182757L;
const ISC_STATUS isc_trace_switch_param_miss = 337182758L;
const ISC_STATUS isc_trace_param_act_notcompat = 337182759L;
const ISC_STATUS isc_trace_mandatory_switch_miss = 337182760L;
const ISC_STATUS isc_err_max = 1280;
const ISC_STATUS isc_err_max = 1287;
#else /* c definitions */
@ -2168,6 +2175,13 @@ const ISC_STATUS isc_err_max = 1280;
#define isc_dsql_window_cant_overr_frame 335545124L
#define isc_dsql_window_duplicate 335545125L
#define isc_sql_too_long 335545126L
#define isc_cfg_stmt_timeout 335545127L
#define isc_att_stmt_timeout 335545128L
#define isc_req_stmt_timeout 335545129L
#define isc_att_shut_killed 335545130L
#define isc_att_shut_idle 335545131L
#define isc_att_shut_db_down 335545132L
#define isc_att_shut_engine 335545133L
#define isc_gfix_db_name 335740929L
#define isc_gfix_invalid_sw 335740930L
#define isc_gfix_incmp_sw 335740932L
@ -2642,7 +2656,7 @@ const ISC_STATUS isc_err_max = 1280;
#define isc_trace_switch_param_miss 337182758L
#define isc_trace_param_act_notcompat 337182759L
#define isc_trace_mandatory_switch_miss 337182760L
#define isc_err_max 1280
#define isc_err_max 1287
#endif

View File

@ -518,6 +518,9 @@
const USHORT f_mon_att_remote_os_user = 17;
const USHORT f_mon_att_auth_method = 18;
const USHORT f_mon_att_sys_flag = 19;
const USHORT f_mon_att_idle_timeout = 20;
const USHORT f_mon_att_idle_timer = 21;
const USHORT f_mon_att_stmt_timeout = 22;
// Relation 35 (MON$TRANSACTIONS)
@ -547,6 +550,8 @@
const USHORT f_mon_stmt_sql_text = 5;
const USHORT f_mon_stmt_stat_id = 6;
const USHORT f_mon_stmt_expl_plan = 7;
const USHORT f_mon_stmt_timeout = 8;
const USHORT f_mon_stmt_timer = 9;
// Relation 37 (MON$CALL_STACK)

View File

@ -831,6 +831,13 @@ Data source : @4"}, /* eds_statement */
{335545124, "Cannot override the window @1 because it has a frame clause. Tip: it can be used without parenthesis in OVER"}, /* dsql_window_cant_overr_frame */
{335545125, "Duplicate window definition for @1"}, /* dsql_window_duplicate */
{335545126, "SQL statement is too long. Maximum size is @1 bytes."}, /* sql_too_long */
{335545127, "Config level timeout expired."}, /* cfg_stmt_timeout */
{335545128, "Attachment level timeout expired."}, /* att_stmt_timeout */
{335545129, "Statement level timeout expired."}, /* req_stmt_timeout */
{335545130, "Killed by database administrator."}, /* att_shut_killed */
{335545131, "Idle timeout expired."}, /* att_shut_idle */
{335545132, "Database is shutdown."}, /* att_shut_db_down */
{335545133, "Engine is shutdown."}, /* att_shut_engine */
{335740929, "data base file name (@1) already given"}, /* gfix_db_name */
{335740930, "invalid switch @1"}, /* gfix_invalid_sw */
{335740932, "incompatible switch combination"}, /* gfix_incmp_sw */

View File

@ -827,6 +827,13 @@ static const struct {
{335545124, -833}, /* 804 dsql_window_cant_overr_frame */
{335545125, -833}, /* 805 dsql_window_duplicate */
{335545126, -902}, /* 806 sql_too_long */
{335545127, -901}, /* 807 cfg_stmt_timeout */
{335545128, -901}, /* 808 att_stmt_timeout */
{335545129, -901}, /* 809 req_stmt_timeout */
{335545130, -902}, /* 810 att_shut_killed */
{335545131, -902}, /* 811 att_shut_idle */
{335545132, -902}, /* 812 att_shut_db_down */
{335545133, -902}, /* 813 att_shut_engine */
{335740929, -901}, /* 1 gfix_db_name */
{335740930, -901}, /* 2 gfix_invalid_sw */
{335740932, -901}, /* 4 gfix_incmp_sw */

View File

@ -827,6 +827,13 @@ static const struct {
{335545124, "42000"}, // 804 dsql_window_cant_overr_frame
{335545125, "42000"}, // 805 dsql_window_duplicate
{335545126, "54001"}, // 806 sql_too_long
{335545127, "HY008"}, // 807 cfg_stmt_timeout
{335545128, "HY008"}, // 808 att_stmt_timeout
{335545129, "HY008"}, // 809 req_stmt_timeout
{335545130, "08003"}, // 810 att_shut_killed
{335545131, "08003"}, // 811 att_shut_idle
{335545132, "08003"}, // 812 att_shut_db_down
{335545133, "08003"}, // 813 att_shut_engine
{335740929, "00000"}, // 1 gfix_db_name
{335740930, "00000"}, // 2 gfix_invalid_sw
{335740932, "00000"}, // 4 gfix_incmp_sw

View File

@ -453,6 +453,7 @@ public:
ExplainPlan = false;
Heading = true;
BailOnError = false;
StmtTimeout = 0;
ISQL_charset[0] = 0;
}
@ -473,6 +474,7 @@ public:
bool ExplainPlan;
bool Heading;
bool BailOnError;
unsigned int StmtTimeout;
SCHAR ISQL_charset[MAXCHARSET_SIZE];
};
@ -4765,7 +4767,8 @@ static processing_state frontend_set(const char* cmd, const char* const* parms,
sqlda_display,
//#endif
sql, warning, sqlCont, heading, bail,
bulk_insert, maxrows, wrong
bulk_insert, maxrows, stmtTimeout,
wrong
};
SetOptions(const optionsMap* inmap, size_t insize, int wrongval)
: OptionsBase(inmap, insize, wrongval)
@ -4803,6 +4806,7 @@ static processing_state frontend_set(const char* cmd, const char* const* parms,
{SetOptions::maxrows, "MAXROWS", 0},
{SetOptions::sqlCont, "ROLE", 0},
{SetOptions::sqlCont, "TRUSTED", 0}, // TRUSTED ROLE, will get DSQL error other case
{SetOptions::stmtTimeout, "LOCAL_TIMEOUT", 0},
};
// Display current set options
@ -4964,14 +4968,28 @@ static processing_state frontend_set(const char* cmd, const char* const* parms,
ret = newMaxRows((*lparms[2]) ? lparms[2] : "0");
break;
default:
case SetOptions::stmtTimeout:
{
TEXT msg_string[MSG_LENGTH];
IUTILS_msg_get(VALID_OPTIONS, msg_string);
isqlGlob.printf("%s\n", msg_string);
int val = strtol(parms[2], NULL, 10);
if (val < 0)
ret = ps_ERR;
else
{
setValues.StmtTimeout = val;
ret = SKIP;
}
}
setoptions.showCommands(isqlGlob.Out);
ret = ps_ERR;
break;
default:
//{
// TEXT msg_string[MSG_LENGTH];
// IUTILS_msg_get(VALID_OPTIONS, msg_string);
// isqlGlob.printf("%s\n", msg_string);
//}
//setoptions.showCommands(isqlGlob.Out);
//ret = ps_ERR;
ret = CONT; // pass unknown SET command to server as is
break;
}
@ -5793,6 +5811,7 @@ static processing_state print_sets()
print_set("Time:", setValues.Time_display);
print_set("Warnings:", setValues.Warnings);
print_set("Bail on error:", setValues.BailOnError);
isqlGlob.printf("%-25s%lu%s", "Local statement timeout:", setValues.StmtTimeout, NEWLINE);
return SKIP;
}
@ -8130,6 +8149,11 @@ static processing_state process_statement(const TEXT* str2)
// check for warnings
ISQL_warning(fbStatus);
global_Stmt->setTimeout(fbStatus, setValues.StmtTimeout);
if (ISQL_errmsg(fbStatus))
setValues.StmtTimeout = 0;
// Find out what kind of statement this is
const int statement_type = process_request_type();
if (!statement_type)
@ -8193,6 +8217,7 @@ static processing_state process_statement(const TEXT* str2)
statement_type == isc_info_sql_stmt_set_generator))
{
DB->execute(fbStatus, D__trans, 0, str2, isqlGlob.SQL_dialect, NULL, NULL, NULL, NULL);
setValues.StmtTimeout = 0;
if (ISQL_errmsg(fbStatus))
{
ret = ps_ERR;
@ -8235,6 +8260,7 @@ static processing_state process_statement(const TEXT* str2)
// This is a non-select DML statement or trans
M__trans = global_Stmt->execute(fbStatus, M__trans, NULL, NULL, NULL, NULL);
setValues.StmtTimeout = 0;
if (ISQL_errmsg(fbStatus))
{
// CVC: Make this conditional if it causes problems. For example
@ -8337,6 +8363,7 @@ static processing_state process_statement(const TEXT* str2)
if (statement_type == isc_info_sql_stmt_exec_procedure)
{
global_Stmt->execute(fbStatus, M__trans, NULL, NULL, message, buffer);
setValues.StmtTimeout = 0;
if (ISQL_errmsg(fbStatus))
{
ret = ps_ERR;
@ -8366,6 +8393,7 @@ static processing_state process_statement(const TEXT* str2)
Firebird::IResultSet* curs = global_Stmt->openCursor(fbStatus, M__trans,
NULL, NULL, message, 0);
setValues.StmtTimeout = 0;
if (ISQL_errmsg(fbStatus))
{
return ps_ERR;

View File

@ -45,6 +45,7 @@
#include "../common/classes/MetaName.h"
#include "../common/StatusArg.h"
#include "../common/isc_proto.h"
#include "../common/classes/RefMutex.h"
using namespace Jrd;
@ -204,7 +205,9 @@ Jrd::Attachment::Attachment(MemoryPool* pool, Database* dbb)
att_dyn_req(*pool),
att_charsets(*pool),
att_charset_ids(*pool),
att_pools(*pool)
att_pools(*pool),
att_idle_timeout(0),
att_stmt_timeout(0)
{
att_internal.grow(irq_MAX);
att_dyn_req.grow(drq_MAX);
@ -371,9 +374,11 @@ void Jrd::Attachment::signalCancel()
}
void Jrd::Attachment::signalShutdown()
void Jrd::Attachment::signalShutdown(ISC_STATUS code)
{
att_flags |= ATT_shutdown;
if (getStable())
getStable()->setShutError(code);
if (att_ext_connection && att_ext_connection->isConnected())
att_ext_connection->cancelExecution();
@ -639,7 +644,7 @@ int Jrd::Attachment::blockingAstShutdown(void* ast_object)
AsyncContextHolder tdbb(dbb, FB_FUNCTION, attachment->att_id_lock);
attachment->signalShutdown();
attachment->signalShutdown(isc_att_shut_killed);
JRD_shutdown_attachment(attachment);
}
@ -765,3 +770,122 @@ JAttachment* Attachment::getInterface() throw()
return att_stable->getInterface();
}
unsigned int Attachment::getActualIdleTimeout() const
{
unsigned int timeout = att_database->dbb_config->getConnIdleTimeout() * 60;
if (att_idle_timeout && (att_idle_timeout < timeout || !timeout))
timeout = att_idle_timeout;
return timeout;
}
void Attachment::setupIdleTimer(bool clear)
{
unsigned int timeout = clear ? 0 : getActualIdleTimeout();
if (!timeout)
{
if (att_idle_timer)
att_idle_timer->reset(0);
}
else
{
if (!att_idle_timer)
att_idle_timer = FB_NEW IdleTimer(getInterface());
att_idle_timer->reset(timeout);
}
}
bool Attachment::getIdleTimerTimestamp(TimeStamp& ts) const
{
if (!att_idle_timer)
return false;
time_t value = att_idle_timer->getExpiryTime();
if (!value)
return false;
struct tm* times = localtime(&value);
if (!times)
return false;
ts = TimeStamp::encode_timestamp(times);
return true;
}
/// Attachment::IdleTimer
void Attachment::IdleTimer::handler()
{
m_fireTime = 0;
if (!m_expTime) // Timer was reset to zero, do nothing
return;
// Ensure attachment is still alive and idle
StableAttachmentPart* stable = m_attachment->getStable();
if (!stable)
return;
MutexEnsureUnlock guard(*stable->getMutex(), FB_FUNCTION);
if (!guard.tryEnter())
return;
if (!m_expTime)
return;
// If timer was reset to fire later, restart ITimer
time_t curTime = time(NULL);
if (curTime < m_expTime)
{
reset(m_expTime - curTime);
return;
}
Attachment* att = stable->getHandle();
att->signalShutdown(isc_att_shut_idle);
JRD_shutdown_attachment(att);
}
int Attachment::IdleTimer::release()
{
if (--refCounter == 0)
{
delete this;
return 0;
}
return 1;
}
void Attachment::IdleTimer::reset(unsigned int timeout)
{
// Start timer if necessary. If timer was already started, don't restart
// (or stop) it - handler() will take care about it.
if (!timeout)
{
m_expTime = 0;
return;
}
const time_t curTime = time(NULL);
m_expTime = curTime + timeout;
FbLocalStatus s;
ITimerControl* timerCtrl = Firebird::TimerInterfacePtr();
if (m_fireTime)
{
if (m_fireTime <= m_expTime)
return;
timerCtrl->stop(&s, this);
check(&s);
m_fireTime = 0;
}
timerCtrl->start(&s, this, (m_expTime - curTime) * 1000 * 1000);
check(&s);
m_fireTime = m_expTime;
}

View File

@ -152,7 +152,7 @@ class StableAttachmentPart : public Firebird::RefCounted, public Firebird::Globa
{
public:
explicit StableAttachmentPart(Attachment* handle)
: att(handle), jAtt(NULL)
: att(handle), jAtt(NULL), shutError(0)
{ }
Attachment* getHandle() throw()
@ -171,6 +171,7 @@ public:
jAtt->detachEngine();
jAtt = ja;
shutError = 0;
}
Firebird::Mutex* getMutex(bool useAsync = false, bool forceAsync = false)
@ -208,9 +209,21 @@ public:
void manualUnlock(ULONG& flags);
void manualAsyncUnlock(ULONG& flags);
void setShutError(ISC_STATUS code)
{
if (!shutError)
shutError = code;
}
ISC_STATUS getShutError() const
{
return shutError;
}
private:
Attachment* att;
JAttachment* jAtt;
ISC_STATUS shutError;
// These mutexes guarantee attachment existence. After releasing both of them with possibly
// zero att_use_count one should check does attachment still exists calling getHandle().
@ -391,7 +404,7 @@ public:
const Firebird::ByteChunk& chunk);
void signalCancel();
void signalShutdown();
void signalShutdown(ISC_STATUS code);
void mergeStats();
@ -412,9 +425,70 @@ public:
JAttachment* getInterface() throw();
unsigned int getIdleTimeout() const
{
return att_idle_timeout;
}
void setIdleTimeout(unsigned int timeOut)
{
att_idle_timeout = timeOut;
}
unsigned int getActualIdleTimeout() const;
unsigned int getStatementTimeout() const
{
return att_stmt_timeout;
}
void setStatementTimeout(unsigned int timeOut)
{
att_stmt_timeout = timeOut;
}
// evaluate new value or clear idle timer
void setupIdleTimer(bool clear);
// returns time when idle timer will be expired, if set
bool getIdleTimerTimestamp(Firebird::TimeStamp& ts) const;
private:
Attachment(MemoryPool* pool, Database* dbb);
~Attachment();
class IdleTimer FB_FINAL :
public Firebird::RefCntIface<Firebird::ITimerImpl<IdleTimer, Firebird::CheckStatusWrapper> >
{
public:
explicit IdleTimer(JAttachment* jAtt) :
m_attachment(jAtt),
m_fireTime(0),
m_expTime(0)
{ }
// ITimer implementation
void handler();
int release();
// Set timeout, seconds
void reset(unsigned int timeout);
time_t getExpiryTime() const
{
return m_expTime;
}
private:
Firebird::RefPtr<JAttachment> m_attachment;
time_t m_fireTime; // when ITimer will fire, could be less than m_expTime
time_t m_expTime; // when actual idle timeout will expire
};
unsigned int att_idle_timeout; // seconds
unsigned int att_stmt_timeout; // milliseconds
Firebird::RefPtr<IdleTimer> att_idle_timer;
};

View File

@ -1362,14 +1362,14 @@ namespace Jrd {
}
if (!found)
att->signalShutdown();
att->signalShutdown(0 /* no special shutdown code */);
}
// Loop through internal attachments list closing one missing valid holders
for (unsigned i = 0; i < knownHolders.getCount(); ++i)
{
if (!validateHoldersGroup(knownHolders[i], keyName))
knownHolders[i].first->signalShutdown();
knownHolders[i].first->signalShutdown(0);
}
}

View File

@ -205,6 +205,9 @@ public:
void setCursorName(Firebird::CheckStatusWrapper* status, const char* name);
unsigned getFlags(Firebird::CheckStatusWrapper* status);
unsigned int getTimeout(Firebird::CheckStatusWrapper* status);
void setTimeout(Firebird::CheckStatusWrapper* status, unsigned int timeOut);
public:
JStatement(dsql_req* handle, StableAttachmentPart* sa, Firebird::Array<UCHAR>& meta);
@ -345,6 +348,11 @@ public:
void detach(Firebird::CheckStatusWrapper* status);
void dropDatabase(Firebird::CheckStatusWrapper* status);
unsigned int getIdleTimeout(Firebird::CheckStatusWrapper* status);
void setIdleTimeout(Firebird::CheckStatusWrapper* status, unsigned int timeOut);
unsigned int getStatementTimeout(Firebird::CheckStatusWrapper* status);
void setStatementTimeout(Firebird::CheckStatusWrapper* status, unsigned int timeOut);
public:
explicit JAttachment(StableAttachmentPart* js);

View File

@ -922,6 +922,15 @@ void Monitoring::putAttachment(SnapshotData::DumpRecord& record, const Jrd::Atta
temp = (attachment->att_flags & ATT_system) ? 1 : 0;
record.storeInteger(f_mon_att_sys_flag, temp);
// session idle timeout, seconds
record.storeInteger(f_mon_att_idle_timeout, attachment->getIdleTimeout());
// when idle timer expires, NULL if not running
TimeStamp idleTimer;
if (attachment->getIdleTimerTimestamp(idleTimer))
record.storeTimestamp(f_mon_att_idle_timer, idleTimer);
// statement timeout, milliseconds
record.storeInteger(f_mon_att_stmt_timeout, attachment->getStatementTimeout());
record.write();
if (attachment->att_database->dbb_flags & DBB_shared)
@ -1020,6 +1029,13 @@ void Monitoring::putRequest(SnapshotData::DumpRecord& record, const jrd_req* req
if (request->req_transaction)
record.storeInteger(f_mon_stmt_tra_id, request->req_transaction->tra_number);
record.storeTimestamp(f_mon_stmt_timestamp, request->req_timestamp);
ISC_TIMESTAMP ts;
if (request->req_timer &&
request->req_timer->getExpireTimestamp(request->req_timestamp.value(), ts))
{
record.storeTimestamp(f_mon_stmt_timer, ts);
}
}
else
record.storeInteger(f_mon_stmt_state, mon_state_idle);
@ -1038,6 +1054,8 @@ void Monitoring::putRequest(SnapshotData::DumpRecord& record, const jrd_req* req
const int stat_id = fb_utils::genUniqueId();
record.storeGlobalId(f_mon_stmt_stat_id, getGlobalId(stat_id));
// statement timeout, milliseconds
record.storeInteger(f_mon_stmt_timeout, request->req_timeout);
record.write();
putStatistics(record, request->req_stats, stat_id, stat_statement);

View File

@ -137,6 +137,11 @@ public:
storeField(field_id, VALUE_TIMESTAMP, sizeof(ISC_TIMESTAMP), &value.value());
}
void storeTimestamp(int field_id, const ISC_TIMESTAMP& value)
{
storeField(field_id, VALUE_TIMESTAMP, sizeof(ISC_TIMESTAMP), &value);
}
void storeString(int field_id, const Firebird::string& value)
{
if (value.length())

View File

@ -218,6 +218,8 @@ const char
CLIENT_PROCESS_NAME[] = "CLIENT_PROCESS",
CURRENT_USER_NAME[] = "CURRENT_USER",
CURRENT_ROLE_NAME[] = "CURRENT_ROLE",
SESSION_IDLE_TIMEOUT[] = "SESSION_IDLE_TIMEOUT",
STATEMENT_TIMEOUT[] = "STATEMENT_TIMEOUT",
// SYSTEM namespace: transaction wise items
TRANSACTION_ID_NAME[] = "TRANSACTION_ID",
ISOLATION_LEVEL_NAME[] = "ISOLATION_LEVEL",
@ -2254,6 +2256,10 @@ dsc* evlGetContext(thread_db* tdbb, const SysFunction*, const NestValueArray& ar
return NULL;
resultStr = role.c_str();
}
else if (nameStr == SESSION_IDLE_TIMEOUT)
resultStr.printf("%" ULONGFORMAT, attachment->getIdleTimeout());
else if (nameStr == STATEMENT_TIMEOUT)
resultStr.printf("%" ULONGFORMAT, attachment->getStatementTimeout());
else if (nameStr == TRANSACTION_ID_NAME)
resultStr.printf("%" SQUADFORMAT, transaction->tra_number);
else if (nameStr == ISOLATION_LEVEL_NAME)

View File

@ -849,6 +849,11 @@ void Statement::prepare(thread_db* tdbb, Transaction* tran, const string& sql, b
m_preparedByReq = m_callerPrivileges ? tdbb->getRequest() : NULL;
}
void Statement::setTimeout(thread_db* tdbb, unsigned int timeout)
{
doSetTimeout(tdbb, timeout);
}
void Statement::execute(thread_db* tdbb, Transaction* tran,
const MetaName* const* in_names, const ValueListNode* in_params,
const ValueListNode* out_params)

View File

@ -318,6 +318,7 @@ public:
Transaction* getTransaction() { return m_transaction; }
void prepare(Jrd::thread_db* tdbb, Transaction* tran, const Firebird::string& sql, bool named);
void setTimeout(Jrd::thread_db* tdbb, unsigned int timeout);
void execute(Jrd::thread_db* tdbb, Transaction* tran,
const Firebird::MetaName* const* in_names, const Jrd::ValueListNode* in_params,
const Jrd::ValueListNode* out_params);
@ -352,6 +353,7 @@ public:
protected:
virtual void doPrepare(Jrd::thread_db* tdbb, const Firebird::string& sql) = 0;
virtual void doSetTimeout(Jrd::thread_db* tdbb, unsigned int timeout) = 0;
virtual void doExecute(Jrd::thread_db* tdbb) = 0;
virtual void doOpen(Jrd::thread_db* tdbb) = 0;
virtual bool doFetch(Jrd::thread_db* tdbb) = 0;

View File

@ -507,6 +507,21 @@ void InternalStatement::doPrepare(thread_db* tdbb, const string& sql)
}
void InternalStatement::doSetTimeout(thread_db* tdbb, unsigned int timeout)
{
FbLocalStatus status;
{
EngineCallbackGuard guard(tdbb, *this, FB_FUNCTION);
m_request->setTimeout(&status, timeout);
}
if (status->getState() & IStatus::STATE_ERRORS)
raise(&status, tdbb, "JStatement::setTimeout");
}
void InternalStatement::doExecute(thread_db* tdbb)
{
JTransaction* transaction = getIntTransaction()->getJrdTran();

View File

@ -130,6 +130,7 @@ protected:
protected:
virtual void doPrepare(Jrd::thread_db* tdbb, const Firebird::string& sql);
virtual void doSetTimeout(Jrd::thread_db* tdbb, unsigned int timeout);
virtual void doExecute(Jrd::thread_db* tdbb);
virtual void doOpen(Jrd::thread_db* tdbb);
virtual bool doFetch(Jrd::thread_db* tdbb);

View File

@ -493,6 +493,24 @@ void IscStatement::doPrepare(thread_db* tdbb, const string& sql)
}
}
void IscStatement::doSetTimeout(thread_db* tdbb, unsigned int timeout)
{
FbLocalStatus status;
{
EngineCallbackGuard guard(tdbb, *this, FB_FUNCTION);
m_iscProvider.fb_dsql_set_timeout(&status, &m_handle, timeout);
}
if (status->getState() & IStatus::STATE_ERRORS)
{
// silently ignore error if timeouts is not supported by remote server
// or loaded client library
if (status[0] == isc_arg_gds && (status[1] == isc_wish_list || status[1] == isc_unavailable))
return;
raise(&status, tdbb, "fb_dsql_set_timeout");
}
}
void IscStatement::doExecute(thread_db* tdbb)
{
FB_API_HANDLE& h_tran = getIscTransaction()->getAPIHandle();
@ -1503,6 +1521,16 @@ ISC_STATUS ISC_EXPORT IscProvider::fb_database_crypt_callback(FbStatusVector* us
return notImplemented(user_status);
}
ISC_STATUS ISC_EXPORT IscProvider::fb_dsql_set_timeout(Jrd::FbStatusVector* user_status,
isc_stmt_handle* stmt_handle,
ULONG timeout)
{
if (m_api.fb_dsql_set_timeout)
return m_api.fb_dsql_set_timeout(IscStatus(user_status), stmt_handle, timeout);
return notImplemented(user_status);
}
void IscProvider::loadAPI()
{
FbLocalStatus status;
@ -1595,7 +1623,8 @@ static FirebirdApiPointers isc_callbacks =
PROTO(isc_service_query),
PROTO(isc_service_start),
PROTO(fb_cancel_operation),
PROTO(fb_database_crypt_callback)
PROTO(fb_database_crypt_callback),
PROTO(fb_dsql_set_timeout)
};

View File

@ -485,6 +485,10 @@ public:
virtual ISC_STATUS ISC_EXPORT fb_database_crypt_callback(Jrd::FbStatusVector*,
void*);
virtual ISC_STATUS API_ROUTINE fb_dsql_set_timeout(Jrd::FbStatusVector*,
isc_stmt_handle*,
ULONG);
};
@ -576,6 +580,7 @@ protected:
protected:
virtual void doPrepare(Jrd::thread_db* tdbb, const Firebird::string& sql);
virtual void doSetTimeout(Jrd::thread_db* tdbb, unsigned int timeout);
virtual void doExecute(Jrd::thread_db* tdbb);
virtual void doOpen(Jrd::thread_db* tdbb);
virtual bool doFetch(Jrd::thread_db* tdbb);

View File

@ -195,3 +195,8 @@
FIELD(fld_system_privileges, nam_system_privileges, dtype_text, 8 , dsc_text_type_fixed , dflt_no_privs, true)
FIELD(fld_b_sql_security, nam_sql_security , dtype_boolean , 1 , 0 , NULL , true)
FIELD(fld_idle_timeout , nam_idle_timeout , dtype_long , sizeof(SLONG) , 0 , NULL , false)
FIELD(fld_idle_timer , nam_idle_timer , dtype_timestamp, TIMESTAMP_SIZE , 0 , NULL , true)
FIELD(fld_stmt_timeout , nam_stmt_timeout , dtype_long , sizeof(SLONG) , 0 , NULL , false)
FIELD(fld_stmt_timer , nam_stmt_timer , dtype_timestamp, TIMESTAMP_SIZE , 0 , NULL , true)

View File

@ -475,6 +475,10 @@ ISC_STATUS ISC_EXPORT isc_dsql_sql_info(ISC_STATUS*,
short,
ISC_SCHAR*);
ISC_STATUS ISC_EXPORT fb_dsql_set_timeout(ISC_STATUS*,
isc_stmt_handle*,
ISC_ULONG);
void ISC_EXPORT isc_encode_date(const void*,
ISC_QUAD*);

View File

@ -238,7 +238,7 @@ void INF_database_info(thread_db* tdbb,
const UCHAR* const end_items = items + item_length;
const UCHAR* const end = info + output_length;
const Jrd::Attachment* const err_att = tdbb->getAttachment();
const Jrd::Attachment* const att = tdbb->getAttachment();
while (items < end_items && *items != isc_info_end)
{
@ -633,7 +633,7 @@ void INF_database_info(thread_db* tdbb,
case fb_info_tpage_warns:
case fb_info_pip_errors:
case fb_info_pip_warns:
err_val = (err_att->att_validation) ? err_att->att_validation->getInfo(item) : 0;
err_val = (att->att_validation) ? att->att_validation->getInfo(item) : 0;
length = INF_convert(err_val, buffer);
break;
@ -769,6 +769,26 @@ void INF_database_info(thread_db* tdbb,
dbb->dbb_crypto_manager->getCurrentState() : 0, buffer);
break;
case fb_info_statement_timeout_db:
length = INF_convert(dbb->dbb_config->getStatementTimeout(), buffer);
break;
case fb_info_statement_timeout_att:
length = INF_convert(att->getStatementTimeout(), buffer);
break;
case fb_info_ses_idle_timeout_db:
length = INF_convert(dbb->dbb_config->getConnIdleTimeout() * 60, buffer);
break;
case fb_info_ses_idle_timeout_att:
length = INF_convert(att->getIdleTimeout(), buffer);
break;
case fb_info_ses_idle_timeout_run:
length = INF_convert(att->getActualIdleTimeout(), buffer);
break;
default:
buffer[0] = item;
item = isc_info_error;

View File

@ -142,6 +142,13 @@ enum db_info_types
fb_info_crypt_state = 126,
fb_info_statement_timeout_db,
fb_info_statement_timeout_att,
fb_info_ses_idle_timeout_db,
fb_info_ses_idle_timeout_att,
fb_info_ses_idle_timeout_run,
isc_info_db_last_value /* Leave this LAST! */
};
@ -419,6 +426,8 @@ enum info_db_provider
#define isc_info_sql_relation_alias 25
#define isc_info_sql_explain_plan 26
#define isc_info_sql_stmt_flags 27
#define isc_info_sql_stmt_timeout_user 28
#define isc_info_sql_stmt_timeout_run 29
/*********************************/
/* SQL information return values */

View File

@ -680,14 +680,21 @@ namespace
// with the flag set cause shutdownMutex mutex is not locked here.
// That's not a danger cause check of att_use_count
// in shutdown code makes it anyway safe.
status_exception::raise(Arg::Gds(isc_att_shutdown));
Arg::Gds err(isc_att_shutdown);
if (sAtt->getShutError())
err << Arg::Gds(sAtt->getShutError());
err.raise();
}
tdbb->setAttachment(attachment);
tdbb->setDatabase(attachment->att_database);
if (!async)
{
attachment->att_use_count++;
attachment->setupIdleTimer(true);
}
}
catch (const Firebird::Exception&)
{
@ -709,7 +716,11 @@ namespace
Jrd::Attachment* attachment = sAtt->getHandle();
if (attachment && !async)
{
attachment->att_use_count--;
if (!attachment->att_use_count)
attachment->setupIdleTimer(false);
}
if (!nolock)
sAtt->getMutex(async)->leave();
@ -1681,8 +1692,12 @@ JAttachment* JProvider::internalAttach(CheckStatusWrapper* user_status, const ch
if (attachment->att_flags & ATT_shutdown)
{
const ISC_STATUS err = jAtt->getStable()->getShutError();
if (dbb->dbb_ast_flags & DBB_shutdown)
ERR_post(Arg::Gds(isc_shutdown) << Arg::Str(org_filename));
else if (err)
ERR_post(Arg::Gds(isc_att_shutdown) << Arg::Gds(err));
else
ERR_post(Arg::Gds(isc_att_shutdown));
}
@ -2915,7 +2930,15 @@ void JAttachment::freeEngineData(CheckStatusWrapper* user_status, bool forceFree
if (forceFree)
flags |= PURGE_NOCHECK;
attachment->signalShutdown();
ISC_STATUS reason = 0;
if (!forceFree)
reason = 0;
else if (engineShutdown)
reason = isc_att_shut_engine;
else if (dbb->dbb_ast_flags & DBB_shutdown)
reason = isc_att_shut_db_down;
attachment->signalShutdown(reason);
purge_attachment(tdbb, getStable(), flags);
att->release();
@ -2985,15 +3008,15 @@ void JAttachment::dropDatabase(CheckStatusWrapper* user_status)
if (attachment->att_flags & ATT_shutdown)
{
const ISC_STATUS err = getStable()->getShutError();
if (dbb->dbb_ast_flags & DBB_shutdown)
{
ERR_post(Arg::Gds(isc_shutdown) << Arg::Str(file_name));
}
else if (err)
ERR_post(Arg::Gds(isc_att_shutdown) << Arg::Gds(err));
else
{
ERR_post(Arg::Gds(isc_att_shutdown));
}
}
if (!CCH_exclusive(tdbb, LCK_PW, WAIT_PERIOD, NULL))
{
@ -4331,6 +4354,82 @@ void JAttachment::transactRequest(CheckStatusWrapper* user_status, ITransaction*
successful_completion(user_status);
}
unsigned int JAttachment::getIdleTimeout(Firebird::CheckStatusWrapper* user_status)
{
unsigned int result = 0;
try
{
EngineContextHolder tdbb(user_status, this, FB_FUNCTION);
check_database(tdbb);
result = getHandle()->getIdleTimeout();
}
catch (const Exception& ex)
{
ex.stuffException(user_status);
return 0;
}
successful_completion(user_status);
return result;
}
void JAttachment::setIdleTimeout(Firebird::CheckStatusWrapper* user_status, unsigned int timeOut)
{
try
{
EngineContextHolder tdbb(user_status, this, FB_FUNCTION);
check_database(tdbb);
getHandle()->setIdleTimeout(timeOut);
}
catch (const Exception& ex)
{
ex.stuffException(user_status);
return;
}
successful_completion(user_status);
}
unsigned int JAttachment::getStatementTimeout(Firebird::CheckStatusWrapper* user_status)
{
unsigned int result = 0;
try
{
EngineContextHolder tdbb(user_status, this, FB_FUNCTION);
check_database(tdbb);
result = getHandle()->getStatementTimeout();
}
catch (const Exception& ex)
{
ex.stuffException(user_status);
return 0;
}
successful_completion(user_status);
return result;
}
void JAttachment::setStatementTimeout(Firebird::CheckStatusWrapper* user_status, unsigned int timeOut)
{
try
{
EngineContextHolder tdbb(user_status, this, FB_FUNCTION);
check_database(tdbb);
getHandle()->setStatementTimeout(timeOut);
}
catch (const Exception& ex)
{
ex.stuffException(user_status);
return;
}
successful_completion(user_status);
}
void JTransaction::getInfo(CheckStatusWrapper* user_status,
unsigned int itemsLength, const unsigned char* items,
@ -5287,6 +5386,66 @@ void JStatement::getInfo(CheckStatusWrapper* user_status,
successful_completion(user_status);
}
unsigned int JStatement::getTimeout(CheckStatusWrapper* user_status)
{
try
{
EngineContextHolder tdbb(user_status, this, FB_FUNCTION);
check_database(tdbb);
try
{
Jrd::dsql_req* req = getHandle();
return req->getTimeout();
}
catch (const Exception& ex)
{
transliterateException(tdbb, ex, user_status, FB_FUNCTION);
return 0;
}
trace_warning(tdbb, user_status, FB_FUNCTION);
}
catch (const Exception& ex)
{
ex.stuffException(user_status);
return 0;
}
successful_completion(user_status);
return 0;
}
void JStatement::setTimeout(CheckStatusWrapper* user_status, unsigned int timeOut)
{
try
{
EngineContextHolder tdbb(user_status, this, FB_FUNCTION);
check_database(tdbb);
try
{
Jrd::dsql_req* req = getHandle();
req->setTimeout(timeOut);
}
catch (const Exception& ex)
{
transliterateException(tdbb, ex, user_status, FB_FUNCTION);
return;
}
trace_warning(tdbb, user_status, FB_FUNCTION);
}
catch (const Exception& ex)
{
ex.stuffException(user_status);
return;
}
successful_completion(user_status);
}
void JAttachment::ping(CheckStatusWrapper* user_status)
{
/**************************************
@ -5442,7 +5601,11 @@ static void check_database(thread_db* tdbb, bool async)
}
else
{
status_exception::raise(Arg::Gds(isc_att_shutdown));
Arg::Gds err(isc_att_shutdown);
if (attachment->getStable() && attachment->getStable()->getShutError())
err << Arg::Gds(attachment->getStable()->getShutError());
err.raise();
}
}
@ -7338,7 +7501,7 @@ namespace
Attachment* attachment = sAtt->getHandle();
if (attachment)
attachment->signalShutdown();
attachment->signalShutdown(isc_att_shut_engine);
}
}
@ -7478,6 +7641,139 @@ static THREAD_ENTRY_DECLARE shutdown_thread(THREAD_ENTRY_PARAM arg)
}
/// TimeoutTimer
#ifdef USE_ITIMER
void TimeoutTimer::handler()
{
m_expired = true;
m_started = 0;
}
int TimeoutTimer::release()
{
if (--refCounter == 0)
{
delete this;
return 0;
}
return 1;
}
unsigned int TimeoutTimer::timeToExpire() const
{
if (!m_started || m_expired)
return 0;
const SINT64 t = fb_utils::query_performance_counter() * 1000 / fb_utils::query_performance_frequency();
const SINT64 r = m_started + m_value - t;
return r > 0 ? r : 0;
}
bool TimeoutTimer::getExpireTimestamp(const ISC_TIMESTAMP start, ISC_TIMESTAMP& exp) const
{
if (!m_started || m_expired)
return false;
static const SINT64 ISC_TICKS_PER_DAY = 24 * 60 * 60 * ISC_TIME_SECONDS_PRECISION;
SINT64 ticks = start.timestamp_date * ISC_TICKS_PER_DAY + start.timestamp_time;
ticks += m_value * ISC_TIME_SECONDS_PRECISION / 1000;
exp.timestamp_date = ticks / ISC_TICKS_PER_DAY;
exp.timestamp_time = ticks % ISC_TICKS_PER_DAY;
return true;
}
void TimeoutTimer::start()
{
FbLocalStatus s;
ITimerControl* timerCtrl = Firebird::TimerInterfacePtr();
m_expired = false;
// todo: timerCtrl->restart to avoid 2 times acquire timerCtrl mutex
if (m_started)
{
timerCtrl->stop(&s, this);
m_started = 0;
}
if (m_value != 0)
{
timerCtrl->start(&s, this, m_value * 1000);
check(&s); // ?? todo
m_started = fb_utils::query_performance_counter() * 1000 / fb_utils::query_performance_frequency();
}
fb_assert(m_value && m_started || !m_value && !m_started);
}
void TimeoutTimer::stop()
{
if (m_started)
{
m_started = 0;
FbLocalStatus s;
ITimerControl* timerCtrl = Firebird::TimerInterfacePtr();
timerCtrl->stop(&s, this);
}
}
#else
bool TimeoutTimer::expired() const
{
if (!m_start)
return false;
const SINT64 t = currTime();
return t > m_start + m_value;
}
unsigned int TimeoutTimer::timeToExpire() const
{
if (!m_start)
return 0;
const SINT64 t = currTime();
const SINT64 r = m_start + m_value - t;
return r > 0 ? r : 0;
}
bool TimeoutTimer::getExpireTimestamp(const ISC_TIMESTAMP start, ISC_TIMESTAMP& exp) const
{
if (!m_start)
return false;
static const SINT64 ISC_TICKS_PER_DAY = 24 * 60 * 60 * ISC_TIME_SECONDS_PRECISION;
SINT64 ticks = start.timestamp_date * ISC_TICKS_PER_DAY + start.timestamp_time;
ticks += m_value * ISC_TIME_SECONDS_PRECISION / 1000;
exp.timestamp_date = ticks / ISC_TICKS_PER_DAY;
exp.timestamp_time = ticks % ISC_TICKS_PER_DAY;
return true;
}
void TimeoutTimer::start()
{
m_start = 0;
if (m_value != 0)
m_start = currTime();
}
void TimeoutTimer::stop()
{
m_start = 0;
}
#endif // USE_ITIMER
// begin thread_db methods
void thread_db::setDatabase(Database* val)
@ -7515,7 +7811,7 @@ SSHORT thread_db::getCharSet() const
return attachment->att_charset;
}
ISC_STATUS thread_db::checkCancelState()
ISC_STATUS thread_db::checkCancelState(ISC_STATUS* secondary)
{
// Test for asynchronous shutdown/cancellation requests.
// But do that only if we're neither in the verb cleanup state
@ -7535,8 +7831,13 @@ ISC_STATUS thread_db::checkCancelState()
if (database->dbb_ast_flags & DBB_shutdown)
return isc_shutdown;
else if (!(tdbb_flags & TDBB_shutdown_manager))
{
if (secondary)
*secondary = attachment->getStable() ? attachment->getStable()->getShutError() : 0;
return isc_att_shutdown;
}
}
// If a cancel has been raised, defer its acknowledgement
// when executing in the context of an internal request or
@ -7556,6 +7857,14 @@ ISC_STATUS thread_db::checkCancelState()
}
}
if (tdbb_reqTimer && tdbb_reqTimer->expired())
{
if (secondary)
*secondary = tdbb_reqTimer->getErrCode();
return isc_cancelled;
}
// Check the thread state for already posted system errors. If any still persists,
// then someone tries to ignore our attempts to interrupt him. Let's insist.
@ -7567,7 +7876,8 @@ ISC_STATUS thread_db::checkCancelState()
bool thread_db::checkCancelState(bool punt)
{
const ISC_STATUS error = checkCancelState();
ISC_STATUS secondary = 0;
const ISC_STATUS error = checkCancelState(&secondary);
if (!error)
return false;
@ -7577,6 +7887,9 @@ bool thread_db::checkCancelState(bool punt)
if (error == isc_shutdown)
status << Arg::Str(attachment->att_filename);
if (secondary)
status << Arg::Gds(secondary);
if (attachment)
attachment->att_flags &= ~ATT_cancel_raise;

View File

@ -349,6 +349,110 @@ const USHORT WIN_garbage_collector = 4; // garbage collector's window
const USHORT WIN_garbage_collect = 8; // scan left a page for garbage collector
#ifdef USE_ITIMER
class TimeoutTimer FB_FINAL :
public Firebird::RefCntIface<Firebird::ITimerImpl<TimeoutTimer, Firebird::CheckStatusWrapper> >
{
public:
explicit TimeoutTimer() :
m_started(0),
m_expired(false),
m_value(0),
m_error(0)
{ }
// ITimer implementation
void handler();
int release();
bool expired() const
{
return m_expired;
}
unsigned int getValue() const
{
return m_value;
}
unsigned int getErrCode() const
{
return m_error;
}
// milliseconds left before timer expiration
unsigned int timeToExpire() const;
// evaluate expire timestamp using start timestamp
bool getExpireTimestamp(const ISC_TIMESTAMP start, ISC_TIMESTAMP& exp) const;
// set timeout value in milliseconds and secondary error code
void setup(unsigned int value, ISC_STATUS error)
{
m_value = value;
m_error = error;
}
void start();
void stop();
private:
SINT64 m_started;
bool m_expired;
unsigned int m_value; // milliseconds
ISC_STATUS m_error;
};
#else
class TimeoutTimer : public Firebird::RefCounted
{
public:
explicit TimeoutTimer() :
m_start(0),
m_value(0),
m_error(0)
{ }
bool expired() const;
unsigned int getValue() const
{
return m_value;
}
unsigned int getErrCode() const
{
return m_error;
}
// milliseconds left before timer expiration
unsigned int timeToExpire() const;
// evaluate expire timestamp using start timestamp
bool getExpireTimestamp(const ISC_TIMESTAMP start, ISC_TIMESTAMP& exp) const;
// set timeout value in milliseconds and secondary error code
void setup(unsigned int value, ISC_STATUS error)
{
m_start = 0;
m_value = value;
m_error = error;
}
void start();
void stop();
private:
SINT64 currTime() const
{
return fb_utils::query_performance_counter() * 1000 / fb_utils::query_performance_frequency();
}
SINT64 m_start;
unsigned int m_value; // milliseconds
ISC_STATUS m_error;
};
#endif // USE_ITIMER
// Thread specific database block
// tdbb_flags
@ -516,9 +620,13 @@ public:
attStat->bumpRelValue(index, relation_id, delta);
}
ISC_STATUS checkCancelState();
ISC_STATUS checkCancelState(ISC_STATUS* secondary = NULL);
bool checkCancelState(bool punt);
bool reschedule(SLONG quantum, bool punt);
const TimeoutTimer* getTimeoutTimer() const
{
return tdbb_reqTimer;
}
void registerBdb(BufferDesc* bdb)
{
@ -587,6 +695,37 @@ public:
#endif
}
}
class TimerGuard
{
public:
TimerGuard(thread_db* tdbb, TimeoutTimer* timer, bool autoStop) :
m_tdbb(tdbb),
m_autoStop(autoStop && timer)
{
fb_assert(m_tdbb->tdbb_reqTimer == NULL);
m_tdbb->tdbb_reqTimer = timer;
if (timer && timer->expired())
m_tdbb->tdbb_quantum = 0;
}
~TimerGuard()
{
if (m_autoStop)
m_tdbb->tdbb_reqTimer->stop();
m_tdbb->tdbb_reqTimer = NULL;
}
private:
thread_db* m_tdbb;
bool m_autoStop;
};
private:
Firebird::RefPtr<TimeoutTimer> tdbb_reqTimer;
};
class ThreadContextHolder

View File

@ -56,6 +56,7 @@
using namespace Jrd;
using namespace Firebird;
static SSHORT adjust_wait(thread_db* tdbb, SSHORT wait);
static void bug_lck(const TEXT*);
static bool compatible(const Lock*, const Lock*, USHORT);
static void enqueue(thread_db*, CheckStatusWrapper*, Lock*, USHORT, SSHORT);
@ -340,6 +341,7 @@ bool LCK_convert(thread_db* tdbb, Lock* lock, USHORT level, SSHORT wait)
WaitCancelGuard guard(tdbb, lock, wait);
FbLocalStatus statusVector;
wait = adjust_wait(tdbb, wait);
const bool result = CONVERT(tdbb, &statusVector, lock, level, wait);
if (!result)
@ -657,6 +659,7 @@ bool LCK_lock(thread_db* tdbb, Lock* lock, USHORT level, SSHORT wait)
WaitCancelGuard guard(tdbb, lock, wait);
FbLocalStatus statusVector;
wait = adjust_wait(tdbb, wait);
ENQUEUE(tdbb, &statusVector, lock, level, wait);
fb_assert(LCK_CHECK_LOCK(lock));
@ -856,6 +859,40 @@ void LCK_write_data(thread_db* tdbb, Lock* lock, SINT64 data)
}
static SSHORT adjust_wait(thread_db* tdbb, SSHORT wait)
{
/**************************************
*
* a d j u s t _ w a i t
*
**************************************
*
* Functional description
* If wait is cancellable and if statement timer was started - calc new wait
* time to ensure it will not take longer than rest of timeout.
*
**************************************/
if ((wait == LCK_NO_WAIT) || (tdbb->tdbb_flags & TDBB_wait_cancel_disable) || !tdbb->getTimeoutTimer())
return wait;
unsigned int tout = tdbb->getTimeoutTimer()->timeToExpire();
if (tout > 0)
{
SSHORT t;
if (tout < 1000)
t = 1;
else if (tout < MAX_SSHORT * 1000)
t = (tout + 999) / 1000;
else
t = MAX_SSHORT;
if ((wait == LCK_WAIT) || (-wait > t))
return -t;
}
return wait;
}
static void bug_lck(const TEXT* string)
{
/**************************************

View File

@ -408,3 +408,8 @@ NAME("SEC$USER_TYPE", nam_sec_user_type)
NAME("RDB$SYSTEM_PRIVILEGES", nam_system_privileges)
NAME("RDB$SQL_SECURITY", nam_sql_security)
NAME("MON$IDLE_TIMEOUT", nam_idle_timeout)
NAME("MON$IDLE_TIMER", nam_idle_timer)
NAME("MON$STATEMENT_TIMEOUT", nam_stmt_timeout)
NAME("MON$STATEMENT_TIMER", nam_stmt_timer)

View File

@ -517,6 +517,9 @@ RELATION(nam_mon_attachments, rel_mon_attachments, ODS_11_1, rel_virtual)
FIELD(f_mon_att_remote_os_user, nam_mon_remote_os_user, fld_os_user, 0, ODS_12_0)
FIELD(f_mon_att_auth_method, nam_mon_auth_method, fld_auth_method, 0, ODS_12_0)
FIELD(f_mon_att_sys_flag, nam_mon_sys_flag, fld_flag, 0, ODS_12_0)
FIELD(f_mon_att_idle_timeout, nam_idle_timeout, fld_idle_timeout, 0, ODS_13_0)
FIELD(f_mon_att_idle_timer, nam_idle_timer, fld_idle_timer, 0, ODS_13_0)
FIELD(f_mon_att_stmt_timeout, nam_stmt_timeout, fld_stmt_timeout, 0, ODS_13_0)
END_RELATION
// Relation 35 (MON$TRANSACTIONS)
@ -546,6 +549,8 @@ RELATION(nam_mon_statements, rel_mon_statements, ODS_11_1, rel_virtual)
FIELD(f_mon_stmt_sql_text, nam_mon_sql_text, fld_source, 0, ODS_11_1)
FIELD(f_mon_stmt_stat_id, nam_mon_stat_id, fld_stat_id, 0, ODS_11_1)
FIELD(f_mon_stmt_expl_plan, nam_mon_expl_plan, fld_source, 0, ODS_11_1)
FIELD(f_mon_stmt_timeout, nam_stmt_timeout, fld_stmt_timeout, 0, ODS_13_0)
FIELD(f_mon_stmt_timer, nam_stmt_timer, fld_stmt_timer, 0, ODS_13_0)
END_RELATION
// Relation 37 (MON$CALL_STACK)

View File

@ -175,6 +175,7 @@ public:
req_ext_stmt(NULL),
req_cursors(*req_pool),
req_ext_resultset(NULL),
req_timeout(0),
req_domain_validation(NULL),
req_auto_trans(*req_pool),
req_sorts(*req_pool),
@ -252,6 +253,8 @@ public:
Savepoint* req_savepoints; // Looper savepoint list
Savepoint* req_proc_sav_point; // procedure savepoint list
Firebird::TimeStamp req_timestamp; // Start time of request
unsigned int req_timeout; // query timeout in milliseconds, set by the dsql_req::setupTimer
Firebird::RefPtr<TimeoutTimer> req_timer; // timeout timer, shared with dsql_req
Firebird::AutoPtr<Jrd::RuntimeStatistics> req_fetch_baseline; // State of request performance counters when we reported it last time
SINT64 req_fetch_elapsed; // Number of clock ticks spent while fetching rows for this request since we reported it last time

View File

@ -532,7 +532,7 @@ static bool shutdown(thread_db* tdbb, SSHORT flag, bool force)
{
if (!(attachment->att_flags & ATT_shutdown))
{
attachment->signalShutdown();
attachment->signalShutdown(isc_att_shut_db_down);
found = true;
}
}

View File

@ -1,7 +1,7 @@
/* MAX_NUMBER is the next number to be used, always one more than the highest message number. */
set bulk_insert INSERT INTO FACILITIES (LAST_CHANGE, FACILITY, FAC_CODE, MAX_NUMBER) VALUES (?, ?, ?, ?);
--
('2016-11-08 11:46:00', 'JRD', 0, 807)
('2016-12-20 12:57:00', 'JRD', 0, 814)
('2015-03-17 18:33:00', 'QLI', 1, 533)
('2015-01-07 18:01:51', 'GFIX', 3, 134)
('1996-11-07 13:39:40', 'GPRE', 4, 1)

View File

@ -914,6 +914,13 @@ Data source : @4', NULL, NULL)
('dsql_window_cant_overr_frame', NULL, 'ExprNodes.cpp', NULL, 0, 804, NULL, 'Cannot override the window @1 because it has a frame clause. Tip: it can be used without parenthesis in OVER', NULL, NULL);
('dsql_window_duplicate', NULL, 'ExprNodes.cpp', NULL, 0, 805, NULL, 'Duplicate window definition for @1', NULL, NULL);
('sql_too_long', 'prepareStatement', 'dsql.cpp', NULL, 0, 806, NULL, 'SQL statement is too long. Maximum size is @1 bytes.', NULL, NULL);
('cfg_stmt_timeout', 'thread_db::checkCancelState', 'jrd.cpp', NULL, 0, 807, NULL, 'Config level timeout expired.', NULL, NULL);
('att_stmt_timeout', 'thread_db::checkCancelState', 'jrd.cpp', NULL, 0, 808, NULL, 'Attachment level timeout expired.', NULL, NULL);
('req_stmt_timeout', 'thread_db::checkCancelState', 'jrd.cpp', NULL, 0, 809, NULL, 'Statement level timeout expired.', NULL, NULL);
('att_shut_killed', NULL, 'jrd.cpp', NULL, 0, 810, NULL, 'Killed by database administrator.', NULL, NULL);
('att_shut_idle', NULL, 'jrd.cpp', NULL, 0, 811, NULL, 'Idle timeout expired.', NULL, NULL);
('att_shut_db_down', NULL, 'jrd.cpp', NULL, 0, 812, NULL, 'Database is shutdown.', NULL, NULL);
('att_shut_engine', NULL, 'jrd.cpp', NULL, 0, 813, NULL, 'Engine is shutdown.', NULL, NULL);
-- QLI
(NULL, NULL, NULL, NULL, 1, 0, NULL, 'expected type', NULL, NULL);
(NULL, NULL, NULL, NULL, 1, 1, NULL, 'bad block type', NULL, NULL);

View File

@ -813,6 +813,13 @@ set bulk_insert INSERT INTO SYSTEM_ERRORS (SQL_CODE, SQL_CLASS, SQL_SUBCLASS, FA
(-833, '42', '000', 0, 804, 'dsql_window_cant_overr_frame', NULL, NULL)
(-833, '42', '000', 0, 805, 'dsql_window_duplicate', NULL, NULL)
(-902, '54', '001', 0, 806, 'sql_too_long', NULL, NULL)
(-901, 'HY', '008', 0, 807, 'cfg_stmt_timeout', NULL, NULL)
(-901, 'HY', '008', 0, 808, 'att_stmt_timeout', NULL, NULL)
(-901, 'HY', '008', 0, 809, 'req_stmt_timeout', NULL, NULL)
(-902, '08', '003', 0, 810, 'att_shut_killed', NULL, NULL)
(-902, '08', '003', 0, 811, 'att_shut_idle', NULL, NULL)
(-902, '08', '003', 0, 812, 'att_shut_db_down', NULL, NULL)
(-902, '08', '003', 0, 813, 'att_shut_engine', NULL, NULL)
-- GFIX
(-901, '00', '000', 3, 1, 'gfix_db_name', NULL, NULL)
(-901, '00', '000', 3, 2, 'gfix_invalid_sw', NULL, NULL)

View File

@ -322,6 +322,28 @@ public:
void free(CheckStatusWrapper* status);
unsigned getFlags(CheckStatusWrapper* status);
unsigned int getTimeout(CheckStatusWrapper* status)
{
if (statement->rsr_rdb->rdb_port->port_protocol < PROTOCOL_VERSION15)
{
status->setErrors(Arg::Gds(isc_wish_list).value());
return 0;
}
return statement->rsr_timeout;
}
void setTimeout(CheckStatusWrapper* status, unsigned int timeOut)
{
if (timeOut && statement->rsr_rdb->rdb_port->port_protocol < PROTOCOL_VERSION15)
{
status->setErrors(Arg::Gds(isc_wish_list).value());
return;
}
statement->rsr_timeout = timeOut;
}
public:
Statement(Rsr* handle, Attachment* a, unsigned aDialect)
: metadata(getPool(), this, NULL),
@ -509,6 +531,11 @@ public:
void detach(CheckStatusWrapper* status);
void dropDatabase(CheckStatusWrapper* status);
unsigned int getIdleTimeout(CheckStatusWrapper* status);
void setIdleTimeout(CheckStatusWrapper* status, unsigned int timeOut);
unsigned int getStatementTimeout(CheckStatusWrapper* status);
void setStatementTimeout(CheckStatusWrapper* status, unsigned int timeOut);
public:
Attachment(Rdb* handle, const PathName& path)
: rdb(handle), dbPath(getPool(), path)
@ -529,7 +556,9 @@ public:
Statement* createStatement(CheckStatusWrapper* status, unsigned dialect);
private:
void execWithCheck(CheckStatusWrapper* status, const string& stmt);
void freeClientData(CheckStatusWrapper* status, bool force = false);
SLONG getSingleInfo(CheckStatusWrapper* status, UCHAR infoItem);
Rdb* rdb;
const PathName dbPath;
@ -1730,6 +1759,102 @@ void Attachment::dropDatabase(CheckStatusWrapper* status)
}
SLONG Attachment::getSingleInfo(CheckStatusWrapper* status, UCHAR infoItem)
{
UCHAR buff[16];
getInfo(status, 1, &infoItem, sizeof(buff), buff);
if (status->getState() & IStatus::STATE_ERRORS)
return 0;
const UCHAR* p = buff;
const UCHAR* const end = buff + sizeof(buff);
UCHAR item;
while ((item = *p++) != isc_info_end && p < end - 1)
{
const SLONG length = gds__vax_integer(p, 2);
p += 2;
if (item == infoItem)
return gds__vax_integer(p, (SSHORT)length);
fb_assert(false);
p += length;
}
return 0;
}
void Attachment::execWithCheck(CheckStatusWrapper* status, const string& stmt)
{
/**************************************
*
* Used to execute "SET xxx TIMEOUT" statements. Checks for protocol version
* and convert expected SQL error into isc_wish_list error. The only possible
* case is when modern network server works with legacy engine.
*
**************************************/
if (rdb->rdb_port->port_protocol >= PROTOCOL_VERSION15)
{
execute(status, NULL, stmt.length(), stmt.c_str(), SQL_DIALECT_CURRENT, NULL, NULL, NULL, NULL);
if (!(status->getState() & IStatus::STATE_ERRORS))
return;
// handle isc_dsql_token_unk_err
const ISC_STATUS* errs = status->getErrors();
if (!fb_utils::containsErrorCode(errs, isc_sqlerr) ||
!fb_utils::containsErrorCode(errs, isc_dsql_token_unk_err))
{
return;
}
status->init();
}
status->setErrors(Arg::Gds(isc_wish_list).value());
}
unsigned int Attachment::getIdleTimeout(CheckStatusWrapper* status)
{
if (rdb->rdb_port->port_protocol >= PROTOCOL_VERSION15)
return getSingleInfo(status, fb_info_ses_idle_timeout_att);
status->setErrors(Arg::Gds(isc_wish_list).value());
return 0;
}
void Attachment::setIdleTimeout(CheckStatusWrapper* status, unsigned int timeOut)
{
string stmt;
stmt.printf("SET SESSION IDLE TIMEOUT %lu", timeOut);
execWithCheck(status, stmt);
}
unsigned int Attachment::getStatementTimeout(CheckStatusWrapper* status)
{
if (rdb->rdb_port->port_protocol >= PROTOCOL_VERSION15)
return getSingleInfo(status, fb_info_statement_timeout_att);
status->setErrors(Arg::Gds(isc_wish_list).value());
return 0;
}
void Attachment::setStatementTimeout(CheckStatusWrapper* status, unsigned int timeOut)
{
string stmt;
stmt.printf("SET STATEMENT TIMEOUT %lu", timeOut);
execWithCheck(status, stmt);
}
Firebird::ITransaction* Statement::execute(CheckStatusWrapper* status, Firebird::ITransaction* apiTra,
IMessageMetadata* inMetadata, void* inBuffer, IMessageMetadata* outMetadata, void* outBuffer)
{
@ -1857,6 +1982,7 @@ Firebird::ITransaction* Statement::execute(CheckStatusWrapper* status, Firebird:
sqldata->p_sqldata_out_blr.cstr_length = out_blr_length;
sqldata->p_sqldata_out_blr.cstr_address = const_cast<UCHAR*>(out_blr);
sqldata->p_sqldata_out_message_number = 0; // out_msg_type
sqldata->p_sqldata_timeout = statement->rsr_timeout;
send_packet(port, packet);
@ -2021,6 +2147,7 @@ ResultSet* Statement::openCursor(CheckStatusWrapper* status, Firebird::ITransact
sqldata->p_sqldata_out_blr.cstr_length = out_blr_length;
sqldata->p_sqldata_out_blr.cstr_address = const_cast<UCHAR*>(out_blr);
sqldata->p_sqldata_out_message_number = 0; // out_msg_type
sqldata->p_sqldata_timeout = statement->rsr_timeout;
send_partial_packet(port, packet);
defer_packet(port, packet, true);

View File

@ -305,7 +305,8 @@ rem_port* XNET_analyze(ClntAuthBlock* cBlock,
REMOTE_PROTOCOL(PROTOCOL_VERSION11, ptype_batch_send, 2),
REMOTE_PROTOCOL(PROTOCOL_VERSION12, ptype_batch_send, 3),
REMOTE_PROTOCOL(PROTOCOL_VERSION13, ptype_batch_send, 4),
REMOTE_PROTOCOL(PROTOCOL_VERSION14, ptype_batch_send, 5)
REMOTE_PROTOCOL(PROTOCOL_VERSION14, ptype_batch_send, 5),
REMOTE_PROTOCOL(PROTOCOL_VERSION15, ptype_batch_send, 6)
};
fb_assert(FB_NELEM(protocols_to_try) <= FB_NELEM(cnct->p_cnct_versions));
cnct->p_cnct_count = FB_NELEM(protocols_to_try);

View File

@ -646,6 +646,11 @@ bool_t xdr_protocol(XDR* xdrs, PACKET* p)
}
MAP(xdr_short, reinterpret_cast<SSHORT&>(sqldata->p_sqldata_out_message_number));
}
{ // scope
rem_port* port = (rem_port*)xdrs->x_public;
if (port->port_protocol >= PROTOCOL_VERSION15)
MAP(xdr_u_long, sqldata->p_sqldata_timeout);
}
DEBUG_PRINTSIZE(xdrs, p->p_operation);
return P_TRUE(xdrs, p);

View File

@ -84,6 +84,7 @@ const USHORT PROTOCOL_VERSION14 = (FB_PROTOCOL_FLAG | 14);
// Protocol 15:
// - supports crypt key callback at connect phaze
// - supports statement timeouts
const USHORT PROTOCOL_VERSION15 = (FB_PROTOCOL_FLAG | 15);
@ -577,6 +578,7 @@ typedef struct p_sqldata
CSTRING p_sqldata_out_blr; // blr describing output message
USHORT p_sqldata_out_message_number;
ULONG p_sqldata_status; // final eof status
ULONG p_sqldata_timeout; // statement timeout
} P_SQLDATA;
typedef struct p_sqlfree

View File

@ -476,6 +476,7 @@ struct Rsr : public Firebird::GlobalStorage, public TypedHandle<rem_type_rsr>
Firebird::string rsr_cursor_name; // Name for cursor to be set on open
bool rsr_delayed_format; // Out format was delayed on execute, set it on fetch
unsigned int rsr_timeout; // Statement timeout to be set on open\execute
Rsr** rsr_self;
public:
@ -498,7 +499,7 @@ public:
rsr_format(0), rsr_message(0), rsr_buffer(0), rsr_status(0),
rsr_id(0), rsr_fmt_length(0),
rsr_rows_pending(0), rsr_msgs_waiting(0), rsr_reorder_level(0), rsr_batch_count(0),
rsr_cursor_name(getPool()), rsr_delayed_format(false), rsr_self(NULL)
rsr_cursor_name(getPool()), rsr_delayed_format(false), rsr_timeout(0), rsr_self(NULL)
{ }
~Rsr()

View File

@ -3383,6 +3383,9 @@ ISC_STATUS rem_port::execute_statement(P_OP op, P_SQLDATA* sqldata, PACKET* send
unsigned flags = statement->rsr_iface->getFlags(&status_vector);
check(&status_vector);
statement->rsr_iface->setTimeout(&status_vector, sqldata->p_sqldata_timeout);
check(&status_vector);
if ((flags & IStatement::FLAG_HAS_CURSOR) && (out_msg_length == 0))
{
statement->rsr_cursor =

View File

@ -379,6 +379,9 @@ public:
void free(Firebird::CheckStatusWrapper* status);
unsigned getFlags(Firebird::CheckStatusWrapper* status);
unsigned int getTimeout(Firebird::CheckStatusWrapper* status);
void setTimeout(Firebird::CheckStatusWrapper* status, unsigned int timeOut);
public:
Firebird::Mutex statementMutex;
YAttachment* attachment;
@ -470,6 +473,11 @@ public:
Firebird::IMessageMetadata* inMetadata, void* inBuffer,
Firebird::IMessageMetadata* outMetadata, void* outBuffer);
unsigned int getIdleTimeout(Firebird::CheckStatusWrapper* status);
void setIdleTimeout(Firebird::CheckStatusWrapper* status, unsigned int timeOut);
unsigned int getStatementTimeout(Firebird::CheckStatusWrapper* status);
void setStatementTimeout(Firebird::CheckStatusWrapper* status, unsigned int timeOut);
public:
Firebird::IProvider* provider;
Firebird::PathName dbPath;

View File

@ -221,6 +221,7 @@ static const TOK tokens[] =
{TOK_HAVING, "HAVING", false},
{TOK_HOUR, "HOUR", false},
{TOK_IDENTITY, "IDENTITY", true},
{TOK_IDLE, "IDLE", true},
{TOK_IF, "IF", true},
{TOK_IGNORE, "IGNORE", true},
{TOK_IIF, "IIF", true},
@ -394,6 +395,7 @@ static const TOK tokens[] =
{TOK_SENSITIVE, "SENSITIVE", false},
{TOK_SEQUENCE, "SEQUENCE", true},
{TOK_SERVERWIDE, "SERVERWIDE", true},
{TOK_SESSION, "SESSION", true},
{TOK_SET, "SET", false},
{TOK_SHADOW, "SHADOW", true},
{TOK_SHARED, "SHARED", true},

View File

@ -2661,6 +2661,29 @@ ISC_STATUS API_ROUTINE isc_dsql_set_cursor_name(ISC_STATUS* userStatus, FB_API_H
}
// Set statement timeout.
ISC_STATUS API_ROUTINE fb_dsql_set_timeout(ISC_STATUS* userStatus, FB_API_HANDLE* stmtHandle,
ULONG timeout)
{
StatusVector status(userStatus);
CheckStatusWrapper statusWrapper(&status);
try
{
RefPtr<IscStatement> statement(translateHandle(statements, stmtHandle));
if (statement->statement)
statement->statement->setTimeout(&statusWrapper, timeout);
}
catch (const Exception& e)
{
e.stuffException(&statusWrapper);
}
return status[1];
}
// Provide information on sql statement.
ISC_STATUS API_ROUTINE isc_dsql_sql_info(ISC_STATUS* userStatus, FB_API_HANDLE* stmtHandle,
SSHORT itemLength, const SCHAR* items, SSHORT bufferLength, SCHAR* buffer)
@ -4464,6 +4487,36 @@ FB_BOOLEAN IscStatement::fetch(CheckStatusWrapper* status, IMessageMetadata* out
return statement->cursor->fetchNext(status, outBuffer) == IStatus::RESULT_OK;
}
unsigned int YStatement::getTimeout(CheckStatusWrapper* status)
{
try
{
YEntry<YStatement> entry(status, this);
return entry.next()->getTimeout(status);
}
catch (const Exception& e)
{
e.stuffException(status);
}
return 0;
}
void YStatement::setTimeout(CheckStatusWrapper* status, unsigned int timeOut)
{
try
{
YEntry<YStatement> entry(status, this);
entry.next()->setTimeout(status, timeOut);
}
catch (const Exception& e)
{
e.stuffException(status);
}
}
//-------------------------------------
@ -5539,6 +5592,66 @@ void YAttachment::getNextTransaction(CheckStatusWrapper* status, ITransaction* t
}
unsigned int YAttachment::getIdleTimeout(CheckStatusWrapper* status)
{
try
{
YEntry<YAttachment> entry(status, this);
return entry.next()->getIdleTimeout(status);
}
catch (const Exception& e)
{
e.stuffException(status);
}
return 0;
}
void YAttachment::setIdleTimeout(CheckStatusWrapper* status, unsigned int timeOut)
{
try
{
YEntry<YAttachment> entry(status, this);
entry.next()->setIdleTimeout(status, timeOut);
}
catch (const Exception& e)
{
e.stuffException(status);
}
}
unsigned int YAttachment::getStatementTimeout(CheckStatusWrapper* status)
{
try
{
YEntry<YAttachment> entry(status, this);
return entry.next()->getStatementTimeout(status);
}
catch (const Exception& e)
{
e.stuffException(status);
}
return 0;
}
void YAttachment::setStatementTimeout(CheckStatusWrapper* status, unsigned int timeOut)
{
try
{
YEntry<YAttachment> entry(status, this);
entry.next()->setStatementTimeout(status, timeOut);
}
catch (const Exception& e)
{
e.stuffException(status);
}
}
//-------------------------------------

View File

@ -89,6 +89,7 @@ ISC_STATUS API_ROUTINE isc_dsql_prepare_m(ISC_STATUS*, FB_API_HANDLE*,
SCHAR*);
ISC_STATUS API_ROUTINE isc_dsql_set_cursor_name(ISC_STATUS*, FB_API_HANDLE*,
const SCHAR*, USHORT);
ISC_STATUS API_ROUTINE fb_dsql_set_timeout(ISC_STATUS*, FB_API_HANDLE*, ULONG timeout);
ISC_STATUS API_ROUTINE isc_dsql_sql_info(ISC_STATUS*, FB_API_HANDLE*, SSHORT,
const SCHAR*, SSHORT, SCHAR*);
//ISC_STATUS API_ROUTINE isc_prepare_transaction2(ISC_STATUS*, FB_API_HANDLE*, USHORT,