From 54dd6a240705e83c44ff0b4201d113b0868630b1 Mon Sep 17 00:00:00 2001 From: Tomasz Sobczyk Date: Sat, 24 Oct 2020 13:13:10 +0200 Subject: [PATCH] Add logger with synchronized regions. --- src/misc.cpp | 2 + src/misc.h | 178 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 180 insertions(+) diff --git a/src/misc.cpp b/src/misc.cpp index e09b8eed..879f4462 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -61,6 +61,8 @@ typedef bool(*fun3_t)(HANDLE, CONST GROUP_AFFINITY*, PGROUP_AFFINITY); using namespace std; +SynchronizedRegionLogger sync_region_cout(std::cout); + namespace { /// Version number. If Version is left empty, then compile date in the format diff --git a/src/misc.h b/src/misc.h index dca959cd..af40ab16 100644 --- a/src/misc.h +++ b/src/misc.h @@ -31,6 +31,7 @@ #include #include #include +#include #include "types.h" @@ -70,6 +71,183 @@ std::ostream& operator<<(std::ostream&, SyncCout); #define sync_cout std::cout << IO_LOCK #define sync_endl std::endl << IO_UNLOCK +// This logger allows printing many parts in a region atomically +// but doesn't block the threads trying to append to other regions. +// Instead if some region tries to pring while other region holds +// the lock the messages are queued to be printed as soon as the +// current region releases the lock. +struct SynchronizedRegionLogger +{ +private: + using RegionId = std::uint64_t; + + struct RegionLock + { + RegionLock(SynchronizedRegionLogger& log, RegionId id) : + logger(&log), region_id(id), is_held(true) + { + } + + RegionLock(const RegionLock&) = delete; + RegionLock& operator=(const RegionLock&) = delete; + + RegionLock(RegionLock&& other) : + logger(other.logger), region_id(other.region_id), is_held(other.is_held) + { + other.logger = nullptr; + other.is_held = false; + } + + RegionLock& operator=(RegionLock&& other) { + if (is_held && logger != nullptr) + { + logger->release_region(region_id); + } + + logger = other.logger; + region_id = other.region_id; + is_held = other.is_held; + + other.is_held = false; + + return *this; + } + + ~RegionLock() { unlock(); } + + void unlock() { + if (is_held) { + is_held = false; + + if (logger != nullptr) + logger->release_region(region_id); + } + } + + template + RegionLock& operator << (const T& value) { + if (logger != nullptr) + logger->write(region_id, value); + + return *this; + } + + private: + SynchronizedRegionLogger* logger; + RegionId region_id; + bool is_held; + }; + + struct Region + { + Region(RegionId rid) : id(rid), is_held(true) {} + + std::vector pending_parts; + RegionId id; + bool is_held; + }; + + RegionId init_next_region() + { + static RegionId next_id = 0; + + std::lock_guard lock(mutex); + + const auto id = next_id++; + regions.emplace_back(id); + + return id; + } + + template + void write(RegionId id, const T& value) { + std::lock_guard lock(mutex); + + if (regions.empty()) + return; + + if (id == regions.front().id) { + // We can just directly print to the output because + // we are at the front of the region queue. + out << value; + } else { + // We have to schedule the print until previous regions are + // processed + auto* region = find_region_nolock(id); + if (region == nullptr) + return; + + std::stringstream ss; + ss << value; + region->pending_parts.emplace_back(std::move(ss).str()); + } + } + + std::ostream& out; + + std::deque regions; + + std::mutex mutex; + + Region* find_region_nolock(RegionId id) { + // Linear search because the amount of concurrent regions should be small. + auto it = std::find_if( + regions.begin(), + regions.end(), + [id](const Region& r) { return r.id == id; }); + + if (it == regions.end()) + return nullptr; + else + return &*it; + } + + void release_region(RegionId id) { + std::lock_guard lock(mutex); + + auto* region = find_region_nolock(id); + if (region == nullptr) + return; + + region->is_held = false; + + process_backlog_nolock(); + } + + void process_backlog_nolock() + { + while(!regions.empty()) { + auto& region = regions.front(); + + for(auto& part : region.pending_parts) { + out << part; + } + + // If the region is still held then we don't + // want to start printing stuff from the next region. + if (region.is_held) + break; + + regions.pop_front(); + } + } + +public: + + SynchronizedRegionLogger(std::ostream& s) : + out(s) + { + } + + [[nodiscard]] RegionLock new_region() { + const auto id = init_next_region(); + return RegionLock(*this, id); + } + +}; + +extern SynchronizedRegionLogger sync_region_cout; + /// xorshift64star Pseudo-Random Number Generator /// This class is based on original code written and dedicated