1 //
2 // libsemigroups - C++ library for semigroups and monoids
3 // Copyright (C) 2019 James D. Mitchell
4 //
5 // This program is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 //
18 
19 // This file contains the declaration of the Race class template for
20 // competitively running different functions/methods in different threads, and
21 // obtaining the winner.
22 
23 // TODO(later):
24 // 1. consider if keeping killed off methods has any uses
25 // 2. update run_until to be similar to Runner::run_until
26 
27 #ifndef LIBSEMIGROUPS_RACE_HPP_
28 #define LIBSEMIGROUPS_RACE_HPP_
29 
30 #include <chrono>   // for nanoseconds
31 #include <cstddef>  // for size_t
32 #include <memory>   // for std::shared_ptr
33 #include <thread>   // for mutex
34 #include <vector>   // for vector
35 
36 #include "libsemigroups-debug.hpp"      // for LIBSEMIGROUPS_ASSERT
37 #include "libsemigroups-exception.hpp"  // for LIBSEMIGROUPS_EXCEPTION
38 #include "report.hpp"                   // for REPORT_DEFAULT, REPORT_TIME
39 #include "runner.hpp"                   // for Runner
40 #include "stl.hpp"                      // for IsCallable
41 #include "timer.hpp"                    // for Timer
42 
43 namespace libsemigroups {
44   namespace detail {
45     class Race final {
46      public:
47       // Construct an empty Race object, with maximum number of threads set to
48       // std::thread::hardware_concurrency.
49       Race();
Race(Race const & other)50       Race(Race const& other) : Race() {
51         // Can't use = default because std::mutex is non-copyable.
52         _runners     = other._runners;
53         _max_threads = other._max_threads;
54         _winner      = other._winner;
55       }
56 
57       Race(Race&&)  = delete;
58       Race& operator=(Race const&) = delete;
59       Race& operator=(Race&&) = delete;
60 
61       ~Race();
62 
63       // Set the maximum number of threads, throws if try to set to 0.
max_threads(size_t val)64       Race& max_threads(size_t val) noexcept {
65         LIBSEMIGROUPS_ASSERT(val != 0);
66         _max_threads = val;
67         return *this;
68       }
69 
max_threads() const70       size_t max_threads() const noexcept {
71         return _max_threads;
72       }
73 
74       // Runs the method Runner::run on every Runner in the Race, and returns
75       // the one that finishes first. The losers are deleted.
winner()76       std::shared_ptr<Runner> winner() {
77         run();
78         return _winner;
79       }
80 
finished() const81       bool finished() const noexcept {
82         return _winner != nullptr;
83       }
84 
85       // Adds a Runner to the race, throws if the race is already over.
86       void add_runner(std::shared_ptr<Runner>);
87 
88       using const_iterator =
89           typename std::vector<std::shared_ptr<Runner>>::const_iterator;
90 
begin() const91       const_iterator begin() const noexcept {
92         return _runners.cbegin();
93       }
94 
end() const95       const_iterator end() const noexcept {
96         return _runners.cend();
97       }
98       // Returns an iterator pointing to the first Runner in the Race.
cbegin() const99       const_iterator cbegin() const noexcept {
100         return _runners.cbegin();
101       }
102 
103       // Returns an iterator pointing to one past the last Runner in the Race.
cend() const104       const_iterator cend() const noexcept {
105         return _runners.cend();
106       }
107 
108       // Returns \c true if there are no Runners in the race, and \c false
109       // otherwise.
110       // std::vector::empty is noexcept
empty() const111       bool empty() const noexcept {
112         return _runners.empty();
113       }
114 
115       // std::vector::size is noexcept
number_runners() const116       size_t number_runners() const noexcept {
117         return _runners.size();
118       }
119 
120       // Runs the race to completion.
121       void run();
122 
123       // Runs the race for the specified amount of time.
124       void run_for(std::chrono::nanoseconds);
125 
126       // Runs until \p func returns \c true, or the race is over. This
127       // repeatedly calls Race::run_for for \p check_interval, and then checks
128       // whether or not \p func() returns true. The object \p func can be any
129       // callable object with 0 parameters and that returns a bool.
130       // This is definitely tested but doesn't show up in the code coverage for
131       // some reason.
132       template <typename TCallable>
run_until(TCallable const & func,std::chrono::nanoseconds check_interval=std::chrono::milliseconds (2))133       void run_until(TCallable const&         func,
134                      std::chrono::nanoseconds check_interval
135                      = std::chrono::milliseconds(2)) {
136         static_assert(detail::IsCallable<TCallable>::value,
137                       "the template parameter TCallable must be callable");
138         static_assert(std::is_same<typename std::result_of<TCallable()>::type,
139                                    bool>::value,
140                       "the template parameter TCallable must return a bool");
141         if (empty()) {
142           LIBSEMIGROUPS_EXCEPTION("no runners given, cannot run_until");
143         }
144         while (!func() && _winner == nullptr) {
145           // if _winner != nullptr, then the race is over.
146           run_for(check_interval);
147           check_interval *= 2;
148         }
149       }
150 
151       template <typename T>
find_runner() const152       std::shared_ptr<T> find_runner() const {
153         static_assert(std::is_base_of<Runner, T>::value,
154                       "the template parameter must be derived from Runner");
155         // We use find_if so that this works even if we haven't computed
156         // anything at all.
157         auto it = std::find_if(_runners.begin(),
158                                _runners.end(),
159                                [](std::shared_ptr<Runner> const& m) {
160                                  auto& r = *(m.get());
161                                  return typeid(r) == typeid(T);
162                                });
163         if (it != _runners.end()) {
164           return std::static_pointer_cast<T>(*it);
165         } else {
166           return nullptr;
167         }
168       }
169 
170      private:
171       // Runs the callable object \p func on every Runner in parallel.
172       template <typename TCallable>
run_func(TCallable const & func)173       void run_func(TCallable const& func) {
174         static_assert(std::is_same<typename std::result_of<TCallable(
175                                        std::shared_ptr<Runner>)>::type,
176                                    void>::value,
177                       "the result of calling the template parameter TCallable "
178                       "must be void");
179         LIBSEMIGROUPS_ASSERT(!empty());
180         if (_winner == nullptr) {
181           size_t nr_threads = std::min(_runners.size(), _max_threads);
182           if (nr_threads == 1) {
183             REPORT_DEFAULT("using 0 additional threads\n");
184             detail::Timer tmr;
185             func(_runners.at(0));
186             _winner = _runners.at(0);
187             REPORT_TIME(tmr);
188             return;
189           }
190           for (size_t i = 0; i < _runners.size(); ++i) {
191             if (_runners[i]->finished()) {
192               REPORT_DEFAULT("using 0 additional threads\n");
193               _winner = _runners[i];
194               REPORT_DEFAULT("#%d is already finished!\n", i);
195               return;
196             }
197           }
198 
199           std::vector<std::thread::id> tids(_runners.size(),
200                                             std::this_thread::get_id());
201 
202           REPORT_DEFAULT("using %d / %d additional threads\n",
203                          nr_threads,
204                          std::thread::hardware_concurrency());
205           detail::Timer tmr;
206           LIBSEMIGROUPS_ASSERT(nr_threads != 0);
207 
208           auto thread_func = [this, &func, &tids](size_t pos) {
209             tids[pos] = std::this_thread::get_id();
210             try {
211               func(_runners.at(pos));
212             } catch (std::exception const& e) {
213               size_t tid = THREAD_ID_MANAGER.tid(tids[pos]);
214               REPORT_DEFAULT("exception thrown by #%d:\n%s\n", tid, e.what());
215               return;
216             }
217             // Stop two Runner* objects from killing each other
218             {
219               std::lock_guard<std::mutex> lg(_mtx);
220               if (_runners.at(pos)->finished()) {
221                 for (auto it = _runners.begin(); it < _runners.begin() + pos;
222                      it++) {
223                   (*it)->kill();
224                 }
225                 for (auto it = _runners.begin() + pos + 1; it < _runners.end();
226                      it++) {
227                   (*it)->kill();
228                 }
229               }
230             }
231           };
232 
233           THREAD_ID_MANAGER.reset();
234 
235           std::vector<std::thread> t;
236           for (size_t i = 0; i < nr_threads; ++i) {
237             t.push_back(std::thread(thread_func, i));
238           }
239           for (size_t i = 0; i < nr_threads; ++i) {
240             t.at(i).join();
241           }
242           REPORT_TIME(tmr);
243           for (auto method = _runners.begin(); method < _runners.end();
244                ++method) {
245             if ((*method)->finished()) {
246               LIBSEMIGROUPS_ASSERT(_winner == nullptr);
247               _winner = *method;
248               size_t tid
249                   = THREAD_ID_MANAGER.tid(tids.at(method - _runners.begin()));
250               REPORT_DEFAULT("#%d is the winner!\n", tid);
251               break;
252             }
253           }
254           if (_winner != nullptr) {
255             for (auto rnnr : _runners) {
256               if (rnnr != _winner) {
257                 rnnr.reset();
258               }
259             }
260             _runners.clear();
261             _runners.push_back(_winner);
262           }
263         }
264       }
265 
266       std::vector<std::shared_ptr<Runner>> _runners;
267       size_t                               _max_threads;
268       std::mutex                           _mtx;
269       std::shared_ptr<Runner>              _winner;
270     };
271   }  // namespace detail
272 }  // namespace libsemigroups
273 
274 #endif  // LIBSEMIGROUPS_RACE_HPP_
275