Lumiera  0.pre.03
»edit your freedom«
call-queue-test.cpp
Go to the documentation of this file.
1 /*
2  CallQueue(Test) - verify queue based dispatch of bound function objects
3 
4  Copyright (C) Lumiera.org
5  2017, Hermann Vosseler <Ichthyostega@web.de>
6 
7  This program is free software; you can redistribute it and/or
8  modify it under the terms of the GNU General Public License as
9  published by the Free Software Foundation; either version 2 of
10  the License, or (at your option) any later version.
11 
12  This program is distributed in the hope that it will be useful,
13  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  GNU General Public License for more details.
16 
17  You should have received a copy of the GNU General Public License
18  along with this program; if not, write to the Free Software
19  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 
21 * *****************************************************/
22 
28 #include "lib/test/run.hpp"
30 #include "lib/sync-barrier.hpp"
31 #include "lib/thread.hpp"
32 #include "lib/sync.hpp"
33 #include "lib/util.hpp"
34 
35 #include "lib/call-queue.hpp"
36 
37 #include <string>
38 
39 
40 
41 namespace lib {
42 namespace test{
43 
44  using lib::Sync;
45  using lib::SyncBarrier;
46  using lib::ThreadJoinable;
47 
48  using util::isnil;
49  using std::string;
50 
51 
52 
53  namespace { // test fixture
54 
55  // --------random-stress-test------
56  uint const NUM_OF_THREADS = 50;
57  uint const MAX_RAND_INCMT = 200;
58  uint const MAX_RAND_STEPS = 500;
59  uint const MAX_RAND_DELAY = 1000;
60  // --------random-stress-test------
61 
62 
63  uint calc_sum = 0;
64  uint ctor_sum = 0;
65  uint dtor_sum = 0;
66 
67  template<uint i>
68  struct Dummy
69  {
70  uint val_;
71 
72  Dummy()
73  : val_(i)
74  {
75  ctor_sum += (val_+1);
76  }
77 
78  ~Dummy()
79  {
80  dtor_sum += val_;
81  }
82 
83  int
84  operator++()
85  {
86  return ++val_;
87  }
88  };
89 
90  template<uint i>
91  void
92  increment (Dummy<i>&& dummy) //NOTE: dummy is consumed here
93  {
94  calc_sum += ++dummy;
95  }
96 
97  }//(End) test fixture
98 
99 
100 
101 
102  /**********************************************************************************/
111  class CallQueue_test : public Test
112  {
113 
114  virtual void
115  run (Arg)
116  {
117  verify_SimpleUse();
118  verify_Consistency();
119  verify_ThreadSafety();
120  }
121 
122 
123  void
124  verify_SimpleUse ()
125  {
126  CallQueue queue;
127  CHECK (isnil (queue));
128 
129  int val = 2;
130  queue.feed ([&]() { val = -1; });
131  CHECK (1 == queue.size());
132  CHECK (val == 2);
133 
134  queue.invoke();
135  CHECK (val == -1);
136  CHECK (0 == queue.size());
137 
138  queue.invoke();
139  CHECK (0 == queue.size());
140  }
141 
142 
153  void
155  {
156  calc_sum = 0;
157  ctor_sum = 0;
158  dtor_sum = 0;
159 
160  CallQueue queue;
161  queue.feed ([]() { increment(Dummy<0>{}); }); //NOTE: each lambda binds a different instantiation of the increment template
162  queue.feed ([]() { increment(Dummy<1>{}); }); // and each invocation closes over an anonymous rvalue instance
163  queue.feed ([]() { increment(Dummy<2>{}); });
164 
165  queue.invoke();
166  queue.invoke();
167  queue.feed ([]() { increment(Dummy<3>{}); });
168  queue.feed ([]() { increment(Dummy<4>{}); });
169 
170  queue.invoke();
171  queue.invoke();
172  queue.invoke();
173 
174  uint expected = (5+1)*5/2;
175  CHECK (calc_sum = expected);
176  CHECK (ctor_sum = expected);
177  CHECK (dtor_sum = expected);
178  }
179 
180 
181 
182  struct Worker
183  : ThreadJoinable<>
184  , Sync<>
185  {
186  uint64_t producerSum = 0;
187  uint64_t consumerSum = 0;
188 
189  SyncBarrier& trigger_;
190 
191  void
192  countConsumerCall (uint increment)
193  {
194  Lock sync{this}; // NOTE: will be invoked from some random other thread
195  consumerSum += increment;
196  }
197 
198  Worker(CallQueue& queue, SyncBarrier& commonTrigger)
199  : ThreadJoinable{"CallQueue_test: concurrent dispatch"
200  , [&]() {
201  uint cnt = rand() % MAX_RAND_STEPS;
202  uint delay = rand() % MAX_RAND_DELAY;
203 
204  trigger_.sync(); // block until all threads are ready
205  for (uint i=0; i<cnt; ++i)
206  {
207  uint increment = rand() % MAX_RAND_INCMT;
208  queue.feed ([=]() { countConsumerCall(increment); });
209  producerSum += increment;
210  usleep (delay);
211  queue.invoke(); // NOTE: dequeue one functor added during our sleep
212  } // and thus belonging to some random other thread
213  }}
214  , trigger_{commonTrigger}
215  { }
216  };
217 
219 
220 
227  void
229  {
230  CallQueue queue;
231  SyncBarrier trigger{NUM_OF_THREADS + 1};
232 
233  // Start a bunch of threads with random access pattern
234  Workers workers{NUM_OF_THREADS,
235  [&](Workers::ElementHolder& storage)
236  {
237  storage.create<Worker> (queue, trigger);
238  }
239  };
240 
241  // unleash all worker functions
242  trigger.sync();
243 
244  // wait for termination of all threads and detect possible exceptions
245  bool allFine{true};
246  for (auto& worker : workers)
247  allFine &= worker.join().isValid();
248  CHECK (allFine);
249 
250  // collect the results of all worker threads
251  uint64_t globalProducerSum = 0;
252  uint64_t globalConsumerSum = 0;
253  for (auto& worker : workers)
254  {
255  globalProducerSum += worker.producerSum;
256  globalConsumerSum += worker.consumerSum;
257  }
258 
259  // VERIFY: locally recorded partial sums match total sum
260  CHECK (globalProducerSum == globalConsumerSum);
261  }
262  };
263 
264 
266  LAUNCHER (CallQueue_test, "unit common");
267 
268 
269 }} // namespace lib::test
Facility for monitor object based locking.
Definition: sync.hpp:217
Variant of the standard case, requiring to wait and join() on the termination of this thread...
Definition: thread.hpp:676
A fixed collection of non-copyable polymorphic objects.
A threadsafe queue for bound void(void) functors.
Definition: call-queue.hpp:58
Definition: run.hpp:49
scoped guard to control the actual locking.
Definition: sync.hpp:234
Implementation namespace for support and library code.
Managing a collection of non-copyable polymorphic objects in compact storage.
Storage Frame to hold one Child object.
Object Monitor based synchronisation.
ThreadJoinable(string const &, FUN &&, ARGS &&...) -> ThreadJoinable< std::invoke_result_t< FUN, ARGS... >>
deduction guide: find out about result value to capture from a generic callable.
A Queue for function invocations, allowing them to be dispatched on demand.
Simple test class runner.
Tiny helper functions and shortcuts to be used everywhere Consider this header to be effectively incl...
lib::Result< RES > join()
put the caller into a blocking wait until this thread has terminated
Definition: thread.hpp:693
Convenience front-end to simplify and codify basic thread handling.
A Dummy object for tests.
Definition: testdummy.hpp:50
A one time N-fold mutual synchronisation barrier.
A N-fold synchronisation latch using yield-wait until fulfilment.