Skip to content

Commit e83d82a

Browse files
author
MarcoFalke
committed
Merge #13247: Add tests to SingleThreadedSchedulerClient() and document the memory model
cbeaa91 Update ValidationInterface() documentation to explicitly specify threading and memory model (Jesse Cohen) b296b42 Update documentation for SingleThreadedSchedulerClient() to specify the memory model (Jesse Cohen) 9994d01 Add Unit Test for SingleThreadedSchedulerClient (Jesse Cohen) Pull request description: As discussed in #13023 I've split this test out into a separate pr This test (and documentation update) makes explicit the guarantee (previously undefined, but implied by the 'SingleThreaded' in `SingleThreadedSchedulerClient()`) - that callbacks pushed to the `SingleThreadedSchedulerClient()` obey the single threaded model for memory and execution - specifically, the callbacks are executed fully and in order, and even in cases where a subsequent callback is executed by a different thread, sequential consistency of memory for all threads executing these callbacks is maintained. Maintaining memory consistency should make the api more developer friendly - especially for users of the validationinterface. To the extent that there are performance implications from this decision, these are not currently present in practice because all use of this scheduler happens on a single thread currently, furthermore the lock should guarantee consistency across callback executions even when callbacks are executed by multiple threads (as the test does). Tree-SHA512: 5d95a7682c402e5ad76b05bc9dfbca99ca64105f62ab9e78f6fc0f6ea8c5277aa399fbb94298e35cc677b0c2181ff17259584bb7ae230e38aa68b85ecbc22856
2 parents 0fb9c87 + cbeaa91 commit e83d82a

File tree

3 files changed

+72
-4
lines changed

3 files changed

+72
-4
lines changed

src/scheduler.h

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,13 @@ class CScheduler
8686

8787
/**
8888
* Class used by CScheduler clients which may schedule multiple jobs
89-
* which are required to be run serially. Does not require such jobs
90-
* to be executed on the same thread, but no two jobs will be executed
91-
* at the same time.
89+
* which are required to be run serially. Jobs may not be run on the
90+
* same thread, but no two jobs will be executed
91+
* at the same time and memory will be release-acquire consistent
92+
* (the scheduler will internally do an acquire before invoking a callback
93+
* as well as a release at the end). In practice this means that a callback
94+
* B() will be able to observe all of the effects of callback A() which executed
95+
* before it.
9296
*/
9397
class SingleThreadedSchedulerClient {
9498
private:
@@ -103,6 +107,13 @@ class SingleThreadedSchedulerClient {
103107

104108
public:
105109
explicit SingleThreadedSchedulerClient(CScheduler *pschedulerIn) : m_pscheduler(pschedulerIn) {}
110+
111+
/**
112+
* Add a callback to be executed. Callbacks are executed serially
113+
* and memory is sequentially consistent between callback executions.
114+
* Practially, this means that callbacks can behave as if they are executed
115+
* in order by a single thread.
116+
*/
106117
void AddToProcessQueue(std::function<void (void)> func);
107118

108119
// Processes all remaining queue members on the calling thread, blocking until queue is empty

src/test/scheduler_tests.cpp

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ BOOST_AUTO_TEST_CASE(manythreads)
6565
size_t nTasks = microTasks.getQueueInfo(first, last);
6666
BOOST_CHECK(nTasks == 0);
6767

68-
for (int i = 0; i < 100; i++) {
68+
for (int i = 0; i < 100; ++i) {
6969
boost::chrono::system_clock::time_point t = now + boost::chrono::microseconds(randomMsec(rng));
7070
boost::chrono::system_clock::time_point tReschedule = now + boost::chrono::microseconds(500 + randomMsec(rng));
7171
int whichCounter = zeroToNine(rng);
@@ -112,4 +112,46 @@ BOOST_AUTO_TEST_CASE(manythreads)
112112
BOOST_CHECK_EQUAL(counterSum, 200);
113113
}
114114

115+
BOOST_AUTO_TEST_CASE(singlethreadedscheduler_ordered)
116+
{
117+
CScheduler scheduler;
118+
119+
// each queue should be well ordered with respect to itself but not other queues
120+
SingleThreadedSchedulerClient queue1(&scheduler);
121+
SingleThreadedSchedulerClient queue2(&scheduler);
122+
123+
// create more threads than queues
124+
// if the queues only permit execution of one task at once then
125+
// the extra threads should effectively be doing nothing
126+
// if they don't we'll get out of order behaviour
127+
boost::thread_group threads;
128+
for (int i = 0; i < 5; ++i) {
129+
threads.create_thread(boost::bind(&CScheduler::serviceQueue, &scheduler));
130+
}
131+
132+
// these are not atomic, if SinglethreadedSchedulerClient prevents
133+
// parallel execution at the queue level no synchronization should be required here
134+
int counter1 = 0;
135+
int counter2 = 0;
136+
137+
// just simply count up on each queue - if execution is properly ordered then
138+
// the callbacks should run in exactly the order in which they were enqueued
139+
for (int i = 0; i < 100; ++i) {
140+
queue1.AddToProcessQueue([i, &counter1]() {
141+
BOOST_CHECK_EQUAL(i, counter1++);
142+
});
143+
144+
queue2.AddToProcessQueue([i, &counter2]() {
145+
BOOST_CHECK_EQUAL(i, counter2++);
146+
});
147+
}
148+
149+
// finish up
150+
scheduler.stop(true);
151+
threads.join_all();
152+
153+
BOOST_CHECK_EQUAL(counter1, 100);
154+
BOOST_CHECK_EQUAL(counter2, 100);
155+
}
156+
115157
BOOST_AUTO_TEST_SUITE_END()

src/validationinterface.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,21 @@ void CallFunctionInValidationInterfaceQueue(std::function<void ()> func);
5353
*/
5454
void SyncWithValidationInterfaceQueue();
5555

56+
/**
57+
* Implement this to subscribe to events generated in validation
58+
*
59+
* Each CValidationInterface() subscriber will receive event callbacks
60+
* in the order in which the events were generated by validation.
61+
* Furthermore, each ValidationInterface() subscriber may assume that
62+
* callbacks effectively run in a single thread with single-threaded
63+
* memory consistency. That is, for a given ValidationInterface()
64+
* instantiation, each callback will complete before the next one is
65+
* invoked. This means, for example when a block is connected that the
66+
* UpdatedBlockTip() callback may depend on an operation performed in
67+
* the BlockConnected() callback without worrying about explicit
68+
* synchronization. No ordering should be assumed across
69+
* ValidationInterface() subscribers.
70+
*/
5671
class CValidationInterface {
5772
protected:
5873
/**

0 commit comments

Comments
 (0)