From 3a160a80c788d81e48acf19a2cf68f29cf125dae Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Thu, 25 Jan 2024 18:52:59 +0300 Subject: Turn libodb-mysql repository into package for muti-package repository --- libodb-mysql/odb/mysql/connection-factory.cxx | 301 ++++++++++++++++++++++++++ 1 file changed, 301 insertions(+) create mode 100644 libodb-mysql/odb/mysql/connection-factory.cxx (limited to 'libodb-mysql/odb/mysql/connection-factory.cxx') diff --git a/libodb-mysql/odb/mysql/connection-factory.cxx b/libodb-mysql/odb/mysql/connection-factory.cxx new file mode 100644 index 0000000..d53a4f4 --- /dev/null +++ b/libodb-mysql/odb/mysql/connection-factory.cxx @@ -0,0 +1,301 @@ +// file : odb/mysql/connection-factory.cxx +// license : GNU GPL v2; see accompanying LICENSE file + +#include // ODB_THREADS_* +#include // LIBODB_MYSQL_THR_KEY_VISIBLE + +#if defined(ODB_THREADS_POSIX) && defined(LIBODB_MYSQL_THR_KEY_VISIBLE) +# include +#endif + +#include // abort + +#include +#include + +#include +#include +#include + +// This key is in the mysql client library. We use it to resolve the +// following problem: Some pthread implementations zero-out slots that +// don't have destructors during thread termination. As a result, when +// our destructor gets called and we call mysql_thread_end(), the thread- +// specific slot used by MySQL may have been reset to 0 and as a result +// MySQL thinks the data has been freed. +// +// To work around this problem we are going to cache the MySQL's slot +// value and if, during destruction, we see that it is 0, we will restore +// the original value before calling mysql_thread_end(). This will work +// fine for as long as the following conditions are met: +// +// 1. MySQL doesn't use the destructor itself. +// 2. Nobody else tried to call mysql_thread_end() before us. +// +// Note: in 5.7 the key has been made static and is no longer accessible. +// +#if defined(ODB_THREADS_POSIX) && defined(LIBODB_MYSQL_THR_KEY_VISIBLE) +extern pthread_key_t THR_KEY_mysys; +#endif + +using namespace std; + +namespace odb +{ + using namespace details; + + namespace mysql + { + namespace + { + static bool main_thread_init_; + + struct mysql_thread_init + { +#ifndef ODB_THREADS_NONE + mysql_thread_init () + : init_ (false) + { + if (!main_thread_init_) + { + if (::mysql_thread_init ()) + { + throw database_exception ( + CR_UNKNOWN_ERROR, "?????", "thread initialization failed"); + } + + init_ = true; + +#if defined(ODB_THREADS_POSIX) && defined(LIBODB_MYSQL_THR_KEY_VISIBLE) + value_ = pthread_getspecific (THR_KEY_mysys); +#endif + } + } + + ~mysql_thread_init () + { + if (init_) + { +#if defined(ODB_THREADS_POSIX) && defined(LIBODB_MYSQL_THR_KEY_VISIBLE) + if (pthread_getspecific (THR_KEY_mysys) == 0) + pthread_setspecific (THR_KEY_mysys, value_); +#endif + mysql_thread_end (); + } + } + + private: + bool init_; +#if defined(ODB_THREADS_POSIX) && defined(LIBODB_MYSQL_THR_KEY_VISIBLE) + void* value_; +#endif +#endif // ODB_THREADS_NONE + }; + + static ODB_TLS_OBJECT (mysql_thread_init) mysql_thread_init_; + + struct mysql_process_init + { + mysql_process_init () + { + // Force allocation of our thread-specific key before THR_KEY_mysys + // in MySQL. This will (hopefully) get us the desired order of TLS + // destructor calls (i.e., our destructor before zeroing-out the + // THR_KEY_mysys value). This is pretty much the only way (except + // maybe guessing the THR_KEY_mysys value) to get clean thread + // termination if THR_KEY_mysys symbol is hidden, as is the case + // in the Fedora build of libmysqlclient. See also the comment + // at the beginning of this file. + // + main_thread_init_ = true; + tls_get (mysql_thread_init_); + main_thread_init_ = false; + + if (mysql_library_init (0 ,0, 0)) + abort (); + } + + ~mysql_process_init () + { + mysql_library_end (); + + // Finalize the main thread now in case TLS destruction + // doesn't happen for the main thread. + // + tls_free (mysql_thread_init_); + } + }; + + static mysql_process_init mysql_process_init_; + } + + // new_connection_factory + // + connection_ptr new_connection_factory:: + connect () + { + tls_get (mysql_thread_init_); + + return connection_ptr (new (shared) connection (*this)); + } + + // connection_pool_factory + // + connection_pool_factory::pooled_connection_ptr connection_pool_factory:: + create () + { + return pooled_connection_ptr (new (shared) pooled_connection (*this)); + } + + connection_pool_factory:: + ~connection_pool_factory () + { + // Wait for all the connections currently in use to return to + // the pool. + // + lock l (mutex_); + while (in_use_ != 0) + { + waiters_++; + cond_.wait (l); + waiters_--; + } + } + + connection_ptr connection_pool_factory:: + connect () + { + tls_get (mysql_thread_init_); + + // The outer loop checks whether the connection we were + // given is still valid. + // + while (true) + { + pooled_connection_ptr c; + + lock l (mutex_); + + // The inner loop tries to find a free connection. + // + while (true) + { + // See if we have a spare connection. + // + if (connections_.size () != 0) + { + c = connections_.back (); + connections_.pop_back (); + + c->callback_ = &c->cb_; + in_use_++; + break; + } + + // See if we can create a new one. + // + if(max_ == 0 || in_use_ < max_) + { + // For new connections we don't need to ping so we + // can return immediately. + // + c = create (); + c->callback_ = &c->cb_; + in_use_++; + return c; + } + + // Wait until someone releases a connection. + // + waiters_++; + cond_.wait (l); + waiters_--; + } + + l.unlock (); + + if (!ping_ || c->ping ()) + return c; + } + + return pooled_connection_ptr (); // Never reached. + } + + void connection_pool_factory:: + database (database_type& db) + { + tls_get (mysql_thread_init_); + + bool first (db_ == 0); + + connection_factory::database (db); + + if (!first) + return; + + if (min_ > 0) + { + connections_.reserve (min_); + + for(size_t i (0); i < min_; ++i) + connections_.push_back (create ()); + } + } + + bool connection_pool_factory:: + release (pooled_connection* c) + { + c->clear (); + c->callback_ = 0; + + lock l (mutex_); + + // Determine if we need to keep or free this connection. + // + bool keep (!c->failed () && + (waiters_ != 0 || + min_ == 0 || + (connections_.size () + in_use_ <= min_))); + + in_use_--; + + if (keep) + { + connections_.push_back (pooled_connection_ptr (inc_ref (c))); + connections_.back ()->recycle (); + } + + if (waiters_ != 0) + cond_.signal (); + + return !keep; + } + + // + // connection_pool_factory::pooled_connection + // + + connection_pool_factory::pooled_connection:: + pooled_connection (connection_pool_factory& f) + : connection (f) + { + cb_.arg = this; + cb_.zero_counter = &zero_counter; + } + + connection_pool_factory::pooled_connection:: + pooled_connection (connection_pool_factory& f, MYSQL* handle) + : connection (f, handle) + { + cb_.arg = this; + cb_.zero_counter = &zero_counter; + } + + bool connection_pool_factory::pooled_connection:: + zero_counter (void* arg) + { + pooled_connection* c (static_cast (arg)); + return static_cast (c->factory_).release (c); + } + } +} -- cgit v1.1