Join GitHub today
GitHub is home to over 20 million developers working together to host and review code, manage projects, and build software together.
Give CValidationInterface Support for calling notifications on the CScheduler Thread #10179
Conversation
fanquake
added the
Validation
label
Apr 10, 2017
laanwj
added
to Blockers in High-priority for review
Apr 13, 2017
|
Rebased after ##10178 merge. |
| + // our own queue here :( | ||
| + CCriticalSection cs_callbacksPending; | ||
| + std::list<std::function<void (void)>> callbacksPending; | ||
| + std::atomic<bool> fCallbacksRunning = ATOMIC_VAR_INIT(false); |
morcos
Apr 24, 2017
Contributor
nit: i'm the last person who should comment on coding style, but isn't this easier to read:
std::atomic<bool> fCallbacksRunning(false)
TheBlueMatt
Apr 24, 2017
Contributor
My C++-fu is not good enough to get that to compile. Not actually sure why, though, frankly.
sipa
Apr 24, 2017
Owner
Could you try
std::atomic<bool> fCallbacksRunning = std::atomic<bool>(false);?
TheBlueMatt
Apr 24, 2017
Contributor
Yes, that works, but I think thats worse than using the init wrapper, which is what that wrapper exists for.
theuni
Apr 26, 2017
Member
Use braced initialization in a class/struct definition:
std::atomic<bool> fCallbacksRunning{false};
TheBlueMatt
Apr 26, 2017
Contributor
Rewrote to just use a regular bool inside the lock, no reason to really have an atomic here.
|
fast review utACK |
|
utACK 010f3ae |
|
I don't currently have tests written, but given nearly everything passes through I've found its pretty well-excersized by existing wallet functional tests. I'll add unit tests for this to my to-do list.
…
|
| + callback = callbacksPending.front(); | ||
| + callbacksPending.pop_front(); | ||
| + } | ||
| + callback(); |
laanwj
Apr 25, 2017
Owner
This doesn't seem exception-safe. If an exception is raised inside here, fCallbacksRunning will stay set forever.
TheBlueMatt
Apr 25, 2017
Contributor
Indeed. Put a generic try {} catch(...) {Log} around it, not sure what else to do there.
laanwj
Apr 26, 2017
Owner
You could catch the exception, clear the flag, and throw again. Though RAII is usually the best way in C++ to cover all exit paths.
sipa
reviewed
Apr 26, 2017
I'll give you the same comment as you gave on #10148... you're adding unused and untested functionality.
The first commit seems a useful refactoring that could be its own PR, but the rest seem to be preparations for something that doesn't exist yet.
| + return; | ||
| + } | ||
| + | ||
| + while (true) { |
sipa
Apr 26, 2017
Owner
Instead of looping until there are no more scheduled callbacks, isn't easier/better to return, but reschedule itself? That way a large set of queued callbacks won't prevent the scheduler from running other (non-callback) jobs in the mean time.
| + } | ||
| + | ||
| + void AddToProcessQueue(const std::function<void (void)>& func) { | ||
| + if (!scheduler) |
| +} | ||
| + | ||
| +CMainSignals::~CMainSignals() { | ||
| + delete internals; |
TheBlueMatt
Apr 26, 2017
Contributor
Cant, sadly, as the whole point was to not make CMainSignalsInterface sit in the .h to avoid having a bost/signals include in half our codebase.
sipa
Apr 26, 2017
Owner
std::unique_ptr seems to work fine with forward-declared types: sipa/bitcoin@a4ecaad
TheBlueMatt
Apr 26, 2017
Contributor
Well, I'll be! Took your patch and squashed (hope you dont mind).
sipa
Apr 26, 2017
Owner
Sorry, but I'm going to insist that you maintain this commit intact (nevermind that it's likely the exact same patch you'd come up with if I hadn't shown you) forever in your PR history. Note: sarcasm.
TheBlueMatt
referenced
this pull request
Apr 27, 2017
Open
Call wallet notify callbacks in scheduler thread (without cs_main) #10286
| +struct CMainSignalsInstance; | ||
| +class CMainSignals { | ||
| +private: | ||
| + std::unique_ptr<CMainSignalsInstance> internals; |
ryanofsky
May 2, 2017
Contributor
In commit "Make ValidationInterface signals-type-agnostic"
I don't think you need to have this internals pointer in order to do the signal routing / CScheduler stuff that's ostensibly the motivation for this change. (The signals could just be regular class members.). If you like this better because it helps hide the boost dependency, though, that seems fine. This is just the pImpl pattern, http://stackoverflow.com/questions/8972588/is-the-pimpl-idiom-really-used-in-practice.
TheBlueMatt
May 2, 2017
Contributor
Yea, the real motivation there was to not have to #include boost signals garbage everywhere, nothing more.
| + // We are not allowed to assume the scheduler only runs in one thread, | ||
| + // but must ensure all callbacks happen in-order, so we end up creating | ||
| + // our own queue here :( | ||
| + CCriticalSection cs_callbacksPending; |
ryanofsky
May 2, 2017
Contributor
In commit "Handle more than one CScheduler thread in CValidationInterface"
This seems like it would a lot simpler it just spawned a single thread to run the callbacks in sequence instead of relying on CScheduler and then doing a bunch of synchronization on top of it to keep things scheduled and prevent more than one callback from executing at the same time.
Also, I think it would be nice if this functionality were implemented in a standalone class that could be unit tested.
Anyway, implementation seems fine, I couldn't find any bugs.
TheBlueMatt
May 2, 2017
Contributor
Moved to scheduler.h, testing left as an excersize for the reader :p.
| + callback(); | ||
| + } | ||
| + | ||
| + void AddToProcessQueue(const std::function<void (void)>& func) { |
ryanofsky
May 2, 2017
•
Contributor
In commit "Handle more than one CScheduler thread in CValidationInterface":
Should pass func by value or rvalue reference so you can move it into the callbacksPending list without a copy.
| + if (callbacksPending.empty()) return; | ||
| + fCallbacksRunning = true; | ||
| + | ||
| + callback = callbacksPending.front(); |
ryanofsky
May 2, 2017
Contributor
In commit "Handle more than one CScheduler thread in CValidationInterface"
Could add std::move here.
|
utACK 643a988. I like the new ProcessQueue location and std::move additions. |
|
Squashed and tweaked commit wording. |
|
utACK 830c26b. Only change aside from the history squashing is switching to the schedule() default arg. |
|
utACK. Since you're adding a new class, would you mind making a few changes to match the new naming rules in the style guide? |
|
Removed C prefix from classes without rebasing. |
|
Very minor rebase. |
ryanofsky
reviewed
Jun 8, 2017
utACK 608fad0. No changes since last review except fixes to conflicting #include lines.
|
Status of this PR? It has had 4 utACKs (though @laanwj's is in strikethrough) as well as comments from @mchrostowski in #10286. |
| + | ||
| + CCriticalSection m_cs_callbacksPending; | ||
| + std::list<std::function<void (void)>> m_callbacksPending; | ||
| + bool m_fCallbacksRunning = false; |
sipa
Jun 12, 2017
Owner
Would you mind changing this to lowercase-only variable names (e.g. m_cs_callbacks_pending, m_callbacks_pending, m_callbacks_running)?
TheBlueMatt
referenced
this pull request
Jun 22, 2017
Open
Small step towards demangling cs_main from CNodeState #10652
|
@laanwj Does the strikethrough utACK means you still have a concern? |
| } | ||
| void CMainSignals::UnregisterBackgroundSignalScheduler() { | ||
| - m_internals->m_scheduler = NULL; | ||
| + m_internals.reset(nullptr); |
theuni
Jun 27, 2017
Member
This potentially leaves events in the SingleThreadedSchedulerClient process queue with a dangling pointer to "this", no? I believe SingleThreadedSchedulerClient needs interrupt/stop functionality.
TheBlueMatt
Jun 27, 2017
Contributor
Interrupt() will stop the processing of events long before we ever call UnregisterBackgroundSignalScheduler in init/bitcoind.cpp/qt/bitcoin.cpp. There isnt really a great way to solve this, I think, without just making the CScheduler own the thread(/group), but I went ahead and pushed a commit which will just call any remaining callbacks when Unregister is called.
ryanofsky
Jul 10, 2017
Contributor
In commit "Support more than one CScheduler thread for serial clients"
It'd be good to just reset the scheduler pointer here instead of going overboard and destroying all the boost signals at this point as well. It just seems like a random and unexpected thing to be doing in a function called UnregisterBackgroundSignalScheduler especially given new flush behavior which allows signals be forwarded after the scheduler is stopped.
|
@sipa Just that I had a concern at the time, and was too quick to utACK. I should re-review. |
| /** Notifies listeners that a key for mining is required (coinbase) */ | ||
| - boost::signals2::signal<void (std::shared_ptr<CReserveScript>&)> ScriptForMining; | ||
| + virtual void GetScriptForMining(std::shared_ptr<CReserveScript>&) {}; |
laanwj
Jun 28, 2017
Owner
#10683 gets rid of GetScriptForMining in validationinterface, which I think it's good because it mutates its argument. Doing that in a decoupled thread would be disastrous.
TheBlueMatt
Jun 28, 2017
Contributor
All of the move-to-background-thread stuff is going to be a slow per-function process. See, eg, #10652 which moves a few functions to the background for net_processing and, of course, #10286 which moves a few to the background for wallet. Agreed that GetScriptForMining should go away and then it wont be a concern :).
laanwj
self-assigned this
Jun 28, 2017
laanwj
removed
from Blockers in High-priority for review
Jun 29, 2017
|
utACK (after rebase) now that #10683 is in. |
TheBlueMatt
added some commits
Apr 27, 2017
|
Rebased. |
|
Redid the shutdown callback-flushing at @morcos's request - now flushes earlier in the Shutdown() process in a much better location - after all our peers have stopped processing (and thus cant generate callbacks) and before wallet flushing. |
TheBlueMatt
added some commits
Jan 19, 2017
|
Added a comment on flush/drop behavior - |
|
utACK 3192975 |
|
re-utACK 3192975, though very hesitantly after thinking through the tear-down some more. The shutdown process is going to get really hard to trace when callbacks start coming in on different threads. I really hope some ownership model will begin to emerge. |
ryanofsky
reviewed
Jul 10, 2017
utACK 3192975. Changes since last review were rebase after multiwallet & ScriptForMining removal, and adding the flush commit.
I left a few suggestions pertaining to the flush commit. I think this code would be good to clean up at some point, though it don't think it should hold up the PR.
| @@ -44,6 +44,10 @@ void CMainSignals::UnregisterBackgroundSignalScheduler() { | ||
| m_internals.reset(nullptr); | ||
| } | ||
| +void CMainSignals::FlushBackgroundCallbacks() { | ||
| + m_internals->m_schedulerClient.EmptyQueue(); |
ryanofsky
Jul 10, 2017
Contributor
In commit "Flush CValidationInterface callbacks prior to destruction"
Shutdown order is convoluted enough that I think it would be good to add asserts, like asserting m_internals is non-null here and that scheduler is shut down or shutting down at this point (nThreadsServicingQueue == 0 || shouldStop). Latter might be more appropriate inside the scheduling code.
| } | ||
| void CMainSignals::UnregisterBackgroundSignalScheduler() { | ||
| - m_internals->m_scheduler = NULL; | ||
| + m_internals.reset(nullptr); |
ryanofsky
Jul 10, 2017
Contributor
In commit "Support more than one CScheduler thread for serial clients"
It'd be good to just reset the scheduler pointer here instead of going overboard and destroying all the boost signals at this point as well. It just seems like a random and unexpected thing to be doing in a function called UnregisterBackgroundSignalScheduler especially given new flush behavior which allows signals be forwarded after the scheduler is stopped.
| @@ -251,6 +251,7 @@ void Shutdown() | ||
| } | ||
| #endif | ||
| UnregisterAllValidationInterfaces(); | ||
| + GetMainSignals().UnregisterBackgroundSignalScheduler(); |
ryanofsky
Jul 10, 2017
Contributor
In commit "Give CMainSignals a reference to the global scheduler"
It seems like the right place for this unregister call would be earlier in Shutdown(), after scheduler thread is cancelled and the last signal is sent, for consistency with the register call, which is made when the scheduler thread is started.
This would let you flush the background queue when the signal scheduler is destroyed and not need separate FlushBackgroundCallbacks and EmptyQueue calls made in advance.
Also this would bring shutdown code closer to an raii-style ordering, which should make cleanup easier in the future and would make the various object lifetimes a little easier to understand now.
TheBlueMatt
Jul 11, 2017
Contributor
We cant unregister the background scheduler from the validation interface until we're sure nothing is gonna generate anymore callbacks (so, really, after the pcoinsTip/etc deletions). If we want to mirror the initialization order, we'd have to move it even further down, not up, as de-init in RAII order would be after wallet deltion.
| @@ -101,6 +101,9 @@ class SingleThreadedSchedulerClient { | ||
| public: | ||
| SingleThreadedSchedulerClient(CScheduler *pschedulerIn) : m_pscheduler(pschedulerIn) {} | ||
| void AddToProcessQueue(std::function<void (void)> func); | ||
| + | ||
| + // Processes all remaining queue members on the calling thread, blocking until queue is empty |
ryanofsky
Jul 10, 2017
Contributor
In commit "Flush CValidationInterface callbacks prior to destruction"
Comment should point out that this should only be called when scheduler is not active. Otherwise this could burn cpu racing with scheduler thread to run the next callback.
laanwj
merged commit 1f668b6
into
bitcoin:master
Jul 11, 2017
1 check passed
laanwj
added a commit
that referenced
this pull request
Jul 11, 2017
|
|
laanwj |
21ed30a
|
TheBlueMatt commentedApr 10, 2017
Apologies for the plumbing-only no-changes PR, but the "move wallet updates out of cs_main into a background thread" changeset is a bit big for all one go, so instead I'm doing this first. It simply gives CValidationInterface the neccessary plumbing to handle callbacks on the CScheduler thread. See TheBlueMatt/bitcoin@4e82e40 for a commit which switches a callback into the background thread.
The CScheduler thread was super lonely, so I decided to use that instead of adding new threads.
This conflicts trivially with #10178, but not enough to avoid doing parallel reviews. After that and this are merged, "move wallet callbacks into background thread" is just one more (somewhat large, sadly) PR away. See https://github.com/TheBlueMatt/bitcoin/commits/2017-01-wallet-cache-inmempool for the full branch.