#include #include #include "SimpleLogFile.h" #include "GlobalConfigFile.h" #include "Random.h" #include "DatabaseWithRetry.h" const std::string DatabaseWithRetry::MASTER = "@RW"; const std::string DatabaseWithRetry::SLAVE = "@RO"; const std::string DatabaseWithRetry::BACK_OFFICE = "@back_office"; const std::string DatabaseWithRetry::CANDLES = "@candles"; const std::string DatabaseWithRetry::CANDLES_MASTER = "@candles_master"; const std::string DatabaseWithRetry::PAPER_MASTER = "@paper_master"; bool DatabaseWithRetry::probablyWorks(std::string const &name) { if (name.empty()) // Presumably someone else already looked up a value in a config file, and // didn't find anything. I don't know why else this would be empty. In // any case, we can't connect to a server named "". return false; std::vector< std::string > pieces = explode("@", name); if ((pieces.size() == 2) && (pieces[0] == "")) // Something like "@RO". Look up "RO" in the config file. See if we // find anything. return !getConfigItem("database_" + pieces[1]).empty(); // We have no reason to believe it's broken, so we optimistically say it's // good. return true; } void DatabaseWithRetry::commonInit() { // These config names and values are the same as can be found in // Common.php. // $local_settings['db_timezone'] = 'US/Eastern'; // $local_settings['db_timenames'] = 'en_US'; // $local_settings['db_timezone'] = 'Europe/Berlin'; // $local_settings['db_timenames'] = 'de_DE'; // getConfigItem: Use db_timenames to set the locale of the connection. // getConfigItem: If you fill in this value it will get copied to MySql's // getConfigItem: lc_time_names variable as is. This was copied from the // getConfigItem: web site and doesn't seem to have much value in our C++ // getConfigItem: servers. const std::string timeNames = getConfigItem("db_timenames"); if (timeNames != "") _initialQueries.push_back("SET lc_time_names = '" + mysqlEscapeString(timeNames) + "'"); // getConfigItem: Use db_timezone to set the timezone of the connection. // getConfigItem: This is probably essential. We use the timestamp type, // getConfigItem: not datetime, for the foreign markets. Your join // getConfigItem: between alerts and alerts_daily might not be right if // getConfigItem: your timezone is wrong. Even in the US markets, // getConfigItem: sometimes we read a datetime value in time_t format, // getConfigItem: and other times in YYYY-MM-DD HH:MM:SS format. const std::string timeZone = getConfigItem("db_timezone"); if (timeZone != "") _initialQueries.push_back("SET time_zone = '" + mysqlEscapeString(timeZone) + "'"); static volatile bool logged = false; if (!(logged || _initialQueries.empty())) { // This might happen more than once. That's not terrible, so I didn't // bother to create a mutex. At least it won't happen a lot. logged = true; TclList logMsg; logMsg<<__FILE__<<__LINE__<<__FUNCTION__; for (std::vector< std::string >::const_iterator it = _initialQueries.begin(); it != _initialQueries.end(); it++) { logMsg<<*it; } sendToLogFile(logMsg); } // getConfigItem: If you set db_log_all_queries=1 you will get a lot of data // getConfigItem: in your log file. Don't try this on a production system. // getConfigItem: Note: You can use DatabaseWithRetry::setLogAllQueries() // getConfigItem: to change this setting for an individual // getConfigItem: DatabaseWithRetry object. The setting in the config // getConfigItem: file is the default. _logAllQueries = getConfigItem("db_log_all_queries") == "1"; } bool DatabaseWithRetry::tryOnce(std::string sql, MYSQL *connection, std::string const &name) const { // The connection is handed to us, so we don't have to request it. // This might be called by findConnection(), and we don't want an infinite // recursion in case of error. if (!connection) return false; ThreadMonitor &m = ThreadMonitor::find(); const std::string prevState = m.getState(); m.setState(name + " query"); /* TclList logMsg; logMsg<pieces = explode("@", databaseName); if ((pieces.size() == 2) && (pieces[0] == "")) { // getConfigItem: This is how we set up database aliases. At some // getConfigItem: point in the code or config file you might say // getConfigItem: that a certain task should use database "@XXX". // getConfigItem: When we try to connect to the database, and we // getConfigItem: see a name like that, we look in the config file // getConfigItem: for the corresponding database_XXX item. Common // getConfigItem: examples include @RO / database_RO for a read // getConfigItem: only database, and @RW / database_RW for the // getConfigItem: master database. You typically see these in // getConfigItem: ../../live_server/config_common.txt. databaseName = getConfigItem("database_" + pieces[1]); } pieces = explode("#", databaseName); if ((pieces.size() == 2) && (pieces[0] == "")) { // This provides a way to change the database name at run time. The // assumption is that the database has gone down, and someone is // providing a replacement but doesn't want to restart the app. This // happened once with the realtick version of the alert server. std::ifstream file(pieces[1].c_str()); // Grab the first word from the file. This will automatically ignore // an optional blank line at the bottom of the file. file>>databaseName; } pieces = explode("|", databaseName); if (pieces.size() > 1) { // Alterntives. Pick one at random. Good if a database goes down. databaseName = pieces[getRandom31() % pieces.size()]; } pieces = explode(":", databaseName); std::string hostName; unsigned int port = 0; std::string database = "mydb"; if (pieces.size()) { hostName = pieces[0]; if (pieces.size() >= 2) { port = strtoulDefault(pieces[1], 0); } if ((pieces.size() >= 3) && (!pieces[2].empty())) { database = pieces[2]; } } _connection = mysql_init(NULL); mysql_options(_connection, MYSQL_OPT_CONNECT_TIMEOUT, "3"); MYSQL *connectAttempt = mysql_real_connect(_connection, hostName.c_str(), "web_client", // user "NeverSayDie!", // password database.c_str(), // Database port, // port NULL, // unix_socket CLIENT_FOUND_ROWS | CLIENT_MULTI_RESULTS); // flags if (!connectAttempt) { reportError(_connection, "connecting to database (hostName = '" + hostName + "'"); mysql_close(_connection); _connection = NULL; wait(1); } if (_connection) { TclList logMsg; logMsg<<__FILE__<<__LINE__<<__FUNCTION__ <::const_iterator it = _initialQueries.begin(); it != _initialQueries.end(); it++) { if (!_connection) break; tryOnce(*it, _connection, s_findConnection); } } return _connection; } void DatabaseWithRetry::dropConnection() const { if (_connection) { TclList logMsg; logMsg<<"DatabaseWithRetry.C" <<"Disconnect" <