Skip to content

mon: Create a local copy of scrub_state to avoid a crash#59050

Merged
yuriw merged 1 commit intoceph:mainfrom
mohit84:issue_67270
Jan 22, 2025
Merged

mon: Create a local copy of scrub_state to avoid a crash#59050
yuriw merged 1 commit intoceph:mainfrom
mohit84:issue_67270

Conversation

@mohit84
Copy link
Contributor

@mohit84 mohit84 commented Aug 6, 2024

During handle of scrub operation if scrub_state is reset via bootstrap then a process might be crash.

Solution: Create a local copy of scrub_state shared_ptr. By doing this the refcounter will be increment
and object will not be destroyed in that scope.

Fixes: https://tracker.ceph.com/issues/67270
Credits: Radoslaw Zarzynski rzarzyns@redhat.com

Contribution Guidelines

  • To sign and title your commits, please refer to Submitting Patches to Ceph.

  • If you are submitting a fix for a stable branch (e.g. "quincy"), please refer to Submitting Patches to Ceph - Backports for the proper workflow.

  • When filling out the below checklist, you may click boxes directly in the GitHub web UI. When entering or editing the entire PR message in the GitHub web UI editor, you may also select a checklist item by adding an x between the brackets: [x]. Spaces and capitalization matter when checking off items this way.

Checklist

  • Tracker (select at least one)
    • References tracker ticket
    • Very recent bug; references commit where it was introduced
    • New feature (ticket optional)
    • Doc update (no ticket needed)
    • Code cleanup (no ticket needed)
  • Component impact
    • Affects Dashboard, opened tracker ticket
    • Affects Orchestrator, opened tracker ticket
    • No impact that needs to be tracked
  • Documentation (select at least one)
    • Updates relevant documentation
    • No doc update is appropriate
  • Tests (select at least one)
Show available Jenkins commands
  • jenkins retest this please
  • jenkins test classic perf
  • jenkins test crimson perf
  • jenkins test signed
  • jenkins test make check
  • jenkins test make check arm64
  • jenkins test submodules
  • jenkins test dashboard
  • jenkins test dashboard cephadm
  • jenkins test api
  • jenkins test docs
  • jenkins render docs
  • jenkins test ceph-volume all
  • jenkins test ceph-volume tox
  • jenkins test windows
  • jenkins test rook e2e

@mohit84 mohit84 requested a review from a team as a code owner August 6, 2024 12:29
@mohit84
Copy link
Contributor Author

mohit84 commented Aug 6, 2024

jenkins test make check

@athanatos
Copy link
Contributor

I don't quite understand how bootstrap could interfere with scrub. Can you expand on that relationship a bit? Does it actually make sense for bootstrap and scrub to occur at the same time?

@rzarzynski
Copy link
Contributor

I think the problem is actually about _reset():

