summaryrefslogtreecommitdiff
path: root/indra/llcommon
diff options
context:
space:
mode:
Diffstat (limited to 'indra/llcommon')
-rw-r--r--indra/llcommon/llpreprocessor.h4
-rw-r--r--indra/llcommon/llstring.cpp104
-rw-r--r--indra/llcommon/llstring.h225
-rw-r--r--indra/llcommon/llthreadsafequeue.h29
-rw-r--r--indra/llcommon/stdtypes.h7
-rw-r--r--indra/llcommon/stringize.h117
-rw-r--r--indra/llcommon/threadpool.cpp88
-rw-r--r--indra/llcommon/threadpool.h71
-rw-r--r--indra/llcommon/threadsafeschedule.h34
-rw-r--r--indra/llcommon/workqueue.cpp2
-rw-r--r--indra/llcommon/workqueue.h184
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) */