diff --git a/builds/install/misc/firebird.conf.in b/builds/install/misc/firebird.conf.in index 8d47540609..248c9480ef 100644 --- a/builds/install/misc/firebird.conf.in +++ b/builds/install/misc/firebird.conf.in @@ -986,3 +986,22 @@ # Type: string # #ServerMode = Super + +# ============================ +# Settings of External Connections Pool +# ============================ + +# Set the maximum number of inactive (idle) external connections to retain at +# the pool. Valid values are between 0 and 1000. If set to zero, pool is disabed, +# i.e. external connection is destroyed immediately after the use. +# +# Type: integer +# +#ExtConnPoolSize = 0 + +# Set the time before destroyng inactive external connection, seconds. +# Valid values are between 1 and 86400. +# +# Type: integer +# +#ExtConnPoolLifeTime = 7200 diff --git a/doc/sql.extensions/README.ddl.txt b/doc/sql.extensions/README.ddl.txt index a05013bda5..3a0726eef9 100644 --- a/doc/sql.extensions/README.ddl.txt +++ b/doc/sql.extensions/README.ddl.txt @@ -555,6 +555,7 @@ USE_GRANTED_BY_CLAUSE Use GRANTED BY in GRANT and REVOKE operators GRANT_REVOKE_ON_ANY_OBJECT GRANT and REVOKE rights on any object in database GRANT_REVOKE_ANY_DDL_RIGHT GRANT and REVOKE any DDL rights CREATE_PRIVILEGED_ROLES Use SET SYSTEM PRIVILEGES in roles +MODIFY_EXT_CONN_POOL Manage properties of pool of external connections 22) New grantee type in GRANT and REVOKE operators - SYSTEM PRIVILEGE. diff --git a/doc/sql.extensions/README.external_connections_pool b/doc/sql.extensions/README.external_connections_pool new file mode 100644 index 0000000000..3262bfcded --- /dev/null +++ b/doc/sql.extensions/README.external_connections_pool @@ -0,0 +1,100 @@ + The pool of external connections. + + To avoid delays when external connections established frequently, external +data source (EDS) subsystem is supplemented by the pool of external connections. +The pool keeps unused external connections for some time and allows to avoid the +cost of connecting / disconnecting for frequently used connection strings. + +Author: + Vlad Khorsun + +How pool works: +- every external connection is associated with a pool when created +- pool maintains two lists: idle connections and active connections +- when some connection become unused (i.e. it have no active requests and no + active transactions), it is reset and placed into idle list (on successful + reset) or closed (if reset failed). + Connection is reset using ALTER SESSION RESET statement. It is considered + successful if no error occurs. +- if the pool has reached max. size, the oldest idle connection is closed +- when engine ask to create a new external connection, the pool first looks + for candidate at the idle list. The search is based on 4 parameters: + - connection string, + - username, + - password, + - role. + The search is case sensitive. +- if suitable connection is found, then it tested if it is still alive + - if it did not pass the check, it is deleted and the search is repeated + (the error is not reported to the user) +- found (and alive) connection is moved from idle list to active list and + returned to the caller +- if there are several suitable connections, the most recently used is chosen +- if there is no suitable connection, the new one is created (and put into + active list) +- when idle connection gets expired (exceeded the lifetime), it is deleted from + the pool and closed. + +Key characteristics: +- absence of "eternal" external connections +- limited number of inactive (idle) external connections at the pool +- support a quick search among the connections (using 4 parameters above) +- the pool is common for all external databases +- the pool is common for all local connections handled by the given Firebird + process + +Pool parameters: +- connection life time: the time interval from the moment of the last usage of + connection after which it will be forcibly closed +- pool size: the maximum allowed number of idle connections in the pool + +Pool management: + New SQL statement is introduced to manage the pool: + + ALTER EXTERNAL CONNECTIONS POOL. + + When prepared it is described as DDL statement but have immediate effect: i.e. +it is executed immediately and completely, not waiting for transaction commit. +Changes applied to the in-memory instance of the pool in current Firebird +process. Therefore change in one Classic process doesn't affect other Classic +processes. Changes is not persistent and after restart Firebird will use pool +settings at firebird.conf (see below). + + New system privilege "MODIFY_EXT_CONN_POOL" is required to run the statement. + +The full syntax is: + +- ALTER EXTERNAL CONNECTIONS POOL SET SIZE + set maximum number of idle connections. + + Valid values are from 0 to 1000. + Value of zero means that pool is disabled. + Default value is set in firebird.conf (see below). + +- ALTER EXTERNAL CONNECTIONS POOL SET LIFETIME , + where is SECOND | MINUTE | HOUR + + Set idle connection lifetime, in seconds. + Valid values are from 1 SECOND to 24 HOUR. + Default value is set in firebird.conf (see below). + +- ALTER EXTERNAL CONNECTIONS POOL CLEAR ALL + Closes all idle connections. + Disassociates all active connections off the pool (such connections will be + closed immediately when gets unused). + +- ALTER EXTERNAL CONNECTIONS POOL CLEAR OLDEST + Closes expired idle connections. + + The state of external connections pool could be queried using new context +variables in 'SYSTEM' namespace: +- EXT_CONN_POOL_SIZE pool size +- EXT_CONN_POOL_LIFETIME idle connection lifetime, in seconds +- EXT_CONN_POOL_IDLE_COUNT count of currently inactive connections +- EXT_CONN_POOL_ACTIVE_COUNT count of active connections, associated with pool + + + Firebird configuration (firebird.conf) got two new settings related with pool: + +- ExtConnPoolSize = 0, pool size, and +- ExtConnPoolLifeTime = 7200, idle connection lifetime, seconds diff --git a/src/common/classes/ClumpletReader.h b/src/common/classes/ClumpletReader.h index 9b55c7a6e1..fbd2f3756b 100644 --- a/src/common/classes/ClumpletReader.h +++ b/src/common/classes/ClumpletReader.h @@ -144,6 +144,9 @@ public: const FB_SIZE_T len = getBufferLength(); return (len == other.getBufferLength()) && (memcmp(getBuffer(), other.getBuffer(), len) == 0); } + // Methods are virtual so writer can override 'em + virtual const UCHAR* getBuffer() const; + virtual const UCHAR* getBufferEnd() const; protected: enum ClumpletType {TraditionalDpb, SingleTpb, StringSpb, IntSpb, BigIntSpb, ByteSpb, Wide}; @@ -155,10 +158,6 @@ protected: Kind kind; UCHAR spbState; // Reflects state of spb parser/writer - // Methods are virtual so writer can override 'em - virtual const UCHAR* getBuffer() const; - virtual const UCHAR* getBufferEnd() const; - // These functions are called when error condition is detected by this class. // They may throw exceptions. If they don't reader tries to do something // sensible, certainly not overwrite memory or read past the end of buffer diff --git a/src/common/config/config.cpp b/src/common/config/config.cpp index 6aa616ba67..1047cf79c6 100644 --- a/src/common/config/config.cpp +++ b/src/common/config/config.cpp @@ -209,9 +209,11 @@ const Config::ConfigEntry Config::entries[MAX_CONFIG_KEY] = #ifdef WIN_NT {TYPE_STRING, "OutputRedirectionFile", (ConfigValue) "nul"}, #else - {TYPE_STRING, "OutputRedirectionFile", (ConfigValue) "/dev/null"} + {TYPE_STRING, "OutputRedirectionFile", (ConfigValue) "/dev/null"}, #endif #endif + {TYPE_INTEGER, "ExtConnPoolSize", (ConfigValue) 0}, + {TYPE_INTEGER, "ExtConnPoolLifeTime", (ConfigValue) 7200} }; /****************************************************************************** @@ -853,3 +855,13 @@ const char* Config::getOutputRedirectionFile() const char* file = (const char*) (getDefaultConfig()->values[KEY_OUTPUT_REDIRECTION_FILE]); return file; } + +int Config::getExtConnPoolSize() +{ + return getDefaultConfig()->get(KEY_EXT_CONN_POOL_SIZE); +} + +int Config::getExtConnPoolLifeTime() +{ + return getDefaultConfig()->get(KEY_EXT_CONN_POOL_LIFETIME); +} diff --git a/src/common/config/config.h b/src/common/config/config.h index 589358b63d..4e7a7f8dcf 100644 --- a/src/common/config/config.h +++ b/src/common/config/config.h @@ -147,6 +147,8 @@ public: KEY_CONN_IDLE_TIMEOUT, KEY_CLIENT_BATCH_BUFFER, KEY_OUTPUT_REDIRECTION_FILE, + KEY_EXT_CONN_POOL_SIZE, + KEY_EXT_CONN_POOL_LIFETIME, MAX_CONFIG_KEY // keep it last }; @@ -365,6 +367,10 @@ public: unsigned int getClientBatchBuffer() const; static const char* getOutputRedirectionFile(); + + static int getExtConnPoolSize(); + + static int getExtConnPoolLifeTime(); }; // Implementation of interface to access master configuration file diff --git a/src/dsql/DdlNodes.epp b/src/dsql/DdlNodes.epp index e6c561d259..3fe5548f82 100644 --- a/src/dsql/DdlNodes.epp +++ b/src/dsql/DdlNodes.epp @@ -63,6 +63,7 @@ #include "../common/StatusArg.h" #include "../auth/SecureRemotePassword/Message.h" #include "../jrd/Mapping.h" +#include "../jrd/extds/ExtDS.h" namespace Jrd { @@ -1161,6 +1162,86 @@ void AlterCharSetNode::execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch } +bool AlterEDSPoolSetNode::checkPermission(thread_db* tdbb, jrd_tra* transaction) +{ + if (!tdbb->getAttachment()->locksmith(tdbb, MODIFY_EXT_CONN_POOL)) + status_exception::raise(Arg::Gds(isc_miss_prvlg) << "MODIFY_EXT_CONN_POOL"); + + return true; +} + +string AlterEDSPoolSetNode::internalPrint(NodePrinter& printer) const +{ + DdlNode::internalPrint(printer); + + NODE_PRINT(printer, m_param); + NODE_PRINT(printer, m_value); + + return "AlterEDSPoolSetNode"; +} + +void AlterEDSPoolSetNode::execute(thread_db* tdbb, DsqlCompilerScratch* /*dsqlScratch*/, jrd_tra* /*transaction*/) +{ + switch (m_param) + { + case POOL_SIZE: + EDS::Manager::getConnPool()->setMaxCount(m_value); + break; + + case POOL_LIFETIME: + EDS::Manager::getConnPool()->setLifeTime(m_value); + break; + + default: + status_exception::raise( + Arg::Gds(isc_random) << Arg::Str("Unknown param for ALTER EXTERNAL CONNECTIONS POOL statement")); + } +} + + +bool AlterEDSPoolClearNode::checkPermission(thread_db* tdbb, jrd_tra* transaction) +{ + if (!tdbb->getAttachment()->locksmith(tdbb, MODIFY_EXT_CONN_POOL)) + status_exception::raise(Arg::Gds(isc_miss_prvlg) << "MODIFY_EXT_CONN_POOL"); + + return true; +} + +string AlterEDSPoolClearNode::internalPrint(NodePrinter& printer) const +{ + DdlNode::internalPrint(printer); + + NODE_PRINT(printer, m_param); + NODE_PRINT(printer, m_value); + + return "AlterEDSPoolClearNode"; +} + +void AlterEDSPoolClearNode::execute(thread_db* tdbb, DsqlCompilerScratch* /*dsqlScratch*/, jrd_tra* /*transaction*/) +{ + switch (m_param) + { + case POOL_ALL: + { + EDS::Manager::getConnPool()->clearIdle(tdbb, true); + break; + } + + case POOL_OLDEST: + { + EDS::Manager::getConnPool()->clearIdle(tdbb, false); + break; + } + + case POOL_DB: + //break; + + default: + status_exception::raise( + Arg::Gds(isc_random) << Arg::Str("Unknown param for ALTER EXTERNAL CONNECTIONS POOL statement")); + } +} + //---------------------- diff --git a/src/dsql/DdlNodes.h b/src/dsql/DdlNodes.h index ead1dc80b6..c605e28faf 100644 --- a/src/dsql/DdlNodes.h +++ b/src/dsql/DdlNodes.h @@ -295,6 +295,65 @@ private: }; +class AlterEDSPoolSetNode : public DdlNode +{ +public: + enum PARAM {POOL_SIZE, POOL_LIFETIME}; + + AlterEDSPoolSetNode(MemoryPool& pool, PARAM prm, int val) : + DdlNode(pool), + m_param(prm), + m_value(val) + { + } + +public: + virtual bool checkPermission(thread_db* tdbb, jrd_tra* transaction); + virtual Firebird::string internalPrint(NodePrinter& printer) const; + virtual void execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction); + +protected: + virtual void putErrorPrefix(Firebird::Arg::StatusVector& statusVector) + { + // TODO: statusVector << Firebird::Arg::Gds(??); + } + +private: + PARAM m_param; + int m_value; +}; + + +class AlterEDSPoolClearNode : public DdlNode +{ +public: + enum PARAM {POOL_ALL, POOL_OLDEST, POOL_DB}; + + AlterEDSPoolClearNode(MemoryPool& pool, PARAM prm, const Firebird::string& val = "") : + DdlNode(pool), + m_param(prm), + m_value(pool) + { + m_value = val; + } + +public: + virtual bool checkPermission(thread_db* tdbb, jrd_tra* transaction); + virtual Firebird::string internalPrint(NodePrinter& printer) const; + virtual void execute(thread_db* tdbb, DsqlCompilerScratch* dsqlScratch, jrd_tra* transaction); + +protected: + virtual void putErrorPrefix(Firebird::Arg::StatusVector& statusVector) + { + // TODO: statusVector << Firebird::Arg::Gds(??); + } + +private: + PARAM m_param; + Firebird::string m_value; +}; + + class CommentOnNode : public DdlNode { public: diff --git a/src/dsql/parse.y b/src/dsql/parse.y index 380f357c27..7215f73a6d 100644 --- a/src/dsql/parse.y +++ b/src/dsql/parse.y @@ -630,6 +630,13 @@ using namespace Firebird; %token VARBINARY %token WINDOW +// external connections pool management +%token CONNECTIONS +%token POOL +%token LIFETIME +%token CLEAR +%token OLDEST + // precedence declarations for expression evaluation %left OR @@ -1972,6 +1979,29 @@ alter_charset_clause { $$ = newNode(*$1, *$5); } ; +// +%type alter_eds_conn_pool_clause +alter_eds_conn_pool_clause + : SET SIZE unsigned_short_integer + { $$ = newNode(AlterEDSPoolSetNode::POOL_SIZE, $3); } + | SET LIFETIME unsigned_short_integer eds_pool_lifetime_mult + { $$ = newNode(AlterEDSPoolSetNode::POOL_LIFETIME, $3 * $4); } + | CLEAR sql_string + { $$ = newNode(AlterEDSPoolClearNode::POOL_DB, $2->getString()); } + | CLEAR ALL + { $$ = newNode(AlterEDSPoolClearNode::POOL_ALL); } + | CLEAR OLDEST + { $$ = newNode(AlterEDSPoolClearNode::POOL_OLDEST); } + ; + +%type eds_pool_lifetime_mult +eds_pool_lifetime_mult : + HOUR { $$ = 3600; } + | MINUTE { $$ = 60; } + | SECOND { $$ = 1; } + ; + + // CREATE DATABASE // ASF: CREATE DATABASE command is divided in three pieces: name, initial options and // remote options. @@ -3873,6 +3903,7 @@ alter_clause | SEQUENCE alter_sequence_clause { $$ = $2; } | MAPPING alter_map_clause(false) { $$ = $2; } | GLOBAL MAPPING alter_map_clause(true) { $$ = $3; } + | EXTERNAL CONNECTIONS POOL alter_eds_conn_pool_clause { $$ = $4; } ; %type alter_domain @@ -8526,6 +8557,11 @@ non_reserved_word | TIES | TOTALORDER | TRAPS + | CONNECTIONS // external connections pool management + | POOL + | LIFETIME + | CLEAR + | OLDEST ; %% diff --git a/src/jrd/Attachment.cpp b/src/jrd/Attachment.cpp index 33d0905c31..5d822ed327 100644 --- a/src/jrd/Attachment.cpp +++ b/src/jrd/Attachment.cpp @@ -197,6 +197,7 @@ Jrd::Attachment::Attachment(MemoryPool* pool, Database* dbb) att_dsql_cache(*pool), att_udf_pointers(*pool), att_ext_connection(NULL), + att_ext_parent(NULL), att_ext_call_depth(0), att_trace_manager(FB_NEW_POOL(*att_pool) TraceManager(this)), att_utility(UTIL_NONE), diff --git a/src/jrd/Attachment.h b/src/jrd/Attachment.h index 6610490903..fac452feb9 100644 --- a/src/jrd/Attachment.h +++ b/src/jrd/Attachment.h @@ -332,6 +332,7 @@ public: ThreadId att_purge_tid; // ID of thread running purge_attachment() EDS::Connection* att_ext_connection; // external connection executed by this attachment + EDS::Connection* att_ext_parent; // external connection, parent of this attachment ULONG att_ext_call_depth; // external connection call depth, 0 for user attachment TraceManager* att_trace_manager; // Trace API manager diff --git a/src/jrd/SysFunction.cpp b/src/jrd/SysFunction.cpp index b373b02b6b..09a4a6d5da 100644 --- a/src/jrd/SysFunction.cpp +++ b/src/jrd/SysFunction.cpp @@ -53,6 +53,7 @@ #include "../jrd/trace/TraceObjects.h" #include "../jrd/Collation.h" #include "../common/classes/FpeControl.h" +#include "../jrd/extds/ExtDS.h" #include using namespace Firebird; @@ -275,6 +276,10 @@ const char // SYSTEM namespace: global and database wise items ENGINE_VERSION[] = "ENGINE_VERSION", DATABASE_NAME[] = "DB_NAME", + EXT_CONN_POOL_SIZE[] = "EXT_CONN_POOL_SIZE", + EXT_CONN_POOL_IDLE[] = "EXT_CONN_POOL_IDLE_COUNT", + EXT_CONN_POOL_ACTIVE[] = "EXT_CONN_POOL_ACTIVE_COUNT", + EXT_CONN_POOL_LIFETIME[] = "EXT_CONN_POOL_LIFETIME", // SYSTEM namespace: connection wise items SESSION_ID_NAME[] = "SESSION_ID", NETWORK_PROTOCOL_NAME[] = "NETWORK_PROTOCOL", @@ -2664,6 +2669,17 @@ dsc* evlGetContext(thread_db* tdbb, const SysFunction*, const NestValueArray& ar resultStr.printf("%" SLONGFORMAT, transaction->tra_lock_timeout); else if (nameStr == READ_ONLY_NAME) resultStr = (transaction->tra_flags & TRA_readonly) ? TRUE_VALUE : FALSE_VALUE; + else if (nameStr == EXT_CONN_POOL_SIZE) + resultStr.printf("%d", EDS::Manager::getConnPool()->getMaxCount()); + else if (nameStr == EXT_CONN_POOL_IDLE) + resultStr.printf("%d", EDS::Manager::getConnPool()->getIdleCount()); + else if (nameStr == EXT_CONN_POOL_ACTIVE) + { + EDS::ConnectionsPool* connPool = EDS::Manager::getConnPool(); + resultStr.printf("%d", connPool->getAllCount() - connPool->getIdleCount()); + } + else if (nameStr == EXT_CONN_POOL_LIFETIME) + resultStr.printf("%d", EDS::Manager::getConnPool()->getLifeTime()); else { // "Context variable %s is not found in namespace %s" diff --git a/src/jrd/SystemPrivileges.h b/src/jrd/SystemPrivileges.h index 79c0c21a72..3e01473542 100644 --- a/src/jrd/SystemPrivileges.h +++ b/src/jrd/SystemPrivileges.h @@ -63,6 +63,7 @@ SYSTEM_PRIVILEGE(GRANT_REVOKE_ON_ANY_OBJECT) SYSTEM_PRIVILEGE(GRANT_REVOKE_ANY_DDL_RIGHT) SYSTEM_PRIVILEGE(CREATE_PRIVILEGED_ROLES) SYSTEM_PRIVILEGE(GET_DBCRYPT_INFO) +SYSTEM_PRIVILEGE(MODIFY_EXT_CONN_POOL) #ifdef FB_JRD_SYSTEM_PRIVILEGES_TMP maxSystemPrivilege diff --git a/src/jrd/extds/ExtDS.cpp b/src/jrd/extds/ExtDS.cpp index 52331c144d..9de688b300 100644 --- a/src/jrd/extds/ExtDS.cpp +++ b/src/jrd/extds/ExtDS.cpp @@ -27,6 +27,8 @@ #include "fb_exception.h" #include "iberror.h" +#include "../../common/classes/Hash.h" +#include "../../common/config/config.h" #include "../../dsql/chars.h" #include "../../dsql/ExprNodes.h" #include "../common/dsc.h" @@ -43,25 +45,61 @@ #include "../mov_proto.h" +#ifdef HAVE_SYS_TYPES_H +#include +#endif + +#ifdef HAVE_UNISTD_H +#include +#endif + +#ifdef WIN_NT +#include +#include +#endif + +//#define EDS_DEBUG + +#ifdef EDS_DEBUG + +#undef FB_ASSERT_FAILURE_STRING +#define FB_ASSERT_FAILURE_STRING "Procces ID %d: assertion (%s) failure: %s %" LINEFORMAT + +#undef fb_assert +#define fb_assert(ex) {if (!(ex)) {gds__log(FB_ASSERT_FAILURE_STRING, getpid(), #ex, __FILE__, __LINE__);}} + +#endif + using namespace Jrd; using namespace Firebird; namespace EDS { + // Manager GlobalPtr Manager::manager; Mutex Manager::m_mutex; Provider* Manager::m_providers = NULL; volatile bool Manager::m_initialized = false; +ConnectionsPool* Manager::m_connPool; + +const ULONG MIN_CONNPOOL_SIZE = 0; +const ULONG MAX_CONNPOOL_SIZE = 1000; + +const ULONG MIN_LIFE_TIME = 1; +const ULONG MAX_LIFE_TIME = 60 * 60 * 24; // one day Manager::Manager(MemoryPool& pool) : PermanentStorage(pool) { + m_connPool = FB_NEW_POOL(pool) ConnectionsPool(pool); } Manager::~Manager() { + fb_assert(m_connPool->getAllCount() == 0); + ThreadContextHolder tdbb; while (m_providers) @@ -71,6 +109,8 @@ Manager::~Manager() to_delete->clearConnections(tdbb); delete to_delete; } + + delete m_connPool; } void Manager::addProvider(Provider* provider) @@ -101,22 +141,11 @@ Provider* Manager::getProvider(const string& prvName) return NULL; } -Connection* Manager::getConnection(thread_db* tdbb, const string& dataSource, - const string& user, const string& pwd, const string& role, TraScope tra_scope) +static void splitDataSourceName(thread_db* tdbb, const string& dataSource, + string& prvName, PathName& dbName) { - if (!m_initialized) - { - MutexLockGuard guard(m_mutex, FB_FUNCTION); - if (!m_initialized) - { - m_initialized = true; - } - } - // dataSource : registered data source name // or connection string : provider::database - string prvName; - PathName dbName; if (dataSource.isEmpty()) { @@ -141,20 +170,94 @@ Connection* Manager::getConnection(thread_db* tdbb, const string& dataSource, dbName = dataSource.ToPathName(); } } - - Provider* prv = getProvider(prvName); - return prv->getConnection(tdbb, dbName, user, pwd, role, tra_scope); } -void Manager::jrdAttachmentEnd(thread_db* tdbb, Jrd::Attachment* att) +static bool isCurrentAccount(UserId* currUserID, + const MetaName& user, const string& pwd, const MetaName& role) { - for (Provider* prv = m_providers; prv; prv = prv->m_next) { - prv->jrdAttachmentEnd(tdbb, att); + const MetaName& attUser = currUserID->getUserName(); + const MetaName& attRole = currUserID->getSqlRole(); + + return ((user.isEmpty() || user == attUser) && pwd.isEmpty() && + (role.isEmpty() || role == attRole)); +} + +Connection* Manager::getConnection(thread_db* tdbb, const string& dataSource, + const string& user, const string& pwd, const string& role, TraScope tra_scope) +{ + Attachment* att = tdbb->getAttachment(); + if (att->att_ext_call_depth >= MAX_CALLBACKS) + ERR_post(Arg::Gds(isc_exec_sql_max_call_exceeded)); + + string prvName; + PathName dbName; + splitDataSourceName(tdbb, dataSource, prvName, dbName); + + Provider* prv = getProvider(prvName); + + const bool isCurrent = (prvName == INTERNAL_PROVIDER_NAME) && + isCurrentAccount(att->att_user, user, pwd, role); + + ClumpletWriter dpb(ClumpletReader::Tagged, MAX_DPB_SIZE); + if (!isCurrent) + prv->generateDPB(tdbb, dpb, user, pwd, role); + + // look up at connections already bound to current attachment + Connection* conn = prv->getBoundConnection(tdbb, dbName, dpb, tra_scope); + if (conn) + return conn; + + // if could be pooled, ask connections pool + + ULONG hash = 0; + + if (!isCurrent) + { + hash = DefaultHash::hash(dbName.c_str(), dbName.length(), MAX_ULONG) + + DefaultHash::hash(dpb.getBuffer(), dpb.getBufferLength(), MAX_ULONG); + + while (true) + { + conn = m_connPool->getConnection(tdbb, prv, hash, dbName, dpb); + if (!conn) + break; + + if (conn->validate(tdbb)) + { + prv->bindConnection(tdbb, conn); + break; + } + + // destroy invalid connection + m_connPool->delConnection(tdbb, conn, true); + } } + + if (!conn) + { + // finally, create new connection + conn = prv->createConnection(tdbb, dbName, dpb, tra_scope); + if (!isCurrent) + m_connPool->addConnection(tdbb, conn, hash); + } + + fb_assert(conn != NULL); + return conn; +} + +void Manager::jrdAttachmentEnd(thread_db* tdbb, Jrd::Attachment* att, bool forced) +{ + for (Provider* prv = m_providers; prv; prv = prv->m_next) + prv->jrdAttachmentEnd(tdbb, att, forced); } int Manager::shutdown() { + FbLocalStatus status; + ThreadContextHolder tdbb(&status); + + m_connPool->clear(tdbb); + for (Provider* prv = m_providers; prv; prv = prv->m_next) { prv->cancelConnections(); } @@ -174,46 +277,56 @@ Provider::Provider(const char* prvName) : Provider::~Provider() { - thread_db* tdbb = JRD_get_thread_data(); - clearConnections(tdbb); + fb_assert(m_connections.isEmpty()); } -Connection* Provider::getConnection(thread_db* tdbb, const PathName& dbName, - const string& user, const string& pwd, const string& role, TraScope tra_scope) +void Provider::generateDPB(thread_db* tdbb, ClumpletWriter& dpb, + const string& user, const string& pwd, const string& role) const { - const Jrd::Attachment* attachment = tdbb->getAttachment(); + dpb.reset(isc_dpb_version1); - if (attachment->att_ext_call_depth >= MAX_CALLBACKS) - ERR_post(Arg::Gds(isc_exec_sql_max_call_exceeded)); + const Attachment *attachment = tdbb->getAttachment(); - { // m_mutex scope - MutexLockGuard guard(m_mutex, FB_FUNCTION); + // bad for connection pooling + dpb.insertInt(isc_dpb_ext_call_depth, attachment->att_ext_call_depth + 1); - Connection** conn_ptr = m_connections.begin(); - Connection** end = m_connections.end(); + if ((getFlags() & prvTrustedAuth) && + isCurrentAccount(attachment->att_user, user, pwd, role)) + { + attachment->att_user->populateDpb(dpb); + } + else + { + if (!user.isEmpty()) { + dpb.insertString(isc_dpb_user_name, user); + } + if (!pwd.isEmpty()) { + dpb.insertString(isc_dpb_password, pwd); + } - for (; conn_ptr < end; conn_ptr++) + if (!role.isEmpty()) { - Connection* conn = *conn_ptr; - if (conn->m_boundAtt == attachment && - conn->isSameDatabase(tdbb, dbName, user, pwd, role) && - conn->isAvailable(tdbb, tra_scope)) - { - if (!conn->isBroken()) - return conn; - - FbLocalStatus status; - status->setErrors(Arg::Gds(isc_att_shutdown).value()); - conn->raise(&status, tdbb, "Provider::getConnection"); - } + dpb.insertByte(isc_dpb_sql_dialect, 0); + dpb.insertString(isc_dpb_sql_role_name, role); } } + CharSet* const cs = INTL_charset_lookup(tdbb, attachment->att_charset); + if (cs) { + dpb.insertString(isc_dpb_lc_ctype, cs->getName()); + } + + // remote network address??? +} + +Connection* Provider::createConnection(thread_db* tdbb, + const PathName& dbName, ClumpletReader& dpb, TraScope tra_scope) +{ Connection* conn = doCreateConnection(); + conn->setup(dbName, dpb); try { - conn->attach(tdbb, dbName, user, pwd, role); - conn->m_boundAtt = attachment; + conn->attach(tdbb); } catch (...) { @@ -221,33 +334,134 @@ Connection* Provider::getConnection(thread_db* tdbb, const PathName& dbName, throw; } - { // m_mutex scope - MutexLockGuard guard(m_mutex, FB_FUNCTION); - m_connections.add(conn); - } - + bindConnection(tdbb, conn); return conn; } -// hvlad: in current implementation I didn't return connections in pool as -// I have not implemented way to delete long idle connections. -void Provider::releaseConnection(thread_db* tdbb, Connection& conn, bool /*inPool*/) +void Provider::bindConnection(thread_db* tdbb, Connection* conn) { - { // m_mutex scope + Attachment* attachment = tdbb->getAttachment(); + MutexLockGuard guard(m_mutex, FB_FUNCTION); + + Attachment* oldAtt = conn->getBoundAtt(); + fb_assert(oldAtt == NULL); + if (m_connections.locate(AttToConn(oldAtt, conn))) + m_connections.fastRemove(); + + conn->setBoundAtt(attachment); + bool ret = m_connections.add(AttToConn(attachment, conn)); + fb_assert(ret); +} + +Connection* Provider::getBoundConnection(Jrd::thread_db* tdbb, + const Firebird::PathName& dbName, Firebird::ClumpletReader& dpb, + TraScope tra_scope) +{ + Database* dbb = tdbb->getDatabase(); + Attachment* att = tdbb->getAttachment(); + + MutexLockGuard guard(m_mutex, FB_FUNCTION); + + AttToConnMap::Accessor acc(&m_connections); + if (acc.locate(locGreatEqual, AttToConn(att, NULL))) + do + { + Connection* conn = acc.current().m_conn; + + if (conn->getBoundAtt() != att) + break; + + if (conn->isSameDatabase(dbName, dpb) && + conn->isAvailable(tdbb, tra_scope)) + { + fb_assert(conn->getProvider() == this); + +#ifdef EDS_DEBUG + if (!ConnectionsPool::checkBoundConnection(tdbb, conn)) + continue; +#endif + + return conn; + } + + } while (acc.getNext()); + + return NULL; +} + +void Provider::jrdAttachmentEnd(thread_db* tdbb, Jrd::Attachment* att, bool forced) +{ + Database* dbb = tdbb->getDatabase(); + + HalfStaticArray toRelease(getPool()); + + { MutexLockGuard guard(m_mutex, FB_FUNCTION); - conn.m_boundAtt = NULL; - - FB_SIZE_T pos; - if (!m_connections.find(&conn, pos)) - { - fb_assert(false); + AttToConnMap::Accessor acc(&m_connections); + if (!acc.locate(locGreatEqual, AttToConn(att, NULL))) return; + + do + { + Connection* conn = acc.current().m_conn; + if (conn->getBoundAtt() != att) + break; + + toRelease.push(conn); + } while(acc.getNext()); + } + + while (!toRelease.isEmpty()) + { + Connection* conn = toRelease.pop(); + releaseConnection(tdbb, *conn, !forced); + } +} + +void Provider::releaseConnection(thread_db* tdbb, Connection& conn, bool inPool) +{ + ConnectionsPool* connPool = conn.getConnPool(); + + { // m_mutex scope + Attachment* att = conn.getBoundAtt(); + MutexLockGuard guard(m_mutex, FB_FUNCTION); + + bool found = false; + AttToConnMap::Accessor acc(&m_connections); + if (acc.locate(AttToConn(att, &conn))) + { + Connection* test = acc.current().m_conn; + fb_assert(test == &conn); + + found = true; + acc.fastRemove(); + }; + + fb_assert(found); + conn.setBoundAtt(NULL); + + if (inPool && connPool) + m_connections.add(AttToConn(NULL, &conn)); + } + + if (!inPool || !connPool || !conn.isConnected() || !conn.resetSession()) + { + if (connPool) + { + { + MutexLockGuard guard(m_mutex, FB_FUNCTION); + AttToConnMap::Accessor acc(&m_connections); + if (acc.locate(AttToConn(NULL, &conn))) + acc.fastRemove(); + } + connPool->delConnection(tdbb, &conn, false); } - m_connections.remove(pos); + Connection::deleteConnection(tdbb, &conn); } - Connection::deleteConnection(tdbb, &conn); + else + connPool->putConnection(tdbb, &conn); } void Provider::clearConnections(thread_db* tdbb) @@ -256,14 +470,13 @@ void Provider::clearConnections(thread_db* tdbb) MutexLockGuard guard(m_mutex, FB_FUNCTION); - Connection** ptr = m_connections.begin(); - Connection** end = m_connections.end(); - - for (; ptr < end; ptr++) - { - Connection::deleteConnection(tdbb, *ptr); - *ptr = NULL; - } + AttToConnMap::Accessor acc(&m_connections); + if (acc.getFirst()) + do + { + Connection* conn = acc.current().m_conn; + Connection::deleteConnection(tdbb, conn); + } while (acc.getNext()); m_connections.clear(); } @@ -272,12 +485,13 @@ void Provider::cancelConnections() { MutexLockGuard guard(m_mutex, FB_FUNCTION); - Connection** ptr = m_connections.begin(); - Connection** end = m_connections.end(); - - for (; ptr < end; ptr++) { - (*ptr)->cancelExecution(true); - } + AttToConnMap::Accessor acc(&m_connections); + if (acc.getFirst()) + do + { + Connection* conn = acc.current().m_conn; + conn->cancelExecution(false); + } while (acc.getNext()); } // Connection @@ -286,11 +500,12 @@ Connection::Connection(Provider& prov) : PermanentStorage(prov.getPool()), m_provider(prov), m_dbName(getPool()), - m_dpb(getPool(), ClumpletReader::Tagged, MAX_DPB_SIZE), + m_dpb(getPool()), + m_boundAtt(NULL), m_transactions(getPool()), m_statements(getPool()), m_freeStatements(NULL), - m_boundAtt(NULL), + m_poolData(this), m_used_stmts(0), m_free_stmts(0), m_deleting(false), @@ -300,6 +515,14 @@ Connection::Connection(Provider& prov) : { } +void Connection::setup(const PathName& dbName, const ClumpletReader& dpb) +{ + m_dbName = dbName; + + m_dpb.clear(); + m_dpb.add(dpb.getBuffer(), dpb.getBufferLength()); +} + void Connection::deleteConnection(thread_db* tdbb, Connection* conn) { conn->m_deleting = true; @@ -312,52 +535,19 @@ void Connection::deleteConnection(thread_db* tdbb, Connection* conn) Connection::~Connection() { + fb_assert(m_boundAtt == NULL); } -void Connection::generateDPB(thread_db* tdbb, ClumpletWriter& dpb, - const MetaName& user, const string& pwd, const MetaName& role) const -{ - dpb.reset(isc_dpb_version1); - - const Jrd::Attachment* attachment = tdbb->getAttachment(); - dpb.insertInt(isc_dpb_ext_call_depth, attachment->att_ext_call_depth + 1); - - if ((m_provider.getFlags() & prvTrustedAuth) && - user.isEmpty() && pwd.isEmpty() && role.isEmpty() && attachment->att_user) - { - attachment->att_user->populateDpb(dpb); - } - else - { - if (!user.isEmpty()) { - dpb.insertString(isc_dpb_user_name, user); - } - if (!pwd.isEmpty()) { - dpb.insertString(isc_dpb_password, pwd); - } - if (!role.isEmpty()) { - dpb.insertString(isc_dpb_sql_role_name, role); - } - } - - CharSet* const cs = INTL_charset_lookup(tdbb, attachment->att_charset); - if (cs) { - dpb.insertString(isc_dpb_lc_ctype, cs->getName()); - } - - // remote network address??? -} - -bool Connection::isSameDatabase(thread_db* tdbb, const PathName& dbName, - const MetaName& user, const string& pwd, const MetaName& role) const +bool Connection::isSameDatabase(const PathName& dbName, ClumpletReader& dpb) const { if (m_dbName != dbName) return false; - ClumpletWriter dpb(ClumpletReader::dpbList, MAX_DPB_SIZE); - generateDPB(tdbb, dpb, user, pwd, role); + // it is not exact comparison as clumplets may have same tags + // but in different order - return m_dpb.simpleCompare(dpb); + const FB_SIZE_T len = m_dpb.getCount(); + return (len == dpb.getBufferLength()) && (memcmp(m_dpb.begin(), dpb.getBuffer(), len) == 0); } @@ -591,6 +781,667 @@ bool Connection::getWrapErrors(const ISC_STATUS* status) return m_wrapErrors; } + +/// ConnectionsPool + +ConnectionsPool::ConnectionsPool(MemoryPool& pool) : + m_pool(pool), + m_idleArray(pool), + m_idleList(NULL), + m_activeList(NULL), + m_allCount(0), + m_maxCount(Config::getExtConnPoolSize()), + m_lifeTime(Config::getExtConnPoolLifeTime()) +{ + if (m_maxCount > MAX_CONNPOOL_SIZE) + m_maxCount = MAX_CONNPOOL_SIZE; + if (m_maxCount < MIN_CONNPOOL_SIZE) + m_maxCount = MIN_CONNPOOL_SIZE; + + if (m_lifeTime > MAX_LIFE_TIME) + m_lifeTime = MAX_LIFE_TIME; + if (m_lifeTime < MIN_LIFE_TIME) + m_lifeTime = MIN_LIFE_TIME; +} + +ConnectionsPool::~ConnectionsPool() +{ + fb_assert(m_idleArray.isEmpty()); + fb_assert(m_idleList == NULL); + fb_assert(m_activeList == NULL); +} + +void ConnectionsPool::removeFromPool(Data* item, FB_SIZE_T pos) +{ + // m_mutex should be locked + fb_assert(item); + + if (item->m_lastUsed != 0) + { + if (pos == -1) + m_idleArray.find(*item, pos); + + fb_assert(m_idleArray[pos] == item); + m_idleArray.remove(pos); + removeFromList(&m_idleList, item); + } + else + removeFromList(&m_activeList, item); + + item->clear(); + m_allCount--; +} + + +// find least recently used item and remove it from pool +// caller should hold m_mutex and destroy returned item +ConnectionsPool::Data* ConnectionsPool::removeOldest() +{ + if (!m_idleList) + return NULL; + + Data* lru = m_idleList->m_prev; + removeFromPool(lru, -1); + + return lru; +} + +Connection* ConnectionsPool::getConnection(thread_db* tdbb, Provider* prv, ULONG hash, + const PathName& dbName, ClumpletReader& dpb) +{ + MutexLockGuard guard(m_mutex, FB_FUNCTION); + + Data data(hash); + + FB_SIZE_T pos; + m_idleArray.find(data, pos); + + for (; pos < m_idleArray.getCount(); pos++) + { + Data* item = m_idleArray[pos]; + if (item->m_hash != data.m_hash) + break; + + Connection* conn = item->m_conn; + if (conn->getProvider() == prv && conn->isSameDatabase(dbName, dpb)) + { + m_idleArray.remove(pos); + removeFromList(&m_idleList, item); + + item->m_lastUsed = 0; // mark as active + addToList(&m_activeList, item); + return conn; + } + } + + return NULL; +} + +void ConnectionsPool::putConnection(thread_db* tdbb, Connection* conn) +{ + fb_assert(conn->getConnPool() == this); + + Connection* oldConn = NULL; + bool startIdleTimer = false; + if (m_maxCount > 0) + { + MutexLockGuard guard(m_mutex, FB_FUNCTION); + + Data* item = conn->getPoolData(); +#ifdef EDS_DEBUG + if (!verifyPool()) + { + string str; + str.printf("Before put Item 0x%08X into pool\n", item); + printPool(str); + gds__log("Procces ID %d: connections pool is corrupted\n%s", getpid(), str.c_str()); + } +#endif + + if (item->m_lastUsed) + { + // Item was already put into idle list + fb_assert(item->m_connPool == this); + return; + } + + if (m_allCount > m_maxCount) + { + Data* oldest = removeOldest(); + if (oldest == item) + { +#ifdef EDS_DEBUG + string str; + str.printf("Item 0x%08X to put into pool is oldest", item); + gds__log("Procces ID %d: %s", getpid(), str.c_str()); +#endif + m_allCount++; + oldest = removeOldest(); + } + if (oldest) + oldConn = oldest->m_conn; + } + + if (item->m_lastUsed) + { + FB_SIZE_T pos; + fb_assert(m_idleArray.find(*item, pos)); + +#ifdef EDS_DEBUG + const bool ok = verifyPool(); + string str; + str.printf("Idle item 0x%08X put back into pool. Pool is %s", item, ok ? "OK" : "corrupted\n"); + + if (!ok) + printPool(str); + + gds__log("Procces ID %d: %s", getpid(), str.c_str()); +#endif + } + else + { + removeFromList(&m_activeList, item); + + time(&item->m_lastUsed); + fb_assert(item->m_lastUsed != 0); + if (item->m_lastUsed == 0) + item->m_lastUsed = 1; + + addToList(&m_idleList, item); + m_idleArray.add(item); + + if (!m_timer) + m_timer = FB_NEW IdleTimer(*this); + + startIdleTimer = true; + } + +#ifdef EDS_DEBUG + if (!verifyPool()) + { + string str; + str.printf("After put Item 0x%08X into pool\n", item); + printPool(str); + gds__log("Procces ID %d: connections pool is corrupted\n%s", getpid(), str.c_str()); + } +#endif + } + else + oldConn = conn; + + if (oldConn) + oldConn->getProvider()->releaseConnection(tdbb, *oldConn, false); + + if (startIdleTimer) + m_timer->start(); +} + +void ConnectionsPool::addConnection(thread_db* tdbb, Connection* conn, ULONG hash) +{ + Data* item = conn->getPoolData(); + item->m_hash = hash; + item->m_lastUsed = 0; + item->setConnPool(this); + + Connection* oldConn = NULL; + { + MutexLockGuard guard(m_mutex, FB_FUNCTION); + +#ifdef EDS_DEBUG + if (!verifyPool()) + { + string str; + printPool(str); + str.printf("Before add Item 0x%08X into pool\n", item); + gds__log("Procces ID %d: connections pool is corrupted\n%s", getpid(), str.c_str()); + } +#endif + if (m_allCount >= m_maxCount) + { + Data* oldest = removeOldest(); + if (oldest) + oldConn = oldest->m_conn; + } + + addToList(&m_activeList, item); + m_allCount++; + +#ifdef EDS_DEBUG + if (!verifyPool()) + { + string str; + printPool(str); + str.printf("After add Item 0x%08X into pool\n", item); + gds__log("Procces ID %d: connections pool is corrupted\n%s", getpid(), str.c_str()); + } +#endif + } + + if (oldConn) + oldConn->getProvider()->releaseConnection(tdbb, *oldConn, false); +} + +void ConnectionsPool::delConnection(thread_db* tdbb, Connection* conn, bool destroy) +{ + { + MutexLockGuard guard(m_mutex, FB_FUNCTION); + Data* item = conn->getPoolData(); + if (item->getConnPool() == this) + removeFromPool(item, -1); +#ifdef EDS_DEBUG + else + { + string str; + str.printf("Item 0x%08X to delete from pool already not there", item); + gds__log("Procces ID %d: %s", getpid(), str.c_str()); + } +#endif + } + + if (destroy) + conn->getProvider()->releaseConnection(tdbb, *conn, false); +} + +void ConnectionsPool::setMaxCount(ULONG val) +{ + if (val < MIN_CONNPOOL_SIZE || val > MAX_CONNPOOL_SIZE) + { + string err; + err.printf("Wrong value for connections pool size (%d). Allowed values are between %d and %d.", + val, MIN_CONNPOOL_SIZE, MAX_CONNPOOL_SIZE); + + ERR_post(Arg::Gds(isc_random) << Arg::Str(err)); + } + + MutexLockGuard guard(m_mutex, FB_FUNCTION); + m_maxCount = val; +} + +void ConnectionsPool::setLifeTime(ULONG val) +{ + if (val < MIN_LIFE_TIME || val > MAX_LIFE_TIME) + { + string err; + err.printf("Wrong value for pooled connection lifetime (%d). Allowed values are between %d and %d.", + val, MIN_LIFE_TIME, MAX_LIFE_TIME); + + ERR_post(Arg::Gds(isc_random) << Arg::Str(err)); + } + + bool startIdleTimer = false; + { + MutexLockGuard guard(m_mutex, FB_FUNCTION); + + startIdleTimer = (m_lifeTime > val) && (m_timer != NULL) && (m_idleList != NULL); + m_lifeTime = val; + } + + if (startIdleTimer) + m_timer->start(); +} + +void ConnectionsPool::clearIdle(thread_db* tdbb, bool all) +{ + Data* free = NULL; + { + MutexLockGuard guard(m_mutex, FB_FUNCTION); + + if (all) + { + while (!m_idleArray.isEmpty()) + { + FB_SIZE_T pos = m_idleArray.getCount() - 1; + Data* item = m_idleArray[pos]; + removeFromPool(item, pos); + + item->m_next = free; + free = item; + } + fb_assert(!m_idleList); + + while (m_activeList) + removeFromPool(m_activeList, -1); + + fb_assert(!m_allCount); + } + else + { + if (!m_idleList) + return; + + time_t t; + time(&t); + t -= m_lifeTime; + + while (m_idleList) + { + Data* item = m_idleList->m_prev; + if (item->m_lastUsed > t) + break; + + removeFromPool(item, -1); + item->m_next = free; + free = item; + }; + } + } + + while (free) + { + Connection* conn = free->m_conn; + free = free->m_next; + conn->getProvider()->releaseConnection(tdbb, *conn, false); + } +} + +void ConnectionsPool::clear(thread_db* tdbb) +{ + fb_assert(!tdbb || !tdbb->getDatabase()); + + MutexLockGuard guard(m_mutex, FB_FUNCTION); + + if (m_timer) + { + m_timer->stop(); + m_timer = NULL; + } + +#ifdef EDS_DEBUG + if (!verifyPool()) + { + string str; + printPool(str); + gds__log("Procces ID %d: connections pool is corrupted (clear)\n%s", getpid(), str.c_str()); + } +#endif + + while (m_idleArray.getCount()) + { + FB_SIZE_T i = m_idleArray.getCount() - 1; + Data* data = m_idleArray[i]; + Connection* conn = data->m_conn; + + removeFromPool(data, i); + conn->getProvider()->releaseConnection(tdbb, *conn, false); + } + fb_assert(!m_idleList); + + while (m_activeList) + { + Data* data = m_activeList; + removeFromPool(data, -1); + } + + fb_assert(m_allCount == 0); +} + +time_t ConnectionsPool::getIdleExpireTime() +{ + if (!m_idleList) + return 0; + + MutexLockGuard guard(m_mutex, FB_FUNCTION); + if (!m_idleList) + return 0; + + return m_idleList->m_prev->m_lastUsed + m_lifeTime; +} + +bool ConnectionsPool::checkBoundConnection(thread_db* tdbb, Connection* conn) +{ + if (conn->isCurrent()) + return true; + + ConnectionsPool::Data* item = conn->getPoolData(); + string s; + + if (!item->getConnPool()) + { + s.printf("Bound connection 0x%08X is not at the pool.\n", conn); + s.append(item->print()); + gds__log(s.c_str()); + return false; + } + + ConnectionsPool* pool = item->m_connPool; + MutexLockGuard guard(pool->m_mutex, FB_FUNCTION); + + if (!item->m_next || !item->m_prev) + { + s.printf("Bound connection 0x%08X is not at the pool list.\n", conn); + s.append(item->print()); + pool->printPool(s); + gds__log(s.c_str()); + return false; + } + + ConnectionsPool::Data* list = NULL; + if (item->m_lastUsed) + { + if (!pool->m_idleArray.exist(*item)) + { + s.printf("Bound connection 0x%08X is not found in idleArray.\n", conn); + s.append(item->print()); + pool->printPool(s); + gds__log(s.c_str()); + return false; + } + list = pool->m_idleList; + } + else + list = pool->m_activeList; + + if (!list) + { + s.printf("Bound connection 0x%08X belongs to the empty list.\n", conn); + s.append(item->print()); + pool->printPool(s); + gds__log(s.c_str()); + return false; + } + + ConnectionsPool::Data* p = list; + do + { + if (p == item) + break; + + p = p->m_next; + } while (p != list); + + if (p == item) + return true; + + s.printf("Bound connection 0x%08X is not found in pool lists.\n", conn); + s.append(item->print()); + pool->printPool(s); + gds__log(s.c_str()); + return false; +} + +void ConnectionsPool::printPool(string& str) +{ + string s; + s.printf("Conn pool 0x%08X, all %d, max %d, lifeTime %d\n", + this, m_allCount, m_maxCount, m_lifeTime); + str.append(s); + + s.printf(" active list 0x%08X:\n", m_activeList); + str.append(s); + + Data* item = m_activeList; + int cntActive = 0; + if (item) + do + { + str.append(item->print()); + item = item->m_next; + cntActive++; + } while (item != m_activeList); + + s.printf(" idle list 0x%08X:\n", m_idleList); + str.append(s); + + item = m_idleList; + int cntIdle = 0; + if (item) + do + { + str.append(item->print()); + item = item->m_next; + cntIdle++; + } while (item != m_idleList); + + s.printf(" active list count: %d\n", cntActive); + str.append(s); + s.printf(" idle list count: %d\n", cntIdle); + str.append(s); + s.printf(" idle array count: %d\n", m_idleArray.getCount()); + str.append(s); + + for (FB_SIZE_T i = 0; i < m_idleArray.getCount(); i++) + str.append(m_idleArray[i]->print()); +} + +string ConnectionsPool::Data::print() +{ + string s; + s.printf(" item 0x%08X, conn 0x%08X, hash %8u, used %" UQUADFORMAT ", next 0x%08X, prev 0x%08X, connected %s\n", + this, m_conn, m_hash, m_lastUsed, m_next, m_prev, + (m_conn && m_conn->isConnected()) ? "yes" : "NO"); + return s; +} + +int ConnectionsPool::Data::verify(ConnectionsPool* connPool, bool active) +{ + int errs = 0; + + if (m_connPool != connPool) + errs++; + if (!m_conn) + errs++; + if (!m_hash) + errs++; + if (!m_lastUsed && !active) + errs++; + if (m_lastUsed && active) + errs++; + if (!m_next || !m_prev) + errs++; + + if (m_conn && !m_conn->isConnected()) + errs++; + + return errs; +} + +bool ConnectionsPool::verifyPool() +{ + int cntIdle = 0, cntActive = 0; + int errs = 0; + + Data* item = m_idleList; + if (item) + do + { + cntIdle++; + errs += item->verify(this, false); + + FB_SIZE_T pos; + if (!m_idleArray.find(*item, pos)) + errs++; + else if (m_idleArray[pos] != item) + errs++; + + item = item->m_next; + } while (item != m_idleList); + + item = m_activeList; + if (item) + do + { + cntActive++; + errs += item->verify(this, true); + item = item->m_next; + } while (item != m_activeList); + + if (cntIdle != m_idleArray.getCount()) + errs++; + + if (cntIdle + cntActive != m_allCount) + errs++; + + return (errs == 0); +} + + +void ConnectionsPool::IdleTimer::handler() +{ + { + MutexLockGuard guard(m_mutex, FB_FUNCTION); + m_time = 0; + } + + FbLocalStatus status; + ThreadContextHolder tdbb(&status); + m_connPool.clearIdle(tdbb, false); + + start(); +} + +int ConnectionsPool::IdleTimer::release() +{ + if (--refCounter == 0) + { + delete this; + return 0; + } + + return 1; +} + +void ConnectionsPool::IdleTimer::start() +{ + FbLocalStatus s; + ITimerControl* timerCtrl = Firebird::TimerInterfacePtr(); + + const time_t expTime = m_connPool.getIdleExpireTime(); + if (expTime == 0) + return; + + MutexLockGuard guard(m_mutex, FB_FUNCTION); + + if (m_time && m_time <= expTime) + return; + + if (m_time) + timerCtrl->stop(&s, this); + + time_t t; + time(&t); + time_t delta = expTime - t; + + if (delta <= 0) + delta = 1; + + m_time = expTime; + timerCtrl->start(&s, this, delta * 1000 * 1000); +} + +void ConnectionsPool::IdleTimer::stop() +{ + MutexLockGuard guard(m_mutex, FB_FUNCTION); + if (!m_time) + return; + + m_time = 0; + + FbLocalStatus s; + ITimerControl* timerCtrl = Firebird::TimerInterfacePtr(); + timerCtrl->stop(&s, this); +} + + // Transaction Transaction::Transaction(Connection& conn) : @@ -1632,7 +2483,7 @@ void EngineCallbackGuard::init(thread_db* tdbb, Connection& conn, const char* fr m_mutex = &conn.m_mutex; m_saveConnection = NULL; - if (m_tdbb) + if (m_tdbb && m_tdbb->getDatabase()) { jrd_tra* transaction = m_tdbb->getTransaction(); if (transaction) @@ -1668,7 +2519,7 @@ EngineCallbackGuard::~EngineCallbackGuard() m_mutex->leave(); } - if (m_tdbb) + if (m_tdbb && m_tdbb->getDatabase()) { Jrd::Attachment* attachment = m_tdbb->getAttachment(); if (attachment && m_stable.hasData()) diff --git a/src/jrd/extds/ExtDS.h b/src/jrd/extds/ExtDS.h index 9985f5b56b..b2de6bbd29 100644 --- a/src/jrd/extds/ExtDS.h +++ b/src/jrd/extds/ExtDS.h @@ -42,6 +42,7 @@ namespace EDS { class Manager; class Provider; class Connection; +class ConnectionsPool; class Transaction; class Statement; class Blob; @@ -67,19 +68,22 @@ public: const Firebird::string& dataSource, const Firebird::string& user, const Firebird::string& pwd, const Firebird::string& role, TraScope tra_scope); - // Notify providers when some jrd attachment is about to be released - static void jrdAttachmentEnd(Jrd::thread_db* tdbb, Jrd::Attachment* att); - static int shutdown(); + static ConnectionsPool* getConnPool() { return m_connPool; } + // Release bound external connections when some jrd attachment is about to be released + static void jrdAttachmentEnd(Jrd::thread_db* tdbb, Jrd::Attachment* att, bool forced); + + static int shutdown(); private: static Firebird::GlobalPtr manager; static Firebird::Mutex m_mutex; static Provider* m_providers; static volatile bool m_initialized; + static ConnectionsPool* m_connPool; }; -// manages connections\connection pool +// manages connections class Provider : public Firebird::GlobalStorage { @@ -89,16 +93,24 @@ class Provider : public Firebird::GlobalStorage public: explicit Provider(const char* prvName); - // return existing or create new Connection - virtual Connection* getConnection(Jrd::thread_db* tdbb, const Firebird::PathName& dbName, - const Firebird::string& user, const Firebird::string& pwd, const Firebird::string& role, + // create new Connection + virtual Connection* createConnection(Jrd::thread_db* tdbb, + const Firebird::PathName& dbName, Firebird::ClumpletReader& dpb, TraScope tra_scope); - // Connection gets unused, release it into pool or delete it completely + // bind connection to the current attachment + void bindConnection(Jrd::thread_db* tdbb, Connection* conn); + + // get available connection already bound to the current attachment + Connection* getBoundConnection(Jrd::thread_db* tdbb, + const Firebird::PathName& dbName, Firebird::ClumpletReader& dpb, + TraScope tra_scope); + + // Connection gets unused, release it into pool or delete it immediately virtual void releaseConnection(Jrd::thread_db* tdbb, Connection& conn, bool inPool = true); - // Notify provider when some jrd attachment is about to be released - virtual void jrdAttachmentEnd(Jrd::thread_db* tdbb, Jrd::Attachment* att) = 0; + // release connections bound to the attachment + virtual void jrdAttachmentEnd(Jrd::thread_db* tdbb, Jrd::Attachment* att, bool forced); // cancel execution of every connection void cancelConnections(); @@ -123,6 +135,10 @@ protected: void clearConnections(Jrd::thread_db* tdbb); virtual Connection* doCreateConnection() = 0; + void generateDPB(Jrd::thread_db* tdbb, Firebird::ClumpletWriter& dpb, + const Firebird::string& user, const Firebird::string& pwd, + const Firebird::string& role) const; + // Protection against simultaneous attach database calls. Not sure we still // need it, but i believe it will not harm Firebird::Mutex m_mutex; @@ -130,7 +146,39 @@ protected: Firebird::string m_name; Provider* m_next; - Firebird::Array m_connections; + class AttToConn + { + public: + Jrd::Attachment* m_att; + Connection* m_conn; + + AttToConn() : + m_att(NULL), + m_conn(NULL) + {} + + AttToConn(Jrd::Attachment* att, Connection* conn) : + m_att(att), + m_conn(conn) + {} + + static const AttToConn& generate(const void*, const AttToConn& item) + { + return item; + } + + static bool greaterThan(const AttToConn& i1, const AttToConn& i2) + { + return (i1.m_att > i2.m_att) || + (i1.m_att == i2.m_att && i1.m_conn > i2.m_conn); + } + }; + + typedef Firebird::BePlusTree + AttToConnMap; + + AttToConnMap m_connections; int m_flags; }; @@ -141,26 +189,238 @@ const int prvNamedParams = 0x0004; // supports named parameters const int prvTrustedAuth = 0x0008; // supports trusted authentication +class ConnectionsPool +{ +public: + ConnectionsPool(Firebird::MemoryPool& pool); + ~ConnectionsPool(); + + // find and return cached connection or NULL + Connection* getConnection(Jrd::thread_db* tdbb, Provider* prv, ULONG hash, const Firebird::PathName& dbName, + Firebird::ClumpletReader& dpb); + + // put unused connection into pool or destroy it + void putConnection(Jrd::thread_db* tdbb, Connection* conn); + + // assotiate new active connection with pool + void addConnection(Jrd::thread_db* tdbb, Connection* conn, ULONG hash); + + // clear connection relation with pool + void delConnection(Jrd::thread_db* tdbb, Connection* conn, bool destroy); + + ULONG getIdleCount() const { return m_idleArray.getCount(); } + ULONG getAllCount() const { return m_allCount; } ; + + ULONG getMaxCount() const { return m_maxCount; } + void setMaxCount(ULONG val); + + ULONG getLifeTime() const { return m_lifeTime; } + void setLifeTime(ULONG val); + + // delete idle connections: all or older than lifetime + void clearIdle(Jrd::thread_db* tdbb, bool all); + + // delete all idle connections, remove from pool all active connections + void clear(Jrd::thread_db* tdbb); + + // return time when oldest idle connection should be released, or zero + time_t getIdleExpireTime(); + + // verify bound connection internals + static bool checkBoundConnection(Jrd::thread_db* tdbb, Connection* conn); +public: + // this class is embedded into Connection but managed by ConnectionsPool + class Data + { + public: + + // constructor for embedded into Connection instance + explicit Data(Connection* conn) + { + clear(); + m_conn = conn; + } + + ConnectionsPool* getConnPool() const { return m_connPool; } + + static const Data& generate(const Data* item) + { + return *item; + } + + static bool greaterThan(const Data& i1, const Data& i2) + { + if (i1.m_hash == i2.m_hash) + { + if (i1.m_lastUsed == i2.m_lastUsed) + return &i1 > &i2; + + return (i1.m_lastUsed < i2.m_lastUsed); + } + + return (i1.m_hash > i2.m_hash); + } + + private: + friend class ConnectionsPool; + + ConnectionsPool* m_connPool; + Connection* m_conn; + ULONG m_hash; + time_t m_lastUsed; + + // placement in connections list + Data* m_next; + Data* m_prev; + + Data(const Data&); + Data& operator=(const Data&); + + // create instance used to search for recently used connection by hash + explicit Data(ULONG hash) + { + clear(); + m_conn = NULL; + m_hash = hash; + m_lastUsed = MAX_SINT64; + } + + void clear() + { + m_connPool = NULL; + // m_conn = NULL; + m_hash = 0; + m_lastUsed = 0; + m_next = m_prev = NULL; + } + + void setConnPool(ConnectionsPool *connPool) + { + fb_assert(!connPool || !m_connPool); + m_connPool = connPool; + } + Firebird::string print(); + int verify(ConnectionsPool *connPool, bool active); + }; + +private: + class IdleTimer FB_FINAL : + public Firebird::RefCntIface > + { + public: + explicit IdleTimer(ConnectionsPool& connPool) : + m_connPool(connPool), + m_time(0) + {} + + // ITimer implementation + void handler(); + int release(); + + void start(); + void stop(); + private: + ConnectionsPool& m_connPool; + Firebird::Mutex m_mutex; + time_t m_time; // time when timer should fire, or zero + }; + + void addToList(Data** head, Data* item) + { + fb_assert(item->m_next == NULL); + fb_assert(item->m_prev == NULL); + fb_assert(head == (item->m_lastUsed ? &m_idleList : &m_activeList)); + + if (*head) + { + item->m_next = (*head); + item->m_prev = (*head)->m_prev; + + item->m_next->m_prev = item; + item->m_prev->m_next = item; + } + else + { + item->m_next = item; + item->m_prev = item; + } + + *head = item; + } + void removeFromList(Data** head, Data* item) + { + if (!item->m_next) + return; + + fb_assert(head == (item->m_lastUsed ? &m_idleList : &m_activeList)); + + if (item->m_next != item) + { + item->m_next->m_prev = item->m_prev; + item->m_prev->m_next = item->m_next; + if (*head == item) + *head = item->m_next; + } + else + { + fb_assert((*head) == item); + *head = NULL; + } + + item->m_next = item->m_prev = NULL; + } + + void removeFromPool(Data* item, FB_SIZE_T pos); + Data* removeOldest(); + + void printPool(Firebird::string& s); + bool verifyPool(); + + // Array of Data*, sorted by [hash, lastUsed desc] + typedef Firebird::SortedArray, Data, Data, Data> + IdleArray; + + Firebird::MemoryPool& m_pool; + Firebird::Mutex m_mutex; + IdleArray m_idleArray; + Data* m_idleList; + Data* m_activeList; + ULONG m_allCount; + ULONG m_maxCount; + ULONG m_lifeTime; // How long idle connection should wait before destroyng, seconds + Firebird::RefPtr m_timer; +}; + + class Connection : public Firebird::PermanentStorage { protected: friend class EngineCallbackGuard; friend class Provider; + // only Provider could create, setup and delete Connections + explicit Connection(Provider& prov); virtual ~Connection(); -public: static void deleteConnection(Jrd::thread_db* tdbb, Connection* conn); + void setup(const Firebird::PathName& dbName, const Firebird::ClumpletReader& dpb); + void setBoundAtt(Jrd::Attachment* att) { m_boundAtt = att; } + +public: Provider* getProvider() { return &m_provider; } - virtual void attach(Jrd::thread_db* tdbb, const Firebird::PathName& dbName, - const Firebird::MetaName& user, const Firebird::string& pwd, - const Firebird::MetaName& role) = 0; + Jrd::Attachment* getBoundAtt() const { return m_boundAtt; } + + ConnectionsPool* getConnPool() { return m_poolData.getConnPool(); } + ConnectionsPool::Data* getPoolData() { return &m_poolData; } + + virtual void attach(Jrd::thread_db* tdbb) = 0; virtual void detach(Jrd::thread_db* tdbb); virtual bool cancelExecution(bool forced) = 0; + virtual bool resetSession() = 0; int getSqlDialect() const { return m_sqlDialect; } @@ -171,10 +431,13 @@ public: virtual bool isAvailable(Jrd::thread_db* tdbb, TraScope traScope) const = 0; virtual bool isConnected() const = 0; + virtual bool validate(Jrd::thread_db* tdbb) = 0; - virtual bool isSameDatabase(Jrd::thread_db* tdbb, const Firebird::PathName& dbName, - const Firebird::MetaName& user, const Firebird::string& pwd, - const Firebird::MetaName& role) const; + virtual bool isSameDatabase(const Firebird::PathName& dbName, + Firebird::ClumpletReader& dpb) const; + + // only Internal provider is able to create "current" connections + virtual bool isCurrent() const { return false; } bool isBroken() const { @@ -213,10 +476,6 @@ public: virtual Blob* createBlob() = 0; protected: - void generateDPB(Jrd::thread_db* tdbb, Firebird::ClumpletWriter& dpb, - const Firebird::MetaName& user, const Firebird::string& pwd, - const Firebird::MetaName& role) const; - virtual Transaction* doCreateTransaction() = 0; virtual Statement* doCreateStatement() = 0; @@ -230,13 +489,14 @@ protected: Provider& m_provider; Firebird::PathName m_dbName; - Firebird::ClumpletWriter m_dpb; + Firebird::UCharBuffer m_dpb; + Jrd::Attachment* m_boundAtt; Firebird::Array m_transactions; Firebird::Array m_statements; Statement* m_freeStatements; - const Jrd::Attachment* m_boundAtt; + ConnectionsPool::Data m_poolData; static const int MAX_CACHED_STMTS = 16; int m_used_stmts; diff --git a/src/jrd/extds/InternalDS.cpp b/src/jrd/extds/InternalDS.cpp index fa1f84d78c..fb519e0b65 100644 --- a/src/jrd/extds/InternalDS.cpp +++ b/src/jrd/extds/InternalDS.cpp @@ -64,26 +64,35 @@ static GlobalPtr reg; // InternalProvider -void InternalProvider::jrdAttachmentEnd(thread_db* tdbb, Jrd::Attachment* att) +void InternalProvider::jrdAttachmentEnd(thread_db* tdbb, Attachment* att, bool forced) { - /*** - hvlad: this inactive code could be useful in the future, for example when EDS - connection pool will be implemented - it allows to remove closed connections - from the pool. + Provider::jrdAttachmentEnd(tdbb, att, forced); - if (m_connections.getCount() == 0) + Connection* conn = att->att_ext_parent; + if (!conn) return; - Connection** ptr = m_connections.end(); - Connection** begin = m_connections.begin(); - - for (ptr--; ptr >= begin; ptr--) { - InternalConnection* conn = (InternalConnection*) *ptr; - if (conn->getJrdAtt() == att->getInterface()) - releaseConnection(tdbb, *conn, false); + Database* dbb = tdbb->getDatabase(); + MutexLockGuard guard(m_mutex, FB_FUNCTION); + + AttToConnMap::Accessor acc(&m_connections); + if (acc.locate(AttToConn(conn->getBoundAtt(), conn))) + { + InternalConnection* intConn = (InternalConnection*) acc.current().m_conn; + if (!intConn->getJrdAtt() || intConn->getJrdAtt()->getHandle() != att) + { + fb_assert(intConn->getJrdAtt() == NULL); + return; + } + fb_assert(intConn == conn); + } + else + return; } - ***/ + + if (conn) + releaseConnection(tdbb, *conn, false); } void InternalProvider::getRemoteError(const FbStatusVector* status, string& err) const @@ -135,22 +144,17 @@ private: FbStatusVector *v; }; -void InternalConnection::attach(thread_db* tdbb, const PathName& dbName, - const MetaName& user, const string& pwd, - const MetaName& role) +void InternalConnection::attach(thread_db* tdbb) { fb_assert(!m_attachment); Database* dbb = tdbb->getDatabase(); - fb_assert(dbName.isEmpty() || dbName == dbb->dbb_database_name.c_str()); + Attachment* attachment = tdbb->getAttachment(); + fb_assert(m_dbName.isEmpty() || m_dbName == dbb->dbb_database_name.c_str()); // Don't wrap raised errors. This is needed for backward compatibility. setWrapErrors(false); - Jrd::Attachment* attachment = tdbb->getAttachment(); - if (attachment->att_user && - (user.isEmpty() || user == attachment->att_user->getUserName()) && - pwd.isEmpty() && - (role.isEmpty() || role == attachment->att_user->getSqlRole())) + if (m_dpb.isEmpty()) { m_isCurrent = true; m_attachment = attachment->getInterface(); @@ -159,11 +163,11 @@ void InternalConnection::attach(thread_db* tdbb, const PathName& dbName, { m_isCurrent = false; m_dbName = dbb->dbb_database_name.c_str(); - generateDPB(tdbb, m_dpb, user, pwd, role); // Avoid change of m_dpb by validatePassword() below - ClumpletWriter newDpb(m_dpb); + ClumpletWriter newDpb(ClumpletReader::Tagged, MAX_DPB_SIZE, m_dpb.begin(), m_dpb.getCount(), 0); validatePassword(tdbb, m_dbName, newDpb); + newDpb.insertInt(isc_dpb_ext_call_depth, attachment->att_ext_call_depth + 1); FbLocalStatus status; { @@ -176,9 +180,11 @@ void InternalConnection::attach(thread_db* tdbb, const PathName& dbName, if (status->getState() & IStatus::STATE_ERRORS) raise(&status, tdbb, "JProvider::attach"); + + attachment->att_ext_parent = this; } - m_sqlDialect = (m_attachment->getHandle()->att_database->dbb_flags & DBB_DB_SQL_dialect_3) ? + m_sqlDialect = (attachment->att_database->dbb_flags & DBB_DB_SQL_dialect_3) ? SQL_DIALECT_V6 : SQL_DIALECT_V5; } @@ -204,7 +210,7 @@ void InternalConnection::doDetach(thread_db* tdbb) att->detach(&status); } - if (status->getErrors()[1] == isc_att_shutdown) + if (status->getErrors()[1] == isc_att_shutdown || status->getErrors()[1] == isc_shutdown) { status->init(); } @@ -233,6 +239,20 @@ bool InternalConnection::cancelExecution(bool /*forced*/) return !(status->getState() & IStatus::STATE_ERRORS); } +bool InternalConnection::resetSession() +{ + fb_assert(!m_isCurrent); + + if (m_isCurrent) + return true; + + FbLocalStatus status; + m_attachment->execute(&status, NULL, 0, "ALTER SESSION RESET", + m_sqlDialect, NULL, NULL, NULL, NULL); + + return !(status->getState() & IStatus::STATE_ERRORS); +} + // this internal connection instance is available for the current execution context if it // a) is current connection and current thread's attachment is equal to // this attachment, or @@ -243,20 +263,46 @@ bool InternalConnection::isAvailable(thread_db* tdbb, TraScope /*traScope*/) con (m_isCurrent && (tdbb->getAttachment() == m_attachment->getHandle())); } -bool InternalConnection::isSameDatabase(thread_db* tdbb, const PathName& dbName, - const MetaName& user, const string& pwd, - const MetaName& role) const +bool InternalConnection::validate(thread_db* tdbb) +{ + if (m_isCurrent) + return true; + + if (!m_attachment) + return false; + + EngineCallbackGuard guard(tdbb, *this, FB_FUNCTION); + FbLocalStatus status; + m_attachment->ping(&status); + return status.isSuccess(); +} + +bool InternalConnection::isSameDatabase(const PathName& dbName, ClumpletReader& dpb) const { if (m_isCurrent) { - const UserId* attUser = m_attachment->getHandle()->att_user; - return (attUser && - (user.isEmpty() || user == attUser->getUserName()) && - pwd.isEmpty() && - (role.isEmpty() || role == attUser->getSqlRole())); + const Attachment* att = m_attachment->getHandle(); + const MetaName& attUser = att->att_user->getUserName(); + const MetaName& attRole = att->att_user->getSqlRole(); + + MetaName str; + + if (dpb.find(isc_dpb_user_name)) + { + dpb.getString(str); + if (str != attUser) + return false; + } + + if (dpb.find(isc_dpb_sql_role_name)) + { + dpb.getString(str); + if (str != attRole) + return false; + } } - return Connection::isSameDatabase(tdbb, dbName, user, pwd, role); + return Connection::isSameDatabase(dbName, dpb); } Transaction* InternalConnection::doCreateTransaction() @@ -358,7 +404,7 @@ void InternalTransaction::doRollback(FbStatusVector* status, thread_db* tdbb, bo m_transaction->rollback(&s); } - if (status->getErrors()[1] == isc_att_shutdown && !retain) + if ((status->getErrors()[1] == isc_att_shutdown || status->getErrors()[1] == isc_shutdown) && !retain) { m_transaction = NULL; status->init(); diff --git a/src/jrd/extds/InternalDS.h b/src/jrd/extds/InternalDS.h index b71e3de2d7..31ba8d8869 100644 --- a/src/jrd/extds/InternalDS.h +++ b/src/jrd/extds/InternalDS.h @@ -40,8 +40,9 @@ public: ~InternalProvider() {} + virtual void jrdAttachmentEnd(Jrd::thread_db* tdbb, Jrd::Attachment* att, bool forced); + virtual void initialize() {} - virtual void jrdAttachmentEnd(Jrd::thread_db* tdbb, Jrd::Attachment* att); virtual void getRemoteError(const Jrd::FbStatusVector* status, Firebird::string& err) const; protected: @@ -63,21 +64,20 @@ protected: virtual ~InternalConnection(); public: - virtual void attach(Jrd::thread_db* tdbb, const Firebird::PathName& dbName, - const Firebird::MetaName& user, const Firebird::string& pwd, - const Firebird::MetaName& role); + virtual void attach(Jrd::thread_db* tdbb); virtual bool cancelExecution(bool forced); + virtual bool resetSession(); virtual bool isAvailable(Jrd::thread_db* tdbb, TraScope traScope) const; virtual bool isConnected() const { return (m_attachment != 0); } + virtual bool validate(Jrd::thread_db* tdbb); - virtual bool isSameDatabase(Jrd::thread_db* tdbb, const Firebird::PathName& dbName, - const Firebird::MetaName& user, const Firebird::string& pwd, - const Firebird::MetaName& role) const; + virtual bool isSameDatabase(const Firebird::PathName& dbName, + Firebird::ClumpletReader& dpb) const; - bool isCurrent() const { return m_isCurrent; } + virtual bool isCurrent() const { return m_isCurrent; } Jrd::JAttachment* getJrdAtt() { return m_attachment; } diff --git a/src/jrd/extds/IscDS.cpp b/src/jrd/extds/IscDS.cpp index baf56db3cd..309a394ff4 100644 --- a/src/jrd/extds/IscDS.cpp +++ b/src/jrd/extds/IscDS.cpp @@ -110,15 +110,14 @@ IscConnection::~IscConnection() { } -void IscConnection::attach(thread_db* tdbb, const PathName& dbName, const MetaName& user, - const string& pwd, const MetaName& role) +void IscConnection::attach(thread_db* tdbb) { - m_dbName = dbName; - generateDPB(tdbb, m_dpb, user, pwd, role); + Attachment* attachment = tdbb->getAttachment(); // Avoid change of m_dpb by validatePassword() below - ClumpletWriter newDpb(m_dpb); + ClumpletWriter newDpb(ClumpletReader::Tagged, MAX_DPB_SIZE, m_dpb.begin(), m_dpb.getCount(), 0); validatePassword(tdbb, m_dbName, newDpb); + newDpb.insertInt(isc_dpb_ext_call_depth, attachment->att_ext_call_depth + 1); FbLocalStatus status; { @@ -240,6 +239,21 @@ bool IscConnection::cancelExecution(bool forced) return !(status->getState() & IStatus::STATE_ERRORS); } +bool IscConnection::resetSession() +{ + if (!m_handle) + return false; + + FbLocalStatus status; + m_iscProvider.isc_dsql_execute_immediate(&status, &m_handle, + NULL, 0, "ALTER SESSION RESET", m_sqlDialect, NULL); + + if (!(status->getState() & IStatus::STATE_ERRORS)) + return true; + + return false; // (status->getErrors()[1] == isc_dsql_error); +} + // this ISC connection instance is available for the current execution context if it // a) has no active statements or supports many active statements // and @@ -260,6 +274,22 @@ bool IscConnection::isAvailable(thread_db* tdbb, TraScope traScope) const return true; } +bool IscConnection::validate(Jrd::thread_db* tdbb) +{ + if (!m_handle) + return false; + + FbLocalStatus status; + + EngineCallbackGuard guard(tdbb, *this, FB_FUNCTION); + + char info[] = {isc_info_attachment_id, isc_info_end}; + char buff[32]; + + return m_iscProvider.isc_database_info(&status, &m_handle, + sizeof(info), info, sizeof(buff), buff) == 0; +} + Blob* IscConnection::createBlob() { return FB_NEW IscBlob(*this); @@ -1096,14 +1126,14 @@ ISC_STATUS ISC_EXPORT IscProvider::isc_dsql_execute2(FbStatusVector* user_status } ISC_STATUS ISC_EXPORT IscProvider::isc_dsql_execute_immediate(FbStatusVector* user_status, - isc_db_handle *, - isc_tr_handle *, - unsigned short, - const char*, - unsigned short, - const XSQLDA *) + isc_db_handle* db_handle, isc_tr_handle* tra_handle, unsigned short length, + const char* str, unsigned short dialect, const XSQLDA* sqlda) { - return notImplemented(user_status); + if (!m_api.isc_dsql_execute_immediate) + return notImplemented(user_status); + + return (*m_api.isc_dsql_execute_immediate) (IscStatus(user_status), + db_handle, tra_handle, length, str, dialect, sqlda); } ISC_STATUS ISC_EXPORT IscProvider::isc_dsql_fetch(FbStatusVector* user_status, @@ -1641,8 +1671,17 @@ void FBProvider::loadAPI() static bool isConnectionBrokenError(FbStatusVector* status) { - ISC_STATUS code = status->getErrors()[1]; - return (fb_utils::isNetworkError(code) || code == isc_att_shutdown); + const ISC_STATUS code = status->getErrors()[1]; + switch (code) + { + case isc_shutdown: + case isc_att_shutdown: + case isc_bad_db_handle: + return true; + + default: + return fb_utils::isNetworkError(code); + } } diff --git a/src/jrd/extds/IscDS.h b/src/jrd/extds/IscDS.h index 5f0614195d..9781cc848d 100644 --- a/src/jrd/extds/IscDS.h +++ b/src/jrd/extds/IscDS.h @@ -49,7 +49,6 @@ public: loadAPI(); } - virtual void jrdAttachmentEnd(Jrd::thread_db* /*tdbb*/, Jrd::Attachment* /*att*/) {} virtual void getRemoteError(const Jrd::FbStatusVector* status, Firebird::string& err) const; protected: @@ -517,15 +516,14 @@ protected: public: FB_API_HANDLE& getAPIHandle() { return m_handle; } - virtual void attach(Jrd::thread_db* tdbb, const Firebird::PathName& dbName, - const Firebird::MetaName& user, const Firebird::string& pwd, - const Firebird::MetaName& role); + virtual void attach(Jrd::thread_db* tdbb); virtual bool cancelExecution(bool forced); + virtual bool resetSession(); virtual bool isAvailable(Jrd::thread_db* tdbb, TraScope traScope) const; - virtual bool isConnected() const { return (m_handle != 0); } + virtual bool validate(Jrd::thread_db* tdbb); virtual Blob* createBlob(); diff --git a/src/jrd/jrd.cpp b/src/jrd/jrd.cpp index 17b4a406e2..77eb8db521 100644 --- a/src/jrd/jrd.cpp +++ b/src/jrd/jrd.cpp @@ -4183,6 +4183,8 @@ void JProvider::shutdown(CheckStatusWrapper* status, unsigned int timeout, const ThreadContextHolder tdbb; + EDS::Manager::shutdown(); + ULONG attach_count, database_count, svc_count; JRD_enum_attachments(NULL, attach_count, database_count, svc_count); @@ -7593,7 +7595,7 @@ static void purge_attachment(thread_db* tdbb, StableAttachmentPart* sAtt, unsign try { // allow to free resources used by dynamic statements - EDS::Manager::jrdAttachmentEnd(tdbb, attachment); + EDS::Manager::jrdAttachmentEnd(tdbb, attachment, forcedPurge); if (!(dbb->dbb_flags & DBB_bugcheck)) { @@ -8014,9 +8016,6 @@ static THREAD_ENTRY_DECLARE shutdown_thread(THREAD_ENTRY_PARAM arg) try { - // Shutdown external datasets manager - EDS::Manager::shutdown(); - { // scope MutexLockGuard guard(databases_mutex, FB_FUNCTION); diff --git a/src/yvalve/keywords.cpp b/src/yvalve/keywords.cpp index 3279086178..bafa55f816 100644 --- a/src/yvalve/keywords.cpp +++ b/src/yvalve/keywords.cpp @@ -116,6 +116,7 @@ static const TOK tokens[] = {TOK_CHARACTER, "CHARACTER", false}, {TOK_CHARACTER_LENGTH, "CHARACTER_LENGTH", false}, {TOK_CHECK, "CHECK", false}, + {TOK_CLEAR, "CLEAR", true}, {TOK_CLOSE, "CLOSE", false}, {TOK_COALESCE, "COALESCE", true}, {TOK_COLLATE, "COLLATE", false}, @@ -129,6 +130,7 @@ static const TOK tokens[] = {TOK_COMPUTED, "COMPUTED", true}, {TOK_CONDITIONAL, "CONDITIONAL", true}, {TOK_CONNECT, "CONNECT", false}, + {TOK_CONNECTIONS, "CONNECTIONS", true}, {TOK_CONSTRAINT, "CONSTRAINT", false}, {TOK_CONTAINING, "CONTAINING", true}, {TOK_CONTINUE, "CONTINUE", true}, @@ -257,6 +259,7 @@ static const TOK tokens[] = {TOK_LEFT, "LEFT", false}, {TOK_LENGTH, "LENGTH", true}, {TOK_LEVEL, "LEVEL", true}, + {TOK_LIFETIME, "LIFETIME", true}, {TOK_LIKE, "LIKE", false}, {TOK_LIMBO, "LIMBO", true}, {TOK_LINGER, "LINGER", true}, @@ -303,6 +306,7 @@ static const TOK tokens[] = {TOK_OCTET_LENGTH, "OCTET_LENGTH", false}, {TOK_OF, "OF", false}, {TOK_OFFSET, "OFFSET", false}, + {TOK_OLDEST, "OLDEST", true}, {TOK_ON, "ON", false}, {TOK_ONLY, "ONLY", false}, {TOK_OPEN, "OPEN", false}, @@ -330,6 +334,7 @@ static const TOK tokens[] = {TOK_PLACING, "PLACING", true}, {TOK_PLAN, "PLAN", false}, {TOK_PLUGIN, "PLUGIN", true}, + {TOK_POOL, "POOL", true}, {TOK_POSITION, "POSITION", false}, {TOK_POST_EVENT, "POST_EVENT", false}, {TOK_POWER, "POWER", true},