// called by bootstrap(), or on leader|peon -> electing
void Monitor::_reset()
{
  dout(10) << __func__ << dendl;

  // disable authentication
  {
    std::lock_guard l(auth_lock);
    authmon()->_set_mon_num_rank(0, 0);
  }

  // ...

  scrub_reset();

  // ...

which can happen when a scrub is ongoing.

Monitor::handle_scrub() already checks the Monitor::state:

void Monitor::handle_scrub(MonOpRequestRef op)
{
  MMonScrub *m = static_cast<MMonScrub*>(op->get_req());
  dout(10) << __func__ << " " << *m << dendl;
  switch (m->op) {
  case MMonScrub::OP_SCRUB:
    {
      if (!is_peon())
        break;

// ...

  case MMonScrub::OP_RESULT:
    {
      if (!is_leader())
        break;

but this doesn't prevent the race with Monitor::scrub_reset().

@mohit84
Copy link
Contributor Author

mohit84 commented Aug 6, 2024

scrub_reset

Thanks Radek for replying the same.
This is the stacktrace hit while running ../qa/workunits/cephtool/test.sh -t mon_mon test case.

#0  Monitor::scrub_reset (this=this@entry=0x5622a4d06000) at /nvme0/ceph/src/mon/Monitor.cc:5780
#1  0x000056229d580f18 in Monitor::_reset (this=this@entry=0x5622a4d06000) at /nvme0/ceph/src/mon/Monitor.cc:1352
#2  0x000056229d58a576 in Monitor::bootstrap (this=0x5622a4d06000) at /nvme0/ceph/src/mon/Monitor.cc:1228
#3  0x000056229d630ced in Elector::dispatch (this=this@entry=0x5622a4d07078, op=...) at /nvme0/ceph/src/mon/Elector.cc:658
#4  0x000056229d59a929 in Monitor::dispatch_op (this=this@entry=0x5622a4d06000, op=...) at /nvme0/ceph/src/mon/Monitor.cc:4785
#5  0x000056229d59bf49 in Monitor::_ms_dispatch (this=this@entry=0x5622a4d06000, m=m@entry=0x5622a5446a00) at /nvme0/ceph/src/mon/Monitor.cc:4546
#6  0x000056229d5d54f4 in Monitor::ms_dispatch (this=0x5622a4d06000, m=0x5622a5446a00) at /nvme0/ceph/src/mon/Monitor.h:952
#7  0x000056229d5a04b6 in Dispatcher::ms_dispatch2 (this=0x5622a4d06000, m=...) at /nvme0/ceph/src/msg/Dispatcher.h:130
#8  0x00007f8b265f4330 in Messenger::ms_deliver_dispatch (this=0x5622a415fe00, m=...) at /nvme0/ceph/src/msg/Messenger.h:731
#9  0x00007f8b265f0b08 in DispatchQueue::entry (this=0x5622a41601b8) at /nvme0/ceph/src/msg/DispatchQueue.cc:201
#10 0x00007f8b2669ea6d in DispatchQueue::DispatchThread::entry (this=<optimized out>) at /nvme0/ceph/src/msg/DispatchQueue.h:101
#11 0x00007f8b26492b01 in Thread::entry_wrapper (this=0x5622a4160348) at /nvme0/ceph/src/common/Thread.cc:87
#12 0x00007f8b26492b19 in Thread::_entry_func (arg=<optimized out>) at /nvme0/ceph/src/common/Thread.cc:74
#13 0x00007f8b24cae947 in start_thread () from /lib64/libc.so.6
#14 0x00007f8b24d34860 in clone3 () from /lib64/libc.so.6

For specific to test case below are the commands those are hitting above stacktrace

ceph mon set election_strategy disallow
ceph mon add disallowed_leader $first
ceph mon set election_strategy connectivity
ceph mon rm disallowed_leader $first
ceph mon set election_strategy classic
expect_failure $TEMP_DIR ceph mon rm disallowed_leader $first

@mohit84
Copy link
Contributor Author

mohit84 commented Aug 6, 2024

scrub_reset

Thanks Radek for replying the same. This is the stacktrace hit while running ../qa/workunits/cephtool/test.sh -t mon_mon test case.

#0  Monitor::scrub_reset (this=this@entry=0x5622a4d06000) at /nvme0/ceph/src/mon/Monitor.cc:5780
#1  0x000056229d580f18 in Monitor::_reset (this=this@entry=0x5622a4d06000) at /nvme0/ceph/src/mon/Monitor.cc:1352
#2  0x000056229d58a576 in Monitor::bootstrap (this=0x5622a4d06000) at /nvme0/ceph/src/mon/Monitor.cc:1228
#3  0x000056229d630ced in Elector::dispatch (this=this@entry=0x5622a4d07078, op=...) at /nvme0/ceph/src/mon/Elector.cc:658
#4  0x000056229d59a929 in Monitor::dispatch_op (this=this@entry=0x5622a4d06000, op=...) at /nvme0/ceph/src/mon/Monitor.cc:4785
#5  0x000056229d59bf49 in Monitor::_ms_dispatch (this=this@entry=0x5622a4d06000, m=m@entry=0x5622a5446a00) at /nvme0/ceph/src/mon/Monitor.cc:4546
#6  0x000056229d5d54f4 in Monitor::ms_dispatch (this=0x5622a4d06000, m=0x5622a5446a00) at /nvme0/ceph/src/mon/Monitor.h:952
#7  0x000056229d5a04b6 in Dispatcher::ms_dispatch2 (this=0x5622a4d06000, m=...) at /nvme0/ceph/src/msg/Dispatcher.h:130
#8  0x00007f8b265f4330 in Messenger::ms_deliver_dispatch (this=0x5622a415fe00, m=...) at /nvme0/ceph/src/msg/Messenger.h:731
#9  0x00007f8b265f0b08 in DispatchQueue::entry (this=0x5622a41601b8) at /nvme0/ceph/src/msg/DispatchQueue.cc:201
#10 0x00007f8b2669ea6d in DispatchQueue::DispatchThread::entry (this=<optimized out>) at /nvme0/ceph/src/msg/DispatchQueue.h:101
#11 0x00007f8b26492b01 in Thread::entry_wrapper (this=0x5622a4160348) at /nvme0/ceph/src/common/Thread.cc:87
#12 0x00007f8b26492b19 in Thread::_entry_func (arg=<optimized out>) at /nvme0/ceph/src/common/Thread.cc:74
#13 0x00007f8b24cae947 in start_thread () from /lib64/libc.so.6
#14 0x00007f8b24d34860 in clone3 () from /lib64/libc.so.6

For specific to test case below are the commands those are hitting above stacktrace

ceph mon set election_strategy disallow ceph mon add disallowed_leader $first ceph mon set election_strategy connectivity ceph mon rm disallowed_leader $first ceph mon set election_strategy classic expect_failure $TEMP_DIR ceph mon rm disallowed_leader $first

Sorry not previous one below is the right stacktrace

#0  Monitor::scrub_reset (this=this@entry=0x5622a4d06000) at /nvme0/ceph/src/mon/Monitor.cc:5780
#1  0x000056229d580f18 in Monitor::_reset (this=this@entry=0x5622a4d06000) at /nvme0/ceph/src/mon/Monitor.cc:1352
#2  0x000056229d58a576 in Monitor::bootstrap (this=0x5622a4d06000) at /nvme0/ceph/src/mon/Monitor.cc:1228
#3  0x000056229d6ba88e in Paxos::do_refresh (this=this@entry=0x56229ff76dc0) at /nvme0/ceph/src/mon/Paxos.cc:1058
#4  0x000056229d6c206f in Paxos::commit_finish (this=0x56229ff76dc0) at /nvme0/ceph/src/mon/Paxos.cc:941
#5  0x000056229d6c55de in C_Committed::finish (this=0x5622a520d490, r=<optimized out>) at /nvme0/ceph/src/mon/Paxos.cc:835
#6  0x000056229d59fb8b in Context::complete (this=0x5622a520d490, r=<optimized out>) at /nvme0/ceph/src/include/Context.h:99
#7  0x000056229d6c5508 in MonitorDBStore::C_DoTransaction::finish (this=0x5622a500f890, r=<optimized out>) at /nvme0/ceph/src/mon/MonitorDBStore.h:389
#8  0x000056229d59fb8b in Context::complete (this=0x5622a500f890, r=<optimized out>) at /nvme0/ceph/src/include/Context.h:99
#9  0x00007f8b26463217 in Finisher::finisher_thread_entry (this=0x7ffc92480280) at /nvme0/ceph/src/common/Finisher.cc:72
#10 0x000056229d54407d in Finisher::FinisherThread::entry (this=<optimized out>) at /nvme0/ceph/src/common/Finisher.h:63
#11 0x00007f8b26492b01 in Thread::entry_wrapper (this=0x7ffc924803b8) at /nvme0/ceph/src/common/Thread.cc:87
#12 0x00007f8b26492b19 in Thread::_entry_func (arg=<optimized out>) at /nvme0/ceph/src/common/Thread.cc:74
#13 0x00007f8b24cae947 in start_thread () from /lib64/libc.so.6
#14 0x00007f8b24d34860 in clone3 () from /lib64/libc.so.6

// it might be possible the scrub_state is reset via bootstrap
// code path and daemon is crashed.
std::shard_ptr<ScrubState> local_scrub_state = scrub_state;
if (nullptr == local_scrub_state) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

scrub_version and scrub_results are also modified by Monitor::scrub_reset(), so similar race affects them.

Perhaps we could group these members to e.g. ScrubContext and manage a pointer with the atomic shared ptr. Alternatively, a lock can be used but – as the scrub can take some time – better to have a dedicated one (like Monitor::auth_lock).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ack

@mohit84
Copy link
Contributor Author

mohit84 commented Aug 9, 2024

jenkins test make check

Copy link
Contributor

@rzarzynski rzarzynski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally looks good; just some nits.

return -EBUSY;
if (scrub_con.load() != nullptr) {
std::shared_ptr<ScrubContext> local_con = scrub_con.load();
if (local_con) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

local_con sounds like local connection but I believe the idea was about context. Perhaps local_context or even local_ctx would do.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

scrub_state.reset();
}
};
std::atomic<std::shared_ptr<ScrubContext>> scrub_con; ///< keeps track of scrub context
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

scrub_context or scrub_ctx.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

std::shared_ptr<ScrubState> scrub_state; ///< keeps track of current scrub

struct ScrubContext {
std::shared_ptr<ScrubState> scrub_state; ///< keeps track of current scrub
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need this to be a pointer?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

if (!scrub_result.empty()) {
clog->info() << "scrub already in progress";
return -EBUSY;
if (scrub_con.load() != nullptr) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need the double check? How about:

Suggested change
if (scrub_con.load() != nullptr) {
if (std::shared_ptr<ScrubContext> local_ctx = scrub_con.load(); scrub_ctx.load() != nullptr) {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@mohit84 mohit84 force-pushed the issue_67270 branch 3 times, most recently from 0782521 to 1e1601a Compare September 18, 2024 06:26
@mohit84
Copy link
Contributor Author

mohit84 commented Sep 18, 2024

jenkins test make check

if (!scrub_result.empty()) {
clog->info() << "scrub already in progress";
return -EBUSY;
if (std::shared_ptr<ScrubContext> local_ctx = scrub_con.load(); scrub_con.load() !=
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are 2 loads from scrub_con; they can race. I think the intention was to compare just local_ctx:

Suggested change
if (std::shared_ptr<ScrubContext> local_ctx = scrub_con.load(); scrub_con.load() !=
if (std::shared_ptr<ScrubContext> local_ctx = scrub_con.load(); local_ctx) {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@mohit84
Copy link
Contributor Author

mohit84 commented Oct 7, 2024

jenkins test make check

Copy link
Contributor

@rzarzynski rzarzynski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Thank you, @mohit84!

scrub_result.clear();
}
};
std::atomic<std::shared_ptr<ScrubContext>> scrub_con; ///< keeps track of scrub_context
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @mohit84 there is a build failure related to this line:

/usr/include/c++/11/atomic: In instantiation of ‘struct std::atomic<std::shared_ptr<Monitor::ScrubContext> >’:
/home/jenkins-build/build/workspace/ceph-dev-new-build/ARCH/x86_64/AVAILABLE_ARCH/x86_64/AVAILABLE_DIST/centos9/DIST/centos9/MACHINE_SIZE/gigantic/release/19.3.0-5886-gc279fd8b/rpm/el9/BUILD/ceph-19.3.0-5886-gc279fd8b/src/mon/Monitor.h:360:46:   required from here
/usr/include/c++/11/atomic:211:21: error: static assertion failed: std::atomic requires a trivially copyable type
  211 |       static_assert(__is_trivially_copyable(_Tp),
      |                     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/include/c++/11/atomic:211:21: note: ‘false’ evaluates to false
make[2]: *** [src/mon/CMakeFiles/mon.dir/build.make:398: src/mon/CMakeFiles/mon.dir/NVMeofGwMap.cc.o] Error 1

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As per error it seems std::atomic only works with trivially copyable type and std::shared_ptr is not trivially copyable. As per cpp reference for c++20 it should work fine but not sure why it is not working?

@rzarzynski
Do you think we need to use mutex instead of using atomic to avoid the same?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the reason is buggy / laggy compiler.

-- The CXX compiler identification is GNU 11.2.0
-- The C compiler identification is GNU 11.2.0
...
/usr/include/c++/11/atomic: In instantiation of 'struct std::atomic<std::shared_ptr<Monitor::ScrubContext> >':
/build/ceph-19.3.0-5886-gc4040e92/src/mon/Monitor.h:360:46:   required from here
/usr/include/c++/11/atomic:211:21: error: static assertion failed: std::atomic requires a trivially copyable type
  211 |       static_assert(__is_trivially_copyable(_Tp),
      |                     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~

godbolt.org shows GCC 11 is unable to compile even:

std::atomic<std::shared_ptr<int>> atomic_ptr_test;

complaining about non-trivial copy-constructibility. I bet the reason is GCC simply lacks the partial specialization for shared_ptr<T>. GCC 12 is fine.

The workaround could be https://www.boost.org/doc/libs/1_75_0/libs/smart_ptr/doc/html/smart_ptr.html#atomic_shared_ptr

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I have changed the code and use boost::atomic_shared_ptr.

@mohit84
Copy link
Contributor Author

mohit84 commented Nov 27, 2024

jenkins test make check

@github-actions
Copy link

github-actions bot commented Dec 3, 2024

This pull request can no longer be automatically merged: a rebase is needed and changes have to be manually resolved

@mohit84
Copy link
Contributor Author

mohit84 commented Dec 4, 2024

jenkins test make check

Copy link
Contributor

@rzarzynski rzarzynski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just nits, LGTM otherwise.


boost::shared_ptr<ScrubContext> local_ctx = scrub_con.load();
if (!local_ctx) {
++errors;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this is unneeded.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

scrub_result.clear();
}
};
boost::atomic_shared_ptr<ScrubContext> scrub_con; ///< keeps track of scrub_context
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: is there any reason for not naming it as scrub_ctx? _con suggest rather a connection which is misleading.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@rzarzynski
Copy link
Contributor

@mohit84: it looks there is some (trivial) problem from the renaming:

/home/jenkins-build/build/workspace/ceph-pull-requests/src/mon/Monitor.cc:5688:50: error: use of undeclared identifier 'scrub_state'
          const utime_t lat = ceph_clock_now() - scrub_state->start;
                                                 ^
/home/jenkins-build/build/workspace/ceph-pull-requests/src/mon/Monitor.cc:5689:61: error: use of undeclared identifier 'lat'
          dout(10) << __func__ << " mon scrub latency: " << lat << dendl;
                                                            ^
/home/jenkins-build/build/workspace/ceph-pull-requests/src/mon/Monitor.cc:5696:5: error: 'break' statement not in loop or switch statement
    break;
    ^
/home/jenkins-build/build/workspace/ceph-pull-requests/src/mon/Monitor.cc:5698:1: error: extraneous closing brace ('}')
}

scrub_result.clear();
}
};
boost::atomic_shared_ptr<ScrubContext> scrub_ctx; ///< keeps track of scrub_context
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

if (scrub_state->finished) {
local_ctx->scrub_result.clear();
if (local_ctx->scrub_state.finished)
const utime_t lat = ceph_clock_now() - scrub_state->start;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, here is the residue.

Suggested change
const utime_t lat = ceph_clock_now() - scrub_state->start;
const utime_t lat = ceph_clock_now() - local_ctx->scrub_state->start;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@mohit84
Copy link
Contributor Author

mohit84 commented Dec 31, 2024

@mohit84: it looks there is some (trivial) problem from the renaming:

/home/jenkins-build/build/workspace/ceph-pull-requests/src/mon/Monitor.cc:5688:50: error: use of undeclared identifier 'scrub_state'
          const utime_t lat = ceph_clock_now() - scrub_state->start;
                                                 ^
/home/jenkins-build/build/workspace/ceph-pull-requests/src/mon/Monitor.cc:5689:61: error: use of undeclared identifier 'lat'
          dout(10) << __func__ << " mon scrub latency: " << lat << dendl;
                                                            ^
/home/jenkins-build/build/workspace/ceph-pull-requests/src/mon/Monitor.cc:5696:5: error: 'break' statement not in loop or switch statement
    break;
    ^
/home/jenkins-build/build/workspace/ceph-pull-requests/src/mon/Monitor.cc:5698:1: error: extraneous closing brace ('}')
}

Thanks for pointing out the build error.

@mohit84
Copy link
Contributor Author

mohit84 commented Dec 31, 2024

jenkins test make check

@mohit84
Copy link
Contributor Author

mohit84 commented Dec 31, 2024

jenkins test make check

…Context

During handle of scrub operation if scrub_state is reset via
bootstrap then a process might be crash.

Solution: Encapsulate all scrub related objects into a single
          ScrubContext and manage it as via boost::atomic_shared_ptr.

Fixes: https://tracker.ceph.com/issues/67270
Credits: Radoslaw Zarzynski <rzarzyns@redhat.com>
Signed-off-by: Mohit Agrawal <moagrawa@redhat.com>
@mohit84
Copy link
Contributor Author

mohit84 commented Dec 31, 2024

jenkins test make check

2 similar comments
@mohit84
Copy link
Contributor Author

mohit84 commented Jan 1, 2025

jenkins test make check

@mohit84
Copy link
Contributor Author

mohit84 commented Jan 1, 2025

jenkins test make check

@mohit84
Copy link
Contributor Author

mohit84 commented Jan 1, 2025

jenkins test make check arm64

@yuriw
Copy link
Contributor

yuriw commented Jan 21, 2025

jenkins test make check arm64

@yuriw yuriw merged commit 2c8bfcf into ceph:main Jan 22, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants