269 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
		
		
			
		
	
	
			269 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
|  | //
 | ||
|  | // Created by WolverinDEV on 03/03/2020.
 | ||
|  | //
 | ||
|  | 
 | ||
|  | #include "src/lock/rw_mutex.h"
 | ||
|  | #include <atomic>
 | ||
|  | #include <vector>
 | ||
|  | #include <thread>
 | ||
|  | #include <iostream>
 | ||
|  | #include <functional>
 | ||
|  | #include <algorithm>
 | ||
|  | 
 | ||
|  | namespace helper { | ||
|  |     std::mutex threads_lock{}; | ||
|  |     size_t running_threads{0}; | ||
|  |     std::vector<std::thread> thread_handles{}; | ||
|  |     bool sync_pending{false}; | ||
|  | 
 | ||
|  |     inline void sync_threads() { | ||
|  |         static std::condition_variable sync_cv; | ||
|  |         static size_t synced_threads{0}; | ||
|  | 
 | ||
|  |         auto timeout = std::chrono::system_clock::now() + std::chrono::seconds{5}; | ||
|  |         std::unique_lock lock{threads_lock}; | ||
|  |         if(++synced_threads == running_threads) { | ||
|  |             sync_cv.notify_all(); | ||
|  |             synced_threads = 0; | ||
|  |             sync_pending = false; | ||
|  |             return; | ||
|  |         } | ||
|  | 
 | ||
|  |         sync_pending = true; | ||
|  |         if(sync_cv.wait_until(lock, timeout) == std::cv_status::timeout) { | ||
|  |             std::cerr << "failed to sync threads" << std::endl; | ||
|  |             abort(); | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     template <typename... args_t> | ||
|  |     inline void create_thread(args_t&&... arguments) { | ||
|  |         auto callback = std::bind(arguments...); | ||
|  | 
 | ||
|  |         std::lock_guard tlock{threads_lock}; | ||
|  |         thread_handles.emplace_back([callback]{ | ||
|  |             callback(); | ||
|  | 
 | ||
|  |             std::lock_guard tlock{threads_lock}; | ||
|  |             auto it = std::find_if(thread_handles.begin(), thread_handles.end(), [](const auto& thread) { | ||
|  |                 return thread.get_id() == std::this_thread::get_id(); | ||
|  |             }); | ||
|  |             if(it == thread_handles.end()) | ||
|  |                 return; /* thread will be joined via await_all_threads */ | ||
|  | 
 | ||
|  |             if(sync_pending) { | ||
|  |                 std::cerr << "thread terminated while sync was pending" << std::endl; | ||
|  |                 abort(); | ||
|  |             } | ||
|  | 
 | ||
|  |             running_threads--; | ||
|  |             it->detach(); | ||
|  |             thread_handles.erase(it); | ||
|  |         }); | ||
|  |         running_threads++; | ||
|  |     } | ||
|  | 
 | ||
|  |     inline void await_all_threads() { | ||
|  |         std::unique_lock thread_lock{threads_lock}; | ||
|  |         while(!thread_handles.empty()) { | ||
|  |             auto thread = std::move(thread_handles.back()); | ||
|  |             thread_handles.pop_back(); | ||
|  |             thread_lock.unlock(); | ||
|  |             thread.join(); | ||
|  |             thread_lock.lock(); | ||
|  | 
 | ||
|  |             if(sync_pending) { | ||
|  |                 std::cerr << "thread terminated while sync was pending" << std::endl; | ||
|  |                 abort(); | ||
|  |             } | ||
|  |             running_threads--; | ||
|  |         } | ||
|  | 
 | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | void testSharedLockLock() { | ||
|  |     ts::rw_mutex lock{}; | ||
|  | 
 | ||
|  |     lock.lock_shared(); | ||
|  |     lock.lock_shared(); | ||
|  | 
 | ||
|  |     lock.unlock_shared(); | ||
|  |     lock.lock_shared(); | ||
|  |     lock.unlock_shared(); | ||
|  |     lock.unlock_shared(); | ||
|  | } | ||
|  | 
 | ||
|  | void testExclusiveLock() { | ||
|  |     ts::rw_mutex lock{}; | ||
|  | 
 | ||
|  |     size_t read_locked{0}, write_locked{0}; | ||
|  |     for(int i = 0; i < 10; i++) { | ||
|  |         if(rand() % 2 == 0) { | ||
|  |             write_locked++; | ||
|  |             lock.lock(); | ||
|  |             lock.unlock(); | ||
|  |         } else { | ||
|  |             read_locked++; | ||
|  |             lock.lock_shared(); | ||
|  |             lock.unlock_shared(); | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     /* to ensure that the read and write function has been tested more than once */ | ||
|  |     assert(read_locked > 1); | ||
|  |     assert(write_locked > 1); | ||
|  | } | ||
|  | 
 | ||
|  | void testRW_Multithread() { | ||
|  |     ts::rw_mutex lock{}; | ||
|  | 
 | ||
|  |     for(int i = 0; i < 20; i++) { | ||
|  |         const auto do_sync = i < 5; | ||
|  | 
 | ||
|  |         std::atomic<bool> write_locked{false}; | ||
|  |         helper::create_thread([&]{ | ||
|  |             lock.lock(); | ||
|  |             write_locked = true; | ||
|  |             if(do_sync) helper::sync_threads(); /* sync point 1 */ | ||
|  |             std::this_thread::sleep_for(std::chrono::seconds{1}); | ||
|  |             write_locked = false; | ||
|  |             lock.unlock(); | ||
|  |         }); | ||
|  | 
 | ||
|  |         helper::create_thread([&]{ | ||
|  |             if(do_sync) helper::sync_threads(); /* sync point 1 */ | ||
|  |             lock.lock_shared(); | ||
|  |             std::cout << "shared locked" << std::endl; | ||
|  |             if(write_locked) { | ||
|  |                 std::cerr << "Shared lock succeeded, but thread has been write locked!" << std::endl; | ||
|  |                 return; | ||
|  |             } | ||
|  |             std::this_thread::sleep_for(std::chrono::seconds{1}); | ||
|  |             std::cout << "unlocking shared lock" << std::endl; | ||
|  |             lock.unlock_shared(); | ||
|  |         }); | ||
|  |         helper::await_all_threads(); | ||
|  |         std::cout << "Loop " << i << " passed" << std::endl; | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | void testLockUpgrade() { | ||
|  |     std::atomic<bool> lock_released{true}; | ||
|  |     std::atomic<bool> lock_shared{false}; | ||
|  |     ts::rw_mutex lock{}; | ||
|  | 
 | ||
|  |     lock.lock_shared(); | ||
|  |     std::cout << "[main] Locked shared lock" << std::endl; | ||
|  |     lock_released = false; | ||
|  |     lock_shared = true; | ||
|  | 
 | ||
|  |     helper::create_thread([&]{ | ||
|  |         std::cout << "Awaiting exclusive lock" << std::endl; | ||
|  |         lock.lock(); | ||
|  |         std::cout << "Received exclusive lock" << std::endl; | ||
|  |         if(!lock_released) { | ||
|  |             std::cerr << "Acquired write lock even thou is hasn't been released!" << std::endl; | ||
|  |             abort(); | ||
|  |         } | ||
|  | 
 | ||
|  |         lock.unlock(); | ||
|  |     }); | ||
|  |     std::this_thread::sleep_for(std::chrono::milliseconds {100}); | ||
|  | 
 | ||
|  |     if(auto err = lock.upgrade_lock(); err) { | ||
|  |         std::cerr << "Failed to upgrade lock: " << err << std::endl; | ||
|  |         abort(); | ||
|  |     } | ||
|  |     std::cout << "[main] Upgraded shared lock" << std::endl; | ||
|  |     lock_shared = false; | ||
|  | 
 | ||
|  |     helper::create_thread([&]{ | ||
|  |         std::cout << "Awaiting shared lock" << std::endl; | ||
|  |         lock.lock_shared(); | ||
|  |         std::cout << "Received shared lock" << std::endl; | ||
|  |         if(!lock_shared) { | ||
|  |             std::cerr << "Acquired write lock even thou is hasn't been lock in shared mode!" << std::endl; | ||
|  |             abort(); | ||
|  |         } | ||
|  | 
 | ||
|  |         lock.unlock_shared(); | ||
|  |     }); | ||
|  | 
 | ||
|  |     std::this_thread::sleep_for(std::chrono::milliseconds {100}); | ||
|  |     lock_shared = true; | ||
|  |     std::cout << "[main] Downgrading exclusive lock" << std::endl; | ||
|  |     lock.downgrade_lock(); | ||
|  | 
 | ||
|  |     std::this_thread::sleep_for(std::chrono::milliseconds {10}); | ||
|  |     lock_released = true; | ||
|  |     std::cout << "[main] Releasing shared lock" << std::endl; | ||
|  |     lock.unlock_shared(); | ||
|  | 
 | ||
|  |     helper::await_all_threads(); | ||
|  | } | ||
|  | 
 | ||
|  | void testLockUpgradeRelease() { | ||
|  |     { | ||
|  |         ts::rw_mutex lock{}; | ||
|  |         lock.lock_shared(); | ||
|  |         (void) lock.upgrade_lock(); | ||
|  |         lock.unlock(); | ||
|  |     } | ||
|  |     { | ||
|  |         ts::rw_mutex lock{}; | ||
|  |         lock.lock_shared(); | ||
|  |         (void) lock.upgrade_lock(); | ||
|  |         lock.downgrade_lock(); | ||
|  |         lock.unlock_shared(); | ||
|  |     } | ||
|  | } | ||
|  | 
 | ||
|  | void testLockGuard() { | ||
|  |     ts::rw_mutex  mutex{}; | ||
|  |     ts::rwshared_lock lock{mutex}; | ||
|  |     bool lock_allowed{false}; | ||
|  | 
 | ||
|  |     if(auto err = lock.auto_lock_exclusive(); err) { | ||
|  |         std::cerr << "Failed to upgrade lock: " << err << std::endl; | ||
|  |         abort(); | ||
|  |     } | ||
|  |     assert(lock.exclusive_locked()); | ||
|  | 
 | ||
|  |     helper::create_thread([&]{ | ||
|  |         std::cout << "Awaiting exclusive lock" << std::endl; | ||
|  |         mutex.lock(); | ||
|  |         std::cout << "Received exclusive lock" << std::endl; | ||
|  |         if(!lock_allowed) { | ||
|  |             std::cerr << "Acquired write lock even thou is hasn't been released!" << std::endl; | ||
|  |             abort(); | ||
|  |         } | ||
|  | 
 | ||
|  |         mutex.unlock(); | ||
|  |     }); | ||
|  |     std::this_thread::sleep_for(std::chrono::milliseconds {100}); | ||
|  | 
 | ||
|  |     lock.auto_lock_shared(); | ||
|  | 
 | ||
|  |     std::this_thread::sleep_for(std::chrono::milliseconds {100}); | ||
|  |     lock_allowed = true; | ||
|  |     lock.auto_unlock(); | ||
|  |     helper::await_all_threads(); | ||
|  | 
 | ||
|  |     lock.lock_exclusive(); | ||
|  | } | ||
|  | 
 | ||
|  | int main() { | ||
|  |     { /* get rid of the unused warnings */ | ||
|  |         ts::rw_unsafe_mutex mutex_a{}; | ||
|  |         ts::rw_safe_mutex mutex_b{}; | ||
|  |     } | ||
|  | 
 | ||
|  |     testSharedLockLock(); | ||
|  |     testExclusiveLock(); | ||
|  |     //testRW_Multithread();
 | ||
|  |     //TODO: Test lock order
 | ||
|  |     testLockUpgrade(); | ||
|  |     testLockUpgradeRelease(); | ||
|  |     testLockGuard(); | ||
|  | } |