#ifndef __DatabaseWithRetry_h_ #define __DatabaseWithRetry_h_ #include #include #include "DatabaseSupport.h" #include "ThreadMonitor.h" #include "SimpleLogFile.h" /* When you make a database request and it fails, we repeat the request. * That's always the case. * * When we retry, we first break the connection, report an error to the log, * sleep briefly, then reconnect. This solves a lot of problems. * * We've considered an option to pass certain errors back rather than retrying. * In particular, a syntax error will never work so retrying doesn't do much. * Creating a new user account is one example (in PHP) where we actually * expect an error sometimes. If you chose a username that's already in use * we report the error all the way back to the end user so he can fix it. * So far we've always been able to make queries (in C++) that don't have to * fail like that. * * This object the caches server connection. That's completely invisible to * the user. We create it and recreate it completely automatically. The * server connection member variable is mutable, so the query methods can be * const. As far as the user is concerned, a query does not change the state * of this object. We send the request to mysql and read the results and * status all in one atomic operation. * * We could create a new server connection each time the user asked for a * query. (Possibly more than one, if there was a problem had we had to * retry.) We could store that in a local variable, and disconnect right * before returning from the function. It is wrong for the caller to assume * otherwise. * * This object is not thread safe for several reasons. */ class DatabaseWithRetry : NoCopy, NoAssign { private: const std::string _databaseServerName; const std::string _debugName; static void wait(long seconds); bool abort() const; mutable MYSQL * _connection; MYSQL *findConnection() const; void dropConnection() const; void reportError(MYSQL *connection, std::string message) const; void reportSqlError(MYSQL *connection, std::string sql) const { reportError(connection, "SQL = \"" + sql + "\""); } void commonInit(); bool tryOnce(std::string sql, MYSQL *connection, std::string const &name) const; std::vector< std::string > _initialQueries; static const std::string s_tryQueryUntilSuccess; static const std::string s_tryAllUntilSuccess; bool _logAllQueries; public: // For debug only. std::string getDatabaseServerName() const { return _databaseServerName; } // True will send a lot of data to the log. False will not. // // Set db_log_all_queries=1 in the config file and this will be on by // default. bool getLogAllQueries() const { return _logAllQueries; } void setLogAllQueries(bool v) { _logAllQueries = v; } static const std::string MASTER; static const std::string SLAVE; static const std::string BACK_OFFICE; static const std::string CANDLES; static const std::string CANDLES_MASTER; static const std::string PAPER_MASTER; // Depricated. Try MASTER or SLAVE. DatabaseWithRetry(bool readOnly, std::string debugName) : _databaseServerName(readOnly?SLAVE:MASTER), _debugName(debugName), _connection(NULL) { commonInit(); } DatabaseWithRetry(std::string databaseServerName, std::string debugName) : _databaseServerName(databaseServerName), _debugName(debugName), _connection(NULL) { commonInit(); } DatabaseWithRetry(const char *databaseServerName, std::string debugName) : _databaseServerName(databaseServerName), _debugName(debugName), _connection(NULL) { // This is exacly the same as the previous constructor. We are forcing // C++ to convert databaseServerName from a char * to a std::string. I // was surprised to find out that this was not the default case! Without // this constructor, if the first argument was a string literal, C++ would // convert the string to a bool, not a std::string! commonInit(); } ~DatabaseWithRetry() { dropConnection(); } // Returns true if it's reasonable to assume that we can connect to the // named database. This doesn't actually check if it's alive. (That could // change at any time, but this function will always return the same value.) // This looks at config settings. For example if you ask for a database // named "@RO" this will return true if the config file contains a setting // for database_RO, and false if it does not. static bool probablyWorks(std::string const &name); bool probablyWorks() { return probablyWorks(getDatabaseServerName()); } void reset() { mysqlThreadInit(); dropConnection(); } // This is the heart of DatabaseWithRetry. You send a query. If the // database reports a problem, this library retries. We report to the log, // but the caller doesn't know the difference. MysqlResultRef tryQueryUntilSuccess(const std::string &sql, std::string const &name = s_tryQueryUntilSuccess) const; // tryAllUntilSuccess() is similar to tryQueryUntilSuccess() but you can // send a list of queries. If anything fails, the entire list is retried. // If we retry a query, we discard the original response and only keep the // most recent response. // // This is required if you're going to use a lock, a transaction, or a // User-Defined Variables. // // Note: If you are going to make changes consider using a transaction. // // Note: DatabaseWithRetry was written long ago and tryAllUntilSuccess() // was originally made to work with locks. It worked well. That said, // you should always prefer transactions to locks. typedef std::vector< MysqlResultRef > ResultList; template < class IteratorType > ResultList tryAllUntilSuccess(IteratorType begin, IteratorType end, std::string const &name = s_tryAllUntilSuccess) const { if (_logAllQueries) { TclList msg; msg< result; if (abort()) { return result; } MYSQL *c = findConnection(); if (!c) { return result; } IteratorType it = begin; while (true) { if (it == end) { // Success! return result; } if (abort()) { result.clear(); return result; } if (!tryOnce(*it, c, name)) break; tm.setState(name + " copy"); result.push_back(MysqlResultRef(new MysqlResult(c))); tm.setState(originalState); it++; } } } }; #endif