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

Merge branch 'ExternalConnectionsPool'

This commit is contained in:
hvlad 2018-06-18 18:00:59 +03:00
commit e688ced75a
21 changed files with 1746 additions and 216 deletions

View File

@ -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

View File

@ -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.

View File

@ -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 <hvlad@users.sourceforge.net>
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 <int>
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 <int> <time_part>,
where <time_part> 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

View File

@ -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

View File

@ -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<int>(KEY_EXT_CONN_POOL_SIZE);
}
int Config::getExtConnPoolLifeTime()
{
return getDefaultConfig()->get<int>(KEY_EXT_CONN_POOL_LIFETIME);
}

View File

@ -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

View File

@ -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"));
}
}
//----------------------

View File

@ -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:

View File

@ -630,6 +630,13 @@ using namespace Firebird;
%token <metaNamePtr> VARBINARY
%token <metaNamePtr> WINDOW
// external connections pool management
%token <metaNamePtr> CONNECTIONS
%token <metaNamePtr> POOL
%token <metaNamePtr> LIFETIME
%token <metaNamePtr> CLEAR
%token <metaNamePtr> OLDEST
// precedence declarations for expression evaluation
%left OR
@ -1972,6 +1979,29 @@ alter_charset_clause
{ $$ = newNode<AlterCharSetNode>(*$1, *$5); }
;
//
%type <ddlNode> alter_eds_conn_pool_clause
alter_eds_conn_pool_clause
: SET SIZE unsigned_short_integer
{ $$ = newNode<AlterEDSPoolSetNode>(AlterEDSPoolSetNode::POOL_SIZE, $3); }
| SET LIFETIME unsigned_short_integer eds_pool_lifetime_mult
{ $$ = newNode<AlterEDSPoolSetNode>(AlterEDSPoolSetNode::POOL_LIFETIME, $3 * $4); }
| CLEAR sql_string
{ $$ = newNode<AlterEDSPoolClearNode>(AlterEDSPoolClearNode::POOL_DB, $2->getString()); }
| CLEAR ALL
{ $$ = newNode<AlterEDSPoolClearNode>(AlterEDSPoolClearNode::POOL_ALL); }
| CLEAR OLDEST
{ $$ = newNode<AlterEDSPoolClearNode>(AlterEDSPoolClearNode::POOL_OLDEST); }
;
%type <intVal> 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 <alterDomainNode> alter_domain
@ -8526,6 +8557,11 @@ non_reserved_word
| TIES
| TOTALORDER
| TRAPS
| CONNECTIONS // external connections pool management
| POOL
| LIFETIME
| CLEAR
| OLDEST
;
%%

View File

@ -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),

View File

@ -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

View File

@ -53,6 +53,7 @@
#include "../jrd/trace/TraceObjects.h"
#include "../jrd/Collation.h"
#include "../common/classes/FpeControl.h"
#include "../jrd/extds/ExtDS.h"
#include <math.h>
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"

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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> 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<Connection*> 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<AttToConn, AttToConn, Firebird::MemoryPool,
AttToConn, AttToConn>
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<Firebird::ITimerImpl<IdleTimer, Firebird::CheckStatusWrapper> >
{
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*, Firebird::EmptyStorage<Data*>, 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<IdleTimer> 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<Transaction*> m_transactions;
Firebird::Array<Statement*> 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;

View File

@ -64,26 +64,35 @@ static GlobalPtr<RegisterInternalProvider> 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;
}
return Connection::isSameDatabase(tdbb, dbName, user, pwd, role);
if (dpb.find(isc_dpb_sql_role_name))
{
dpb.getString(str);
if (str != attRole)
return false;
}
}
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();

View File

@ -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; }

View File

@ -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)
{
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);
}
}

View File

@ -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();

View File

@ -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);

View File

@ -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},