-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Redesign stoppable objects #3741
Conversation
Make JobQueue a child Stoppable of ApplicationImp.
Call stopped() in JobQueue::onStop.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tested these changes with reporting mode and everything seems to be working fine. Tested both ETL and read-only mode.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
Please, @cjcobb23 and @cdy20 can you review the merge with 1.8.0-b2? It involves the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left one comment about the merge but I'm good with the code as is.
src/ripple/app/main/Application.cpp
Outdated
@@ -1033,7 +1021,9 @@ class ApplicationImp : public Application, public BasicApp | |||
{ | |||
reportingETL_->stop(); | |||
#ifdef RIPPLED_REPORTING |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think you actually need the ifdef
here. RelationalDBInterfacePostgres
is not conditionally compiled. It's just PgPool
that is conditionally compiled (along with the other classes in Pg.h).
This change removes the `Stoppable` and `RootStoppable` classes and models stoppable objects as state machines in the pattern of `std::thread`. Stoppable objects have two states, started and stopped, and two synchronous methods that atomically transition between those states, `start` (or a constructor) and `stop`.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another incremental review. Nothing really significant.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm finally done going through all of the commits.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice work!
This change implements a new model for "stoppable" objects, heretofore represented by the
Stoppable
interface. The benefits include:In this change, stoppable objects are modeled after
std::thread
, which is already very well documented and widely understood.std::thread
std::thread
is a simple state machine with these states:std::thread
starts in this state. An activestd::thread
that is joined, detached, or move-assigned to anotherstd::thread
transitions to this state.std::thread
starts in this state. An emptystd::thread
that is move-assigned from an activestd::thread
transitions to this state.std::thread
whose destructor is called transitions to this state.An active
std::thread
that is destroyed or overwritten callsstd::terminate
, and an emptystd::thread
that is joined or detached throws an exception, meaning those are not legal transitions. To prevent trying them, callers can check the return value ofstd::thread::joinable
to see which state the object is in:true
for active, andfalse
for empty.Once a
std::thread
is joined, it never "restarts". Instead, you can move an activestd::thread
started elsewhere into an emptystd::thread
.Methods that initiate a state transition (move assignment,
join
, anddetach
) return only after the state transition is complete. State transitions are atomic and synchronous (i.e. blocking).Stoppable
The new stoppable objects in rippled no longer implement a common interface, but instead follow a common pattern:
std::thread
conceptvoid start()
1void join()
void stop()
Unlike
Stoppable::onStop
andStoppable::onChildrenStopped
, the newstop
methods are guaranteed to block until the state transition is complete, just likestd::thread::join
.Caveats
I would like to start every stoppable in its constructor, like
std::thread
, but some stoppable objects are constructed but never started in some tests (e.g.ApplicationImp
inTxQ1_test::testMaximum
). To accommodate them, we give them a single synchronousstart
method whose pre-condition is that the object is stopped and whose post-condition is that the object is started.I would like to give
stop
the pre-condition that the object is started, matching the behavior ofstd::thread::join
, but some stoppables are "stopped" twice, e.g.LoadManager
in the testSubscribe_test::testServer
.I would like to give their destructors the pre-condition that the object is stopped, like
std::thread
, but some stoppables are never stopped, e.g.JobQueue
(which technically "starts" in its constructor) inTxQ1_test::testMaximum
.Stoppables often exhibit overlapping but unnested lifetimes.
JobQueue
starts in its constructor, called byApplicationImp
's constructor, butApplicationImp
starts in its ownstart
method, or in itssetup
method, depending on how you look at it, both of which are called after its constructor, butJobQueue
is stopped early in thestop
method ofApplicationImp
. I would prefer that they stop in the reverse order that they started.RAII
std::thread
expects that each non-default-construction matches with exactly one call tostd::thread::join
. C++ already has an excellent technique for guaranteeing matching function calls: constructors and destructors, and the RAII pattern.std::thread
does not follow that pattern, which led frustrated developers to createstd::jthread
as an alternative. Trying to make the stoppable objects in rippled follow the RAII pattern requires significant changes that I consider just one bridge too far for this changeset. I want to stop here and build support for these changes before pushing further.Commits
I've tried to make each commit small, separately reviewable, build, and pass all tests, to make it easy to review step-by-step, if that's your preference, but it means there are many commits.
Footnotes
Or
bool start()
in the pesky case ofNodeStore::DatabaseShard
. ↩