From 19eb73ff543c81c6886725a20dea0060cb0c0c26 Mon Sep 17 00:00:00 2001 From: Daniel Wilhelm Date: Fri, 2 Oct 2015 14:56:27 +0200 Subject: 7.3 --- zen/async_task.h | 26 +++-- zen/dir_watcher.cpp | 41 +++---- zen/file_access.cpp | 31 +++--- zen/format_unit.cpp | 18 +-- zen/guid.h | 2 +- zen/long_path_prefix.h | 1 - zen/process_priority.cpp | 1 - zen/recycler.cpp | 2 +- zen/scope_guard.h | 3 +- zen/stl_tools.h | 13 ++- zen/string_base.h | 27 +++-- zen/string_tools.h | 6 +- zen/string_traits.h | 14 ++- zen/symlink_target.h | 5 +- zen/thread.h | 285 +++++++++++++++++++++++++++++++++++++---------- zen/zstring.cpp | 26 ++--- 16 files changed, 339 insertions(+), 162 deletions(-) (limited to 'zen') diff --git a/zen/async_task.h b/zen/async_task.h index 5c6f7f6e..d8f489a3 100644 --- a/zen/async_task.h +++ b/zen/async_task.h @@ -11,7 +11,6 @@ #include #include "thread.h" #include "scope_guard.h" -//#include "type_tools.h" namespace zen { @@ -19,25 +18,25 @@ namespace zen class AsyncTasks { public: - AsyncTasks() : inRecursion(false) {} + AsyncTasks() {} template - void add(Fun doAsync, Fun2 evalOnGui) - //equivalent to "evalOnGui(doAsync())" - // -> doAsync: the usual thread-safety requirements apply! + void add(Fun runAsync, Fun2 evalOnGui) + //equivalent to "evalOnGui(runAsync())" + // -> runAsync: the usual thread-safety requirements apply! // -> evalOnGui: no thread-safety concerns, but must only reference variables with greater-equal lifetime than the AsyncTask instance! { tasks.push_back(zen::runAsync([=]() -> std::function { - auto result = doAsync(); + auto result = runAsync(); return [=]{ evalOnGui(result); }; })); } template - void add2(Fun doAsync, Fun2 evalOnGui) //for evalOnGui taking no parameters + void add2(Fun runAsync, Fun2 evalOnGui) //for evalOnGui taking no parameters { - tasks.push_back(zen::runAsync([doAsync, evalOnGui]() -> std::function { doAsync(); return [evalOnGui]{ evalOnGui(); }; })); + tasks.push_back(zen::runAsync([runAsync, evalOnGui]() -> std::function { runAsync(); return [evalOnGui]{ evalOnGui(); }; })); } void evalResults() //call from gui thread repreatedly @@ -47,9 +46,9 @@ public: inRecursion = true; ZEN_ON_SCOPE_EXIT(inRecursion = false); - tasks.remove_if([](boost::unique_future>& ft) -> bool + tasks.remove_if([](std::future>& ft) -> bool { - if (ft.is_ready()) + if (isReady(ft)) { (ft.get())(); return true; @@ -62,8 +61,11 @@ public: bool empty() const { return tasks.empty(); } private: - bool inRecursion; - std::list>> tasks; + AsyncTasks (const AsyncTasks&) = delete; + AsyncTasks& operator=(const AsyncTasks&) = delete; + + bool inRecursion = false; + std::list>> tasks; }; } diff --git a/zen/dir_watcher.cpp b/zen/dir_watcher.cpp index e93e2b06..4abf3c0a 100644 --- a/zen/dir_watcher.cpp +++ b/zen/dir_watcher.cpp @@ -7,7 +7,7 @@ #include "dir_watcher.h" #include #include -#include "thread.h" //includes +#include "thread.h" #include "scope_guard.h" #ifdef ZEN_WIN @@ -16,8 +16,11 @@ #include "long_path_prefix.h" #elif defined ZEN_LINUX + #include #include - #include + #include //fcntl + #include //close + #include //NAME_MAX #include "file_traverser.h" #elif defined ZEN_MAC @@ -37,7 +40,7 @@ public: //context of worker thread void addChanges(const char* buffer, DWORD bytesWritten, const Zstring& dirpath) //throw () { - boost::lock_guard dummy(lockAccess); + std::lock_guard dummy(lockAccess); if (bytesWritten == 0) //according to docu this may happen in case of internal buffer overflow: report some "dummy" change changedFiles.emplace_back(DirWatcher::ACTION_CREATE, L"Overflow."); @@ -89,7 +92,7 @@ public: ////context of main thread //void addChange(const Zstring& dirpath) //throw () //{ - // boost::lock_guard dummy(lockAccess); + // std::lock_guard dummy(lockAccess); // changedFiles.insert(dirpath); //} @@ -97,7 +100,7 @@ public: //context of main thread void fetchChanges(std::vector& output) //throw FileError { - boost::lock_guard dummy(lockAccess); + std::lock_guard dummy(lockAccess); //first check whether errors occurred in thread if (errorInfo) @@ -115,7 +118,7 @@ public: //context of worker thread void reportError(const std::wstring& msg, const std::wstring& description, DWORD errorCode) //throw() { - boost::lock_guard dummy(lockAccess); + std::lock_guard dummy(lockAccess); ErrorInfo newInfo = { copyStringTo(msg), copyStringTo(description), errorCode }; errorInfo = make_unique(newInfo); @@ -124,7 +127,7 @@ public: private: typedef Zbase BasicWString; //thread safe string class for UI texts - boost::mutex lockAccess; + std::mutex lockAccess; std::vector changedFiles; struct ErrorInfo @@ -174,13 +177,13 @@ public: ::CloseHandle(hDir); } - void operator()() //thread entry + void operator()() const //thread entry { std::vector buffer(64 * 1024); //needs to be aligned on a DWORD boundary; maximum buffer size restricted by some networks protocols (according to docu) for (;;) { - boost::this_thread::interruption_point(); + interruptionPoint(); //throw ThreadInterruption //actual work OVERLAPPED overlapped = {}; @@ -244,7 +247,7 @@ public: ::SleepEx(50, // __in DWORD dwMilliseconds, true); // __in BOOL bAlertable - boost::this_thread::interruption_point(); + interruptionPoint(); //throw ThreadInterruption } guardAio.dismiss(); @@ -271,7 +274,7 @@ class HandleVolumeRemoval public: HandleVolumeRemoval(HANDLE hDir, const Zstring& displayPath, - boost::thread& worker) : + InterruptibleThread& worker) : notificationHandle(registerFolderRemovalNotification(hDir, //throw FileError displayPath, [this] { this->onRequestRemoval (); }, //noexcept! @@ -307,7 +310,7 @@ private: void onRemovalFinished() { operationComplete = true; } //noexcept! DeviceNotificationHandle* notificationHandle; - boost::thread& worker_; + InterruptibleThread& worker_; bool removalRequested; bool operationComplete; }; @@ -316,7 +319,7 @@ private: struct DirWatcher::Pimpl { - boost::thread worker; + InterruptibleThread worker; std::shared_ptr shared; std::unique_ptr volRemoval; }; @@ -330,7 +333,7 @@ DirWatcher::DirWatcher(const Zstring& dirPath) : //throw FileError ReadChangesAsync reader(dirPath, pimpl_->shared); //throw FileError pimpl_->volRemoval = zen::make_unique(reader.getDirHandle(), dirPath, pimpl_->worker); //throw FileError - pimpl_->worker = boost::thread(std::move(reader)); + pimpl_->worker = InterruptibleThread(std::move(reader)); } @@ -339,10 +342,8 @@ DirWatcher::~DirWatcher() if (pimpl_->worker.joinable()) //= thread::detach() precondition! -> may already be joined by HandleVolumeRemoval::onRequestRemoval() { pimpl_->worker.interrupt(); - //if (pimpl_->worker.joinable()) pimpl_->worker.join(); -> we don't have time to wait... will take ~50ms anyway - pimpl_->worker.detach(); //we have to be explicit since C++11: [thread.thread.destr] ~thread() calls std::terminate() if joinable()!!! + pimpl_->worker.detach(); //we don't have time to wait... will take ~50ms anyway: } - //caveat: exitting the app may simply kill this thread! } @@ -355,13 +356,13 @@ std::vector DirWatcher::getChanges(const std::functionvolRemoval->requestReceived()) { - const boost::chrono::steady_clock::time_point endTime = boost::chrono::steady_clock::now() + boost::chrono::seconds(15); + const std::chrono::steady_clock::time_point endTime = std::chrono::steady_clock::now() + std::chrono::seconds(15); //HandleVolumeRemoval::finished() not guaranteed! note: Windows gives unresponsive applications ca. 10 seconds until unmounting the usb stick in worst case - while (!pimpl_->volRemoval->finished() && boost::chrono::steady_clock::now() < endTime) + while (!pimpl_->volRemoval->finished() && std::chrono::steady_clock::now() < endTime) { processGuiMessages(); //DBT_DEVICEREMOVECOMPLETE message is sent here! - boost::this_thread::sleep_for(boost::chrono::milliseconds(50)); //throw boost::thread_interrupted + std::this_thread::sleep_for(std::chrono::milliseconds(50)); } output.emplace_back(ACTION_DELETE, baseDirPath); //report removal as change to main directory diff --git a/zen/file_access.cpp b/zen/file_access.cpp index 09a1eb07..84d3b264 100644 --- a/zen/file_access.cpp +++ b/zen/file_access.cpp @@ -434,26 +434,29 @@ void renameFile_sub(const Zstring& pathSource, const Zstring& pathTarget) //thro if (lastError == ERROR_NOT_SAME_DEVICE) throw ErrorDifferentVolume(errorMsg, errorDescr); - else if (lastError == ERROR_ALREADY_EXISTS || //-> used on Win7 x64 + if (lastError == ERROR_ALREADY_EXISTS || //-> used on Win7 x64 lastError == ERROR_FILE_EXISTS) //-> used by XP??? throw ErrorTargetExisting(errorMsg, errorDescr); - else - throw FileError(errorMsg, errorDescr); + throw FileError(errorMsg, errorDescr); } #elif defined ZEN_LINUX || defined ZEN_MAC - if (::rename(pathSource.c_str(), pathTarget.c_str()) != 0) + //rename() will never fail with EEXIST, but always overwrite! + //=> OS X: no solution + //=> Linux: renameat2() with RENAME_NOREPLACE -> still new, probably buggy + const bool alreadyExists = somethingExists(pathTarget); //we have to let go of atomicity! + + if (alreadyExists || ::rename(pathSource.c_str(), pathTarget.c_str()) != 0) { - const int lastError = errno; //copy before directly or indirectly making other system calls! + const int lastError = alreadyExists ? EEXIST : errno; //copy before directly or indirectly making other system calls! const std::wstring errorMsg = replaceCpy(replaceCpy(_("Cannot move file %x to %y."), L"%x", L"\n" + fmtPath(pathSource)), L"%y", L"\n" + fmtPath(pathTarget)); const std::wstring errorDescr = formatSystemError(L"rename", lastError); if (lastError == EXDEV) throw ErrorDifferentVolume(errorMsg, errorDescr); - else if (lastError == EEXIST) - throw ErrorTargetExisting(errorMsg, errorDescr); - else - throw FileError(errorMsg, errorDescr); + if (lastError == EEXIST) + throw ErrorTargetExisting(errorMsg, errorDescr); + throw FileError(errorMsg, errorDescr); } #endif } @@ -2039,12 +2042,6 @@ DWORD CALLBACK copyCallbackInternal(LARGE_INTEGER totalFileSize, return PROGRESS_CONTINUE; } -#if defined _MSC_VER && _MSC_VER > 1800 - #error get rid! -#endif -const bool supportNonEncryptedDestination = winXpOrLater(); //encrypted destination is not supported with Windows 2000 -//caveat: function scope static initialization is not thread-safe in VS 2010! -> still not sufficient if multiple threads access during static init!!! - InSyncAttributes copyFileWindowsDefault(const Zstring& sourceFile, //throw FileError, ErrorTargetExisting, ErrorFileLocked, ErrorFallbackToCopyAsBackupStream const Zstring& targetFile, @@ -2062,8 +2059,8 @@ InSyncAttributes copyFileWindowsDefault(const Zstring& sourceFile, //throw FileE DWORD copyFlags = COPY_FILE_FAIL_IF_EXISTS; - if (supportNonEncryptedDestination) - copyFlags |= COPY_FILE_ALLOW_DECRYPTED_DESTINATION; //allow copying from encrypted to non-encrypted location + //encrypted destination is not supported with Windows 2000! -> whatever + copyFlags |= COPY_FILE_ALLOW_DECRYPTED_DESTINATION; //allow copying from encrypted to non-encrypted location //if (vistaOrLater()) //see http://blogs.technet.com/b/askperf/archive/2007/05/08/slow-large-file-copy-issues.aspx // copyFlags |= COPY_FILE_NO_BUFFERING; //no perf difference at worst, improvement for large files (20% in test NTFS -> NTFS) diff --git a/zen/format_unit.cpp b/zen/format_unit.cpp index 5f529f9c..9624458c 100644 --- a/zen/format_unit.cpp +++ b/zen/format_unit.cpp @@ -190,7 +190,10 @@ public: private: static const IntegerFormat& getInst() { - static IntegerFormat inst; //not threadsafe in MSVC until C++11, but not required right now +#if defined _MSC_VER && _MSC_VER < 1900 +#error function scope static initialization is not yet thread-safe! +#endif + static IntegerFormat inst; return inst; } @@ -276,14 +279,6 @@ std::wstring zen::ffs_Impl::includeNumberSeparator(const std::wstring& number) } -#ifdef ZEN_WIN -namespace -{ -const bool useNewLocalTimeCalculation = zen::vistaOrLater(); -} -#endif - - std::wstring zen::utcToLocalTimeString(std::int64_t utcTime) { auto errorMsg = [&] { return _("Error") + L" (time_t: " + numberTo(utcTime) + L")"; }; @@ -293,6 +288,11 @@ std::wstring zen::utcToLocalTimeString(std::int64_t utcTime) SYSTEMTIME systemTimeLocal = {}; +#if defined _MSC_VER && _MSC_VER < 1900 +#error function scope static initialization is not yet thread-safe! +#endif + static const bool useNewLocalTimeCalculation = zen::vistaOrLater(); + //http://msdn.microsoft.com/en-us/library/ms724277(VS.85).aspx if (useNewLocalTimeCalculation) //DST conversion like in Windows 7: NTFS stays fixed, but FAT jumps by one hour { diff --git a/zen/guid.h b/zen/guid.h index 2ebfa132..d4b59eb4 100644 --- a/zen/guid.h +++ b/zen/guid.h @@ -10,7 +10,7 @@ #include #include -#ifdef __GNUC__ //boost should start cleaning this mess up +#ifdef __GNUC__ //boost should clean this mess up #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wshadow" #pragma GCC diagnostic ignored "-Wuninitialized" diff --git a/zen/long_path_prefix.h b/zen/long_path_prefix.h index f7ceb5e7..3db2722b 100644 --- a/zen/long_path_prefix.h +++ b/zen/long_path_prefix.h @@ -122,7 +122,6 @@ Zstring zen::ntPathToWin32Path(const Zstring& path) //noexcept const DWORD charsWritten = ::GetEnvironmentVariable(L"SystemRoot", //_In_opt_ LPCTSTR lpName, &buf[0], //_Out_opt_ LPTSTR lpBuffer, bufSize); //_In_ DWORD nSize - if (0 < charsWritten && charsWritten < bufSize) return replaceCpy(path, L"\\SystemRoot\\", appendSeparator(Zstring(&buf[0], charsWritten)), false); } diff --git a/zen/process_priority.cpp b/zen/process_priority.cpp index c5932900..577e33a6 100644 --- a/zen/process_priority.cpp +++ b/zen/process_priority.cpp @@ -5,7 +5,6 @@ // ************************************************************************** #include "process_priority.h" -//#include "sys_error.h" #include "i18n.h" #ifdef ZEN_WIN diff --git a/zen/recycler.cpp b/zen/recycler.cpp index 6cd34a17..75083d57 100644 --- a/zen/recycler.cpp +++ b/zen/recycler.cpp @@ -187,7 +187,7 @@ bool zen::recycleBinExists(const Zstring& dirpath, const std::function& &recInfo); //__inout LPSHQUERYRBINFO pSHQueryRBInfo }); - while (ft.wait_for(boost::chrono::milliseconds(50)) != boost::future_status::ready) + while (ft.wait_for(std::chrono::milliseconds(50)) != std::future_status::ready) if (onUpdateGui) onUpdateGui(); //may throw! diff --git a/zen/scope_guard.h b/zen/scope_guard.h index 8477c7ee..5e917853 100644 --- a/zen/scope_guard.h +++ b/zen/scope_guard.h @@ -8,8 +8,7 @@ #define ZEN_SCOPEGUARD_8971632487321434 #include -//#include //std::decay -//#include +#include //std::decay //best of Zen, Loki and C++11 diff --git a/zen/stl_tools.h b/zen/stl_tools.h index bd76e264..685f5118 100644 --- a/zen/stl_tools.h +++ b/zen/stl_tools.h @@ -9,7 +9,7 @@ #include #include -#include +#include "type_tools.h" //enhancements for @@ -22,6 +22,9 @@ void vector_remove_if(V& vec, Predicate p); template void vector_append(V& vec, const W& vec2); +template +void removeDuplicates(V& v); + template void set_append(V& s, const W& s2); @@ -72,6 +75,14 @@ void vector_remove_if(V& vec, Predicate p) } +template inline +void removeDuplicates(V& v) +{ + std::sort(v.begin(), v.end()); + v.erase(std::unique(v.begin(), v.end()), v.end()); +} + + template inline void vector_append(V& vec, const W& vec2) { diff --git a/zen/string_base.h b/zen/string_base.h index f4ca5f2e..1bf8ed68 100644 --- a/zen/string_base.h +++ b/zen/string_base.h @@ -195,11 +195,10 @@ private: struct Descriptor { Descriptor(size_t len, size_t cap) : - refCount(1), length (static_cast(len)), capacity(static_cast(cap)) { static_assert(ATOMIC_INT_LOCK_FREE == 2, ""); } //2: "the types are always lock-free" - std::atomic refCount; + std::atomic refCount { 1 }; //std:atomic is uninitialized by default! std::uint32_t length; std::uint32_t capacity; //allocated size without null-termination }; @@ -222,11 +221,13 @@ public: Zbase(const Char* source); //implicit conversion from a C-string Zbase(const Char* source, size_t length); Zbase(const Zbase& source); - Zbase(Zbase&& tmp); //make noexcept in C++11 + Zbase(Zbase&& tmp) noexcept; explicit Zbase(Char source); //dangerous if implicit: Char buffer[]; return buffer[0]; ups... forgot &, but not a compiler error! - //allow explicit construction from different string type, prevent ambiguity via SFINAE - template explicit Zbase(const S& other, typename S::value_type = 0); - ~Zbase(); //make noexcept in C++11 + +//allow explicit construction from different string type, prevent ambiguity via SFINAE +//template explicit Zbase(const S& other, typename S::value_type = 0); + + ~Zbase(); //operator const Char* () const; //NO implicit conversion to a C-string!! Many problems... one of them: if we forget to provide operator overloads, it'll just work with a Char*... @@ -263,11 +264,11 @@ public: Zbase& assign(const Char* source, size_t len); Zbase& append(const Char* source, size_t len); void resize(size_t newSize, Char fillChar = 0); - void swap(Zbase& other); //make noexcept in C++11 + void swap(Zbase& other); void push_back(Char val) { operator+=(val); } //STL access Zbase& operator=(const Zbase& source); - Zbase& operator=(Zbase&& tmp); //make noexcept in C++11 + Zbase& operator=(Zbase&& tmp) noexcept; Zbase& operator=(const Char* source); Zbase& operator=(Char source); Zbase& operator+=(const Zbase& other); @@ -377,14 +378,14 @@ Zbase::Zbase(const Zbase& source) template class SP, class AP> inline -Zbase::Zbase(Zbase&& tmp) +Zbase::Zbase(Zbase&& tmp) noexcept { rawStr = tmp.rawStr; tmp.rawStr = nullptr; //usually nullptr would violate the class invarants, but it is good enough for the destructor! //caveat: do not increment ref-count of an unshared string! We'd lose optimization opportunity of reusing its memory! } - +/* template class SP, class AP> template inline Zbase::Zbase(const S& other, typename S::value_type) @@ -394,11 +395,13 @@ Zbase::Zbase(const S& other, typename S::value_type) std::copy(other.c_str(), other.c_str() + sourceLen, rawStr); rawStr[sourceLen] = 0; } - +*/ template class SP, class AP> inline Zbase::~Zbase() { + static_assert(noexcept(this->~Zbase()), ""); //has exception spec of compiler-generated destructor by default + this->destroy(rawStr); //rawStr may be nullptr; see move constructor! } @@ -650,7 +653,7 @@ Zbase& Zbase::operator=(const Zbase& o template class SP, class AP> inline -Zbase& Zbase::operator=(Zbase&& tmp) +Zbase& Zbase::operator=(Zbase&& tmp) noexcept { swap(tmp); //don't use unifying assignment but save one move-construction in the r-value case instead! return *this; diff --git a/zen/string_tools.h b/zen/string_tools.h index c04adf96..9708464e 100644 --- a/zen/string_tools.h +++ b/zen/string_tools.h @@ -18,7 +18,7 @@ #include "stl_tools.h" #include "string_traits.h" - + //enhance arbitray string class with useful non-member functions: namespace zen { @@ -48,11 +48,11 @@ template void replace ( S& str, const T& oldT template S replaceCpy(const S& str, const T& oldTerm, const U& newTerm, bool replaceAll = true); //high-performance conversion between numbers and strings -template S printNumber(const T& format, const Num& number); //format a single number using std::snprintf() - template S numberTo(const Num& number); template Num stringTo(const S& str); +template S printNumber(const T& format, const Num& number); //format a single number using std::snprintf() + //string to string conversion: converts string-like type into char-compatible target string class template T copyStringTo(const S& str); diff --git a/zen/string_traits.h b/zen/string_traits.h index 5f91bdc4..add53d3a 100644 --- a/zen/string_traits.h +++ b/zen/string_traits.h @@ -7,6 +7,7 @@ #ifndef STRING_TRAITS_HEADER_813274321443234 #define STRING_TRAITS_HEADER_813274321443234 +#include //strlen #include "type_tools.h" //uniform access to string-like types, both classes and character arrays @@ -143,19 +144,22 @@ struct GetCharType : ResultType::CharTy namespace implementation { +//strlen/wcslen are vectorized since VS14 CTP3 +inline size_t cStringLength(const char* str) { return std::strlen(str); } +inline size_t cStringLength(const wchar_t* str) { return std::wcslen(str); } + +//no significant perf difference for "comparison" test case between cStringLength/wcslen: +#if 0 template inline -size_t cStringLength(const C* str) //naive implementation seems somewhat faster than "optimized" strlen/wcslen! +size_t cStringLength(const C* str) { -#if defined _MSC_VER && _MSC_VER > 1800 - static_assert(false, "strlen/wcslen are vectorized in VS14 CTP3 -> test again!"); -#endif - static_assert(IsSameType::value || IsSameType::value, ""); size_t len = 0; while (*str++ != 0) ++len; return len; } +#endif template ::isStringClass>::Type> inline const typename GetCharType::Type* strBegin(const S& str) //SFINAE: T must be a "string" diff --git a/zen/symlink_target.h b/zen/symlink_target.h index c8c8c4be..aa320dfe 100644 --- a/zen/symlink_target.h +++ b/zen/symlink_target.h @@ -25,7 +25,7 @@ namespace zen { #ifdef ZEN_WIN - bool isSymlink(const WIN32_FIND_DATA& data); //*not* a simple FILE_ATTRIBUTE_REPARSE_POINT check! + bool isSymlink(const WIN32_FIND_DATA& data); //checking FILE_ATTRIBUTE_REPARSE_POINT is insufficient! bool isSymlink(DWORD fileAttributes, DWORD reparseTag); #endif @@ -162,8 +162,7 @@ Zstring getResolvedFilePath_impl(const Zstring& linkPath) //throw FileError const SysDllFun getFinalPathNameByHandle(L"kernel32.dll", "GetFinalPathNameByHandleW"); if (!getFinalPathNameByHandle) throw FileError(replaceCpy(_("Cannot determine final path for %x."), L"%x", fmtPath(linkPath)), replaceCpy(_("Cannot find system function %x."), L"%x", L"\"GetFinalPathNameByHandleW\"")); - - + const HANDLE hFile = ::CreateFile(applyLongPathPrefix(linkPath).c_str(), //_In_ LPCTSTR lpFileName, 0, //_In_ DWORD dwDesiredAccess, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, //_In_ DWORD dwShareMode, diff --git a/zen/thread.h b/zen/thread.h index 6d647de8..a3b8760b 100644 --- a/zen/thread.h +++ b/zen/thread.h @@ -4,39 +4,62 @@ // * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * // ************************************************************************** -#ifndef BOOST_THREAD_WRAP_H_78963234 -#define BOOST_THREAD_WRAP_H_78963234 - -//temporary solution until C++11 thread becomes fully available (considering std::thread's non-interruptibility and std::async craziness, this may be NEVER) -#include - -//workaround this pathetic boost thread warning mess -#ifdef __GNUC__ - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wswitch-enum" - #pragma GCC diagnostic ignored "-Wstrict-aliasing" - #pragma GCC diagnostic ignored "-Wredundant-decls" - #pragma GCC diagnostic ignored "-Wshadow" - #ifndef __clang__ //clang defines __GNUC__, but doesn't support this warning - #pragma GCC diagnostic ignored "-Wunused-local-typedefs" - #endif -#endif -#ifdef _MSC_VER - #pragma warning(push) - #pragma warning(disable: 4702 4913) //unreachable code; user defined binary operator ',' exists but no overload could convert all operands, default built-in binary operator ',' used -#endif +#ifndef STD_THREAD_WRAP_H_7896323423432 +#define STD_THREAD_WRAP_H_7896323423432 -#include - -#ifdef __GNUC__ - #pragma GCC diagnostic pop -#endif -#ifdef _MSC_VER - #pragma warning(pop) -#endif +#include +#include +#include +#include namespace zen { +class InterruptionStatus; + + +class InterruptibleThread +{ +public: + InterruptibleThread() {} + InterruptibleThread (InterruptibleThread&& tmp) = default; + InterruptibleThread& operator=(InterruptibleThread&& tmp) = default; + + template + InterruptibleThread(Function f); + + bool joinable () const { return stdThread.joinable(); } + void interrupt(); + void join () { stdThread.join(); } + void detach () { stdThread.detach(); } + + template + bool tryJoinFor(const std::chrono::duration& relTime) + { + if (threadCompleted.wait_for(relTime) == std::future_status::ready) + { + stdThread.join(); //runs thread-local destructors => this better be fast!!! + return true; + } + return false; + } + +private: + std::thread stdThread; + std::shared_ptr intStatus_; + std::future threadCompleted; +}; + +//context of worker thread: +void interruptionPoint(); //throw ThreadInterruption + +template +void interruptibleWait(std::condition_variable& cv, std::unique_lock& lock, Predicate pred); //throw ThreadInterruption + +template +void interruptibleSleep(const std::chrono::duration& relTime); //throw ThreadInterruption + +//------------------------------------------------------------------------------------------ + /* std::async replacement without crappy semantics: 1. guaranteed to run asynchronously @@ -45,16 +68,20 @@ std::async replacement without crappy semantics: Example: Zstring dirpath = ... auto ft = zen::runAsync([=](){ return zen::dirExists(dirpath); }); - if (ft.wait_for(boost::chrono::milliseconds(200)) == boost::future_status::ready && ft.get()) + if (ft.wait_for(std::chrono::milliseconds(200)) == std::future_status::ready && ft.get()) //dir exising */ template -auto runAsync(Function fun) -> boost::unique_future; +auto runAsync(Function fun) -> std::future; //wait for all with a time limit: return true if *all* results are available! template bool wait_for_all_timed(InputIterator first, InputIterator last, const Duration& wait_duration); +template inline +bool isReady(const std::future& f) { return f.wait_for(std::chrono::seconds(0)) == std::future_status::ready; } +//------------------------------------------------------------------------------------------ + //wait until first job is successful or all failed: substitute until std::when_any is available template class GetFirstResult @@ -77,6 +104,7 @@ private: size_t jobsTotal_; }; +//------------------------------------------------------------------------------------------ //value associated with mutex and guaranteed protected access: template @@ -89,7 +117,7 @@ public: template void access(Function fun) { - boost::lock_guard dummy(lockValue); + std::lock_guard dummy(lockValue); fun(value_); } @@ -97,7 +125,7 @@ private: Protected (const Protected&) = delete; Protected& operator=(const Protected&) = delete; - boost::mutex lockValue; + std::mutex lockValue; T value_; }; @@ -110,22 +138,15 @@ private: //###################### implementation ###################### -#ifndef BOOST_HAS_THREADS - #error just some paranoia check... -#endif template inline -auto runAsync(Function fun) -> boost::unique_future +auto runAsync(Function fun) -> std::future { typedef decltype(fun()) ResultType; -#if defined BOOST_THREAD_PROVIDES_SIGNATURE_PACKAGED_TASK //mirror "boost/thread/future.hpp", hopefully they know what they're doing - boost::packaged_task pt(std::move(fun)); -#else - boost::packaged_task pt(std::move(fun)); -#endif + std::packaged_task pt(std::move(fun)); auto fut = pt.get_future(); - boost::thread(std::move(pt)).detach(); //we have to explicitly detach since C++11: [thread.thread.destr] ~thread() calls std::terminate() if joinable()!!! + std::thread(std::move(pt)).detach(); //we have to explicitly detach since C++11: [thread.thread.destr] ~thread() calls std::terminate() if joinable()!!! return fut; } @@ -133,9 +154,9 @@ auto runAsync(Function fun) -> boost::unique_future template inline bool wait_for_all_timed(InputIterator first, InputIterator last, const Duration& duration) { - const boost::chrono::steady_clock::time_point endTime = boost::chrono::steady_clock::now() + duration; + const std::chrono::steady_clock::time_point endTime = std::chrono::steady_clock::now() + duration; for (; first != last; ++first) - if (first->wait_until(endTime) != boost::future_status::ready) + if (first->wait_until(endTime) != std::future_status::ready) return false; //time elapsed return true; } @@ -155,27 +176,26 @@ public: void reportFinished(std::unique_ptr&& result) { { - boost::lock_guard dummy(lockResult); + std::lock_guard dummy(lockResult); ++jobsFinished; if (!result_) result_ = std::move(result); } - conditionJobDone.notify_all(); //instead of notify_one(); workaround bug: https://svn.boost.org/trac/boost/ticket/7796 - //condition handling, see: http://www.boost.org/doc/libs/1_43_0/doc/html/thread/synchronization.html#thread.synchronization.condvar_ref + conditionJobDone.notify_all(); //better notify all, considering bugs like: https://svn.boost.org/trac/boost/ticket/7796 } //context: main thread template bool waitForResult(size_t jobsTotal, const Duration& duration) { - boost::unique_lock dummy(lockResult); - return conditionJobDone.wait_for(dummy, duration, [&] { return this->jobDone(jobsTotal); }); //throw boost::thread_interrupted + std::unique_lock dummy(lockResult); + return conditionJobDone.wait_for(dummy, duration, [&] { return this->jobDone(jobsTotal); }); } std::unique_ptr getResult(size_t jobsTotal) { - boost::unique_lock dummy(lockResult); - conditionJobDone.wait(dummy, [&] { return this->jobDone(jobsTotal); }); //throw boost::thread_interrupted + std::unique_lock dummy(lockResult); + conditionJobDone.wait(dummy, [&] { return this->jobDone(jobsTotal); }); #ifndef NDEBUG assert(!returnedResult); @@ -191,10 +211,10 @@ private: bool returnedResult; #endif - boost::mutex lockResult; + std::mutex lockResult; size_t jobsFinished; // std::unique_ptr result_; //our condition is: "have result" or "jobsFinished == jobsTotal" - boost::condition_variable conditionJobDone; + std::condition_variable conditionJobDone; }; @@ -207,8 +227,7 @@ template template inline void GetFirstResult::addJob(Fun f) //f must return a std::unique_ptr containing a value on success { - auto asyncResult = this->asyncResult_; //capture member variable, not "this"! - boost::thread t([asyncResult, f] { asyncResult->reportFinished(f()); }); + std::thread t([asyncResult = this->asyncResult_, f = std::move(f)] { asyncResult->reportFinished(f()); }); ++jobsTotal_; t.detach(); //we have to be explicit since C++11: [thread.thread.destr] ~thread() calls std::terminate() if joinable()!!! } @@ -221,6 +240,158 @@ bool GetFirstResult::timedWait(const Duration& duration) const { return async template inline std::unique_ptr GetFirstResult::get() const { return asyncResult_->getResult(jobsTotal_); } + +//------------------------------------------------------------------------------------------ + +//thread_local with non-POD seems to cause memory leaks on VS 14 => pointer only is fine: +#ifdef _MSC_VER + #define ZEN_THREAD_LOCAL_SPECIFIER __declspec(thread) +#elif defined __GNUC__ || defined __clang__ + #define ZEN_THREAD_LOCAL_SPECIFIER __thread +#else + #error "game over" +#endif + + +class ThreadInterruption {}; + + +class InterruptionStatus +{ +public: + //context of InterruptibleThread instance: + void interrupt() + { + interrupted = true; + + { + std::lock_guard dummy(lockSleep); //needed! makes sure the following signal is not lost! + //usually we'd make "interrupted" non-atomic, but this is already given due to interruptibleWait() handling + } + conditionSleepInterruption.notify_all(); + + std::lock_guard dummy(lockConditionPtr); + if (activeCondition) + activeCondition->notify_all(); //signal may get lost! + //alternative design locking the cv's mutex here could be dangerous: potential for dead lock! + } + + //context of worker thread: + void checkInterruption() //throw ThreadInterruption + { + if (interrupted) + throw ThreadInterruption(); + } + + //context of worker thread: + template + void interruptibleWait(std::condition_variable& cv, std::unique_lock& lock, Predicate pred) //throw ThreadInterruption + { + setConditionVar(&cv); + ZEN_ON_SCOPE_EXIT(setConditionVar(nullptr)); + + //"interrupted" is not protected by cv's mutex => signal may get lost!!! => add artifical time out to mitigate! CPU: 0.25% vs 0% for longer time out! + while (!cv.wait_for(lock, std::chrono::milliseconds(1), [&] { return this->interrupted || pred(); })) + ; + + checkInterruption(); //throw ThreadInterruption + } + + //context of worker thread: + template + void interruptibleSleep(const std::chrono::duration& relTime) //throw ThreadInterruption + { + std::unique_lock lock(lockSleep); + if (conditionSleepInterruption.wait_for(lock, relTime, [&] { return static_cast(this->interrupted); })) + throw ThreadInterruption(); + } + +private: + void setConditionVar(std::condition_variable* cv) + { + std::lock_guard dummy(lockConditionPtr); + activeCondition = cv; + } + + std::atomic interrupted{ false }; //std:atomic is uninitialized by default! + + std::condition_variable* activeCondition = nullptr; + std::mutex lockConditionPtr; //serialize pointer access (only!) + + std::condition_variable conditionSleepInterruption; + std::mutex lockSleep; +}; + + +namespace impl +{ +inline +InterruptionStatus*& refThreadLocalInterruptionStatus() +{ + static ZEN_THREAD_LOCAL_SPECIFIER InterruptionStatus* threadLocalInterruptionStatus = nullptr; + return threadLocalInterruptionStatus; +} +} + +//context of worker thread: +inline +void interruptionPoint() //throw ThreadInterruption +{ + assert(impl::refThreadLocalInterruptionStatus()); + if (impl::refThreadLocalInterruptionStatus()) + impl::refThreadLocalInterruptionStatus()->checkInterruption(); //throw ThreadInterruption +} + + +//context of worker thread: +template inline +void interruptibleWait(std::condition_variable& cv, std::unique_lock& lock, Predicate pred) //throw ThreadInterruption +{ + assert(impl::refThreadLocalInterruptionStatus()); + if (impl::refThreadLocalInterruptionStatus()) + impl::refThreadLocalInterruptionStatus()->interruptibleWait(cv, lock, pred); + else + cv.wait(lock, pred); +} + +//context of worker thread: +template inline +void interruptibleSleep(const std::chrono::duration& relTime) //throw ThreadInterruption +{ + assert(impl::refThreadLocalInterruptionStatus()); + if (impl::refThreadLocalInterruptionStatus()) + impl::refThreadLocalInterruptionStatus()->interruptibleSleep(relTime); + else + std::this_thread::sleep_for(relTime); +} + + +template inline +InterruptibleThread::InterruptibleThread(Function f) : intStatus_(std::make_shared()) +{ + std::promise pFinished; + threadCompleted = pFinished.get_future(); + + stdThread = std::thread([f = std::move(f), + intStatus = this->intStatus_, + pFinished = std::move(pFinished)]() mutable + { + assert(!impl::refThreadLocalInterruptionStatus()); + impl::refThreadLocalInterruptionStatus() = intStatus.get(); + ZEN_ON_SCOPE_EXIT(impl::refThreadLocalInterruptionStatus() = nullptr); + ZEN_ON_SCOPE_EXIT(pFinished.set_value()); + + try + { + f(); //throw ThreadInterruption + } + catch (ThreadInterruption&) {} + }); +} + + +inline +void InterruptibleThread::interrupt() { intStatus_->interrupt(); } } -#endif //BOOST_THREAD_WRAP_H_78963234 +#endif //STD_THREAD_WRAP_H_7896323423432 diff --git a/zen/zstring.cpp b/zen/zstring.cpp index 8dcd4736..e2a756e6 100644 --- a/zen/zstring.cpp +++ b/zen/zstring.cpp @@ -43,22 +43,12 @@ time per call | function #ifdef ZEN_WIN namespace { -//warning: LOCALE_INVARIANT is NOT available with Windows 2000, so we have to make yet another distinction... -const LCID ZSTRING_INVARIANT_LOCALE = zen::winXpOrLater() ? - LOCALE_INVARIANT : - MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT); //see: http://msdn.microsoft.com/en-us/goglobal/bb688122.aspx - //try to call "CompareStringOrdinal" for low-level string comparison: unfortunately available not before Windows Vista! //by a factor ~3 faster than old string comparison using "LCMapString" typedef int (WINAPI* CompareStringOrdinalFunc)(LPCWSTR lpString1, int cchCount1, LPCWSTR lpString2, int cchCount2, BOOL bIgnoreCase); const SysDllFun compareStringOrdinal = SysDllFun(L"kernel32.dll", "CompareStringOrdinal"); //watch for dependencies in global namespace!!! -//caveat: function scope static initialization is not thread-safe in VS 2010! -#if defined _MSC_VER && _MSC_VER > 1800 - #error not true anymore -#endif - } @@ -92,7 +82,7 @@ int cmpFilePath(const Zchar* lhs, size_t lhsLen, const Zchar* rhs, size_t rhsLen auto copyToUpperCase = [&](const wchar_t* strIn, wchar_t* strOut) { //faster than CharUpperBuff + wmemcpy or CharUpper + wmemcpy and same speed like ::CompareString() - if (::LCMapString(ZSTRING_INVARIANT_LOCALE, //__in LCID Locale, + if (::LCMapString(LOCALE_INVARIANT, //__in LCID Locale, LCMAP_UPPERCASE, //__in DWORD dwMapFlags, strIn, //__in LPCTSTR lpSrcStr, static_cast(minSize), //__in int cchSrc, @@ -138,13 +128,15 @@ Zstring makeUpperCopy(const Zstring& str) Zstring output; output.resize(len); + //LOCALE_INVARIANT is NOT available with Windows 2000 -> ok + //use Windows' upper case conversion: faster than ::CharUpper() - if (::LCMapString(ZSTRING_INVARIANT_LOCALE, //__in LCID Locale, - LCMAP_UPPERCASE, //__in DWORD dwMapFlags, - str.c_str(), //__in LPCTSTR lpSrcStr, - len, //__in int cchSrc, - &*output.begin(), //__out LPTSTR lpDestStr, - len) == 0) //__in int cchDest + if (::LCMapString(LOCALE_INVARIANT, //__in LCID Locale, + LCMAP_UPPERCASE, //__in DWORD dwMapFlags, + str.c_str(), //__in LPCTSTR lpSrcStr, + len, //__in int cchSrc, + &*output.begin(), //__out LPTSTR lpDestStr, + len) == 0) //__in int cchDest throw std::runtime_error("Error comparing strings (LCMapString). " + std::string(__FILE__) + ":" + numberTo(__LINE__)); return output; -- cgit