mirror of
https://github.com/codership/wsrep-lib.git
synced 2025-07-28 20:02:00 +03:00
codership/wsrep-lib#106 Relaxed assumptions about threading model
Sanity checks to detect concurrency bugs were assuming a threading model where each client state would always be processed within single thread of execution. This however may be too strong assumption if the application uses some kind of thread pooling. This patch relaxes those assumptions by removing current_thread_id_ from client_state and relaxing assertions against owning_thread_id_. This patch also adds a new method wait_rollback_complete_and_acquire_ownership() into client_state. This method is idempotent and can be used to gain control to client_state before before_command() is called. The method will wait until possible background rollback process is over and marks the state to s_exec to protect the state against new background rollbacks. Other fixes/improvements: - High priority globals state is restored after discarding streaming. - Allowed server_state transition donor -> synced. - Client state method store_globals() was renamed to acquire_ownership() to better describe the intent. Method store_globals() was left for backwards compatibility and marked deprecated.
This commit is contained in:
@ -36,8 +36,7 @@ void wsrep::client_state::open(wsrep::client_id id)
|
||||
assert(state_ == s_none);
|
||||
debug_log_state("open: enter");
|
||||
owning_thread_id_ = wsrep::this_thread::get_id();
|
||||
current_thread_id_ = owning_thread_id_;
|
||||
has_rollbacker_ = false;
|
||||
rollbacker_active_ = false;
|
||||
sync_wait_gtid_ = wsrep::gtid::undefined();
|
||||
last_written_gtid_ = wsrep::gtid::undefined();
|
||||
state(lock, s_idle);
|
||||
@ -88,25 +87,30 @@ int wsrep::client_state::before_command()
|
||||
{
|
||||
wsrep::unique_lock<wsrep::mutex> lock(mutex_);
|
||||
debug_log_state("before_command: enter");
|
||||
assert(state_ == s_idle);
|
||||
if (transaction_.active() &&
|
||||
server_state_.rollback_mode() == wsrep::server_state::rm_sync)
|
||||
// If the state is s_exec, the processing thread has already grabbed
|
||||
// control with wait_rollback_complete_and_acquire_ownership()
|
||||
if (state_ != s_exec)
|
||||
{
|
||||
/*
|
||||
* has_rollbacker() returns false, when background rollback is over
|
||||
*/
|
||||
while (has_rollbacker())
|
||||
{
|
||||
cond_.wait(lock);
|
||||
}
|
||||
assert(state_ == s_idle);
|
||||
do_wait_rollback_complete_and_acquire_ownership(lock);
|
||||
assert(state_ == s_exec);
|
||||
}
|
||||
store_globals(); // Marks the control for this thread
|
||||
state(lock, s_exec);
|
||||
else
|
||||
{
|
||||
// This thread must have acquired control by other means,
|
||||
// for example via wait_rollback_complete_and_acquire_ownership().
|
||||
assert(wsrep::this_thread::get_id() == owning_thread_id_);
|
||||
}
|
||||
|
||||
// If the transaction is active, it must be either executing,
|
||||
// aborted as rolled back by rollbacker, or must_abort if the
|
||||
// client thread gained control via
|
||||
// wait_rollback_complete_and_acquire_ownership()
|
||||
// just before BF abort happened.
|
||||
assert(transaction_.active() == false ||
|
||||
(transaction_.state() == wsrep::transaction::s_executing ||
|
||||
transaction_.state() == wsrep::transaction::s_aborted ||
|
||||
(transaction_.state() == wsrep::transaction::s_must_abort &&
|
||||
server_state_.rollback_mode() == wsrep::server_state::rm_async)));
|
||||
transaction_.state() == wsrep::transaction::s_must_abort));
|
||||
|
||||
if (transaction_.active())
|
||||
{
|
||||
@ -257,6 +261,33 @@ int wsrep::client_state::after_statement()
|
||||
return 0;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// Rollbacker synchronization //
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void wsrep::client_state::sync_rollback_complete()
|
||||
{
|
||||
wsrep::unique_lock<wsrep::mutex> lock(mutex_);
|
||||
debug_log_state("sync_rollback_complete: enter");
|
||||
assert(state_ == s_idle && mode_ == m_local &&
|
||||
transaction_.state() == wsrep::transaction::s_aborted);
|
||||
set_rollbacker_active(false);
|
||||
cond_.notify_all();
|
||||
debug_log_state("sync_rollback_complete: leave");
|
||||
}
|
||||
|
||||
void wsrep::client_state::wait_rollback_complete_and_acquire_ownership()
|
||||
{
|
||||
wsrep::unique_lock<wsrep::mutex> lock(mutex_);
|
||||
debug_log_state("wait_rollback_complete_and_acquire_ownership: enter");
|
||||
if (state_ == s_idle)
|
||||
{
|
||||
do_wait_rollback_complete_and_acquire_ownership(lock);
|
||||
}
|
||||
assert(state_ == s_exec);
|
||||
debug_log_state("wait_rollback_complete_and_acquire_ownership: leave");
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// Streaming //
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
@ -443,6 +474,32 @@ int wsrep::client_state::sync_wait(int timeout)
|
||||
// Private //
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void wsrep::client_state::do_acquire_ownership(
|
||||
wsrep::unique_lock<wsrep::mutex>& lock WSREP_UNUSED)
|
||||
{
|
||||
assert(lock.owns_lock());
|
||||
// Be strict about client state for clients in local mode. The
|
||||
// owning_thread_id_ is used to detect bugs which are caused by
|
||||
// more than one thread operating the client state at the time,
|
||||
// for example thread handling the client session and background
|
||||
// rollbacker.
|
||||
assert(state_ == s_idle || mode_ != m_local);
|
||||
owning_thread_id_ = wsrep::this_thread::get_id();
|
||||
}
|
||||
|
||||
void wsrep::client_state::do_wait_rollback_complete_and_acquire_ownership(
|
||||
wsrep::unique_lock<wsrep::mutex>& lock)
|
||||
{
|
||||
assert(lock.owns_lock());
|
||||
assert(state_ == s_idle);
|
||||
while (is_rollbacker_active())
|
||||
{
|
||||
cond_.wait(lock);
|
||||
}
|
||||
do_acquire_ownership(lock);
|
||||
state(lock, s_exec);
|
||||
}
|
||||
|
||||
void wsrep::client_state::update_last_written_gtid(const wsrep::gtid& gtid)
|
||||
{
|
||||
assert(last_written_gtid_.is_undefined() ||
|
||||
@ -468,19 +525,9 @@ void wsrep::client_state::state(
|
||||
wsrep::unique_lock<wsrep::mutex>& lock WSREP_UNUSED,
|
||||
enum wsrep::client_state::state state)
|
||||
{
|
||||
// For locally processing client states (local, toi, rsu)
|
||||
// changing the state is allowed only from the owning thread.
|
||||
// In high priority mode the processing thread however may
|
||||
// change and we check only that store_globals() has been
|
||||
// called by the current thread to gain ownership.
|
||||
//
|
||||
// Note that this check assumes that there is always a single
|
||||
// thread per local client connection. This may not always
|
||||
// hold and the sanity check mechanism may need to be revised.
|
||||
assert((mode_ != m_high_priority &&
|
||||
wsrep::this_thread::get_id() == owning_thread_id_) ||
|
||||
(mode_ == m_high_priority &&
|
||||
wsrep::this_thread::get_id() == current_thread_id_));
|
||||
// Verify that the current thread has gained control to the
|
||||
// connection by calling before_command()
|
||||
assert(wsrep::this_thread::get_id() == owning_thread_id_);
|
||||
assert(lock.owns_lock());
|
||||
static const char allowed[state_max_][state_max_] =
|
||||
{
|
||||
@ -497,7 +544,6 @@ void wsrep::client_state::state(
|
||||
<< state_ << " -> " << state;
|
||||
assert(0);
|
||||
}
|
||||
|
||||
state_hist_.push_back(state_);
|
||||
state_ = state;
|
||||
if (state_hist_.size() > 10)
|
||||
|
@ -71,6 +71,7 @@ static inline int resolve_return_error(bool const vote,
|
||||
|
||||
static void
|
||||
discard_streaming_applier(wsrep::server_state& server_state,
|
||||
wsrep::high_priority_service& high_priority_service,
|
||||
wsrep::high_priority_service* streaming_applier,
|
||||
const wsrep::ws_meta& ws_meta)
|
||||
{
|
||||
@ -78,6 +79,7 @@ discard_streaming_applier(wsrep::server_state& server_state,
|
||||
ws_meta.server_id(), ws_meta.transaction_id());
|
||||
server_state.server_service().release_high_priority_service(
|
||||
streaming_applier);
|
||||
high_priority_service.store_globals();
|
||||
}
|
||||
|
||||
static int apply_fragment(wsrep::server_state& server_state,
|
||||
@ -135,7 +137,10 @@ static int apply_fragment(wsrep::server_state& server_state,
|
||||
}
|
||||
else
|
||||
{
|
||||
discard_streaming_applier(server_state, streaming_applier,ws_meta);
|
||||
discard_streaming_applier(server_state,
|
||||
high_priority_service,
|
||||
streaming_applier,
|
||||
ws_meta);
|
||||
ret = resolve_return_error(err.size() > 0, ret, apply_err);
|
||||
}
|
||||
}
|
||||
@ -188,7 +193,8 @@ static int commit_fragment(wsrep::server_state& server_state,
|
||||
|
||||
if (!ret)
|
||||
{
|
||||
discard_streaming_applier(server_state, streaming_applier, ws_meta);
|
||||
discard_streaming_applier(server_state, high_priority_service,
|
||||
streaming_applier, ws_meta);
|
||||
}
|
||||
|
||||
return ret;
|
||||
@ -227,7 +233,8 @@ static int rollback_fragment(wsrep::server_state& server_state,
|
||||
|
||||
if (!ret)
|
||||
{
|
||||
discard_streaming_applier(server_state, streaming_applier, ws_meta);
|
||||
discard_streaming_applier(server_state, high_priority_service,
|
||||
streaming_applier, ws_meta);
|
||||
|
||||
if (adopt_error == 0)
|
||||
{
|
||||
@ -1282,7 +1289,7 @@ void wsrep::server_state::state(
|
||||
{ 1, 0, 0, 1, 1, 0, 0, 1, 1}, /* cted */
|
||||
{ 1, 1, 0, 0, 0, 1, 0, 0, 1}, /* jer */
|
||||
{ 1, 0, 0, 1, 0, 0, 1, 1, 1}, /* jed */
|
||||
{ 1, 0, 0, 1, 0, 1, 0, 0, 1}, /* dor */
|
||||
{ 1, 0, 0, 1, 0, 1, 0, 1, 1}, /* dor */
|
||||
{ 1, 0, 0, 1, 0, 1, 1, 0, 1}, /* sed */
|
||||
{ 1, 0, 0, 0, 0, 0, 0, 0, 0} /* ding */
|
||||
};
|
||||
@ -1438,6 +1445,7 @@ void wsrep::server_state::close_orphaned_sr_transactions(
|
||||
|
||||
streaming_appliers_.erase(i++);
|
||||
server_service_.release_high_priority_service(streaming_applier);
|
||||
high_priority_service.store_globals();
|
||||
wsrep::ws_meta ws_meta(
|
||||
wsrep::gtid(),
|
||||
wsrep::stid(server_id, transaction_id, wsrep::client_id()),
|
||||
@ -1478,6 +1486,7 @@ void wsrep::server_state::close_transactions_at_disconnect(
|
||||
}
|
||||
streaming_appliers_.erase(i++);
|
||||
server_service_.release_high_priority_service(streaming_applier);
|
||||
high_priority_service.store_globals();
|
||||
}
|
||||
streaming_appliers_recovered_ = false;
|
||||
}
|
||||
|
@ -906,7 +906,7 @@ bool wsrep::transaction::bf_abort(
|
||||
// between releasing the lock and before background
|
||||
// rollbacker gets control.
|
||||
state(lock, wsrep::transaction::s_aborting);
|
||||
client_state_.set_rollbacker(true);
|
||||
client_state_.set_rollbacker_active(true);
|
||||
|
||||
if (client_state_.mode() == wsrep::client_state::m_high_priority)
|
||||
{
|
||||
@ -974,21 +974,12 @@ void wsrep::transaction::state(
|
||||
<< " -> " << to_string(next_state));
|
||||
|
||||
assert(lock.owns_lock());
|
||||
// BF aborter is allowed to change the state to must abort and
|
||||
// further to aborting and aborted if the background rollbacker
|
||||
// is launched.
|
||||
//
|
||||
// For high priority streaming applier threads the assertion must
|
||||
// be relaxed to check only current thread id which indicates that
|
||||
// the store_globals() has been called before processing of write set
|
||||
// starts.
|
||||
assert((client_state_.owning_thread_id_ == wsrep::this_thread::get_id() ||
|
||||
next_state == s_must_abort ||
|
||||
next_state == s_aborting ||
|
||||
next_state == s_aborted)
|
||||
||
|
||||
(client_state_.mode() == wsrep::client_state::m_high_priority &&
|
||||
wsrep::this_thread::get_id() == client_state_.current_thread_id_));
|
||||
// BF aborter is allowed to change the state without gaining control
|
||||
// to the state if the next state is s_must_abort or s_aborting.
|
||||
assert(client_state_.owning_thread_id_ == wsrep::this_thread::get_id() ||
|
||||
next_state == s_must_abort ||
|
||||
next_state == s_aborting);
|
||||
|
||||
static const char allowed[n_states][n_states] =
|
||||
{ /* ex pr ce co oc ct cf ma ab ad mr re */
|
||||
{ 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0}, /* ex */
|
||||
@ -1007,11 +998,9 @@ void wsrep::transaction::state(
|
||||
|
||||
if (!allowed[state_][next_state])
|
||||
{
|
||||
std::ostringstream os;
|
||||
os << "unallowed state transition for transaction "
|
||||
<< id_ << ": " << wsrep::to_string(state_)
|
||||
<< " -> " << wsrep::to_string(next_state);
|
||||
wsrep::log_warning() << os.str();
|
||||
wsrep::log_debug() << "unallowed state transition for transaction "
|
||||
<< id_ << ": " << wsrep::to_string(state_)
|
||||
<< " -> " << wsrep::to_string(next_state);
|
||||
assert(0);
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user