/* * Copyright 2018-present Facebook, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include using namespace std::literals; namespace folly { namespace test { /** * Like DeterministicSchedule, but allows setting callbacks that can be run * for the current thread when an atomic access occurs, and after. This * allows us to construct thread interleavings by hand * * Constructing a ManualSchedule is required to ensure that we maintain * per-test state for threads * * This can also be used to order thread movement, as an alternative to * maintaining condition variables and/or semaphores for the purposes of * testing, for example * * auto one = std::thread{[&]() { * schedule.wait(1); * two(); * schedule.post(2); * }}; * * auto two = std::thread{[&]() { * one(); * schedule.post(1); * schedule.wait(2); * three(); * }}; * * The code above is guaranteed to call one(), then two(), and then three() */ class ManualSchedule { public: ManualSchedule() = default; ~ManualSchedule() { // delete this schedule from the global map auto schedules = schedules_.wlock(); for_each(*schedules, [&](auto& schedule, auto, auto iter) { if (schedule.second == this) { schedules->erase(iter); } }); } /** * These will be invoked by DeterministicAtomic to signal atomic access * before and after the operation */ static void beforeSharedAccess() { if (folly::kIsDebug) { auto id = std::this_thread::get_id(); // get the schedule assigned for the current thread, if one exists, // otherwise proceed as normal auto schedule = get_ptr(*schedules_.wlock(), id); if (!schedule) { return; } // now try and get the callbacks for this thread, if there is a callback // registered for the test, it must mean that we have a callback auto callback = get_ptr((*(*schedule)->callbacks_.wlock()), id); if (!callback) { return; } (*callback)(); } } static void afterSharedAccess(bool) { beforeSharedAccess(); } /** * Set a callback that will be called on every subsequent atomic access. * This will be invoked before and after every atomic access, for the thread * that called setCallback */ void setCallback(std::function callback) { schedules_.wlock()->insert({std::this_thread::get_id(), this}); callbacks_.wlock()->insert({std::this_thread::get_id(), callback}); } /** * Delete the callback set for this thread on atomic accesses */ void removeCallbacks() { callbacks_.wlock()->erase(std::this_thread::get_id()); } /** * wait() and post() for easy testing */ void wait(int id) { if (folly::kIsDebug) { auto& baton = (*batons_.wlock())[id]; baton.wait(); } } void post(int id) { if (folly::kIsDebug) { auto& baton = (*batons_.wlock())[id]; baton.post(); } } private: // the map of threads to the schedule started for that test static Synchronized> schedules_; // the map of callbacks to be executed for a thread's atomic accesses Synchronized>> callbacks_; // batons for testing, this map will only ever be written to, so it is safe // to hold references outside lock Synchronized>> batons_; }; Synchronized> ManualSchedule::schedules_; template using ManualAtomic = test::DeterministicAtomicImpl; template