Author: wyoung
Date: Thu Dec 27 04:18:04 2007
New Revision: 2028
URL: http://svn.gna.org/viewcvs/mysqlpp?rev=2028&view=rev
Log:
Fixes to thread safety, memory management and last-used time handling in
ConnectionPool. Initial patch by Jonathan Wakely, greatly reworked.
Modified:
trunk/lib/cpool.cpp
trunk/lib/cpool.h
Modified: trunk/lib/cpool.cpp
URL:
http://svn.gna.org/viewcvs/mysqlpp/trunk/lib/cpool.cpp?rev=2028&r1=2027&r2=2028&view=diff
==============================================================================
--- trunk/lib/cpool.cpp (original)
+++ trunk/lib/cpool.cpp Thu Dec 27 04:18:04 2007
@@ -1,9 +1,10 @@
/***********************************************************************
cpool.cpp - Implements the ConnectionPool class.
- Copyright (c) 2007 by Educational Technology Resources, Inc.
- Others may also hold copyrights on code in this file. See the
- CREDITS file in the top directory of the distribution for details.
+ Copyright (c) 2007 by Educational Technology Resources, Inc. and
+ (c) 2007 by Jonathan Wakely. Others may also hold copyrights on
+ code in this file. See the CREDITS file in the top directory of
+ the distribution for details.
This file is part of MySQL++.
@@ -27,7 +28,71 @@
#include "connection.h"
+#include <algorithm>
+#include <functional>
+
namespace mysqlpp {
+
+
+/// \brief Functor to test whether a given ConnectionInfo object is
+/// "too old".
+///
+/// This is a template only because ConnectionInfo is private. Making
+/// it a template means the private type is only used at the point of
+/// instantiation, where it is accessible.
+
+template <typename ConnInfoT>
+class TooOld : std::unary_function<ConnInfoT, bool>
+{
+public:
+ TooOld(unsigned int tmax) :
+ min_age_(time(0) - tmax)
+ {
+ }
+
+ bool operator()(const ConnInfoT& conn_info) const
+ {
+ return !conn_info.in_use && conn_info.last_used <= min_age_;
+ }
+
+private:
+ time_t min_age_;
+};
+
+
+
+//// clear /////////////////////////////////////////////////////////////
+// Destroy all connections in the pool and drain pool.
+
+void
+ConnectionPool::clear()
+{
+ ScopedLock lock(mutex_); // ensure we're not interfered with
+
+ for (PoolIt it = pool_.begin(); it != pool_.end(); ++it) {
+ destroy(it->conn);
+ }
+ pool_.clear();
+}
+
+
+//// find_mru //////////////////////////////////////////////////////////
+// Find most recently used available connection. Uses operator< for
+// ConnectionInfo to order pool with MRU connection last. Returns 0 if
+// there are no connections not in use.
+
+Connection*
+ConnectionPool::find_mru()
+{
+ PoolIt mru = std::max_element(pool_.begin(), pool_.end());
+ if (mru != pool_.end() && !mru->in_use) {
+ mru->in_use = true;
+ return mru->conn;
+ }
+ else {
+ return 0;
+ }
+}
//// grab //////////////////////////////////////////////////////////////
@@ -35,50 +100,15 @@
Connection*
ConnectionPool::grab()
{
- mutex_.lock();
-
- // Find first unused connection in the pool
- PoolIt it = pool_.begin();
- while ((it != pool_.end()) && it->in_use) {
- ++it;
- }
-
- if (it != pool_.end()) {
- // Found at least one unused connection. Try to find more, and
- // decide which is the most recently used.
- unsigned int tmax = max_idle_time();
- time_t now = time(0);
- PoolIt mru = it;
- ++it;
- while (it != pool_.end()) {
- if (it->in_use) {
- // Can't use this one, it's busy
- ++it;
- }
- else if ((now - it->last_used) > tmax) {
- // This one's too old; nuke it
- PoolIt doomed = it;
- ++it;
- pool_.erase(doomed);
- }
- else if (it->last_used > mru->last_used) {
- // Ah, found a free one more recently used;
hang onto it
- mru = it;
- ++it;
- }
- }
-
- mru->in_use = true;
- mutex_.unlock();
- return mru->conn;
+ ScopedLock lock(mutex_); // ensure we're not interfered with
+ remove_old_connections();
+ if (Connection* mru = find_mru()) {
+ return mru;
}
else {
- // Pool was empty when this function was called, so create and
- // return a new one.
+ // No free connections, so create and return a new one.
pool_.push_back(ConnectionInfo(create()));
- Connection* pc = pool_.back().conn;
- mutex_.unlock();
- return pc;
+ return pool_.back().conn;
}
}
@@ -88,17 +118,34 @@
void
ConnectionPool::release(const Connection* pc)
{
- mutex_.lock();
+ ScopedLock lock(mutex_); // ensure we're not interfered with
for (PoolIt it = pool_.begin(); it != pool_.end(); ++it) {
if (it->conn == pc) {
it->in_use = false;
+ it->last_used = time(0);
break;
}
}
+}
- mutex_.unlock();
+
+//// remove_old_connections ////////////////////////////////////////////
+// Remove connections that were last used too long ago.
+
+void
+ConnectionPool::remove_old_connections()
+{
+ PoolIt it = pool_.begin(), doomed;
+ TooOld<ConnectionInfo> too_old(max_idle_time());
+
+ while ((it = std::find_if(it, pool_.end(), too_old)) != pool_.end()) {
+ doomed = it++;
+ destroy(doomed->conn);
+ pool_.erase(doomed);
+ }
}
+
} // end namespace mysqlpp
Modified: trunk/lib/cpool.h
URL:
http://svn.gna.org/viewcvs/mysqlpp/trunk/lib/cpool.h?rev=2028&r1=2027&r2=2028&view=diff
==============================================================================
--- trunk/lib/cpool.h (original)
+++ trunk/lib/cpool.h Thu Dec 27 04:18:04 2007
@@ -2,9 +2,10 @@
/// \brief Declares the ConnectionPool class.
/***********************************************************************
- Copyright (c) 2007 by Educational Technology Resources, Inc.
- Others may also hold copyrights on code in this file. See the
- CREDITS file in the top directory of the distribution for details.
+ Copyright (c) 2007 by Educational Technology Resources, Inc. and
+ (c) 2007 by Jonathan Wakely. Others may also hold copyrights on
+ code in this file. See the CREDITS file in the top directory of
+ the distribution for details.
This file is part of MySQL++.
@@ -31,6 +32,7 @@
#include <list>
+#include <assert.h>
#include <time.h>
namespace mysqlpp {
@@ -63,20 +65,44 @@
ConnectionPool() { }
/// \brief Dtor
- virtual ~ConnectionPool() { }
-
- /// \brief Get the most recently used free connection in the pool.
+ ///
+ /// If this assertion is raised, it means the derived class isn't
+ /// calling clear() in its dtor.
+ virtual ~ConnectionPool() { assert(pool_.empty()); }
+
+ /// \brief Drains the pool, freeing all allocated memory.
+ ///
+ /// A derived class must call this in its dtor to avoid leaking all
+ /// Connection objects still in existence. We can't do it up at
+ /// this level because this class's dtor can't call our subclass's
+ /// destroy() method.
+ void clear();
+
+ /// \brief Grab a free connection from the pool.
///
/// This method creates a new connection if an unused one doesn't
/// exist, and destroys any that have remained unused for too long.
+ /// If there is more than one free connection, we return the most
+ /// recently used one; this allows older connections to die off over
+ /// time when the caller's need for connections decreases.
///
- /// \retval a pointer to the most recently used connection
+ /// Do not delete the returned pointer. This object manages the
+ /// lifetime of connection objects it creates.
+ ///
+ /// \retval a pointer to the connection
Connection* grab();
- /// \brief Mark the given connection as no longer in use.
+ /// \brief Return a connection to the pool
///
- /// If you don't call this function, connections returned by
- /// connection() will never go away!
+ /// Marks the connection as no longer in use. Also resets the
+ /// last-used time to the current time, so a call implies that the
+ /// connection was used shortly prior.
+ ///
+ /// This means that you must always release connections as soon as
+ /// you're done with them. Don't hold on to idle connections! If
+ /// you delay releasing them, it screws up the "most recently used"
+ /// algorithm. If you never release them, they can never be closed
+ /// when idle, so you might as well not be using a pool.
void release(const Connection* pc);
protected:
@@ -92,6 +118,15 @@
///
/// \retval A connected Connection object
virtual Connection* create() = 0;
+
+ /// \brief Destroy a connection
+ ///
+ /// Subclasses must override this.
+ ///
+ /// This is for destroying the objects returned by create().
+ /// Because we can't know what the derived class did to create the
+ /// connection we can't reliably know how to destroy it.
+ virtual void destroy(Connection*) = 0;
/// \brief Returns the maximum number of seconds a connection is
/// able to remain idle before it is dropped.
@@ -116,9 +151,26 @@
in_use(true)
{
}
+
+ // Strict weak ordering for ConnectionInfo objects.
+ //
+ // This ordering defines all in-use connections to be "less
+ // than" those not in use. Within each group, connections
+ // less recently touched are less than those more recent.
+ bool operator<(const ConnectionInfo& rhs) const
+ {
+ const ConnectionInfo& lhs = *this;
+ return lhs.in_use == rhs.in_use ?
+ lhs.last_used < rhs.last_used :
+ lhs.in_use;
+ }
};
typedef std::list<ConnectionInfo> PoolT;
typedef PoolT::iterator PoolIt;
+
+ //// Internal support functions
+ Connection* find_mru();
+ void remove_old_connections();
//// Internal data
PoolT pool_;
_______________________________________________
Mysqlpp-commits mailing list
[email protected]
https://mail.gna.org/listinfo/mysqlpp-commits