diff options
Diffstat (limited to 'indra/llcommon')
-rw-r--r-- | indra/llcommon/llpreprocessor.h | 4 | ||||
-rw-r--r-- | indra/llcommon/llstring.cpp | 104 | ||||
-rw-r--r-- | indra/llcommon/llstring.h | 225 | ||||
-rw-r--r-- | indra/llcommon/llthreadsafequeue.h | 29 | ||||
-rw-r--r-- | indra/llcommon/stdtypes.h | 7 | ||||
-rw-r--r-- | indra/llcommon/stringize.h | 117 | ||||
-rw-r--r-- | indra/llcommon/threadpool.cpp | 88 | ||||
-rw-r--r-- | indra/llcommon/threadpool.h | 71 | ||||
-rw-r--r-- | indra/llcommon/threadsafeschedule.h | 34 | ||||
-rw-r--r-- | indra/llcommon/workqueue.cpp | 2 | ||||
-rw-r--r-- | indra/llcommon/workqueue.h | 184 |
11 files changed, 673 insertions, 192 deletions
diff --git a/indra/llcommon/llpreprocessor.h b/indra/llcommon/llpreprocessor.h index b17a8e761a..dc586b0008 100644 --- a/indra/llcommon/llpreprocessor.h +++ b/indra/llcommon/llpreprocessor.h @@ -171,7 +171,9 @@ #define LL_DLLIMPORT #endif // LL_WINDOWS -#if ! defined(LL_WINDOWS) +#if __clang__ || ! defined(LL_WINDOWS) +// Only on Windows, and only with the Microsoft compiler (vs. clang) is +// wchar_t potentially not a distinct type. #define LL_WCHAR_T_NATIVE 1 #else // LL_WINDOWS // https://docs.microsoft.com/en-us/cpp/preprocessor/predefined-macros diff --git a/indra/llcommon/llstring.cpp b/indra/llcommon/llstring.cpp index f6f9f97809..bdea1e76ea 100644 --- a/indra/llcommon/llstring.cpp +++ b/indra/llcommon/llstring.cpp @@ -212,7 +212,7 @@ S32 utf16chars_to_wchar(const U16* inchars, llwchar* outchar) return inchars - base; } -llutf16string wstring_to_utf16str(const LLWString &utf32str, S32 len) +llutf16string wstring_to_utf16str(const llwchar* utf32str, size_t len) { llutf16string out; @@ -234,27 +234,19 @@ llutf16string wstring_to_utf16str(const LLWString &utf32str, S32 len) return out; } -llutf16string wstring_to_utf16str(const LLWString &utf32str) +llutf16string utf8str_to_utf16str( const char* utf8str, size_t len ) { - const S32 len = (S32)utf32str.length(); - return wstring_to_utf16str(utf32str, len); -} - -llutf16string utf8str_to_utf16str ( const std::string& utf8str ) -{ - LLWString wstr = utf8str_to_wstring ( utf8str ); + LLWString wstr = utf8str_to_wstring ( utf8str, len ); return wstring_to_utf16str ( wstr ); } - -LLWString utf16str_to_wstring(const llutf16string &utf16str, S32 len) +LLWString utf16str_to_wstring(const U16* utf16str, size_t len) { LLWString wout; - if((len <= 0) || utf16str.empty()) return wout; + if (len == 0) return wout; S32 i = 0; - // craziness to make gcc happy (llutf16string.c_str() is tweaked on linux): - const U16* chars16 = &(*(utf16str.begin())); + const U16* chars16 = utf16str; while (i < len) { llwchar cur_char; @@ -264,12 +256,6 @@ LLWString utf16str_to_wstring(const llutf16string &utf16str, S32 len) return wout; } -LLWString utf16str_to_wstring(const llutf16string &utf16str) -{ - const S32 len = (S32)utf16str.length(); - return utf16str_to_wstring(utf16str, len); -} - // Length in llwchar (UTF-32) of the first len units (16 bits) of the given UTF-16 string. S32 utf16str_wstring_length(const llutf16string &utf16str, const S32 utf16_len) { @@ -389,8 +375,7 @@ S32 wstring_utf8_length(const LLWString& wstr) return len; } - -LLWString utf8str_to_wstring(const std::string& utf8str, S32 len) +LLWString utf8str_to_wstring(const char* utf8str, size_t len) { LLWString wout; @@ -478,13 +463,7 @@ LLWString utf8str_to_wstring(const std::string& utf8str, S32 len) return wout; } -LLWString utf8str_to_wstring(const std::string& utf8str) -{ - const S32 len = (S32)utf8str.length(); - return utf8str_to_wstring(utf8str, len); -} - -std::string wstring_to_utf8str(const LLWString& utf32str, S32 len) +std::string wstring_to_utf8str(const llwchar* utf32str, size_t len) { std::string out; @@ -500,20 +479,9 @@ std::string wstring_to_utf8str(const LLWString& utf32str, S32 len) return out; } -std::string wstring_to_utf8str(const LLWString& utf32str) -{ - const S32 len = (S32)utf32str.length(); - return wstring_to_utf8str(utf32str, len); -} - -std::string utf16str_to_utf8str(const llutf16string& utf16str) +std::string utf16str_to_utf8str(const U16* utf16str, size_t len) { - return wstring_to_utf8str(utf16str_to_wstring(utf16str)); -} - -std::string utf16str_to_utf8str(const llutf16string& utf16str, S32 len) -{ - return wstring_to_utf8str(utf16str_to_wstring(utf16str, len), len); + return wstring_to_utf8str(utf16str_to_wstring(utf16str, len)); } std::string utf8str_trim(const std::string& utf8str) @@ -654,17 +622,16 @@ std::string utf8str_removeCRLF(const std::string& utf8str) } #if LL_WINDOWS -std::string ll_convert_wide_to_string(const wchar_t* in) +unsigned int ll_wstring_default_code_page() { - return ll_convert_wide_to_string(in, CP_UTF8); + return CP_UTF8; } -std::string ll_convert_wide_to_string(const wchar_t* in, unsigned int code_page) +std::string ll_convert_wide_to_string(const wchar_t* in, size_t len_in, unsigned int code_page) { std::string out; if(in) { - int len_in = wcslen(in); int len_out = WideCharToMultiByte( code_page, 0, @@ -696,12 +663,7 @@ std::string ll_convert_wide_to_string(const wchar_t* in, unsigned int code_page) return out; } -std::wstring ll_convert_string_to_wide(const std::string& in) -{ - return ll_convert_string_to_wide(in, CP_UTF8); -} - -std::wstring ll_convert_string_to_wide(const std::string& in, unsigned int code_page) +std::wstring ll_convert_string_to_wide(const char* in, size_t len, unsigned int code_page) { // From review: // We can preallocate a wide char buffer that is the same length (in wchar_t elements) as the utf8 input, @@ -713,10 +675,10 @@ std::wstring ll_convert_string_to_wide(const std::string& in, unsigned int code_ // reserve an output buffer that will be destroyed on exit, with a place // to put NULL terminator - std::vector<wchar_t> w_out(in.length() + 1); + std::vector<wchar_t> w_out(len + 1); memset(&w_out[0], 0, w_out.size()); - int real_output_str_len = MultiByteToWideChar(code_page, 0, in.c_str(), in.length(), + int real_output_str_len = MultiByteToWideChar(code_page, 0, in, len, &w_out[0], w_out.size() - 1); //looks like MultiByteToWideChar didn't add null terminator to converted string, see EXT-4858. @@ -726,30 +688,32 @@ std::wstring ll_convert_string_to_wide(const std::string& in, unsigned int code_ return {&w_out[0]}; } -LLWString ll_convert_wide_to_wstring(const std::wstring& in) +LLWString ll_convert_wide_to_wstring(const wchar_t* in, size_t len) { - // This function, like its converse, is a placeholder, encapsulating a - // guilty little hack: the only "official" way nat has found to convert - // between std::wstring (16 bits on Windows) and LLWString (UTF-32) is - // by using iconv, which we've avoided so far. It kinda sorta works to - // just copy individual characters... - // The point is that if/when we DO introduce some more official way to - // perform such conversions, we should only have to call it here. - return { in.begin(), in.end() }; + // Whether or not std::wstring and llutf16string are distinct types, they + // both hold UTF-16LE characters. (See header file comments.) Pretend this + // wchar_t* sequence is really a U16* sequence and use the conversion we + // define above. + return utf16str_to_wstring(reinterpret_cast<const U16*>(in), len); } -std::wstring ll_convert_wstring_to_wide(const LLWString& in) +std::wstring ll_convert_wstring_to_wide(const llwchar* in, size_t len) { - // See comments in ll_convert_wide_to_wstring() - return { in.begin(), in.end() }; + // first, convert to llutf16string, for which we have a real implementation + auto utf16str{ wstring_to_utf16str(in, len) }; + // then, because each U16 char must be UTF-16LE encoded, pretend the U16* + // string pointer is a wchar_t* and instantiate a std::wstring of the same + // length. + return { reinterpret_cast<const wchar_t*>(utf16str.c_str()), utf16str.length() }; } std::string ll_convert_string_to_utf8_string(const std::string& in) { - auto w_mesg = ll_convert_string_to_wide(in, CP_ACP); - std::string out_utf8(ll_convert_wide_to_string(w_mesg.c_str(), CP_UTF8)); - - return out_utf8; + // If you pass code_page, you must also pass length, otherwise the code + // page parameter will be mistaken for length. + auto w_mesg = ll_convert_string_to_wide(in, in.length(), CP_ACP); + // CP_UTF8 is default -- see ll_wstring_default_code_page() above. + return ll_convert_wide_to_string(w_mesg); } namespace diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h index 4263122f36..d94f549480 100644 --- a/indra/llcommon/llstring.h +++ b/indra/llcommon/llstring.h @@ -27,9 +27,11 @@ #ifndef LL_LLSTRING_H #define LL_LLSTRING_H +#include <boost/call_traits.hpp> #include <boost/optional/optional.hpp> #include <string> #include <cstdio> +#include <cwchar> // std::wcslen() //#include <locale> #include <iomanip> #include <algorithm> @@ -527,14 +529,71 @@ struct ll_convert_impl<T, T> T operator()(const T& in) const { return in; } }; +// simple construction from char* +template<typename T> +struct ll_convert_impl<T, const typename T::value_type*> +{ + T operator()(const typename T::value_type* in) const { return { in }; } +}; + // specialize ll_convert_impl<TO, FROM> to return EXPR #define ll_convert_alias(TO, FROM, EXPR) \ template<> \ struct ll_convert_impl<TO, FROM> \ { \ - TO operator()(const FROM& in) const { return EXPR; } \ + /* param_type optimally passes both char* and string */ \ + TO operator()(typename boost::call_traits<FROM>::param_type in) const { return EXPR; } \ +} + +// If all we're doing is copying characters, pass this to ll_convert_alias as +// EXPR. Since it expands into the 'return EXPR' slot in the ll_convert_impl +// specialization above, it implies TO{ in.begin(), in.end() }. +#define LL_CONVERT_COPY_CHARS { in.begin(), in.end() } + +// Generic name for strlen() / wcslen() - the default implementation should +// (!) work with U16 and llwchar, but we don't intend to engage it. +template <typename CHARTYPE> +size_t ll_convert_length(const CHARTYPE* zstr) +{ + const CHARTYPE* zp; + // classic C string scan + for (zp = zstr; *zp; ++zp) + ; + return (zp - zstr); } +// specialize where we have a library function; may use intrinsic operations +template <> +inline size_t ll_convert_length<wchar_t>(const wchar_t* zstr) { return std::wcslen(zstr); } +template <> +inline size_t ll_convert_length<char> (const char* zstr) { return std::strlen(zstr); } + +// ll_convert_forms() is short for a bunch of boilerplate. It defines +// longname(const char*, len), longname(const char*), longname(const string&) +// and longname(const string&, len) so calls written pre-ll_convert() will +// work. Most of these overloads will be unified once we turn on C++17 and can +// use std::string_view. +// It also uses aliasmacro to ensure that both ll_convert<OUTSTR>(const char*) +// and ll_convert<OUTSTR>(const string&) will work. +#define ll_convert_forms(aliasmacro, OUTSTR, INSTR, longname) \ +LL_COMMON_API OUTSTR longname(const INSTR::value_type* in, size_t len); \ +inline auto longname(const INSTR& in, size_t len) \ +{ \ + return longname(in.c_str(), len); \ +} \ +inline auto longname(const INSTR::value_type* in) \ +{ \ + return longname(in, ll_convert_length(in)); \ +} \ +inline auto longname(const INSTR& in) \ +{ \ + return longname(in.c_str(), in.length()); \ +} \ +/* string param */ \ +aliasmacro(OUTSTR, INSTR, longname(in)); \ +/* char* param */ \ +aliasmacro(OUTSTR, const INSTR::value_type*, longname(in)) + // Make the incoming string a utf8 string. Replaces any unknown glyph // with the UNKNOWN_CHARACTER. Once any unknown glyph is found, the rest // of the data may not be recovered. @@ -571,63 +630,47 @@ LL_COMMON_API std::string rawstr_to_utf8(const std::string& raw); // LL_WCHAR_T_NATIVE. typedef std::basic_string<U16> llutf16string; -#if ! defined(LL_WCHAR_T_NATIVE) -// wchar_t is identical to U16, and std::wstring is identical to llutf16string. -// Defining an ll_convert alias involving llutf16string would collide with the -// comparable preferred alias involving std::wstring. (In this scenario, if -// you pass llutf16string, it will engage the std::wstring specialization.) -#define ll_convert_u16_alias(TO, FROM, EXPR) // nothing -#else // defined(LL_WCHAR_T_NATIVE) -// wchar_t is a distinct native type, so llutf16string is also a distinct -// type, and there IS a point to converting separately to/from llutf16string. -// (But why? Windows APIs are still defined in terms of wchar_t, and -// in this scenario llutf16string won't work for them!) -#define ll_convert_u16_alias(TO, FROM, EXPR) ll_convert_alias(TO, FROM, EXPR) +// Considering wchar_t, llwchar and U16, there are three relevant cases: +#if LLWCHAR_IS_WCHAR_T // every which way but Windows +// llwchar is identical to wchar_t, LLWString is identical to std::wstring. +// U16 is distinct, llutf16string is distinct (though pretty useless). +// Given conversions to/from LLWString and to/from llutf16string, conversions +// involving std::wstring would collide. +#define ll_convert_wstr_alias(TO, FROM, EXPR) // nothing +// but we can define conversions involving llutf16string without collisions +#define ll_convert_u16_alias(TO, FROM, EXPR) ll_convert_alias(TO, FROM, EXPR) + +#elif defined(LL_WCHAR_T_NATIVE) // Windows, either clang or MS /Zc:wchar_t +// llwchar (32-bit), wchar_t (16-bit) and U16 are all different types. +// Conversions to/from LLWString, to/from std::wstring and to/from llutf16string +// can all be defined. +#define ll_convert_wstr_alias(TO, FROM, EXPR) ll_convert_alias(TO, FROM, EXPR) +#define ll_convert_u16_alias(TO, FROM, EXPR) ll_convert_alias(TO, FROM, EXPR) + +#else // ! LL_WCHAR_T_NATIVE: Windows with MS /Zc:wchar_t- +// wchar_t is identical to U16, std::wstring is identical to llutf16string. +// Given conversions to/from LLWString and to/from std::wstring, conversions +// involving llutf16string would collide. +#define ll_convert_u16_alias(TO, FROM, EXPR) // nothing +// but we can define conversions involving std::wstring without collisions +#define ll_convert_wstr_alias(TO, FROM, EXPR) ll_convert_alias(TO, FROM, EXPR) +#endif + +ll_convert_forms(ll_convert_u16_alias, LLWString, llutf16string, utf16str_to_wstring); +ll_convert_forms(ll_convert_u16_alias, llutf16string, LLWString, wstring_to_utf16str); +ll_convert_forms(ll_convert_u16_alias, llutf16string, std::string, utf8str_to_utf16str); +ll_convert_forms(ll_convert_alias, LLWString, std::string, utf8str_to_wstring); -#if LL_WINDOWS -// LL_WCHAR_T_NATIVE is defined on non-Windows systems because, in fact, -// wchar_t is native. Everywhere but Windows, we use it for llwchar (see -// stdtypes.h). That makes LLWString identical to std::wstring, so these -// aliases for std::wstring would collide with those for LLWString. Only -// define on Windows, where converting between std::wstring and llutf16string -// means copying chars. -ll_convert_alias(llutf16string, std::wstring, llutf16string(in.begin(), in.end())); -ll_convert_alias(std::wstring, llutf16string, std::wstring(in.begin(), in.end())); -#endif // LL_WINDOWS -#endif // defined(LL_WCHAR_T_NATIVE) - -LL_COMMON_API LLWString utf16str_to_wstring(const llutf16string &utf16str, S32 len); -LL_COMMON_API LLWString utf16str_to_wstring(const llutf16string &utf16str); -ll_convert_u16_alias(LLWString, llutf16string, utf16str_to_wstring(in)); - -LL_COMMON_API llutf16string wstring_to_utf16str(const LLWString &utf32str, S32 len); -LL_COMMON_API llutf16string wstring_to_utf16str(const LLWString &utf32str); -ll_convert_u16_alias(llutf16string, LLWString, wstring_to_utf16str(in)); - -LL_COMMON_API llutf16string utf8str_to_utf16str ( const std::string& utf8str, S32 len); -LL_COMMON_API llutf16string utf8str_to_utf16str ( const std::string& utf8str ); -ll_convert_u16_alias(llutf16string, std::string, utf8str_to_utf16str(in)); - -LL_COMMON_API LLWString utf8str_to_wstring(const std::string &utf8str, S32 len); -LL_COMMON_API LLWString utf8str_to_wstring(const std::string &utf8str); // Same function, better name. JC inline LLWString utf8string_to_wstring(const std::string& utf8_string) { return utf8str_to_wstring(utf8_string); } -// best name of all -ll_convert_alias(LLWString, std::string, utf8string_to_wstring(in)); -// LL_COMMON_API S32 wchar_to_utf8chars(llwchar inchar, char* outchars); -LL_COMMON_API std::string wstring_to_utf8str(const LLWString &utf32str, S32 len); -LL_COMMON_API std::string wstring_to_utf8str(const LLWString &utf32str); -ll_convert_alias(std::string, LLWString, wstring_to_utf8str(in)); -LL_COMMON_API std::string utf16str_to_utf8str(const llutf16string &utf16str, S32 len); -LL_COMMON_API std::string utf16str_to_utf8str(const llutf16string &utf16str); -ll_convert_u16_alias(std::string, llutf16string, utf16str_to_utf8str(in)); +ll_convert_forms(ll_convert_alias, std::string, LLWString, wstring_to_utf8str); +ll_convert_forms(ll_convert_u16_alias, std::string, llutf16string, utf16str_to_utf8str); -#if LL_WINDOWS +// an older alias for utf16str_to_utf8str(llutf16string) inline std::string wstring_to_utf8str(const llutf16string &utf16str) { return utf16str_to_utf8str(utf16str);} -#endif // Length of this UTF32 string in bytes when transformed to UTF8 LL_COMMON_API S32 wstring_utf8_length(const LLWString& wstr); @@ -701,42 +744,48 @@ LL_COMMON_API std::string utf8str_removeCRLF(const std::string& utf8str); //@{ /** - * @brief Convert a wide string to std::string + * @brief Convert a wide string to/from std::string + * Convert a Windows wide string to/from our LLWString * * This replaces the unsafe W2A macro from ATL. */ -LL_COMMON_API std::string ll_convert_wide_to_string(const wchar_t* in, unsigned int code_page); -LL_COMMON_API std::string ll_convert_wide_to_string(const wchar_t* in); // default CP_UTF8 -inline std::string ll_convert_wide_to_string(const std::wstring& in, unsigned int code_page) -{ - return ll_convert_wide_to_string(in.c_str(), code_page); -} -inline std::string ll_convert_wide_to_string(const std::wstring& in) -{ - return ll_convert_wide_to_string(in.c_str()); -} -ll_convert_alias(std::string, std::wstring, ll_convert_wide_to_string(in)); - -/** - * Converts a string to wide string. - */ -LL_COMMON_API std::wstring ll_convert_string_to_wide(const std::string& in, - unsigned int code_page); -LL_COMMON_API std::wstring ll_convert_string_to_wide(const std::string& in); - // default CP_UTF8 -ll_convert_alias(std::wstring, std::string, ll_convert_string_to_wide(in)); - -/** - * Convert a Windows wide string to our LLWString - */ -LL_COMMON_API LLWString ll_convert_wide_to_wstring(const std::wstring& in); -ll_convert_alias(LLWString, std::wstring, ll_convert_wide_to_wstring(in)); - -/** - * Convert LLWString to Windows wide string - */ -LL_COMMON_API std::wstring ll_convert_wstring_to_wide(const LLWString& in); -ll_convert_alias(std::wstring, LLWString, ll_convert_wstring_to_wide(in)); +// Avoid requiring this header to #include the Windows header file declaring +// our actual default code_page by delegating this function to our .cpp file. +LL_COMMON_API unsigned int ll_wstring_default_code_page(); + +// This is like ll_convert_forms(), with the added complexity of a code page +// parameter that may or may not be passed. +#define ll_convert_cp_forms(aliasmacro, OUTSTR, INSTR, longname) \ +/* declare the only nontrivial implementation (in .cpp file) */ \ +LL_COMMON_API OUTSTR longname( \ + const INSTR::value_type* in, \ + size_t len, \ + unsigned int code_page=ll_wstring_default_code_page()); \ +/* if passed only a char pointer, scan for nul terminator */ \ +inline auto longname(const INSTR::value_type* in) \ +{ \ + return longname(in, ll_convert_length(in)); \ +} \ +/* if passed string and length, extract its char pointer */ \ +inline auto longname( \ + const INSTR& in, \ + size_t len, \ + unsigned int code_page=ll_wstring_default_code_page()) \ +{ \ + return longname(in.c_str(), len, code_page); \ +} \ +/* if passed only a string object, no scan, pass known length */ \ +inline auto longname(const INSTR& in) \ +{ \ + return longname(in.c_str(), in.length()); \ +} \ +aliasmacro(OUTSTR, INSTR, longname(in)); \ +aliasmacro(OUTSTR, const INSTR::value_type*, longname(in)) + +ll_convert_cp_forms(ll_convert_wstr_alias, std::string, std::wstring, ll_convert_wide_to_string); +ll_convert_cp_forms(ll_convert_wstr_alias, std::wstring, std::string, ll_convert_string_to_wide); + ll_convert_forms(ll_convert_wstr_alias, LLWString, std::wstring, ll_convert_wide_to_wstring); + ll_convert_forms(ll_convert_wstr_alias, std::wstring, LLWString, ll_convert_wstring_to_wide); /** * Converts incoming string into utf8 string @@ -1937,4 +1986,14 @@ void LLStringUtilBase<T>::truncate(string_type& string, size_type count) string.resize(count < cur_size ? count : cur_size); } +// The good thing about *declaration* macros, vs. usage macros, is that now +// we're done with them: we don't need them to bleed into the consuming source +// file. +#undef ll_convert_alias +#undef ll_convert_u16_alias +#undef ll_convert_wstr_alias +#undef LL_CONVERT_COPY_CHARS +#undef ll_convert_forms +#undef ll_convert_cp_forms + #endif // LL_STRING_H diff --git a/indra/llcommon/llthreadsafequeue.h b/indra/llcommon/llthreadsafequeue.h index 06e8d8f609..a588175074 100644 --- a/indra/llcommon/llthreadsafequeue.h +++ b/indra/llcommon/llthreadsafequeue.h @@ -270,6 +270,7 @@ template <typename ElementT, typename QueueT> template <typename CALLABLE> bool LLThreadSafeQueue<ElementT, QueueT>::tryLock(CALLABLE&& callable) { + LL_PROFILE_ZONE_SCOPED; lock_t lock1(mLock, std::defer_lock); if (!lock1.try_lock()) return false; @@ -286,6 +287,7 @@ bool LLThreadSafeQueue<ElementT, QueueT>::tryLockUntil( const std::chrono::time_point<Clock, Duration>& until, CALLABLE&& callable) { + LL_PROFILE_ZONE_SCOPED; lock_t lock1(mLock, std::defer_lock); if (!lock1.try_lock_until(until)) return false; @@ -299,6 +301,7 @@ template <typename ElementT, typename QueueT> template <typename T> bool LLThreadSafeQueue<ElementT, QueueT>::push_(lock_t& lock, T&& element) { + LL_PROFILE_ZONE_SCOPED; if (mStorage.size() >= mCapacity) return false; @@ -314,6 +317,7 @@ template <typename ElementT, typename QueueT> template<typename T> void LLThreadSafeQueue<ElementT, QueueT>::push(T&& element) { + LL_PROFILE_ZONE_SCOPED; lock_t lock1(mLock); while (true) { @@ -334,10 +338,23 @@ void LLThreadSafeQueue<ElementT, QueueT>::push(T&& element) } +template <typename ElementT, typename QueueT> +template<typename T> +void LLThreadSafeQueue<ElementT, QueueT>::push(T&& element) +{ + LL_PROFILE_ZONE_SCOPED; + if (! pushIfOpen(std::forward<T>(element))) + { + LLTHROW(LLThreadSafeQueueInterrupt()); + } +} + + template<typename ElementT, typename QueueT> template<typename T> bool LLThreadSafeQueue<ElementT, QueueT>::tryPush(T&& element) { + LL_PROFILE_ZONE_SCOPED; return tryLock( [this, element=std::move(element)](lock_t& lock) { @@ -354,6 +371,7 @@ bool LLThreadSafeQueue<ElementT, QueueT>::tryPushFor( const std::chrono::duration<Rep, Period>& timeout, T&& element) { + LL_PROFILE_ZONE_SCOPED; // Convert duration to time_point: passing the same timeout duration to // each of multiple calls is wrong. return tryPushUntil(std::chrono::steady_clock::now() + timeout, @@ -367,6 +385,7 @@ bool LLThreadSafeQueue<ElementT, QueueT>::tryPushUntil( const std::chrono::time_point<Clock, Duration>& until, T&& element) { + LL_PROFILE_ZONE_SCOPED; return tryLockUntil( until, [this, until, element=std::move(element)](lock_t& lock) @@ -399,6 +418,7 @@ template <typename ElementT, typename QueueT> typename LLThreadSafeQueue<ElementT, QueueT>::pop_result LLThreadSafeQueue<ElementT, QueueT>::pop_(lock_t& lock, ElementT& element) { + LL_PROFILE_ZONE_SCOPED; // If mStorage is empty, there's no head element. if (mStorage.empty()) return mClosed? DONE : EMPTY; @@ -420,6 +440,7 @@ LLThreadSafeQueue<ElementT, QueueT>::pop_(lock_t& lock, ElementT& element) template<typename ElementT, typename QueueT> ElementT LLThreadSafeQueue<ElementT, QueueT>::pop(void) { + LL_PROFILE_ZONE_SCOPED; lock_t lock1(mLock); ElementT value; while (true) @@ -448,6 +469,7 @@ ElementT LLThreadSafeQueue<ElementT, QueueT>::pop(void) template<typename ElementT, typename QueueT> bool LLThreadSafeQueue<ElementT, QueueT>::tryPop(ElementT & element) { + LL_PROFILE_ZONE_SCOPED; return tryLock( [this, &element](lock_t& lock) { @@ -465,6 +487,7 @@ bool LLThreadSafeQueue<ElementT, QueueT>::tryPopFor( const std::chrono::duration<Rep, Period>& timeout, ElementT& element) { + LL_PROFILE_ZONE_SCOPED; // Convert duration to time_point: passing the same timeout duration to // each of multiple calls is wrong. return tryPopUntil(std::chrono::steady_clock::now() + timeout, element); @@ -477,6 +500,7 @@ bool LLThreadSafeQueue<ElementT, QueueT>::tryPopUntil( const std::chrono::time_point<Clock, Duration>& until, ElementT& element) { + LL_PROFILE_ZONE_SCOPED; return tryLockUntil( until, [this, until, &element](lock_t& lock) @@ -496,6 +520,7 @@ LLThreadSafeQueue<ElementT, QueueT>::tryPopUntil_( const std::chrono::time_point<Clock, Duration>& until, ElementT& element) { + LL_PROFILE_ZONE_SCOPED; while (true) { pop_result popped = pop_(lock, element); @@ -522,6 +547,7 @@ LLThreadSafeQueue<ElementT, QueueT>::tryPopUntil_( template<typename ElementT, typename QueueT> size_t LLThreadSafeQueue<ElementT, QueueT>::size(void) { + LL_PROFILE_ZONE_SCOPED; lock_t lock(mLock); return mStorage.size(); } @@ -530,6 +556,7 @@ size_t LLThreadSafeQueue<ElementT, QueueT>::size(void) template<typename ElementT, typename QueueT> void LLThreadSafeQueue<ElementT, QueueT>::close() { + LL_PROFILE_ZONE_SCOPED; lock_t lock(mLock); mClosed = true; lock.unlock(); @@ -543,6 +570,7 @@ void LLThreadSafeQueue<ElementT, QueueT>::close() template<typename ElementT, typename QueueT> bool LLThreadSafeQueue<ElementT, QueueT>::isClosed() { + LL_PROFILE_ZONE_SCOPED; lock_t lock(mLock); return mClosed; } @@ -551,6 +579,7 @@ bool LLThreadSafeQueue<ElementT, QueueT>::isClosed() template<typename ElementT, typename QueueT> bool LLThreadSafeQueue<ElementT, QueueT>::done() { + LL_PROFILE_ZONE_SCOPED; lock_t lock(mLock); return mClosed && mStorage.empty(); } diff --git a/indra/llcommon/stdtypes.h b/indra/llcommon/stdtypes.h index 887f6ab733..b07805b628 100644 --- a/indra/llcommon/stdtypes.h +++ b/indra/llcommon/stdtypes.h @@ -42,10 +42,17 @@ typedef unsigned int U32; // Windows wchar_t is 16-bit, whichever way /Zc:wchar_t is set. In effect, // Windows wchar_t is always a typedef, either for unsigned short or __wchar_t. // (__wchar_t, available either way, is Microsoft's native 2-byte wchar_t type.) +// The version of clang available with VS 2019 also defines wchar_t as __wchar_t +// which is also 16 bits. // In any case, llwchar should be a UTF-32 type. typedef U32 llwchar; #else typedef wchar_t llwchar; +// What we'd actually want is a simple module-scope 'if constexpr' to test +// std::is_same<wchar_t, llwchar>::value and use that to define, or not +// define, string conversion specializations. Since we don't have that, we'll +// have to rely on #if instead. Sorry, Dr. Stroustrup. +#define LLWCHAR_IS_WCHAR_T 1 #endif #if LL_WINDOWS diff --git a/indra/llcommon/stringize.h b/indra/llcommon/stringize.h index 38dd198ad3..12df693910 100644 --- a/indra/llcommon/stringize.h +++ b/indra/llcommon/stringize.h @@ -31,58 +31,109 @@ #include <sstream> #include <llstring.h> +#include <boost/call_traits.hpp> /** - * gstringize(item) encapsulates an idiom we use constantly, using - * operator<<(std::ostringstream&, TYPE) followed by std::ostringstream::str() - * or their wstring equivalents - * to render a string expressing some item. + * stream_to(std::ostream&, items, ...) streams each item in the parameter list + * to the passed std::ostream using the insertion operator <<. This can be + * used, for instance, to make a simple print() function, e.g.: + * + * @code + * template <typename... Items> + * void print(Items&&... items) + * { + * stream_to(std::cout, std::forward<Items>(items)...); + * } + * @endcode */ -template <typename CHARTYPE, typename T> -std::basic_string<CHARTYPE> gstringize(const T& item) +// recursion tail +template <typename CHARTYPE> +void stream_to(std::basic_ostream<CHARTYPE>& out) {} +// stream one or more items +template <typename CHARTYPE, typename T, typename... Items> +void stream_to(std::basic_ostream<CHARTYPE>& out, T&& item, Items&&... items) { - std::basic_ostringstream<CHARTYPE> out; - out << item; - return out.str(); + out << std::forward<T>(item); + stream_to(out, std::forward<Items>(items)...); } +// why we use function overloads, not function template specializations: +// http://www.gotw.ca/publications/mill17.htm + /** - *partial specialization of stringize for handling wstring - *TODO: we should have similar specializations for wchar_t[] but not until it is needed. + * gstringize(item, ...) encapsulates an idiom we use constantly, using + * operator<<(std::ostringstream&, TYPE) followed by std::ostringstream::str() + * or their wstring equivalents to render a string expressing one or more items. */ -inline std::string stringize(const std::wstring& item) +// two or more args - the case of a single argument is handled separately +template <typename CHARTYPE, typename T0, typename T1, typename... Items> +auto gstringize(T0&& item0, T1&& item1, Items&&... items) { - return wstring_to_utf8str(item); + std::basic_ostringstream<CHARTYPE> out; + stream_to(out, std::forward<T0>(item0), std::forward<T1>(item1), + std::forward<Items>(items)...); + return out.str(); } -/** - * Specialization of gstringize for std::string return types - */ -template <typename T> -std::string stringize(const T& item) +// generic single argument: stream to out, as above +template <typename CHARTYPE, typename T> +struct gstringize_impl { - return gstringize<char>(item); + auto operator()(typename boost::call_traits<T>::param_type arg) + { + std::basic_ostringstream<CHARTYPE> out; + out << arg; + return out.str(); + } +}; + +// partially specialize for a single STRING argument - +// note that ll_convert<T>(T) already handles the trivial case +template <typename OUTCHAR, typename INCHAR> +struct gstringize_impl<OUTCHAR, std::basic_string<INCHAR>> +{ + auto operator()(const std::basic_string<INCHAR>& arg) + { + return ll_convert<std::basic_string<OUTCHAR>>(arg); + } +}; + +// partially specialize for a single CHARTYPE* argument - +// since it's not a basic_string and we do want to optimize this common case +template <typename OUTCHAR, typename INCHAR> +struct gstringize_impl<OUTCHAR, INCHAR*> +{ + auto operator()(const INCHAR* arg) + { + return ll_convert<std::basic_string<OUTCHAR>>(arg); + } +}; + +// gstringize(single argument) +template <typename CHARTYPE, typename T> +auto gstringize(T&& item) +{ + // use decay<T> so we don't require separate specializations + // for T, const T, T&, const T& ... + return gstringize_impl<CHARTYPE, std::decay_t<T>>()(std::forward<T>(item)); } /** - * Specialization for generating wstring from string. - * Both a convenience function and saves a miniscule amount of overhead. + * Specialization of gstringize for std::string return types */ -inline std::wstring wstringize(const std::string& item) +template <typename... Items> +auto stringize(Items&&... items) { - // utf8str_to_wstring() returns LLWString, which isn't necessarily the - // same as std::wstring - LLWString s(utf8str_to_wstring(item)); - return std::wstring(s.begin(), s.end()); + return gstringize<char>(std::forward<Items>(items)...); } /** * Specialization of gstringize for std::wstring return types */ -template <typename T> -std::wstring wstringize(const T& item) +template <typename... Items> +auto wstringize(Items&&... items) { - return gstringize<wchar_t>(item); + return gstringize<wchar_t>(std::forward<Items>(items)...); } /** @@ -146,11 +197,9 @@ void destringize_f(std::basic_string<CHARTYPE> const & str, Functor const & f) * std::istringstream in(str); * in >> item1 >> item2 >> item3 ... ; * @endcode - * @NOTE - once we get generic lambdas, we shouldn't need DEWSTRINGIZE() any - * more since DESTRINGIZE() should do the right thing with a std::wstring. But - * until then, the lambda we pass must accept the right std::basic_istream. */ -#define DESTRINGIZE(STR, EXPRESSION) (destringize_f((STR), [&](std::istream& in){in >> EXPRESSION;})) -#define DEWSTRINGIZE(STR, EXPRESSION) (destringize_f((STR), [&](std::wistream& in){in >> EXPRESSION;})) +#define DESTRINGIZE(STR, EXPRESSION) (destringize_f((STR), [&](auto& in){in >> EXPRESSION;})) +// legacy name, just use DESTRINGIZE() going forward +#define DEWSTRINGIZE(STR, EXPRESSION) DESTRINGIZE(STR, EXPRESSION) #endif /* ! defined(LL_STRINGIZE_H) */ diff --git a/indra/llcommon/threadpool.cpp b/indra/llcommon/threadpool.cpp new file mode 100644 index 0000000000..ba914035e2 --- /dev/null +++ b/indra/llcommon/threadpool.cpp @@ -0,0 +1,88 @@ +/** + * @file threadpool.cpp + * @author Nat Goodspeed + * @date 2021-10-21 + * @brief Implementation for threadpool. + * + * $LicenseInfo:firstyear=2021&license=viewerlgpl$ + * Copyright (c) 2021, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "threadpool.h" +// STL headers +// std headers +// external library headers +// other Linden headers +#include "llerror.h" +#include "llevents.h" +#include "stringize.h" + +LL::ThreadPool::ThreadPool(const std::string& name, size_t threads, size_t capacity): + mQueue(name, capacity), + mName("ThreadPool:" + name), + mThreadCount(threads) +{} + +void LL::ThreadPool::start() +{ + for (size_t i = 0; i < mThreadCount; ++i) + { + std::string tname{ stringize(mName, ':', (i+1), '/', mThreadCount) }; + mThreads.emplace_back(tname, [this, tname]() + { + LL_PROFILER_SET_THREAD_NAME(tname.c_str()); + run(tname); + }); + } + // Listen on "LLApp", and when the app is shutting down, close the queue + // and join the workers. + LLEventPumps::instance().obtain("LLApp").listen( + mName, + [this](const LLSD& stat) + { + std::string status(stat["status"]); + if (status != "running") + { + // viewer is starting shutdown -- proclaim the end is nigh! + LL_DEBUGS("ThreadPool") << mName << " saw " << status << LL_ENDL; + close(); + } + return false; + }); +} + +LL::ThreadPool::~ThreadPool() +{ + close(); +} + +void LL::ThreadPool::close() +{ + if (! mQueue.isClosed()) + { + LL_DEBUGS("ThreadPool") << mName << " closing queue and joining threads" << LL_ENDL; + mQueue.close(); + for (auto& pair: mThreads) + { + LL_DEBUGS("ThreadPool") << mName << " waiting on thread " << pair.first << LL_ENDL; + pair.second.join(); + } + LL_DEBUGS("ThreadPool") << mName << " shutdown complete" << LL_ENDL; + } +} + +void LL::ThreadPool::run(const std::string& name) +{ + LL_DEBUGS("ThreadPool") << name << " starting" << LL_ENDL; + run(); + LL_DEBUGS("ThreadPool") << name << " stopping" << LL_ENDL; +} + +void LL::ThreadPool::run() +{ + mQueue.runUntilClose(); +} diff --git a/indra/llcommon/threadpool.h b/indra/llcommon/threadpool.h new file mode 100644 index 0000000000..b79c9b9090 --- /dev/null +++ b/indra/llcommon/threadpool.h @@ -0,0 +1,71 @@ +/** + * @file threadpool.h + * @author Nat Goodspeed + * @date 2021-10-21 + * @brief ThreadPool configures a WorkQueue along with a pool of threads to + * service it. + * + * $LicenseInfo:firstyear=2021&license=viewerlgpl$ + * Copyright (c) 2021, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_THREADPOOL_H) +#define LL_THREADPOOL_H + +#include "workqueue.h" +#include <string> +#include <thread> +#include <utility> // std::pair +#include <vector> + +namespace LL +{ + + class ThreadPool + { + public: + /** + * Pass ThreadPool a string name. This can be used to look up the + * relevant WorkQueue. + */ + ThreadPool(const std::string& name, size_t threads=1, size_t capacity=1024); + virtual ~ThreadPool(); + + /** + * Launch the ThreadPool. Until this call, a constructed ThreadPool + * launches no threads. That permits coders to derive from ThreadPool, + * or store it as a member of some other class, but refrain from + * launching it until all other construction is complete. + */ + void start(); + + /** + * ThreadPool listens for application shutdown messages on the "LLApp" + * LLEventPump. Call close() to shut down this ThreadPool early. + */ + void close(); + + std::string getName() const { return mName; } + size_t getWidth() const { return mThreads.size(); } + /// obtain a non-const reference to the WorkQueue to post work to it + WorkQueue& getQueue() { return mQueue; } + + /** + * Override run() if you need special processing. The default run() + * implementation simply calls WorkQueue::runUntilClose(). + */ + virtual void run(); + + private: + void run(const std::string& name); + + WorkQueue mQueue; + std::string mName; + size_t mThreadCount; + std::vector<std::pair<std::string, std::thread>> mThreads; + }; + +} // namespace LL + +#endif /* ! defined(LL_THREADPOOL_H) */ diff --git a/indra/llcommon/threadsafeschedule.h b/indra/llcommon/threadsafeschedule.h index c8ad23532b..601681d550 100644 --- a/indra/llcommon/threadsafeschedule.h +++ b/indra/llcommon/threadsafeschedule.h @@ -98,12 +98,14 @@ namespace LL // we could minimize redundancy by breaking out a common base class... void push(const DataTuple& tuple) { + LL_PROFILE_ZONE_SCOPED; push(tuple_cons(Clock::now(), tuple)); } /// individually pass each component of the TimeTuple void push(const TimePoint& time, Args&&... args) { + LL_PROFILE_ZONE_SCOPED; push(TimeTuple(time, std::forward<Args>(args)...)); } @@ -114,6 +116,7 @@ namespace LL // and call that overload. void push(Args&&... args) { + LL_PROFILE_ZONE_SCOPED; push(Clock::now(), std::forward<Args>(args)...); } @@ -124,18 +127,21 @@ namespace LL /// DataTuple with implicit now bool tryPush(const DataTuple& tuple) { + LL_PROFILE_ZONE_SCOPED; return tryPush(tuple_cons(Clock::now(), tuple)); } /// individually pass components bool tryPush(const TimePoint& time, Args&&... args) { + LL_PROFILE_ZONE_SCOPED; return tryPush(TimeTuple(time, std::forward<Args>(args)...)); } /// individually pass components with implicit now bool tryPush(Args&&... args) { + LL_PROFILE_ZONE_SCOPED; return tryPush(Clock::now(), std::forward<Args>(args)...); } @@ -148,6 +154,7 @@ namespace LL bool tryPushFor(const std::chrono::duration<Rep, Period>& timeout, const DataTuple& tuple) { + LL_PROFILE_ZONE_SCOPED; return tryPushFor(timeout, tuple_cons(Clock::now(), tuple)); } @@ -156,6 +163,7 @@ namespace LL bool tryPushFor(const std::chrono::duration<Rep, Period>& timeout, const TimePoint& time, Args&&... args) { + LL_PROFILE_ZONE_SCOPED; return tryPushFor(TimeTuple(time, std::forward<Args>(args)...)); } @@ -164,6 +172,7 @@ namespace LL bool tryPushFor(const std::chrono::duration<Rep, Period>& timeout, Args&&... args) { + LL_PROFILE_ZONE_SCOPED; return tryPushFor(Clock::now(), std::forward<Args>(args)...); } @@ -176,6 +185,7 @@ namespace LL bool tryPushUntil(const std::chrono::time_point<Clock, Duration>& until, const DataTuple& tuple) { + LL_PROFILE_ZONE_SCOPED; return tryPushUntil(until, tuple_cons(Clock::now(), tuple)); } @@ -184,6 +194,7 @@ namespace LL bool tryPushUntil(const std::chrono::time_point<Clock, Duration>& until, const TimePoint& time, Args&&... args) { + LL_PROFILE_ZONE_SCOPED; return tryPushUntil(until, TimeTuple(time, std::forward<Args>(args)...)); } @@ -192,6 +203,7 @@ namespace LL bool tryPushUntil(const std::chrono::time_point<Clock, Duration>& until, Args&&... args) { + LL_PROFILE_ZONE_SCOPED; return tryPushUntil(until, Clock::now(), std::forward<Args>(args)...); } @@ -209,12 +221,14 @@ namespace LL // haven't yet jumped through those hoops. DataTuple pop() { + LL_PROFILE_ZONE_SCOPED; return tuple_cdr(popWithTime()); } /// pop TimeTuple by value TimeTuple popWithTime() { + LL_PROFILE_ZONE_SCOPED; lock_t lock(super::mLock); // We can't just sit around waiting forever, given that there may // be items in the queue that are not yet ready but will *become* @@ -254,6 +268,7 @@ namespace LL /// tryPop(DataTuple&) bool tryPop(DataTuple& tuple) { + LL_PROFILE_ZONE_SCOPED; TimeTuple tt; if (! super::tryPop(tt)) return false; @@ -264,6 +279,7 @@ namespace LL /// for when Args has exactly one type bool tryPop(typename std::tuple_element<1, TimeTuple>::type& value) { + LL_PROFILE_ZONE_SCOPED; TimeTuple tt; if (! super::tryPop(tt)) return false; @@ -275,6 +291,7 @@ namespace LL template <typename Rep, typename Period, typename Tuple> bool tryPopFor(const std::chrono::duration<Rep, Period>& timeout, Tuple& tuple) { + LL_PROFILE_ZONE_SCOPED; // It's important to use OUR tryPopUntil() implementation, rather // than delegating immediately to our base class. return tryPopUntil(Clock::now() + timeout, tuple); @@ -285,6 +302,7 @@ namespace LL bool tryPopUntil(const std::chrono::time_point<Clock, Duration>& until, TimeTuple& tuple) { + LL_PROFILE_ZONE_SCOPED; // super::tryPopUntil() wakes up when an item becomes available or // we hit 'until', whichever comes first. Thing is, the current // head of the queue could become ready sooner than either of @@ -304,20 +322,25 @@ namespace LL pop_result tryPopUntil_(lock_t& lock, const TimePoint& until, TimeTuple& tuple) { + LL_PROFILE_ZONE_SCOPED; TimePoint adjusted = until; if (! super::mStorage.empty()) { + LL_PROFILE_ZONE_NAMED("tpu - adjust"); // use whichever is earlier: the head item's timestamp, or // the caller's limit adjusted = min(std::get<0>(super::mStorage.front()), adjusted); } // now delegate to base-class tryPopUntil_() pop_result popped; - while ((popped = pop_result(super::tryPopUntil_(lock, adjusted, tuple))) == WAITING) { - // If super::tryPopUntil_() returns WAITING, it means there's - // a head item, but it's not yet time. But it's worth looping - // back to recheck. + LL_PROFILE_ZONE_NAMED("tpu - super"); + while ((popped = pop_result(super::tryPopUntil_(lock, adjusted, tuple))) == WAITING) + { + // If super::tryPopUntil_() returns WAITING, it means there's + // a head item, but it's not yet time. But it's worth looping + // back to recheck. + } } return popped; } @@ -327,6 +350,7 @@ namespace LL bool tryPopUntil(const std::chrono::time_point<Clock, Duration>& until, DataTuple& tuple) { + LL_PROFILE_ZONE_SCOPED; TimeTuple tt; if (! tryPopUntil(until, tt)) return false; @@ -339,6 +363,7 @@ namespace LL bool tryPopUntil(const std::chrono::time_point<Clock, Duration>& until, typename std::tuple_element<1, TimeTuple>::type& value) { + LL_PROFILE_ZONE_SCOPED; TimeTuple tt; if (! tryPopUntil(until, tt)) return false; @@ -362,6 +387,7 @@ namespace LL // considering whether to deliver the current head element bool canPop(const TimeTuple& head) const override { + LL_PROFILE_ZONE_SCOPED; // an item with a future timestamp isn't yet ready to pop // (should we add some slop for overhead?) return std::get<0>(head) <= Clock::now(); diff --git a/indra/llcommon/workqueue.cpp b/indra/llcommon/workqueue.cpp index b32357e832..1e89d87cff 100644 --- a/indra/llcommon/workqueue.cpp +++ b/indra/llcommon/workqueue.cpp @@ -44,6 +44,7 @@ void LL::WorkQueue::runUntilClose() { for (;;) { + LL_PROFILE_ZONE_SCOPED; callWork(mQueue.pop()); } } @@ -74,6 +75,7 @@ bool LL::WorkQueue::runOne() bool LL::WorkQueue::runUntil(const TimePoint& until) { + LL_PROFILE_ZONE_SCOPED; // Should we subtract some slop to allow for typical Work execution time? // How much slop? Work work; diff --git a/indra/llcommon/workqueue.h b/indra/llcommon/workqueue.h index 5ec790da79..8e4b38c2f3 100644 --- a/indra/llcommon/workqueue.h +++ b/indra/llcommon/workqueue.h @@ -222,6 +222,7 @@ namespace LL template <typename Rep, typename Period> bool runFor(const std::chrono::duration<Rep, Period>& timeslice) { + LL_PROFILE_ZONE_SCOPED; return runUntil(TimePoint::clock::now() + timeslice); } @@ -330,6 +331,189 @@ namespace LL getWeak(), TimePoint::clock::now(), interval, std::move(callable))); } + /// general case: arbitrary C++ return type + template <typename CALLABLE, typename FOLLOWUP, typename RETURNTYPE> + struct WorkQueue::MakeReplyLambda + { + auto operator()(CALLABLE&& callable, FOLLOWUP&& callback) + { + // Call the callable in any case -- but to minimize + // copying the result, immediately bind it into the reply + // lambda. The reply lambda also binds the original + // callback, so that when we, the originating WorkQueue, + // finally receive and process the reply lambda, we'll + // call the bound callback with the bound result -- on the + // same thread that originally called postTo(). + return + [result = std::forward<CALLABLE>(callable)(), + callback = std::move(callback)] + () + { callback(std::move(result)); }; + } + }; + + /// specialize for CALLABLE returning void + template <typename CALLABLE, typename FOLLOWUP> + struct WorkQueue::MakeReplyLambda<CALLABLE, FOLLOWUP, void> + { + auto operator()(CALLABLE&& callable, FOLLOWUP&& callback) + { + // Call the callable, which produces no result. + std::forward<CALLABLE>(callable)(); + // Our completion callback is simply the caller's callback. + return std::move(callback); + } + }; + + template <typename CALLABLE, typename FOLLOWUP> + auto WorkQueue::makeReplyLambda(CALLABLE&& callable, FOLLOWUP&& callback) + { + return MakeReplyLambda<CALLABLE, FOLLOWUP, + decltype(std::forward<CALLABLE>(callable)())>() + (std::move(callable), std::move(callback)); + } + + template <typename CALLABLE, typename FOLLOWUP> + bool WorkQueue::postTo(weak_t target, + const TimePoint& time, CALLABLE&& callable, FOLLOWUP&& callback) + { + LL_PROFILE_ZONE_SCOPED; + // We're being asked to post to the WorkQueue at target. + // target is a weak_ptr: have to lock it to check it. + auto tptr = target.lock(); + if (! tptr) + // can't post() if the target WorkQueue has been destroyed + return false; + + // Here we believe target WorkQueue still exists. Post to it a + // lambda that packages our callable, our callback and a weak_ptr + // to this originating WorkQueue. + tptr->post( + time, + [reply = super::getWeak(), + callable = std::move(callable), + callback = std::move(callback)] + () + { + // Use postMaybe() below in case this originating WorkQueue + // has been closed or destroyed. Remember, the outer lambda is + // now running on a thread servicing the target WorkQueue, and + // real time has elapsed since postTo()'s tptr->post() call. + try + { + // Make a reply lambda to repost to THIS WorkQueue. + // Delegate to makeReplyLambda() so we can partially + // specialize on void return. + postMaybe(reply, makeReplyLambda(std::move(callable), std::move(callback))); + } + catch (...) + { + // Either variant of makeReplyLambda() is responsible for + // calling the caller's callable. If that throws, return + // the exception to the originating thread. + postMaybe( + reply, + // Bind the current exception to transport back to the + // originating WorkQueue. Once there, rethrow it. + [exc = std::current_exception()](){ std::rethrow_exception(exc); }); + } + }); + + // looks like we were able to post() + return true; + } + + template <typename CALLABLE> + bool WorkQueue::postMaybe(weak_t target, const TimePoint& time, CALLABLE&& callable) + { + LL_PROFILE_ZONE_SCOPED; + // target is a weak_ptr: have to lock it to check it + auto tptr = target.lock(); + if (tptr) + { + try + { + tptr->post(time, std::forward<CALLABLE>(callable)); + // we were able to post() + return true; + } + catch (const Closed&) + { + // target WorkQueue still exists, but is Closed + } + } + // either target no longer exists, or its WorkQueue is Closed + return false; + } + + /// general case: arbitrary C++ return type + template <typename CALLABLE, typename RETURNTYPE> + struct WorkQueue::WaitForResult + { + auto operator()(WorkQueue* self, const TimePoint& time, CALLABLE&& callable) + { + LLCoros::Promise<RETURNTYPE> promise; + self->post( + time, + // We dare to bind a reference to Promise because it's + // specifically designed for cross-thread communication. + [&promise, callable = std::move(callable)]() + { + try + { + // call the caller's callable and trigger promise with result + promise.set_value(callable()); + } + catch (...) + { + promise.set_exception(std::current_exception()); + } + }); + auto future{ LLCoros::getFuture(promise) }; + // now, on the calling thread, wait for that result + LLCoros::TempStatus st("waiting for WorkQueue::waitForResult()"); + return future.get(); + } + }; + + /// specialize for CALLABLE returning void + template <typename CALLABLE> + struct WorkQueue::WaitForResult<CALLABLE, void> + { + void operator()(WorkQueue* self, const TimePoint& time, CALLABLE&& callable) + { + LLCoros::Promise<void> promise; + self->post( + time, + // &promise is designed for cross-thread access + [&promise, callable = std::move(callable)]() + { + try + { + callable(); + promise.set_value(); + } + catch (...) + { + promise.set_exception(std::current_exception()); + } + }); + auto future{ LLCoros::getFuture(promise) }; + // block until set_value() + LLCoros::TempStatus st("waiting for void WorkQueue::waitForResult()"); + future.get(); + } + }; + + template <typename CALLABLE> + auto WorkQueue::waitForResult(const TimePoint& time, CALLABLE&& callable) + { + checkCoroutine("waitForResult()"); + // derive callable's return type so we can specialize for void + return WaitForResult<CALLABLE, decltype(std::forward<CALLABLE>(callable)())>() + (this, time, std::forward<CALLABLE>(callable)); + } + } // namespace LL #endif /* ! defined(LL_WORKQUEUE_H) */ |