// // Copyright (C) 2018 Codership Oy // /** @file client_state.hpp * * * Return value conventions: * * The calls which may alter either client_state or associated * transaction state will generally return zero on success and * non-zero on failure. More detailed error information is stored * into client state and persisted there until explicitly cleared. */ #ifndef WSREP_CLIENT_STATE_HPP #define WSREP_CLIENT_STATE_HPP #include "server_state.hpp" #include "provider.hpp" #include "transaction.hpp" #include "client_id.hpp" #include "client_service.hpp" #include "mutex.hpp" #include "lock.hpp" #include "buffer.hpp" #include "thread.hpp" namespace wsrep { class server_state; class provider; enum client_error { e_success, e_error_during_commit, e_deadlock_error, e_interrupted_error, e_size_exceeded_error, e_append_fragment_error }; static inline const char* to_c_string(enum client_error error) { switch (error) { case e_success: return "success"; case e_error_during_commit: return "error_during_commit"; case e_deadlock_error: return "deadlock_error"; case e_interrupted_error: return "interrupted_error"; case e_size_exceeded_error: return "size_exceeded"; case e_append_fragment_error: return "append_fragment_error"; } return "unknown"; } static inline std::string to_string(enum client_error error) { return to_c_string(error); } /** * Client State */ class client_state { public: /** * Client mode enumeration. */ enum mode { /** Operates in local only mode, no replication. */ m_local, /** Generates write sets for replication by the provider. */ m_replicating, /** High priority mode */ m_high_priority, /** Client is in total order isolation mode */ m_toi }; static const int mode_max_ = m_toi + 1; /** * Client state enumeration. * */ enum state { /** * Client session has not been initialized yet. */ s_none, /** * Client is idle, the control is in the application which * uses the DBMS system. */ s_idle, /** * The control of the client processing is inside the DBMS * system. */ s_exec, /** * Client handler is sending result to client. */ s_result, /** * The client session is terminating. */ s_quitting }; static const int state_max_ = s_quitting + 1; /** * Store variables related to global execution context. * This method should be called every time the thread * operating the client state changes. */ void store_globals() { current_thread_id_ = wsrep::this_thread::get_id(); } /** * Destructor. */ virtual ~client_state() { assert(transaction_.active() == false); } /** @name Client session handling */ /** @{ */ /** * This method should be called when opening the client session. * * Initializes client id and changes the state to s_idle. */ void open(wsrep::client_id); /** * This method should be called before closing the client session. * * The state is changed to s_quitting and any open transactions * are rolled back. */ void close(); /** * This method should be called after closing the client session * to clean up. * * The state is changed to s_none. */ void cleanup(); /** @} */ /** @name Client command handling */ /** @{ */ /** * This mehod should be called before the processing of command * received from DBMS client starts. * * This method will wait until the possible synchronous * rollback for associated transaction has finished. * The method has a side effect of changing the client * context state to executing. * * @return Zero in case of success, non-zero in case of the * associated transaction was BF aborted. */ int before_command(); /** * This method should be called before returning * a result to DBMS client. * * The method will check if the transaction associated to * the connection has been aborted. Rollback is performed * if needed. After the call, current_error() will return an error * code associated to the client state. If the error code is * not success, the transaction associated to the client state * has been aborted and rolled back. */ void after_command_before_result(); /** * Method which should be called after returning the * control back to DBMS client.. * * The method will do the check if the transaction associated * to the connection has been aborted. If so, rollback is * performed and the transaction is left to aborted state. * The next call to before_command() will return an error and * the error state can be examined after after_command_before_resul() * is called. * * This method has a side effect of changing state to * idle. */ void after_command_after_result(); /** @} */ /** @name Statement level operations */ /** @{ */ /** * Before statement execution operations. * * Check if server is synced and if dirty reads are allowed. * * @return Zero in case of success, non-zero if the statement * is not allowed to be executed due to read or write * isolation requirements. */ int before_statement(); /** * Return values for after_statement() method. */ enum after_statement_result { /** Statement was executed succesfully */ asr_success, /** Statement execution encountered an error, the transaction * was rolled back */ asr_error, /** Statement execution encountered an error, the transaction was rolled back. However the statement was self contained (e.g. autocommit statement) so it can be retried. */ asr_may_retry }; /** * After statement execution operations. * * * Check for must_replay state * * Do rollback if requested */ enum after_statement_result after_statement(); /** @} */ /** @name Replication interface */ /** @{ */ /** * Start a new transaction with a transaction id. * * @todo This method should * - Register the transaction on server level for bookkeeping * - Isolation levels? Or part of the transaction? */ int start_transaction(const wsrep::transaction_id& id) { assert(state_ == s_exec); return transaction_.start_transaction(id); } /** * Append a key into transaction write set. */ int append_key(const wsrep::key& key) { assert(mode_ == m_replicating); assert(state_ == s_exec); return transaction_.append_key(key); } /** * Append data into transaction write set. */ int append_data(const wsrep::const_buffer& data) { assert(mode_ == m_replicating); assert(state_ == s_exec); return transaction_.append_data(data); } /** @} */ /** @name Streaming replication interface */ /** @{ */ /** * This method should be called after every row operation. */ int after_row() { assert(mode_ == m_replicating); assert(state_ == s_exec); return (transaction_.streaming_context_.fragment_size() ? transaction_.after_row() : 0); } /** * Enable streaming replication. * * Currently it is not possible to change the fragment unit * for active streaming transaction. * * @param fragment_unit Desired fragment unit * @param fragment_size Desired fragment size * * @return Zero on success, non-zero if the streaming cannot be * enabled. */ int enable_streaming( enum wsrep::streaming_context::fragment_unit fragment_unit, size_t fragment_size); /** @name Applying interface */ /** @{ */ int start_transaction(const wsrep::ws_handle& wsh, const wsrep::ws_meta& meta) { assert(mode_ == m_high_priority); return transaction_.start_transaction(wsh, meta); } /** @name Commit ordering interface */ /** @{ */ int before_prepare() { wsrep::unique_lock lock(mutex_); assert(state_ == s_exec); return transaction_.before_prepare(lock); } int after_prepare() { wsrep::unique_lock lock(mutex_); assert(state_ == s_exec); return transaction_.after_prepare(lock); } int before_commit() { assert(state_ == s_exec || mode_ == m_local); return transaction_.before_commit(); } int ordered_commit() { assert(state_ == s_exec || mode_ == m_local); return transaction_.ordered_commit(); } int after_commit() { assert(state_ == s_exec || mode_ == m_local); return transaction_.after_commit(); } /** @} */ int before_rollback() { assert(state_ == s_idle || state_ == s_exec || state_ == s_result || state_ == s_quitting); return transaction_.before_rollback(); } int after_rollback() { assert(state_ == s_idle || state_ == s_exec || state_ == s_result || state_ == s_quitting); return transaction_.after_rollback(); } /** @} */ // // BF aborting // int bf_abort(wsrep::seqno bf_seqno) { wsrep::unique_lock lock(mutex_); assert(mode_ == m_replicating); return transaction_.bf_abort(lock, bf_seqno); } // // Replaying // int start_replaying(const wsrep::ws_meta& ws_meta) { assert(mode_ == m_high_priority); return transaction_.start_replaying(ws_meta); } void adopt_transaction(wsrep::transaction& transaction) { assert(mode_ == m_high_priority); transaction_.start_transaction(transaction.id()); transaction_.streaming_context_ = transaction.streaming_context_; } /** @name Non-transactional operations */ /** @{*/ /** * Enter total order isolation critical section. * @param key_array Array of keys * @param buffer Buffer containing the action to execute inside * total order isolation section * @param flags Provider flags for TOI operation * * @return Zero on success, non-zero otherwise. */ int enter_toi(const wsrep::key_array& key_array, const wsrep::const_buffer& buffer, int flags); /** * Leave total order isolation critical section. */ int leave_toi(); /** * Begin non-blocking operation. */ int begin_nbo(const wsrep::key_array&); /** * End non-blocking operation */ int end_nbo(); /** * Get reference to the client mutex. * * @return Reference to the client mutex. */ wsrep::mutex& mutex() { return mutex_; } /** * Get server context associated the the client session. * * @return Reference to server context. */ wsrep::server_state& server_state() const { return server_state_; } wsrep::client_service& client_service() const { return client_service_; } /** * Get reference to the Provider which is associated * with the client context. * * @return Reference to the provider. * @throw wsrep::runtime_error if no providers are associated * with the client context. * * @todo Should be removed. */ wsrep::provider& provider() const; /** * Get Client identifier. * * @return Client Identifier */ client_id id() const { return id_; } /** * Get Client mode. * * @todo Enforce mutex protection if called from other threads. * * @return Client mode. */ enum mode mode() const { return mode_; } /** * Get Client state. * * @todo Enforce mutex protection if called from other threads. * * @return Client state */ enum state state() const { return state_; } /** * Return a const reference to the transaction associated * with the client state. */ const wsrep::transaction& transaction() const { return transaction_; } const wsrep::ws_meta& toi_meta() const { return toi_meta_; } /** * Set debug logging level. * * Levels: * 0 - Debug logging is disabled * 1..n - Debug logging with increasing verbosity. */ void debug_log_level(int level) { debug_log_level_ = level; } /** * Return current debug logging level. The return value * is a maximum of client state and server state debug log * levels. * * @return Current debug log level. */ int debug_log_level() const { return std::max(debug_log_level_, server_state_.debug_log_level()); } // // Error handling // /** * Reset the current error state. * * @todo There should be some protection about when this can * be done. */ void reset_error() { current_error_ = wsrep::e_success; } /** * Return current error code. * * @return Current error code. */ enum wsrep::client_error current_error() const { return current_error_; } protected: /** * Client context constuctor. This is protected so that it * can be called from derived class constructors only. */ client_state(wsrep::mutex& mutex, wsrep::server_state& server_state, wsrep::client_service& client_service, const client_id& id, enum mode mode) : owning_thread_id_(wsrep::this_thread::get_id()) , current_thread_id_(owning_thread_id_) , mutex_(mutex) , server_state_(server_state) , client_service_(client_service) , id_(id) , mode_(mode) , state_(s_none) , transaction_(*this) , toi_meta_() , allow_dirty_reads_() , debug_log_level_(0) , current_error_(wsrep::e_success) { } private: client_state(const client_state&); client_state& operator=(client_state&); friend class client_state_switch; friend class high_priority_context; friend class client_toi_mode; friend class transaction; void debug_log_state(const char*) const; void state(wsrep::unique_lock& lock, enum state state); void mode(wsrep::unique_lock& lock, enum mode mode); void override_error(enum wsrep::client_error error); wsrep::thread::id owning_thread_id_; wsrep::thread::id current_thread_id_; wsrep::mutex& mutex_; wsrep::server_state& server_state_; wsrep::client_service& client_service_; wsrep::client_id id_; enum mode mode_; enum state state_; wsrep::transaction transaction_; wsrep::ws_meta toi_meta_; bool allow_dirty_reads_; int debug_log_level_; wsrep::client_error current_error_; }; static inline const char* to_c_string( enum wsrep::client_state::state state) { switch (state) { case wsrep::client_state::s_none: return "none"; case wsrep::client_state::s_idle: return "idle"; case wsrep::client_state::s_exec: return "exec"; case wsrep::client_state::s_result: return "result"; case wsrep::client_state::s_quitting: return "quit"; } return "unknown"; } static inline std::string to_string(enum wsrep::client_state::state state) { return to_c_string(state); } static inline const char* to_c_string(enum wsrep::client_state::mode mode) { switch (mode) { case wsrep::client_state::m_local: return "local"; case wsrep::client_state::m_replicating: return "replicating"; case wsrep::client_state::m_high_priority: return "high priority"; case wsrep::client_state::m_toi: return "toi"; } return "unknown"; } static inline std::string to_string(enum wsrep::client_state::mode mode) { return to_c_string(mode); } class client_state_switch { public: client_state_switch(wsrep::client_state& orig_context, wsrep::client_state& current_context) : orig_context_(orig_context) , current_context_(current_context) { current_context_.client_service_.store_globals(); } ~client_state_switch() { orig_context_.client_service_.store_globals(); } private: client_state& orig_context_; client_state& current_context_; }; /** * Utility class to switch the client state to high priority * mode. The client is switched back to the original mode * when the high priority context goes out of scope. */ class high_priority_context { public: high_priority_context(wsrep::client_state& client) : client_(client) , orig_mode_(client.mode_) { wsrep::unique_lock lock(client.mutex_); client.mode(lock, wsrep::client_state::m_high_priority); } virtual ~high_priority_context() { wsrep::unique_lock lock(client_.mutex_); assert(client_.mode() == wsrep::client_state::m_high_priority); client_.mode(lock, orig_mode_); } private: wsrep::client_state& client_; enum wsrep::client_state::mode orig_mode_; }; /** * */ class client_toi_mode { public: client_toi_mode(wsrep::client_state& client) : client_(client) , orig_mode_(client.mode_) { wsrep::unique_lock lock(client.mutex_); client.mode(lock, wsrep::client_state::m_toi); } ~client_toi_mode() { wsrep::unique_lock lock(client_.mutex_); assert(client_.mode() == wsrep::client_state::m_toi); client_.mode(lock, orig_mode_); } private: wsrep::client_state& client_; enum wsrep::client_state::mode orig_mode_; }; template class scoped_client_state { public: scoped_client_state(wsrep::client_state* client_state, D deleter) : client_state_(client_state) , deleter_(deleter) { if (client_state_ == 0) { throw wsrep::runtime_error("Null client_state provided"); } } wsrep::client_state& client_state() { return *client_state_; } ~scoped_client_state() { deleter_(client_state_); } private: scoped_client_state(const scoped_client_state&); scoped_client_state& operator=(const scoped_client_state&); wsrep::client_state* client_state_; D deleter_; }; } #endif // WSREP_CLIENT_STATE_HPP