Merge remote-tracking branch 'origin/ll-vs2017' into fs-vs2017
commit
a135b5b428
|
|
@ -464,9 +464,9 @@
|
|||
<key>archive</key>
|
||||
<map>
|
||||
<key>hash</key>
|
||||
<string>5f210778a1fd7bcbc286ea1eec09f5d9</string>
|
||||
<string>1e7a892d237096c0c252dc26bb0bcf1d</string>
|
||||
<key>url</key>
|
||||
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/44384/392137/boost-1.67-darwin64-531381.tar.bz2</string>
|
||||
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/48114/427588/boost-1.67-darwin64-533856.tar.bz2</string>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>darwin64</string>
|
||||
|
|
@ -500,9 +500,9 @@
|
|||
<key>archive</key>
|
||||
<map>
|
||||
<key>hash</key>
|
||||
<string>59add8c74a7955917c88a25ebc84b568</string>
|
||||
<string>3fdcf88309a340c5e0b6db7be5182c1a</string>
|
||||
<key>url</key>
|
||||
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/44393/392045/boost-1.67-windows-531381.tar.bz2</string>
|
||||
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/48105/427355/boost-1.67-windows-533856.tar.bz2</string>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>windows</string>
|
||||
|
|
@ -512,9 +512,9 @@
|
|||
<key>archive</key>
|
||||
<map>
|
||||
<key>hash</key>
|
||||
<string>9a6d1801c88e18f42a2228c21043800f</string>
|
||||
<string>ba2bd6b732d32b6feff6c329cff67041</string>
|
||||
<key>url</key>
|
||||
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/44389/392031/boost-1.67-windows64-531381.tar.bz2</string>
|
||||
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/48116/427536/boost-1.67-windows64-533856.tar.bz2</string>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>windows64</string>
|
||||
|
|
@ -606,9 +606,9 @@
|
|||
<key>archive</key>
|
||||
<map>
|
||||
<key>hash</key>
|
||||
<string>1a731435cb559831ab418d5f60e346cd</string>
|
||||
<string>ca6bb7108b8cf4bac4c4acac515b9b5f</string>
|
||||
<key>url</key>
|
||||
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/44416/392216/colladadom-2.3.531391-darwin64-531391.tar.bz2</string>
|
||||
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/48135/427607/colladadom-2.3.533872-darwin64-533872.tar.bz2</string>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>darwin64</string>
|
||||
|
|
@ -642,9 +642,9 @@
|
|||
<key>archive</key>
|
||||
<map>
|
||||
<key>hash</key>
|
||||
<string>1076600dc11b5c36dddcd80c2b291530</string>
|
||||
<string>2a0161276047081ac27e2b5c260e61d8</string>
|
||||
<key>url</key>
|
||||
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/44417/392222/colladadom-2.3.531391-windows-531391.tar.bz2</string>
|
||||
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/48138/427646/colladadom-2.3.533872-windows-533872.tar.bz2</string>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>windows</string>
|
||||
|
|
@ -654,16 +654,16 @@
|
|||
<key>archive</key>
|
||||
<map>
|
||||
<key>hash</key>
|
||||
<string>5977db3bd9b5c02b4337096de92fb598</string>
|
||||
<string>b0d938c122f1e3a2ab5fdbb33bae165c</string>
|
||||
<key>url</key>
|
||||
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/44414/392209/colladadom-2.3.531391-windows64-531391.tar.bz2</string>
|
||||
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/48134/427621/colladadom-2.3.533872-windows64-533872.tar.bz2</string>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>windows64</string>
|
||||
</map>
|
||||
</map>
|
||||
<key>version</key>
|
||||
<string>2.3.531391</string>
|
||||
<string>2.3.533872</string>
|
||||
</map>
|
||||
<key>curl</key>
|
||||
<map>
|
||||
|
|
@ -1656,9 +1656,9 @@
|
|||
<key>archive</key>
|
||||
<map>
|
||||
<key>hash</key>
|
||||
<string>eaba8da53b483e72ea8237cde78e26da</string>
|
||||
<string>cfa0065ee16facb7e4372bd9be834124</string>
|
||||
<key>url</key>
|
||||
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/44415/392202/googlemock-1.7.0.531390-darwin64-531390.tar.bz2</string>
|
||||
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/48136/427614/googlemock-1.7.0.533873-darwin64-533873.tar.bz2</string>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>darwin64</string>
|
||||
|
|
@ -1692,9 +1692,9 @@
|
|||
<key>archive</key>
|
||||
<map>
|
||||
<key>hash</key>
|
||||
<string>6013840ad4d6c8a2e68fde2403e0800b</string>
|
||||
<string>f36799013f5f5dcc07526b55902f2188</string>
|
||||
<key>url</key>
|
||||
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/44412/392188/googlemock-1.7.0.531390-windows-531390.tar.bz2</string>
|
||||
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/48144/427663/googlemock-1.7.0.533873-windows-533873.tar.bz2</string>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>windows</string>
|
||||
|
|
@ -1704,16 +1704,16 @@
|
|||
<key>archive</key>
|
||||
<map>
|
||||
<key>hash</key>
|
||||
<string>7e57a4f153e0f2cbcea097652b37602c</string>
|
||||
<string>9722f105a90eddb6142aef6c7b42eb18</string>
|
||||
<key>url</key>
|
||||
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/44409/392173/googlemock-1.7.0.531390-windows64-531390.tar.bz2</string>
|
||||
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/48141/427655/googlemock-1.7.0.533873-windows64-533873.tar.bz2</string>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>windows64</string>
|
||||
</map>
|
||||
</map>
|
||||
<key>version</key>
|
||||
<string>1.7.0.531390</string>
|
||||
<string>1.7.0.533873</string>
|
||||
</map>
|
||||
<key>gstreamer</key>
|
||||
<map>
|
||||
|
|
@ -3558,9 +3558,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
|
|||
<key>archive</key>
|
||||
<map>
|
||||
<key>hash</key>
|
||||
<string>9b341f9a6f36eb595ef2ea88425add21</string>
|
||||
<string>1ba3fb47e32eda07b9308babb278ee69</string>
|
||||
<key>url</key>
|
||||
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/47032/414972/viewer_manager-2.0.533067-darwin64-533067.tar.bz2</string>
|
||||
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/48017/426433/viewer_manager-2.0.533794-darwin64-533794.tar.bz2</string>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>darwin64</string>
|
||||
|
|
@ -3594,9 +3594,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
|
|||
<key>archive</key>
|
||||
<map>
|
||||
<key>hash</key>
|
||||
<string>ef076d8d018c55e25124b7a577bae132</string>
|
||||
<string>65bd60bf3fe8a9d6f8efc230dcf3bd6b</string>
|
||||
<key>url</key>
|
||||
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/47034/414989/viewer_manager-2.0.533067-windows-533067.tar.bz2</string>
|
||||
<string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/48018/426440/viewer_manager-2.0.533794-windows-533794.tar.bz2</string>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>windows</string>
|
||||
|
|
@ -3607,7 +3607,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
|
|||
<key>source_type</key>
|
||||
<string>hg</string>
|
||||
<key>version</key>
|
||||
<string>2.0.533067</string>
|
||||
<string>2.0.533794</string>
|
||||
</map>
|
||||
<key>vlc-bin</key>
|
||||
<map>
|
||||
|
|
|
|||
|
|
@ -32,7 +32,8 @@
|
|||
|
||||
struct WearableEntry : public LLDictionaryEntry
|
||||
{
|
||||
WearableEntry(const std::string &name,
|
||||
WearableEntry(LLWearableType& wtype,
|
||||
const std::string &name,
|
||||
const std::string& default_new_name,
|
||||
LLAssetType::EType assetType,
|
||||
LLInventoryType::EIconName iconName,
|
||||
|
|
@ -41,7 +42,7 @@ struct WearableEntry : public LLDictionaryEntry
|
|||
LLDictionaryEntry(name),
|
||||
mAssetType(assetType),
|
||||
mDefaultNewName(default_new_name),
|
||||
mLabel(LLWearableType::getInstance()->mTrans->getString(name)),
|
||||
mLabel(wtype.mTrans->getString(name)),
|
||||
mIconName(iconName),
|
||||
mDisableCameraSwitch(disable_camera_switch),
|
||||
mAllowMultiwear(allow_multiwear)
|
||||
|
|
@ -56,10 +57,10 @@ struct WearableEntry : public LLDictionaryEntry
|
|||
BOOL mAllowMultiwear;
|
||||
};
|
||||
|
||||
class LLWearableDictionary : public LLSingleton<LLWearableDictionary>,
|
||||
class LLWearableDictionary : public LLParamSingleton<LLWearableDictionary>,
|
||||
public LLDictionary<LLWearableType::EType, WearableEntry>
|
||||
{
|
||||
LLSINGLETON(LLWearableDictionary);
|
||||
LLSINGLETON(LLWearableDictionary, LLWearableType&);
|
||||
|
||||
// [RLVa:KB] - Checked: 2010-03-03 (RLVa-1.2.0a) | Added: RLVa-1.2.0a
|
||||
protected:
|
||||
|
|
@ -68,7 +69,7 @@ protected:
|
|||
// [/RLVa:KB]
|
||||
};
|
||||
|
||||
LLWearableDictionary::LLWearableDictionary()
|
||||
LLWearableDictionary::LLWearableDictionary(LLWearableType& wtype)
|
||||
{
|
||||
if (!LLWearableType::instanceExists())
|
||||
{
|
||||
|
|
@ -76,30 +77,30 @@ LLWearableDictionary::LLWearableDictionary()
|
|||
// Todo: consider merging LLWearableType and LLWearableDictionary
|
||||
LL_WARNS() << "Initing LLWearableDictionary without LLWearableType" << LL_ENDL;
|
||||
}
|
||||
addEntry(LLWearableType::WT_SHAPE, new WearableEntry("shape", "New Shape", LLAssetType::AT_BODYPART, LLInventoryType::ICONNAME_BODYPART_SHAPE, FALSE, FALSE));
|
||||
addEntry(LLWearableType::WT_SKIN, new WearableEntry("skin", "New Skin", LLAssetType::AT_BODYPART, LLInventoryType::ICONNAME_BODYPART_SKIN, FALSE, FALSE));
|
||||
addEntry(LLWearableType::WT_HAIR, new WearableEntry("hair", "New Hair", LLAssetType::AT_BODYPART, LLInventoryType::ICONNAME_BODYPART_HAIR, FALSE, FALSE));
|
||||
addEntry(LLWearableType::WT_EYES, new WearableEntry("eyes", "New Eyes", LLAssetType::AT_BODYPART, LLInventoryType::ICONNAME_BODYPART_EYES, FALSE, FALSE));
|
||||
addEntry(LLWearableType::WT_SHIRT, new WearableEntry("shirt", "New Shirt", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_SHIRT, FALSE, TRUE));
|
||||
addEntry(LLWearableType::WT_PANTS, new WearableEntry("pants", "New Pants", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_PANTS, FALSE, TRUE));
|
||||
addEntry(LLWearableType::WT_SHOES, new WearableEntry("shoes", "New Shoes", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_SHOES, FALSE, TRUE));
|
||||
addEntry(LLWearableType::WT_SOCKS, new WearableEntry("socks", "New Socks", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_SOCKS, FALSE, TRUE));
|
||||
addEntry(LLWearableType::WT_JACKET, new WearableEntry("jacket", "New Jacket", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_JACKET, FALSE, TRUE));
|
||||
addEntry(LLWearableType::WT_GLOVES, new WearableEntry("gloves", "New Gloves", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_GLOVES, FALSE, TRUE));
|
||||
addEntry(LLWearableType::WT_UNDERSHIRT, new WearableEntry("undershirt", "New Undershirt", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_UNDERSHIRT, FALSE, TRUE));
|
||||
addEntry(LLWearableType::WT_UNDERPANTS, new WearableEntry("underpants", "New Underpants", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_UNDERPANTS, FALSE, TRUE));
|
||||
addEntry(LLWearableType::WT_SKIRT, new WearableEntry("skirt", "New Skirt", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_SKIRT, FALSE, TRUE));
|
||||
addEntry(LLWearableType::WT_ALPHA, new WearableEntry("alpha", "New Alpha", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_ALPHA, FALSE, TRUE));
|
||||
addEntry(LLWearableType::WT_TATTOO, new WearableEntry("tattoo", "New Tattoo", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_TATTOO, FALSE, TRUE));
|
||||
addEntry(LLWearableType::WT_UNIVERSAL, new WearableEntry("universal", "New Universal", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_UNIVERSAL, FALSE, TRUE));
|
||||
addEntry(LLWearableType::WT_SHAPE, new WearableEntry(wtype, "shape", "New Shape", LLAssetType::AT_BODYPART, LLInventoryType::ICONNAME_BODYPART_SHAPE, FALSE, FALSE));
|
||||
addEntry(LLWearableType::WT_SKIN, new WearableEntry(wtype, "skin", "New Skin", LLAssetType::AT_BODYPART, LLInventoryType::ICONNAME_BODYPART_SKIN, FALSE, FALSE));
|
||||
addEntry(LLWearableType::WT_HAIR, new WearableEntry(wtype, "hair", "New Hair", LLAssetType::AT_BODYPART, LLInventoryType::ICONNAME_BODYPART_HAIR, FALSE, FALSE));
|
||||
addEntry(LLWearableType::WT_EYES, new WearableEntry(wtype, "eyes", "New Eyes", LLAssetType::AT_BODYPART, LLInventoryType::ICONNAME_BODYPART_EYES, FALSE, FALSE));
|
||||
addEntry(LLWearableType::WT_SHIRT, new WearableEntry(wtype, "shirt", "New Shirt", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_SHIRT, FALSE, TRUE));
|
||||
addEntry(LLWearableType::WT_PANTS, new WearableEntry(wtype, "pants", "New Pants", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_PANTS, FALSE, TRUE));
|
||||
addEntry(LLWearableType::WT_SHOES, new WearableEntry(wtype, "shoes", "New Shoes", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_SHOES, FALSE, TRUE));
|
||||
addEntry(LLWearableType::WT_SOCKS, new WearableEntry(wtype, "socks", "New Socks", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_SOCKS, FALSE, TRUE));
|
||||
addEntry(LLWearableType::WT_JACKET, new WearableEntry(wtype, "jacket", "New Jacket", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_JACKET, FALSE, TRUE));
|
||||
addEntry(LLWearableType::WT_GLOVES, new WearableEntry(wtype, "gloves", "New Gloves", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_GLOVES, FALSE, TRUE));
|
||||
addEntry(LLWearableType::WT_UNDERSHIRT, new WearableEntry(wtype, "undershirt", "New Undershirt", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_UNDERSHIRT, FALSE, TRUE));
|
||||
addEntry(LLWearableType::WT_UNDERPANTS, new WearableEntry(wtype, "underpants", "New Underpants", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_UNDERPANTS, FALSE, TRUE));
|
||||
addEntry(LLWearableType::WT_SKIRT, new WearableEntry(wtype, "skirt", "New Skirt", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_SKIRT, FALSE, TRUE));
|
||||
addEntry(LLWearableType::WT_ALPHA, new WearableEntry(wtype, "alpha", "New Alpha", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_ALPHA, FALSE, TRUE));
|
||||
addEntry(LLWearableType::WT_TATTOO, new WearableEntry(wtype, "tattoo", "New Tattoo", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_TATTOO, FALSE, TRUE));
|
||||
addEntry(LLWearableType::WT_UNIVERSAL, new WearableEntry(wtype, "universal", "New Universal", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_UNIVERSAL, FALSE, TRUE));
|
||||
|
||||
// [SL:KB] - Patch: Appearance-Misc | Checked: 2011-05-29 (Catznip-2.6)
|
||||
addEntry(LLWearableType::WT_PHYSICS, new WearableEntry("physics", "New Physics", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_PHYSICS, TRUE, FALSE));
|
||||
addEntry(LLWearableType::WT_PHYSICS, new WearableEntry(wtype, "physics", "New Physics", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_PHYSICS, TRUE, FALSE));
|
||||
// [/SL:KB]
|
||||
// addEntry(LLWearableType::WT_PHYSICS, new WearableEntry("physics", "New Physics", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_PHYSICS, TRUE, TRUE));
|
||||
// addEntry(LLWearableType::WT_PHYSICS, new WearableEntry(wtype, "physics", "New Physics", LLAssetType::AT_CLOTHING, LLInventoryType::ICONNAME_CLOTHING_PHYSICS, TRUE, TRUE));
|
||||
|
||||
addEntry(LLWearableType::WT_INVALID, new WearableEntry("invalid", "Invalid Wearable", LLAssetType::AT_NONE, LLInventoryType::ICONNAME_UNKNOWN, FALSE, FALSE));
|
||||
addEntry(LLWearableType::WT_NONE, new WearableEntry("none", "Invalid Wearable", LLAssetType::AT_NONE, LLInventoryType::ICONNAME_NONE, FALSE, FALSE));
|
||||
addEntry(LLWearableType::WT_INVALID, new WearableEntry(wtype, "invalid", "Invalid Wearable", LLAssetType::AT_NONE, LLInventoryType::ICONNAME_UNKNOWN, FALSE, FALSE));
|
||||
addEntry(LLWearableType::WT_NONE, new WearableEntry(wtype, "none", "Invalid Wearable", LLAssetType::AT_NONE, LLInventoryType::ICONNAME_NONE, FALSE, FALSE));
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -116,6 +117,14 @@ LLWearableType::~LLWearableType()
|
|||
delete mTrans;
|
||||
}
|
||||
|
||||
void LLWearableType::initSingleton()
|
||||
{
|
||||
// To make sure all wrapping functions will crash without initing LLWearableType;
|
||||
LLWearableDictionary::initParamSingleton(*this);
|
||||
|
||||
// Todo: consider merging LLWearableType and LLWearableDictionary
|
||||
}
|
||||
|
||||
// static
|
||||
LLWearableType::EType LLWearableType::typeNameToType(const std::string& type_name)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -47,7 +47,8 @@ class LLWearableType : public LLParamSingleton<LLWearableType>
|
|||
LLSINGLETON(LLWearableType, LLTranslationBridge* trans);
|
||||
~LLWearableType();
|
||||
friend struct WearableEntry;
|
||||
public:
|
||||
void initSingleton();
|
||||
public:
|
||||
enum EType
|
||||
{
|
||||
WT_SHAPE = 0,
|
||||
|
|
|
|||
|
|
@ -187,6 +187,7 @@ set(llcommon_HEADER_FILES
|
|||
llleaplistener.h
|
||||
llliveappconfig.h
|
||||
lllivefile.h
|
||||
llmainthreadtask.h
|
||||
llmd5.h
|
||||
llmemory.h
|
||||
llmemorystream.h
|
||||
|
|
@ -246,6 +247,7 @@ set(llcommon_HEADER_FILES
|
|||
llwin32headers.h
|
||||
llwin32headerslean.h
|
||||
llworkerthread.h
|
||||
lockstatic.h
|
||||
stdtypes.h
|
||||
stringize.h
|
||||
timer.h
|
||||
|
|
@ -374,6 +376,7 @@ if (LL_TESTS)
|
|||
LL_ADD_INTEGRATION_TEST(llheteromap "" "${test_libs}")
|
||||
LL_ADD_INTEGRATION_TEST(llinstancetracker "" "${test_libs}")
|
||||
LL_ADD_INTEGRATION_TEST(llleap "" "${test_libs}")
|
||||
LL_ADD_INTEGRATION_TEST(llmainthreadtask "" "${test_libs}")
|
||||
LL_ADD_INTEGRATION_TEST(llpounceable "" "${test_libs}")
|
||||
LL_ADD_INTEGRATION_TEST(llprocess "" "${test_libs}")
|
||||
LL_ADD_INTEGRATION_TEST(llprocessor "" "${test_libs}")
|
||||
|
|
|
|||
|
|
@ -41,17 +41,7 @@
|
|||
|
||||
#include "llstring.h"
|
||||
|
||||
#if LL_WINDOWS
|
||||
#pragma warning (push)
|
||||
#pragma warning (disable:4265)
|
||||
#endif
|
||||
// warning C4265: 'std::_Pad' : class has virtual functions, but destructor is not virtual
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#if LL_WINDOWS
|
||||
#pragma warning (pop)
|
||||
#endif
|
||||
#include "mutex.h"
|
||||
|
||||
struct apr_dso_handle_t;
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@
|
|||
#include "llcoros.h"
|
||||
// STL headers
|
||||
// std headers
|
||||
#include <atomic>
|
||||
// external library headers
|
||||
#include <boost/bind.hpp>
|
||||
#include <boost/fiber/fiber.hpp>
|
||||
|
|
@ -73,10 +74,9 @@ LLCoros::CoroData& LLCoros::get_CoroData(const std::string& caller)
|
|||
// canonical values.
|
||||
if (! current)
|
||||
{
|
||||
// It's tempting to provide a distinct name for each thread's "main
|
||||
// coroutine." But as getName() has always returned the empty string
|
||||
// to mean "not in a coroutine," empty string should suffice here.
|
||||
static thread_local CoroData sMain("");
|
||||
static std::atomic<int> which_thread(0);
|
||||
// Use alternate CoroData constructor.
|
||||
static thread_local CoroData sMain(which_thread++);
|
||||
// We need not reset() the local_ptr to this instance; we'll simply
|
||||
// find it again every time we discover that current is null.
|
||||
current = &sMain;
|
||||
|
|
@ -197,6 +197,13 @@ std::string LLCoros::getName()
|
|||
return get_CoroData("getName()").mName;
|
||||
}
|
||||
|
||||
//static
|
||||
std::string LLCoros::logname()
|
||||
{
|
||||
LLCoros::CoroData& data(get_CoroData("logname()"));
|
||||
return data.mName.empty()? data.getKey() : data.mName;
|
||||
}
|
||||
|
||||
void LLCoros::setStackSize(S32 stacksize)
|
||||
{
|
||||
LL_DEBUGS("LLCoros") << "Setting coroutine stack size to " << stacksize << LL_ENDL;
|
||||
|
|
@ -211,12 +218,11 @@ void LLCoros::printActiveCoroutines(const std::string& when)
|
|||
{
|
||||
LL_INFOS("LLCoros") << "-------------- List of active coroutines ------------";
|
||||
F64 time = LLTimer::getTotalSeconds();
|
||||
for (auto it(CoroData::beginInstances()), end(CoroData::endInstances());
|
||||
it != end; ++it)
|
||||
for (auto& cd : CoroData::instance_snapshot())
|
||||
{
|
||||
F64 life_time = time - it->mCreationTime;
|
||||
F64 life_time = time - cd.mCreationTime;
|
||||
LL_CONT << LL_NEWLINE
|
||||
<< it->mName << ' ' << it->mStatus << " life: " << life_time;
|
||||
<< cd.getKey() << ' ' << cd.mStatus << " life: " << life_time;
|
||||
}
|
||||
LL_CONT << LL_ENDL;
|
||||
LL_INFOS("LLCoros") << "-----------------------------------------------------" << LL_ENDL;
|
||||
|
|
@ -355,3 +361,16 @@ LLCoros::CoroData::CoroData(const std::string& name):
|
|||
mCreationTime(LLTimer::getTotalSeconds())
|
||||
{
|
||||
}
|
||||
|
||||
LLCoros::CoroData::CoroData(int n):
|
||||
// This constructor is used for the thread_local instance belonging to the
|
||||
// default coroutine on each thread. We must give each one a different
|
||||
// LLInstanceTracker key because LLInstanceTracker's map spans all
|
||||
// threads, but we want the default coroutine on each thread to have the
|
||||
// empty string as its visible name because some consumers test for that.
|
||||
LLInstanceTracker<CoroData, std::string>("main" + stringize(n)),
|
||||
mName(),
|
||||
mConsuming(false),
|
||||
mCreationTime(LLTimer::getTotalSeconds())
|
||||
{
|
||||
}
|
||||
|
|
|
|||
|
|
@ -142,6 +142,13 @@ public:
|
|||
*/
|
||||
static std::string getName();
|
||||
|
||||
/**
|
||||
* This variation returns a name suitable for log messages: the explicit
|
||||
* name for an explicitly-launched coroutine, or "mainN" for the default
|
||||
* coroutine on a thread.
|
||||
*/
|
||||
static std::string logname();
|
||||
|
||||
/**
|
||||
* For delayed initialization. To be clear, this will only affect
|
||||
* coroutines launched @em after this point. The underlying facility
|
||||
|
|
@ -272,6 +279,7 @@ private:
|
|||
struct CoroData: public LLInstanceTracker<CoroData, std::string>
|
||||
{
|
||||
CoroData(const std::string& name);
|
||||
CoroData(int n);
|
||||
|
||||
// tweaked name of the current coroutine
|
||||
const std::string mName;
|
||||
|
|
@ -292,12 +300,7 @@ namespace llcoro
|
|||
{
|
||||
|
||||
inline
|
||||
std::string logname()
|
||||
{
|
||||
static std::string main("main");
|
||||
std::string name(LLCoros::getName());
|
||||
return name.empty()? main : name;
|
||||
}
|
||||
std::string logname() { return LLCoros::logname(); }
|
||||
|
||||
} // llcoro
|
||||
|
||||
|
|
|
|||
|
|
@ -307,28 +307,35 @@ namespace LLError
|
|||
{
|
||||
#ifdef __GNUC__
|
||||
// GCC: type_info::name() returns a mangled class name,st demangle
|
||||
// passing nullptr, 0 forces allocation of a unique buffer we can free
|
||||
// fixing MAINT-8724 on OSX 10.14
|
||||
// passing nullptr, 0 forces allocation of a unique buffer we can free
|
||||
// fixing MAINT-8724 on OSX 10.14
|
||||
int status = -1;
|
||||
char* name = abi::__cxa_demangle(mangled, nullptr, 0, &status);
|
||||
std::string result(name ? name : mangled);
|
||||
free(name);
|
||||
return result;
|
||||
std::string result(name ? name : mangled);
|
||||
free(name);
|
||||
return result;
|
||||
|
||||
#elif LL_WINDOWS
|
||||
// DevStudio: type_info::name() includes the text "class " at the start
|
||||
|
||||
static const std::string class_prefix = "class ";
|
||||
// Visual Studio: type_info::name() includes the text "class " at the start
|
||||
std::string name = mangled;
|
||||
if (0 != name.compare(0, class_prefix.length(), class_prefix))
|
||||
for (const auto& prefix : std::vector<std::string>{ "class ", "struct " })
|
||||
{
|
||||
LL_DEBUGS() << "Did not see '" << class_prefix << "' prefix on '"
|
||||
<< name << "'" << LL_ENDL;
|
||||
return name;
|
||||
if (0 == name.compare(0, prefix.length(), prefix))
|
||||
{
|
||||
return name.substr(prefix.length());
|
||||
}
|
||||
}
|
||||
// huh, that's odd, we should see one or the other prefix -- but don't
|
||||
// try to log unless logging is already initialized
|
||||
if (is_available())
|
||||
{
|
||||
// in Python, " or ".join(vector) -- but in C++, a PITB
|
||||
LL_DEBUGS() << "Did not see 'class' or 'struct' prefix on '"
|
||||
<< name << "'" << LL_ENDL;
|
||||
}
|
||||
return name;
|
||||
|
||||
return name.substr(class_prefix.length());
|
||||
|
||||
#else
|
||||
#else // neither GCC nor Visual Studio
|
||||
return mangled;
|
||||
#endif
|
||||
}
|
||||
|
|
@ -1213,8 +1220,25 @@ namespace
|
|||
}
|
||||
|
||||
namespace {
|
||||
LLMutex gLogMutex;
|
||||
LLMutex gCallStacksLogMutex;
|
||||
// We need a couple different mutexes, but we want to use the same mechanism
|
||||
// for both. Make getMutex() a template function with different instances
|
||||
// for different MutexDiscriminator values.
|
||||
enum MutexDiscriminator
|
||||
{
|
||||
LOG_MUTEX,
|
||||
STACKS_MUTEX
|
||||
};
|
||||
// Some logging calls happen very early in processing -- so early that our
|
||||
// module-static variables aren't yet initialized. getMutex() wraps a
|
||||
// function-static LLMutex so that early calls can still have a valid
|
||||
// LLMutex instance.
|
||||
template <MutexDiscriminator MTX>
|
||||
LLMutex* getMutex()
|
||||
{
|
||||
// guaranteed to be initialized the first time control reaches here
|
||||
static LLMutex sMutex;
|
||||
return &sMutex;
|
||||
}
|
||||
|
||||
bool checkLevelMap(const LevelMap& map, const std::string& key,
|
||||
LLError::ELevel& level)
|
||||
|
|
@ -1267,7 +1291,7 @@ namespace LLError
|
|||
|
||||
bool Log::shouldLog(CallSite& site)
|
||||
{
|
||||
LLMutexTrylock lock(&gLogMutex, 5);
|
||||
LLMutexTrylock lock(getMutex<LOG_MUTEX>(), 5);
|
||||
if (!lock.isLocked())
|
||||
{
|
||||
return false;
|
||||
|
|
@ -1318,7 +1342,7 @@ namespace LLError
|
|||
|
||||
std::ostringstream* Log::out()
|
||||
{
|
||||
LLMutexTrylock lock(&gLogMutex,5);
|
||||
LLMutexTrylock lock(getMutex<LOG_MUTEX>(),5);
|
||||
// If we hit a logging request very late during shutdown processing,
|
||||
// when either of the relevant LLSingletons has already been deleted,
|
||||
// DO NOT resurrect them.
|
||||
|
|
@ -1338,7 +1362,7 @@ namespace LLError
|
|||
|
||||
void Log::flush(std::ostringstream* out, char* message)
|
||||
{
|
||||
LLMutexTrylock lock(&gLogMutex,5);
|
||||
LLMutexTrylock lock(getMutex<LOG_MUTEX>(),5);
|
||||
if (!lock.isLocked())
|
||||
{
|
||||
return;
|
||||
|
|
@ -1378,7 +1402,7 @@ namespace LLError
|
|||
|
||||
void Log::flush(std::ostringstream* out, const CallSite& site)
|
||||
{
|
||||
LLMutexTrylock lock(&gLogMutex,5);
|
||||
LLMutexTrylock lock(getMutex<LOG_MUTEX>(),5);
|
||||
if (!lock.isLocked())
|
||||
{
|
||||
return;
|
||||
|
|
@ -1549,34 +1573,34 @@ namespace LLError
|
|||
S32 LLCallStacks::sIndex = 0 ;
|
||||
|
||||
//static
|
||||
void LLCallStacks::allocateStackBuffer()
|
||||
{
|
||||
if(sBuffer == NULL)
|
||||
{
|
||||
sBuffer = new char*[512] ;
|
||||
sBuffer[0] = new char[512 * 128] ;
|
||||
for(S32 i = 1 ; i < 512 ; i++)
|
||||
{
|
||||
sBuffer[i] = sBuffer[i-1] + 128 ;
|
||||
}
|
||||
sIndex = 0 ;
|
||||
}
|
||||
}
|
||||
void LLCallStacks::allocateStackBuffer()
|
||||
{
|
||||
if(sBuffer == NULL)
|
||||
{
|
||||
sBuffer = new char*[512] ;
|
||||
sBuffer[0] = new char[512 * 128] ;
|
||||
for(S32 i = 1 ; i < 512 ; i++)
|
||||
{
|
||||
sBuffer[i] = sBuffer[i-1] + 128 ;
|
||||
}
|
||||
sIndex = 0 ;
|
||||
}
|
||||
}
|
||||
|
||||
void LLCallStacks::freeStackBuffer()
|
||||
{
|
||||
if(sBuffer != NULL)
|
||||
{
|
||||
delete [] sBuffer[0] ;
|
||||
delete [] sBuffer ;
|
||||
sBuffer = NULL ;
|
||||
}
|
||||
}
|
||||
void LLCallStacks::freeStackBuffer()
|
||||
{
|
||||
if(sBuffer != NULL)
|
||||
{
|
||||
delete [] sBuffer[0] ;
|
||||
delete [] sBuffer ;
|
||||
sBuffer = NULL ;
|
||||
}
|
||||
}
|
||||
|
||||
//static
|
||||
void LLCallStacks::push(const char* function, const int line)
|
||||
{
|
||||
LLMutexTrylock lock(&gCallStacksLogMutex, 5);
|
||||
//static
|
||||
void LLCallStacks::push(const char* function, const int line)
|
||||
{
|
||||
LLMutexTrylock lock(getMutex<STACKS_MUTEX>(), 5);
|
||||
if (!lock.isLocked())
|
||||
{
|
||||
return;
|
||||
|
|
@ -1611,7 +1635,7 @@ namespace LLError
|
|||
//static
|
||||
void LLCallStacks::end(std::ostringstream* _out)
|
||||
{
|
||||
LLMutexTrylock lock(&gCallStacksLogMutex, 5);
|
||||
LLMutexTrylock lock(getMutex<STACKS_MUTEX>(), 5);
|
||||
if (!lock.isLocked())
|
||||
{
|
||||
return;
|
||||
|
|
@ -1633,7 +1657,7 @@ namespace LLError
|
|||
//static
|
||||
void LLCallStacks::print()
|
||||
{
|
||||
LLMutexTrylock lock(&gCallStacksLogMutex, 5);
|
||||
LLMutexTrylock lock(getMutex<STACKS_MUTEX>(), 5);
|
||||
if (!lock.isLocked())
|
||||
{
|
||||
return;
|
||||
|
|
@ -1676,7 +1700,7 @@ namespace LLError
|
|||
|
||||
bool debugLoggingEnabled(const std::string& tag)
|
||||
{
|
||||
LLMutexTrylock lock(&gLogMutex, 5);
|
||||
LLMutexTrylock lock(getMutex<LOG_MUTEX>(), 5);
|
||||
if (!lock.isLocked())
|
||||
{
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -57,35 +57,17 @@ LLEventTimer::~LLEventTimer()
|
|||
//static
|
||||
void LLEventTimer::updateClass()
|
||||
{
|
||||
std::list<LLEventTimer*> completed_timers;
|
||||
|
||||
// <FS:ND> Minimize calls to getInstances per frame
|
||||
// for (instance_iter iter = beginInstances(); iter != endInstances(); )
|
||||
|
||||
instance_iter end = endInstances();
|
||||
for (instance_iter iter = beginInstances(); iter != end; )
|
||||
// </FS:ND>
|
||||
for (auto& timer : instance_snapshot())
|
||||
{
|
||||
LLEventTimer& timer = *iter++;
|
||||
F32 et = timer.mEventTimer.getElapsedTimeF32();
|
||||
if (timer.mEventTimer.getStarted() && et > timer.mPeriod) {
|
||||
timer.mEventTimer.reset();
|
||||
if ( timer.tick() )
|
||||
{
|
||||
completed_timers.push_back( &timer );
|
||||
delete &timer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( completed_timers.size() > 0 )
|
||||
{
|
||||
for (std::list<LLEventTimer*>::iterator completed_iter = completed_timers.begin();
|
||||
completed_iter != completed_timers.end();
|
||||
completed_iter++ )
|
||||
{
|
||||
delete *completed_iter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,11 @@
|
|||
// `_GNU_SOURCE` macro or `BOOST_STACKTRACE_GNU_SOURCE_NOT_REQUIRED` if
|
||||
// _Unwind_Backtrace is available without `_GNU_SOURCE`."
|
||||
#define BOOST_STACKTRACE_GNU_SOURCE_NOT_REQUIRED
|
||||
#if LL_WINDOWS
|
||||
// On Windows, header-only implementation causes macro collisions -- use
|
||||
// prebuilt library
|
||||
#define BOOST_STACKTRACE_LINK
|
||||
#endif // LL_WINDOWS
|
||||
#include <boost/stacktrace.hpp>
|
||||
// other Linden headers
|
||||
#include "llerror.h"
|
||||
|
|
|
|||
|
|
@ -199,27 +199,26 @@ TimeBlockTreeNode& BlockTimerStatHandle::getTreeNode() const
|
|||
|
||||
void BlockTimer::bootstrapTimerTree()
|
||||
{
|
||||
for (BlockTimerStatHandle::instance_tracker_t::instance_iter it = BlockTimerStatHandle::instance_tracker_t::beginInstances(), end_it = BlockTimerStatHandle::instance_tracker_t::endInstances();
|
||||
it != end_it;
|
||||
++it)
|
||||
for (auto& base : BlockTimerStatHandle::instance_snapshot())
|
||||
{
|
||||
BlockTimerStatHandle& timer = static_cast<BlockTimerStatHandle&>(*it);
|
||||
// because of indirect derivation from LLInstanceTracker, have to downcast
|
||||
BlockTimerStatHandle& timer = static_cast<BlockTimerStatHandle&>(base);
|
||||
if (&timer == &BlockTimer::getRootTimeBlock()) continue;
|
||||
|
||||
// bootstrap tree construction by attaching to last timer to be on stack
|
||||
// when this timer was called
|
||||
if (timer.getParent() == &BlockTimer::getRootTimeBlock())
|
||||
{
|
||||
{
|
||||
TimeBlockAccumulator& accumulator = timer.getCurrentAccumulator();
|
||||
|
||||
if (accumulator.mLastCaller)
|
||||
{
|
||||
{
|
||||
timer.setParent(accumulator.mLastCaller);
|
||||
accumulator.mParent = accumulator.mLastCaller;
|
||||
}
|
||||
}
|
||||
// no need to push up tree on first use, flag can be set spuriously
|
||||
accumulator.mMoveUpTree = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -312,12 +311,10 @@ void BlockTimer::processTimes()
|
|||
updateTimes();
|
||||
|
||||
// reset for next frame
|
||||
for (BlockTimerStatHandle::instance_tracker_t::instance_iter it = BlockTimerStatHandle::instance_tracker_t::beginInstances(),
|
||||
end_it = BlockTimerStatHandle::instance_tracker_t::endInstances();
|
||||
it != end_it;
|
||||
++it)
|
||||
for (auto& base : BlockTimerStatHandle::instance_snapshot())
|
||||
{
|
||||
BlockTimerStatHandle& timer = static_cast<BlockTimerStatHandle&>(*it);
|
||||
// because of indirect derivation from LLInstanceTracker, have to downcast
|
||||
BlockTimerStatHandle& timer = static_cast<BlockTimerStatHandle&>(base);
|
||||
TimeBlockAccumulator& accumulator = timer.getCurrentAccumulator();
|
||||
|
||||
accumulator.mLastCaller = NULL;
|
||||
|
|
@ -368,12 +365,10 @@ void BlockTimer::logStats()
|
|||
LLSD sd;
|
||||
|
||||
{
|
||||
for (BlockTimerStatHandle::instance_tracker_t::instance_iter it = BlockTimerStatHandle::instance_tracker_t::beginInstances(),
|
||||
end_it = BlockTimerStatHandle::instance_tracker_t::endInstances();
|
||||
it != end_it;
|
||||
++it)
|
||||
for (auto& base : BlockTimerStatHandle::instance_snapshot())
|
||||
{
|
||||
BlockTimerStatHandle& timer = static_cast<BlockTimerStatHandle&>(*it);
|
||||
// because of indirect derivation from LLInstanceTracker, have to downcast
|
||||
BlockTimerStatHandle& timer = static_cast<BlockTimerStatHandle&>(base);
|
||||
LLTrace::PeriodicRecording& frame_recording = LLTrace::get_frame_recording();
|
||||
sd[timer.getName()]["Time"] = (LLSD::Real) (frame_recording.getLastRecording().getSum(timer).value());
|
||||
sd[timer.getName()]["Calls"] = (LLSD::Integer) (frame_recording.getLastRecording().getSum(timer.callCount()));
|
||||
|
|
|
|||
|
|
@ -27,25 +27,15 @@
|
|||
#include "linden_common.h"
|
||||
// associated header
|
||||
#include "llinstancetracker.h"
|
||||
#include "llapr.h"
|
||||
|
||||
#include "llerror.h"
|
||||
// STL headers
|
||||
// std headers
|
||||
// external library headers
|
||||
// other Linden headers
|
||||
|
||||
void LLInstanceTrackerBase::StaticBase::incrementDepth()
|
||||
void LLInstanceTrackerPrivate::logerrs(const char* cls, const std::string& arg1,
|
||||
const std::string& arg2, const std::string& arg3)
|
||||
{
|
||||
++sIterationNestDepth;
|
||||
}
|
||||
|
||||
void LLInstanceTrackerBase::StaticBase::decrementDepth()
|
||||
{
|
||||
llassert(sIterationNestDepth);
|
||||
--sIterationNestDepth;
|
||||
}
|
||||
|
||||
U32 LLInstanceTrackerBase::StaticBase::getDepth()
|
||||
{
|
||||
return sIterationNestDepth;
|
||||
LL_ERRS("LLInstanceTracker") << LLError::Log::demangle(cls)
|
||||
<< arg1 << arg2 << arg3 << LL_ENDL;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,440 +28,432 @@
|
|||
#ifndef LL_LLINSTANCETRACKER_H
|
||||
#define LL_LLINSTANCETRACKER_H
|
||||
|
||||
#include <atomic>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
#include <typeinfo>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
|
||||
#include "mutex.h"
|
||||
|
||||
#include <atomic>
|
||||
#include "llstringtable.h"
|
||||
#include <boost/iterator/transform_iterator.hpp>
|
||||
#include <boost/iterator/indirect_iterator.hpp>
|
||||
// <FS:CR>
|
||||
#ifdef LL_DEBUG
|
||||
#include "llerror.h"
|
||||
#endif
|
||||
// </FS:CR>
|
||||
#include <boost/iterator/filter_iterator.hpp>
|
||||
|
||||
// As of 2017-05-06, as far as nat knows, only clang supports __has_feature().
|
||||
// Unfortunately VS2013's preprocessor shortcut logic doesn't prevent it from
|
||||
// producing (fatal) warnings for defined(__clang__) && __has_feature(...).
|
||||
// Have to work around that.
|
||||
#if ! defined(__clang__)
|
||||
#define __has_feature(x) 0
|
||||
#endif // __clang__
|
||||
#include "lockstatic.h"
|
||||
#include "stringize.h"
|
||||
|
||||
#if defined(LL_TEST_llinstancetracker) && __has_feature(cxx_noexcept)
|
||||
// ~LLInstanceTracker() performs llassert_always() validation. That's fine in
|
||||
// production code, since the llassert_always() is implemented as an LL_ERRS
|
||||
// message, which will crash-with-message. In our integration test executable,
|
||||
// though, this llassert_always() throws an exception instead so we can test
|
||||
// error conditions and continue running the test. However -- as of C++11,
|
||||
// destructors are implicitly noexcept(true). Unless we mark
|
||||
// ~LLInstanceTracker() noexcept(false), the test executable crashes even on
|
||||
// the ATTEMPT to throw.
|
||||
#define LLINSTANCETRACKER_DTOR_NOEXCEPT noexcept(false)
|
||||
#else
|
||||
// If we're building for production, or in fact building *any other* test, or
|
||||
// we're using a compiler that doesn't support __has_feature(), or we're not
|
||||
// compiling with a C++ version that supports noexcept -- don't specify it.
|
||||
#define LLINSTANCETRACKER_DTOR_NOEXCEPT
|
||||
#endif
|
||||
|
||||
// As of 2017-05-06, as far as nat knows, only clang supports __has_feature().
|
||||
// Unfortunately VS2013's preprocessor shortcut logic doesn't prevent it from
|
||||
// producing (fatal) warnings for defined(__clang__) && __has_feature(...).
|
||||
// Have to work around that.
|
||||
#if ! defined(__clang__)
|
||||
#define __has_feature(x) 0
|
||||
#endif // __clang__
|
||||
|
||||
#if defined(LL_TEST_llinstancetracker) && __has_feature(cxx_noexcept)
|
||||
// ~LLInstanceTracker() performs llassert_always() validation. That's fine in
|
||||
// production code, since the llassert_always() is implemented as an LL_ERRS
|
||||
// message, which will crash-with-message. In our integration test executable,
|
||||
// though, this llassert_always() throws an exception instead so we can test
|
||||
// error conditions and continue running the test. However -- as of C++11,
|
||||
// destructors are implicitly noexcept(true). Unless we mark
|
||||
// ~LLInstanceTracker() noexcept(false), the test executable crashes even on
|
||||
// the ATTEMPT to throw.
|
||||
#define LLINSTANCETRACKER_DTOR_NOEXCEPT noexcept(false)
|
||||
#else
|
||||
// If we're building for production, or in fact building *any other* test, or
|
||||
// we're using a compiler that doesn't support __has_feature(), or we're not
|
||||
// compiling with a C++ version that supports noexcept -- don't specify it.
|
||||
#define LLINSTANCETRACKER_DTOR_NOEXCEPT
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Base class manages "class-static" data that must actually have singleton
|
||||
* semantics: one instance per process, rather than one instance per module as
|
||||
* sometimes happens with data simply declared static.
|
||||
*/
|
||||
class LL_COMMON_API LLInstanceTrackerBase
|
||||
/*****************************************************************************
|
||||
* StaticBase
|
||||
*****************************************************************************/
|
||||
namespace LLInstanceTrackerPrivate
|
||||
{
|
||||
protected:
|
||||
/// It's not essential to derive your STATICDATA (for use with
|
||||
/// getStatic()) from StaticBase; it's just that both known
|
||||
/// implementations do.
|
||||
struct StaticBase
|
||||
{
|
||||
// <FS:ND> Only needed in debug builds
|
||||
#ifdef LL_DEBUG
|
||||
StaticBase()
|
||||
: sIterationNestDepth(0)
|
||||
{}
|
||||
// We need to be able to lock static data while manipulating it.
|
||||
std::mutex mMutex;
|
||||
};
|
||||
|
||||
#else
|
||||
StaticBase()
|
||||
{}
|
||||
#endif
|
||||
// </FS:ND>
|
||||
|
||||
void incrementDepth();
|
||||
void decrementDepth();
|
||||
U32 getDepth();
|
||||
private:
|
||||
#ifdef LL_WINDOWS
|
||||
std::atomic_uint32_t sIterationNestDepth;
|
||||
#else
|
||||
std::atomic_uint sIterationNestDepth;
|
||||
#endif
|
||||
};
|
||||
};
|
||||
|
||||
LL_COMMON_API void assert_main_thread();
|
||||
void logerrs(const char* cls, const std::string&, const std::string&, const std::string&);
|
||||
} // namespace LLInstanceTrackerPrivate
|
||||
|
||||
/*****************************************************************************
|
||||
* LLInstanceTracker with key
|
||||
*****************************************************************************/
|
||||
enum EInstanceTrackerAllowKeyCollisions
|
||||
{
|
||||
LLInstanceTrackerErrorOnCollision,
|
||||
LLInstanceTrackerReplaceOnCollision
|
||||
LLInstanceTrackerErrorOnCollision,
|
||||
LLInstanceTrackerReplaceOnCollision
|
||||
};
|
||||
|
||||
/// This mix-in class adds support for tracking all instances of the specified class parameter T
|
||||
/// The (optional) key associates a value of type KEY with a given instance of T, for quick lookup
|
||||
/// If KEY is not provided, then instances are stored in a simple set
|
||||
/// @NOTE: see explicit specialization below for default KEY==void case
|
||||
/// @NOTE: this class is not thread-safe unless used as read-only
|
||||
template<typename T, typename KEY = void, EInstanceTrackerAllowKeyCollisions KEY_COLLISION_BEHAVIOR = LLInstanceTrackerErrorOnCollision>
|
||||
class LLInstanceTracker : public LLInstanceTrackerBase
|
||||
template<typename T, typename KEY = void,
|
||||
EInstanceTrackerAllowKeyCollisions KEY_COLLISION_BEHAVIOR = LLInstanceTrackerErrorOnCollision>
|
||||
class LLInstanceTracker
|
||||
{
|
||||
protected:
|
||||
typedef LLInstanceTracker<T, KEY> self_t;
|
||||
typedef typename std::multimap<KEY, T*> InstanceMap;
|
||||
struct StaticData: public StaticBase
|
||||
{
|
||||
InstanceMap sMap;
|
||||
};
|
||||
static StaticData& getStatic() { static StaticData sData; return sData;}
|
||||
static InstanceMap& getMap_() { return getStatic().sMap; }
|
||||
typedef std::map<KEY, std::shared_ptr<T>> InstanceMap;
|
||||
struct StaticData: public LLInstanceTrackerPrivate::StaticBase
|
||||
{
|
||||
InstanceMap mMap;
|
||||
};
|
||||
typedef llthread::LockStatic<StaticData> LockStatic;
|
||||
|
||||
public:
|
||||
class instance_iter : public boost::iterator_facade<instance_iter, T, boost::forward_traversal_tag>
|
||||
{
|
||||
public:
|
||||
typedef boost::iterator_facade<instance_iter, T, boost::forward_traversal_tag> super_t;
|
||||
|
||||
instance_iter(const typename InstanceMap::iterator& it)
|
||||
: mIterator(it)
|
||||
{
|
||||
// <FS:ND> Minimize calls to getStatic
|
||||
#ifdef LL_DEBUG
|
||||
getStatic().incrementDepth();
|
||||
#endif
|
||||
// </FS:ND>
|
||||
}
|
||||
// snapshot of std::pair<const KEY, std::shared_ptr<T>> pairs
|
||||
class snapshot
|
||||
{
|
||||
// It's very important that what we store in this snapshot are
|
||||
// weak_ptrs, NOT shared_ptrs. That's how we discover whether any
|
||||
// instance has been deleted during the lifespan of a snapshot.
|
||||
typedef std::vector<std::pair<const KEY, std::weak_ptr<T>>> VectorType;
|
||||
// Dereferencing our iterator produces a std::shared_ptr for each
|
||||
// instance that still exists. Since we store weak_ptrs, that involves
|
||||
// two chained transformations:
|
||||
// - a transform_iterator to lock the weak_ptr and return a shared_ptr
|
||||
// - a filter_iterator to skip any shared_ptr that has become invalid.
|
||||
// It is very important that we filter lazily, that is, during
|
||||
// traversal. Any one of our stored weak_ptrs might expire during
|
||||
// traversal.
|
||||
typedef std::pair<const KEY, std::shared_ptr<T>> strong_pair;
|
||||
// Note for future reference: nat has not yet had any luck (up to
|
||||
// Boost 1.67) trying to use boost::transform_iterator with a hand-
|
||||
// coded functor, only with actual functions. In my experience, an
|
||||
// internal boost::result_of() operation fails, even with an explicit
|
||||
// result_type typedef. But this works.
|
||||
static strong_pair strengthen(typename VectorType::value_type& pair)
|
||||
{
|
||||
return { pair.first, pair.second.lock() };
|
||||
}
|
||||
static bool dead_skipper(const strong_pair& pair)
|
||||
{
|
||||
return bool(pair.second);
|
||||
}
|
||||
|
||||
~instance_iter()
|
||||
{
|
||||
// <FS:ND> Minimize calls to getStatic
|
||||
#ifdef LL_DEBUG
|
||||
getStatic().decrementDepth();
|
||||
#endif
|
||||
// </FS:ND>
|
||||
}
|
||||
public:
|
||||
snapshot():
|
||||
// populate our vector with a snapshot of (locked!) InstanceMap
|
||||
// note, this assigns pair<KEY, shared_ptr> to pair<KEY, weak_ptr>
|
||||
mData(mLock->mMap.begin(), mLock->mMap.end())
|
||||
{
|
||||
// release the lock once we've populated mData
|
||||
mLock.unlock();
|
||||
}
|
||||
|
||||
// You can't make a transform_iterator (or anything else) that
|
||||
// literally stores a C++ function (decltype(strengthen)) -- but you
|
||||
// can make a transform_iterator based on a _function pointer._
|
||||
typedef boost::transform_iterator<decltype(strengthen)*,
|
||||
typename VectorType::iterator> strong_iterator;
|
||||
typedef boost::filter_iterator<decltype(dead_skipper)*, strong_iterator> iterator;
|
||||
|
||||
private:
|
||||
friend class boost::iterator_core_access;
|
||||
iterator begin() { return make_iterator(mData.begin()); }
|
||||
iterator end() { return make_iterator(mData.end()); }
|
||||
|
||||
void increment() { mIterator++; }
|
||||
bool equal(instance_iter const& other) const
|
||||
{
|
||||
return mIterator == other.mIterator;
|
||||
}
|
||||
private:
|
||||
iterator make_iterator(typename VectorType::iterator iter)
|
||||
{
|
||||
// transform_iterator only needs the base iterator and the transform.
|
||||
// filter_iterator wants the predicate and both ends of the range.
|
||||
return iterator(dead_skipper,
|
||||
strong_iterator(iter, strengthen),
|
||||
strong_iterator(mData.end(), strengthen));
|
||||
}
|
||||
|
||||
T& dereference() const
|
||||
{
|
||||
return *(mIterator->second);
|
||||
}
|
||||
// lock static data during construction
|
||||
#if ! LL_WINDOWS
|
||||
LockStatic mLock;
|
||||
#else // LL_WINDOWS
|
||||
// We want to be able to use (e.g.) our instance_snapshot subclass as:
|
||||
// for (auto& inst : T::instance_snapshot()) ...
|
||||
// But when this snapshot base class directly contains LockStatic, as
|
||||
// above, Visual Studio 2017 requires us to code instead:
|
||||
// for (auto& inst : std::move(T::instance_snapshot())) ...
|
||||
// nat thinks this should be unnecessary, as an anonymous class
|
||||
// instance is already a temporary. It shouldn't need to be cast to
|
||||
// rvalue reference (the role of std::move()). clang evidently agrees,
|
||||
// as the short form works fine with Xcode on Mac.
|
||||
// To support the succinct usage, instead of directly storing
|
||||
// LockStatic, store std::shared_ptr<LockStatic>, which is copyable.
|
||||
std::shared_ptr<LockStatic> mLockp{std::make_shared<LockStatic>()};
|
||||
LockStatic& mLock{*mLockp};
|
||||
#endif // LL_WINDOWS
|
||||
VectorType mData;
|
||||
};
|
||||
|
||||
typename InstanceMap::iterator mIterator;
|
||||
};
|
||||
// iterate over this for references to each instance
|
||||
class instance_snapshot: public snapshot
|
||||
{
|
||||
private:
|
||||
static T& instance_getter(typename snapshot::iterator::reference pair)
|
||||
{
|
||||
return *pair.second;
|
||||
}
|
||||
public:
|
||||
typedef boost::transform_iterator<decltype(instance_getter)*,
|
||||
typename snapshot::iterator> iterator;
|
||||
iterator begin() { return iterator(snapshot::begin(), instance_getter); }
|
||||
iterator end() { return iterator(snapshot::end(), instance_getter); }
|
||||
|
||||
class key_iter : public boost::iterator_facade<key_iter, KEY, boost::forward_traversal_tag>
|
||||
{
|
||||
public:
|
||||
typedef boost::iterator_facade<key_iter, KEY, boost::forward_traversal_tag> super_t;
|
||||
void deleteAll()
|
||||
{
|
||||
for (auto it(snapshot::begin()), end(snapshot::end()); it != end; ++it)
|
||||
{
|
||||
delete it->second.get();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
key_iter(typename InstanceMap::iterator it)
|
||||
: mIterator(it)
|
||||
{
|
||||
// <FS:ND> Minimize calls to getStatic
|
||||
#ifdef LL_DEBUG
|
||||
getStatic().incrementDepth();
|
||||
#endif
|
||||
// </FS:ND>
|
||||
}
|
||||
// iterate over this for each key
|
||||
class key_snapshot: public snapshot
|
||||
{
|
||||
private:
|
||||
static KEY key_getter(typename snapshot::iterator::reference pair)
|
||||
{
|
||||
return pair.first;
|
||||
}
|
||||
public:
|
||||
typedef boost::transform_iterator<decltype(key_getter)*,
|
||||
typename snapshot::iterator> iterator;
|
||||
iterator begin() { return iterator(snapshot::begin(), key_getter); }
|
||||
iterator end() { return iterator(snapshot::end(), key_getter); }
|
||||
};
|
||||
|
||||
key_iter(const key_iter& other)
|
||||
: mIterator(other.mIterator)
|
||||
{
|
||||
// <FS:ND> Minimize calls to getStatic
|
||||
#ifdef LL_DEBUG
|
||||
getStatic().incrementDepth();
|
||||
#endif
|
||||
// </FS:ND>
|
||||
}
|
||||
static T* getInstance(const KEY& k)
|
||||
{
|
||||
LockStatic lock;
|
||||
const InstanceMap& map(lock->mMap);
|
||||
typename InstanceMap::const_iterator found = map.find(k);
|
||||
return (found == map.end()) ? NULL : found->second.get();
|
||||
}
|
||||
|
||||
~key_iter()
|
||||
{
|
||||
// <FS:ND> Minimize calls to getStatic
|
||||
#ifdef LL_DEBUG
|
||||
getStatic().decrementDepth();
|
||||
#endif
|
||||
// </FS:ND>
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
friend class boost::iterator_core_access;
|
||||
|
||||
void increment() { mIterator++; }
|
||||
bool equal(key_iter const& other) const
|
||||
{
|
||||
return mIterator == other.mIterator;
|
||||
}
|
||||
|
||||
KEY& dereference() const
|
||||
{
|
||||
return const_cast<KEY&>(mIterator->first);
|
||||
}
|
||||
|
||||
typename InstanceMap::iterator mIterator;
|
||||
};
|
||||
|
||||
static T* getInstance(const KEY& k)
|
||||
{
|
||||
const InstanceMap& map(getMap_());
|
||||
typename InstanceMap::const_iterator found = map.find(k);
|
||||
return (found == map.end()) ? NULL : found->second;
|
||||
}
|
||||
|
||||
static instance_iter beginInstances()
|
||||
{
|
||||
return instance_iter(getMap_().begin());
|
||||
}
|
||||
|
||||
static instance_iter endInstances()
|
||||
{
|
||||
return instance_iter(getMap_().end());
|
||||
}
|
||||
|
||||
// while iterating over instances, might want to request the key
|
||||
virtual const KEY& getKey() const { return mInstanceKey; }
|
||||
|
||||
static S32 instanceCount()
|
||||
{
|
||||
return getMap_().size();
|
||||
}
|
||||
|
||||
static key_iter beginKeys()
|
||||
{
|
||||
return key_iter(getMap_().begin());
|
||||
}
|
||||
static key_iter endKeys()
|
||||
{
|
||||
return key_iter(getMap_().end());
|
||||
}
|
||||
static S32 instanceCount()
|
||||
{
|
||||
return LockStatic()->mMap.size();
|
||||
}
|
||||
|
||||
protected:
|
||||
LLInstanceTracker(const KEY& key)
|
||||
{
|
||||
// make sure static data outlives all instances
|
||||
getStatic();
|
||||
add_(key);
|
||||
}
|
||||
virtual ~LLInstanceTracker() LLINSTANCETRACKER_DTOR_NOEXCEPT
|
||||
{
|
||||
// it's unsafe to delete instances of this type while all instances are being iterated over.
|
||||
|
||||
// <FS:ND> Minimize calls to getStatic
|
||||
#ifdef LL_DEBUG
|
||||
llassert_always(getStatic().getDepth() == 0);
|
||||
#endif
|
||||
// </FS:ND>
|
||||
|
||||
remove_();
|
||||
}
|
||||
virtual void setKey(KEY key) { remove_(); add_(key); }
|
||||
LLInstanceTracker(const KEY& key)
|
||||
{
|
||||
// We do not intend to manage the lifespan of this object with
|
||||
// shared_ptr, so give it a no-op deleter. We store shared_ptrs in our
|
||||
// InstanceMap specifically so snapshot can store weak_ptrs so we can
|
||||
// detect deletions during traversals.
|
||||
std::shared_ptr<T> ptr(static_cast<T*>(this), [](T*){});
|
||||
LockStatic lock;
|
||||
add_(lock, key, ptr);
|
||||
}
|
||||
public:
|
||||
virtual ~LLInstanceTracker()
|
||||
{
|
||||
LockStatic lock;
|
||||
remove_(lock);
|
||||
}
|
||||
protected:
|
||||
virtual void setKey(KEY key)
|
||||
{
|
||||
LockStatic lock;
|
||||
// Even though the shared_ptr we store in our map has a no-op deleter
|
||||
// for T itself, letting the use count decrement to 0 will still
|
||||
// delete the use-count object. Capture the shared_ptr we just removed
|
||||
// and re-add it to the map with the new key.
|
||||
auto ptr = remove_(lock);
|
||||
add_(lock, key, ptr);
|
||||
}
|
||||
public:
|
||||
virtual const KEY& getKey() const { return mInstanceKey; }
|
||||
|
||||
private:
|
||||
LLInstanceTracker( const LLInstanceTracker& );
|
||||
const LLInstanceTracker& operator=( const LLInstanceTracker& );
|
||||
LLInstanceTracker( const LLInstanceTracker& ) = delete;
|
||||
LLInstanceTracker& operator=( const LLInstanceTracker& ) = delete;
|
||||
|
||||
void add_(const KEY& key)
|
||||
{
|
||||
mInstanceKey = key;
|
||||
InstanceMap& map = getMap_();
|
||||
typename InstanceMap::iterator insertion_point_it = map.lower_bound(key);
|
||||
if (insertion_point_it != map.end()
|
||||
&& insertion_point_it->first == key)
|
||||
{ // found existing entry with that key
|
||||
switch(KEY_COLLISION_BEHAVIOR)
|
||||
{
|
||||
case LLInstanceTrackerErrorOnCollision:
|
||||
{
|
||||
// use assert here instead of LL_ERRS(), otherwise the error will be ignored since this call is made during global object initialization
|
||||
llassert_always_msg(false, "Instance with this same key already exists!");
|
||||
break;
|
||||
}
|
||||
case LLInstanceTrackerReplaceOnCollision:
|
||||
{
|
||||
// replace pointer, but leave key (should have compared equal anyway)
|
||||
insertion_point_it->second = static_cast<T*>(this);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{ // new key
|
||||
map.insert(insertion_point_it, std::make_pair(key, static_cast<T*>(this)));
|
||||
}
|
||||
}
|
||||
void remove_()
|
||||
{
|
||||
InstanceMap& map = getMap_();
|
||||
typename InstanceMap::iterator iter = map.find(mInstanceKey);
|
||||
if (iter != map.end())
|
||||
{
|
||||
map.erase(iter);
|
||||
}
|
||||
}
|
||||
// for logging
|
||||
template <typename K>
|
||||
static std::string report(K key) { return stringize(key); }
|
||||
static std::string report(const std::string& key) { return "'" + key + "'"; }
|
||||
static std::string report(const char* key) { return report(std::string(key)); }
|
||||
|
||||
// caller must instantiate LockStatic
|
||||
void add_(LockStatic& lock, const KEY& key, const std::shared_ptr<T>& ptr)
|
||||
{
|
||||
mInstanceKey = key;
|
||||
InstanceMap& map = lock->mMap;
|
||||
switch(KEY_COLLISION_BEHAVIOR)
|
||||
{
|
||||
case LLInstanceTrackerErrorOnCollision:
|
||||
{
|
||||
// map stores shared_ptr to self
|
||||
auto pair = map.emplace(key, ptr);
|
||||
if (! pair.second)
|
||||
{
|
||||
LLInstanceTrackerPrivate::logerrs(typeid(*this).name(), " instance with key ",
|
||||
report(key), " already exists!");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case LLInstanceTrackerReplaceOnCollision:
|
||||
map[key] = ptr;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
std::shared_ptr<T> remove_(LockStatic& lock)
|
||||
{
|
||||
InstanceMap& map = lock->mMap;
|
||||
typename InstanceMap::iterator iter = map.find(mInstanceKey);
|
||||
if (iter != map.end())
|
||||
{
|
||||
auto ret = iter->second;
|
||||
map.erase(iter);
|
||||
return ret;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
private:
|
||||
KEY mInstanceKey;
|
||||
KEY mInstanceKey;
|
||||
};
|
||||
|
||||
/*****************************************************************************
|
||||
* LLInstanceTracker without key
|
||||
*****************************************************************************/
|
||||
// TODO:
|
||||
// - For the case of omitted KEY template parameter, consider storing
|
||||
// std::map<T*, std::shared_ptr<T>> instead of std::set<std::shared_ptr<T>>.
|
||||
// That might let us share more of the implementation between KEY and
|
||||
// non-KEY LLInstanceTracker subclasses.
|
||||
// - Even if not that, consider trying to unify the snapshot implementations.
|
||||
// The trouble is that the 'iterator' published by each (and by their
|
||||
// subclasses) must reflect the specific type of the callables that
|
||||
// distinguish them. (Maybe make instance_snapshot() and key_snapshot()
|
||||
// factory functions that pass lambdas to a factory function for the generic
|
||||
// template class?)
|
||||
|
||||
/// explicit specialization for default case where KEY is void
|
||||
/// use a simple std::set<T*>
|
||||
template<typename T, EInstanceTrackerAllowKeyCollisions KEY_COLLISION_BEHAVIOR>
|
||||
class LLInstanceTracker<T, void, KEY_COLLISION_BEHAVIOR> : public LLInstanceTrackerBase
|
||||
class LLInstanceTracker<T, void, KEY_COLLISION_BEHAVIOR>
|
||||
{
|
||||
protected:
|
||||
typedef LLInstanceTracker<T, void> self_t;
|
||||
typedef typename std::set<T*> InstanceSet;
|
||||
struct StaticData: public StaticBase
|
||||
{
|
||||
InstanceSet sSet;
|
||||
};
|
||||
static StaticData& getStatic() { static StaticData sData; return sData; }
|
||||
static InstanceSet& getSet_() { return getStatic().sSet; }
|
||||
typedef std::set<std::shared_ptr<T>> InstanceSet;
|
||||
struct StaticData: public LLInstanceTrackerPrivate::StaticBase
|
||||
{
|
||||
InstanceSet mSet;
|
||||
};
|
||||
typedef llthread::LockStatic<StaticData> LockStatic;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Storing a dumb T* somewhere external is a bad idea, since
|
||||
* LLInstanceTracker subclasses are explicitly destroyed rather than
|
||||
* managed by smart pointers. It's legal to declare stack instances of an
|
||||
* LLInstanceTracker subclass. But it's reasonable to store a
|
||||
* std::weak_ptr<T>, which will become invalid when the T instance is
|
||||
* destroyed.
|
||||
*/
|
||||
std::weak_ptr<T> getWeak()
|
||||
{
|
||||
return mSelf;
|
||||
}
|
||||
|
||||
static S32 instanceCount() { return LockStatic()->mSet.size(); }
|
||||
|
||||
/**
|
||||
* Does a particular instance still exist? Of course, if you already have
|
||||
* a T* in hand, you need not call getInstance() to @em locate the
|
||||
* instance -- unlike the case where getInstance() accepts some kind of
|
||||
* key. Nonetheless this method is still useful to @em validate a
|
||||
* particular T*, since each instance's destructor removes itself from the
|
||||
* underlying set.
|
||||
*/
|
||||
static T* getInstance(T* k)
|
||||
{
|
||||
const InstanceSet& set(getSet_());
|
||||
typename InstanceSet::const_iterator found = set.find(k);
|
||||
return (found == set.end())? NULL : *found;
|
||||
}
|
||||
static S32 instanceCount() { return getSet_().size(); }
|
||||
// snapshot of std::shared_ptr<T> pointers
|
||||
class snapshot
|
||||
{
|
||||
// It's very important that what we store in this snapshot are
|
||||
// weak_ptrs, NOT shared_ptrs. That's how we discover whether any
|
||||
// instance has been deleted during the lifespan of a snapshot.
|
||||
typedef std::vector<std::weak_ptr<T>> VectorType;
|
||||
// Dereferencing our iterator produces a std::shared_ptr for each
|
||||
// instance that still exists. Since we store weak_ptrs, that involves
|
||||
// two chained transformations:
|
||||
// - a transform_iterator to lock the weak_ptr and return a shared_ptr
|
||||
// - a filter_iterator to skip any shared_ptr that has become invalid.
|
||||
typedef std::shared_ptr<T> strong_ptr;
|
||||
static strong_ptr strengthen(typename VectorType::value_type& ptr)
|
||||
{
|
||||
return ptr.lock();
|
||||
}
|
||||
static bool dead_skipper(const strong_ptr& ptr)
|
||||
{
|
||||
return bool(ptr);
|
||||
}
|
||||
|
||||
class instance_iter : public boost::iterator_facade<instance_iter, T, boost::forward_traversal_tag>
|
||||
{
|
||||
public:
|
||||
instance_iter(const typename InstanceSet::iterator& it)
|
||||
: mIterator(it)
|
||||
{
|
||||
// <FS:ND> Minimize calls to getStatic
|
||||
#ifdef LL_DEBUG
|
||||
getStatic().incrementDepth();
|
||||
#endif
|
||||
// </FS:ND>
|
||||
}
|
||||
public:
|
||||
snapshot():
|
||||
// populate our vector with a snapshot of (locked!) InstanceSet
|
||||
// note, this assigns stored shared_ptrs to weak_ptrs for snapshot
|
||||
mData(mLock->mSet.begin(), mLock->mSet.end())
|
||||
{
|
||||
// release the lock once we've populated mData
|
||||
mLock.unlock();
|
||||
}
|
||||
|
||||
instance_iter(const instance_iter& other)
|
||||
: mIterator(other.mIterator)
|
||||
{
|
||||
// <FS:ND> Minimize calls to getStatic
|
||||
#ifdef LL_DEBUG
|
||||
getStatic().incrementDepth();
|
||||
#endif
|
||||
}
|
||||
typedef boost::transform_iterator<decltype(strengthen)*,
|
||||
typename VectorType::iterator> strong_iterator;
|
||||
typedef boost::filter_iterator<decltype(dead_skipper)*, strong_iterator> iterator;
|
||||
|
||||
~instance_iter()
|
||||
{
|
||||
// <FS:ND> Minimize calls to getStatic
|
||||
#ifdef LL_DEBUG
|
||||
getStatic().decrementDepth();
|
||||
#endif
|
||||
// </FS:ND>
|
||||
}
|
||||
iterator begin() { return make_iterator(mData.begin()); }
|
||||
iterator end() { return make_iterator(mData.end()); }
|
||||
|
||||
private:
|
||||
friend class boost::iterator_core_access;
|
||||
private:
|
||||
iterator make_iterator(typename VectorType::iterator iter)
|
||||
{
|
||||
// transform_iterator only needs the base iterator and the transform.
|
||||
// filter_iterator wants the predicate and both ends of the range.
|
||||
return iterator(dead_skipper,
|
||||
strong_iterator(iter, strengthen),
|
||||
strong_iterator(mData.end(), strengthen));
|
||||
}
|
||||
|
||||
void increment() { mIterator++; }
|
||||
bool equal(instance_iter const& other) const
|
||||
{
|
||||
return mIterator == other.mIterator;
|
||||
}
|
||||
// lock static data during construction
|
||||
#if ! LL_WINDOWS
|
||||
LockStatic mLock;
|
||||
#else // LL_WINDOWS
|
||||
// We want to be able to use our instance_snapshot subclass as:
|
||||
// for (auto& inst : T::instance_snapshot()) ...
|
||||
// But when this snapshot base class directly contains LockStatic, as
|
||||
// above, Visual Studio 2017 requires us to code instead:
|
||||
// for (auto& inst : std::move(T::instance_snapshot())) ...
|
||||
// nat thinks this should be unnecessary, as an anonymous class
|
||||
// instance is already a temporary. It shouldn't need to be cast to
|
||||
// rvalue reference (the role of std::move()). clang evidently agrees,
|
||||
// as the short form works fine with Xcode on Mac.
|
||||
// To support the succinct usage, instead of directly storing
|
||||
// LockStatic, store std::shared_ptr<LockStatic>, which is copyable.
|
||||
std::shared_ptr<LockStatic> mLockp{std::make_shared<LockStatic>()};
|
||||
LockStatic& mLock{*mLockp};
|
||||
#endif // LL_WINDOWS
|
||||
VectorType mData;
|
||||
};
|
||||
|
||||
T& dereference() const
|
||||
{
|
||||
return **mIterator;
|
||||
}
|
||||
// iterate over this for references to each instance
|
||||
struct instance_snapshot: public snapshot
|
||||
{
|
||||
typedef boost::indirect_iterator<typename snapshot::iterator> iterator;
|
||||
iterator begin() { return iterator(snapshot::begin()); }
|
||||
iterator end() { return iterator(snapshot::end()); }
|
||||
|
||||
typename InstanceSet::iterator mIterator;
|
||||
};
|
||||
|
||||
static instance_iter beginInstances() { return instance_iter(getSet_().begin()); }
|
||||
static instance_iter endInstances() { return instance_iter(getSet_().end()); }
|
||||
void deleteAll()
|
||||
{
|
||||
for (auto it(snapshot::begin()), end(snapshot::end()); it != end; ++it)
|
||||
{
|
||||
delete it->get();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
protected:
|
||||
LLInstanceTracker()
|
||||
{
|
||||
// make sure static data outlives all instances
|
||||
getStatic();
|
||||
getSet_().insert(static_cast<T*>(this));
|
||||
}
|
||||
virtual ~LLInstanceTracker() LLINSTANCETRACKER_DTOR_NOEXCEPT
|
||||
{
|
||||
// it's unsafe to delete instances of this type while all instances are being iterated over.
|
||||
LLInstanceTracker()
|
||||
{
|
||||
// Since we do not intend for this shared_ptr to manage lifespan, give
|
||||
// it a no-op deleter.
|
||||
std::shared_ptr<T> ptr(static_cast<T*>(this), [](T*){});
|
||||
// save corresponding weak_ptr for future reference
|
||||
mSelf = ptr;
|
||||
// Also store it in our class-static set to track this instance.
|
||||
LockStatic()->mSet.emplace(ptr);
|
||||
}
|
||||
public:
|
||||
virtual ~LLInstanceTracker()
|
||||
{
|
||||
// convert weak_ptr to shared_ptr because that's what we store in our
|
||||
// InstanceSet
|
||||
LockStatic()->mSet.erase(mSelf.lock());
|
||||
}
|
||||
protected:
|
||||
LLInstanceTracker(const LLInstanceTracker& other):
|
||||
LLInstanceTracker()
|
||||
{}
|
||||
|
||||
// <FS:ND> Minimize calls to getStatic
|
||||
#ifdef LL_DEBUG
|
||||
llassert_always(getStatic().getDepth() == 0);
|
||||
#endif
|
||||
// </FS:ND>
|
||||
|
||||
getSet_().erase(static_cast<T*>(this));
|
||||
}
|
||||
|
||||
LLInstanceTracker(const LLInstanceTracker& other)
|
||||
{
|
||||
getSet_().insert(static_cast<T*>(this));
|
||||
}
|
||||
private:
|
||||
// Storing a weak_ptr to self is a bit like deriving from
|
||||
// std::enable_shared_from_this(), except more explicit.
|
||||
std::weak_ptr<T> mSelf;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -208,13 +208,11 @@ void LLLeapListener::getAPIs(const LLSD& request) const
|
|||
{
|
||||
Response reply(LLSD(), request);
|
||||
|
||||
for (LLEventAPI::instance_iter eai(LLEventAPI::beginInstances()),
|
||||
eaend(LLEventAPI::endInstances());
|
||||
eai != eaend; ++eai)
|
||||
for (auto& ea : LLEventAPI::instance_snapshot())
|
||||
{
|
||||
LLSD info;
|
||||
info["desc"] = eai->getDesc();
|
||||
reply[eai->getName()] = info;
|
||||
info["desc"] = ea.getDesc();
|
||||
reply[ea.getName()] = info;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
/**
|
||||
* @file llmainthreadtask.cpp
|
||||
* @author Nat Goodspeed
|
||||
* @date 2019-12-05
|
||||
* @brief Implementation for llmainthreadtask.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2019&license=viewerlgpl$
|
||||
* Copyright (c) 2019, Linden Research, Inc.
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
// Precompiled header
|
||||
#include "linden_common.h"
|
||||
// associated header
|
||||
#include "llmainthreadtask.h"
|
||||
// STL headers
|
||||
// std headers
|
||||
// external library headers
|
||||
// other Linden headers
|
||||
|
||||
// This file is required by our CMake integration-test machinery. It
|
||||
// contributes no code to the viewer executable.
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
/**
|
||||
* @file llmainthreadtask.h
|
||||
* @author Nat Goodspeed
|
||||
* @date 2019-12-04
|
||||
* @brief LLMainThreadTask dispatches work to the main thread. When invoked on
|
||||
* the main thread, it performs the work inline.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2019&license=viewerlgpl$
|
||||
* Copyright (c) 2019, Linden Research, Inc.
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
#if ! defined(LL_LLMAINTHREADTASK_H)
|
||||
#define LL_LLMAINTHREADTASK_H
|
||||
|
||||
#include "lleventtimer.h"
|
||||
#include "llthread.h"
|
||||
#include "llmake.h"
|
||||
#include <future>
|
||||
#include <type_traits> // std::result_of
|
||||
|
||||
/**
|
||||
* LLMainThreadTask provides a way to perform some task specifically on the
|
||||
* main thread, waiting for it to complete. A task consists of a C++ nullary
|
||||
* invocable (i.e. any callable that requires no arguments) with arbitrary
|
||||
* return type.
|
||||
*
|
||||
* Instead of instantiating LLMainThreadTask, pass your invocable to its
|
||||
* static dispatch() method. dispatch() returns the result of calling your
|
||||
* task. (Or, if your task throws an exception, dispatch() throws that
|
||||
* exception. See std::packaged_task.)
|
||||
*
|
||||
* When you call dispatch() on the main thread (as determined by
|
||||
* on_main_thread() in llthread.h), it simply calls your task and returns the
|
||||
* result.
|
||||
*
|
||||
* When you call dispatch() on a secondary thread, it instantiates an
|
||||
* LLEventTimer subclass scheduled immediately. Next time the main loop calls
|
||||
* LLEventTimer::updateClass(), your task will be run, and LLMainThreadTask
|
||||
* will fulfill a future with its result. Meanwhile the requesting thread
|
||||
* blocks on that future. As soon as it is set, the requesting thread wakes up
|
||||
* with the task result.
|
||||
*/
|
||||
class LLMainThreadTask
|
||||
{
|
||||
private:
|
||||
// Don't instantiate this class -- use dispatch() instead.
|
||||
LLMainThreadTask() {}
|
||||
|
||||
public:
|
||||
/// dispatch() is the only way to invoke this functionality.
|
||||
template <typename CALLABLE>
|
||||
static auto dispatch(CALLABLE&& callable) -> decltype(callable())
|
||||
{
|
||||
if (on_main_thread())
|
||||
{
|
||||
// we're already running on the main thread, perfect
|
||||
return callable();
|
||||
}
|
||||
else
|
||||
{
|
||||
// It's essential to construct LLEventTimer subclass instances on
|
||||
// the heap because, on completion, LLEventTimer deletes them.
|
||||
// Once we enable C++17, we can use Class Template Argument
|
||||
// Deduction. Until then, use llmake_heap().
|
||||
auto* task = llmake_heap<Task>(std::forward<CALLABLE>(callable));
|
||||
auto future = task->mTask.get_future();
|
||||
// Now simply block on the future.
|
||||
return future.get();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename CALLABLE>
|
||||
struct Task: public LLEventTimer
|
||||
{
|
||||
Task(CALLABLE&& callable):
|
||||
// no wait time: call tick() next chance we get
|
||||
LLEventTimer(0),
|
||||
mTask(std::forward<CALLABLE>(callable))
|
||||
{}
|
||||
BOOL tick() override
|
||||
{
|
||||
// run the task on the main thread, will populate the future
|
||||
// obtained by get_future()
|
||||
mTask();
|
||||
// tell LLEventTimer we're done (one shot)
|
||||
return TRUE;
|
||||
}
|
||||
// Given arbitrary CALLABLE, which might be a lambda, how are we
|
||||
// supposed to obtain its signature for std::packaged_task? It seems
|
||||
// redundant to have to add an argument list to engage result_of, then
|
||||
// add the argument list again to complete the signature. At least we
|
||||
// only support a nullary CALLABLE.
|
||||
std::packaged_task<typename std::result_of<CALLABLE()>::type()> mTask;
|
||||
};
|
||||
};
|
||||
|
||||
#endif /* ! defined(LL_LLMAINTHREADTASK_H) */
|
||||
|
|
@ -12,10 +12,8 @@
|
|||
*
|
||||
* also relevant:
|
||||
*
|
||||
* Template argument deduction for class templates
|
||||
* http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0091r3.html
|
||||
* was apparently adopted in June 2016? Unclear when compilers will
|
||||
* portably support this, but there is hope.
|
||||
* Template argument deduction for class templates (C++17)
|
||||
* https://en.cppreference.com/w/cpp/language/class_template_argument_deduction
|
||||
*
|
||||
* $LicenseInfo:firstyear=2015&license=viewerlgpl$
|
||||
* Copyright (c) 2015, Linden Research, Inc.
|
||||
|
|
@ -25,10 +23,62 @@
|
|||
#if ! defined(LL_LLMAKE_H)
|
||||
#define LL_LLMAKE_H
|
||||
|
||||
// If we're using a compiler newer than VS 2013, use variadic llmake().
|
||||
#if (! defined(_MSC_VER)) || (_MSC_VER > 1800)
|
||||
|
||||
/**
|
||||
* Usage: llmake<SomeTemplate>(args...)
|
||||
*
|
||||
* Deduces the types T... of 'args' and returns an instance of
|
||||
* SomeTemplate<T...>(args...).
|
||||
*/
|
||||
template <template<typename...> class CLASS_TEMPLATE, typename... ARGS>
|
||||
CLASS_TEMPLATE<ARGS...> llmake(ARGS && ... args)
|
||||
{
|
||||
return CLASS_TEMPLATE<ARGS...>(std::forward<ARGS>(args)...);
|
||||
}
|
||||
|
||||
#else // older implementation for VS 2013
|
||||
|
||||
template <template<typename> class CLASS_TEMPLATE, typename ARG1>
|
||||
CLASS_TEMPLATE<ARG1> llmake(const ARG1& arg1)
|
||||
{
|
||||
return CLASS_TEMPLATE<ARG1>(arg1);
|
||||
}
|
||||
|
||||
template <template<typename, typename> class CLASS_TEMPLATE, typename ARG1, typename ARG2>
|
||||
CLASS_TEMPLATE<ARG1, ARG2> llmake(const ARG1& arg1, const ARG2& arg2)
|
||||
{
|
||||
return CLASS_TEMPLATE<ARG1, ARG2>(arg1, arg2);
|
||||
}
|
||||
|
||||
#endif // VS 2013 workaround
|
||||
|
||||
/// dumb pointer template just in case that's what's wanted
|
||||
template <typename T>
|
||||
using dumb_pointer = T*;
|
||||
|
||||
/**
|
||||
* Same as llmake(), but returns a pointer to a new heap instance of
|
||||
* SomeTemplate<T...>(args...) using the pointer of your choice.
|
||||
*
|
||||
* @code
|
||||
* auto* dumb = llmake_heap<SomeTemplate>(args...);
|
||||
* auto shared = llmake_heap<SomeTemplate, std::shared_ptr>(args...);
|
||||
* auto unique = llmake_heap<SomeTemplate, std::unique_ptr>(args...);
|
||||
* @endcode
|
||||
*/
|
||||
// POINTER_TEMPLATE is characterized as template<typename...> rather than as
|
||||
// template<typename T> because (e.g.) std::unique_ptr has multiple template
|
||||
// arguments. Even though we only engage one, std::unique_ptr doesn't match a
|
||||
// template template parameter that itself takes only one template parameter.
|
||||
template <template<typename...> class CLASS_TEMPLATE,
|
||||
template<typename...> class POINTER_TEMPLATE=dumb_pointer,
|
||||
typename... ARGS>
|
||||
POINTER_TEMPLATE<CLASS_TEMPLATE<ARGS...>> llmake_heap(ARGS&&... args)
|
||||
{
|
||||
return POINTER_TEMPLATE<CLASS_TEMPLATE<ARGS...>>(
|
||||
new CLASS_TEMPLATE<ARGS...>(std::forward<ARGS>(args)...));
|
||||
}
|
||||
|
||||
#endif /* ! defined(LL_LLMAKE_H) */
|
||||
|
|
|
|||
|
|
@ -32,8 +32,7 @@
|
|||
//============================================================================
|
||||
|
||||
LLMutex::LLMutex() :
|
||||
mCount(0),
|
||||
mLockingThread(NO_THREAD)
|
||||
mCount(0)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -55,7 +54,7 @@ void LLMutex::lock()
|
|||
|
||||
#if MUTEX_DEBUG
|
||||
// Have to have the lock before we can access the debug info
|
||||
U32 id = LLThread::currentID();
|
||||
auto id = LLThread::currentID();
|
||||
if (mIsLocked[id] != FALSE)
|
||||
LL_ERRS() << "Already locked in Thread: " << id << LL_ENDL;
|
||||
mIsLocked[id] = TRUE;
|
||||
|
|
@ -74,13 +73,13 @@ void LLMutex::unlock()
|
|||
|
||||
#if MUTEX_DEBUG
|
||||
// Access the debug info while we have the lock
|
||||
U32 id = LLThread::currentID();
|
||||
auto id = LLThread::currentID();
|
||||
if (mIsLocked[id] != TRUE)
|
||||
LL_ERRS() << "Not locked in Thread: " << id << LL_ENDL;
|
||||
mIsLocked[id] = FALSE;
|
||||
#endif
|
||||
|
||||
mLockingThread = NO_THREAD;
|
||||
mLockingThread = LLThread::id_t();
|
||||
mMutex.unlock();
|
||||
}
|
||||
|
||||
|
|
@ -102,7 +101,7 @@ bool LLMutex::isSelfLocked()
|
|||
return mLockingThread == LLThread::currentID();
|
||||
}
|
||||
|
||||
U32 LLMutex::lockingThread() const
|
||||
LLThread::id_t LLMutex::lockingThread() const
|
||||
{
|
||||
return mLockingThread;
|
||||
}
|
||||
|
|
@ -122,7 +121,7 @@ bool LLMutex::trylock()
|
|||
|
||||
#if MUTEX_DEBUG
|
||||
// Have to have the lock before we can access the debug info
|
||||
U32 id = LLThread::currentID();
|
||||
auto id = LLThread::currentID();
|
||||
if (mIsLocked[id] != FALSE)
|
||||
LL_ERRS() << "Already locked in Thread: " << id << LL_ENDL;
|
||||
mIsLocked[id] = TRUE;
|
||||
|
|
|
|||
|
|
@ -28,20 +28,12 @@
|
|||
#define LL_LLMUTEX_H
|
||||
|
||||
#include "stdtypes.h"
|
||||
#include "llthread.h"
|
||||
#include <boost/noncopyable.hpp>
|
||||
|
||||
#if LL_WINDOWS
|
||||
#pragma warning (push)
|
||||
#pragma warning (disable:4265)
|
||||
#endif
|
||||
// 'std::_Pad' : class has virtual functions, but destructor is not virtual
|
||||
#include <mutex>
|
||||
#include "mutex.h"
|
||||
#include <condition_variable>
|
||||
|
||||
#if LL_WINDOWS
|
||||
#pragma warning (pop)
|
||||
#endif
|
||||
|
||||
//============================================================================
|
||||
|
||||
#define MUTEX_DEBUG (LL_DEBUG || LL_RELEASE_WITH_DEBUG_INFO)
|
||||
|
|
@ -53,11 +45,6 @@
|
|||
class LL_COMMON_API LLMutex
|
||||
{
|
||||
public:
|
||||
typedef enum
|
||||
{
|
||||
NO_THREAD = 0xFFFFFFFF
|
||||
} e_locking_thread;
|
||||
|
||||
LLMutex();
|
||||
virtual ~LLMutex();
|
||||
|
||||
|
|
@ -66,15 +53,15 @@ public:
|
|||
void unlock(); // undefined behavior when called on mutex not being held
|
||||
bool isLocked(); // non-blocking, but does do a lock/unlock so not free
|
||||
bool isSelfLocked(); //return true if locked in a same thread
|
||||
U32 lockingThread() const; //get ID of locking thread
|
||||
|
||||
LLThread::id_t lockingThread() const; //get ID of locking thread
|
||||
|
||||
protected:
|
||||
std::mutex mMutex;
|
||||
mutable U32 mCount;
|
||||
mutable U32 mLockingThread;
|
||||
mutable LLThread::id_t mLockingThread;
|
||||
|
||||
#if MUTEX_DEBUG
|
||||
std::map<U32, BOOL> mIsLocked;
|
||||
std::map<LLThread::id_t, BOOL> mIsLocked;
|
||||
#endif
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -28,9 +28,10 @@
|
|||
|
||||
#include <boost/noncopyable.hpp>
|
||||
#include <boost/intrusive_ptr.hpp>
|
||||
#include "llmutex.h"
|
||||
#include "llatomic.h"
|
||||
|
||||
class LLMutex;
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// RefCount objects should generally only be accessed by way of LLPointer<>'s
|
||||
// see llthread.h for LLThreadSafeRefCount
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
#include "llerror.h"
|
||||
#include "llerrorcontrol.h" // LLError::is_available()
|
||||
#include "lldependencies.h"
|
||||
#include "llexception.h"
|
||||
#include "llcoros.h"
|
||||
#include "llexception.h"
|
||||
#include <boost/foreach.hpp>
|
||||
|
|
@ -42,8 +43,6 @@ namespace {
|
|||
void log(LLError::ELevel level,
|
||||
const char* p1, const char* p2, const char* p3, const char* p4);
|
||||
|
||||
void logdebugs(const char* p1="", const char* p2="", const char* p3="", const char* p4="");
|
||||
|
||||
bool oktolog();
|
||||
} // anonymous namespace
|
||||
|
||||
|
|
@ -117,7 +116,7 @@ private:
|
|||
// stack for every running coroutine. Therefore this stack must be based
|
||||
// on a coroutine-local pointer.
|
||||
// This local_ptr isn't static because it's a member of an LLSingleton.
|
||||
LLCoros::local_ptr<LLSingletonBase::list_t> mInitializing;
|
||||
LLCoros::local_ptr<list_t> mInitializing;
|
||||
|
||||
public:
|
||||
// Instantiate this to obtain a reference to the coroutine-specific
|
||||
|
|
@ -297,7 +296,7 @@ void LLSingletonBase::MasterList::LockedInitializing::log(const char* verb, cons
|
|||
}
|
||||
}
|
||||
|
||||
void LLSingletonBase::capture_dependency(EInitState initState)
|
||||
void LLSingletonBase::capture_dependency()
|
||||
{
|
||||
MasterList::LockedInitializing locked_list;
|
||||
list_t& initializing(locked_list.get());
|
||||
|
|
@ -329,21 +328,8 @@ void LLSingletonBase::capture_dependency(EInitState initState)
|
|||
LLSingletonBase* foundp(*found);
|
||||
out << classname(foundp) << " -> ";
|
||||
}
|
||||
// We promise to capture dependencies from both the constructor
|
||||
// and the initSingleton() method, so an LLSingleton's instance
|
||||
// pointer is on the initializing list during both. Now that we've
|
||||
// detected circularity, though, we must distinguish the two. If
|
||||
// the recursive call is from the constructor, we CAN'T honor it:
|
||||
// otherwise we'd be returning a pointer to a partially-
|
||||
// constructed object! But from initSingleton() is okay: that
|
||||
// method exists specifically to support circularity.
|
||||
// Decide which log helper to call.
|
||||
if (initState == CONSTRUCTING)
|
||||
{
|
||||
logerrs("LLSingleton circularity in Constructor: ", out.str().c_str(),
|
||||
classname(this).c_str(), "");
|
||||
}
|
||||
else if (it_next == initializing.end())
|
||||
if (it_next == initializing.end())
|
||||
{
|
||||
// Points to self after construction, but during initialization.
|
||||
// Singletons can initialize other classes that depend onto them,
|
||||
|
|
@ -391,7 +377,7 @@ LLSingletonBase::vec_t LLSingletonBase::dep_sort()
|
|||
SingletonDeps sdeps;
|
||||
// Lock while traversing the master list
|
||||
MasterList::LockedMaster master;
|
||||
BOOST_FOREACH(LLSingletonBase* sp, master.get())
|
||||
for (LLSingletonBase* sp : master.get())
|
||||
{
|
||||
// Build the SingletonDeps structure by adding, for each
|
||||
// LLSingletonBase* sp in the master list, sp itself. It has no
|
||||
|
|
@ -408,14 +394,14 @@ LLSingletonBase::vec_t LLSingletonBase::dep_sort()
|
|||
// extracts just the first (key) element from each sorted_iterator, then
|
||||
// uses vec_t's range constructor... but frankly this is more
|
||||
// straightforward, as long as we remember the above reserve() call!
|
||||
BOOST_FOREACH(SingletonDeps::sorted_iterator::value_type pair, sdeps.sort())
|
||||
for (const SingletonDeps::sorted_iterator::value_type& pair : sdeps.sort())
|
||||
{
|
||||
ret.push_back(pair.first);
|
||||
}
|
||||
// The master list is not itself pushed onto the master list. Add it as
|
||||
// the very last entry -- it is the LLSingleton on which ALL others
|
||||
// depend! -- so our caller will process it.
|
||||
ret.push_back(MasterList::getInstance());
|
||||
ret.push_back(&master.Lock::get());
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -426,13 +412,9 @@ void LLSingletonBase::cleanup_()
|
|||
{
|
||||
cleanupSingleton();
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
logwarns("Exception in ", classname(this).c_str(), "::cleanupSingleton(): ", e.what());
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
logwarns("Unknown exception in ", classname(this).c_str(), "::cleanupSingleton()");
|
||||
LOG_UNHANDLED_EXCEPTION(classname(this) + "::cleanupSingleton()");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -503,10 +485,6 @@ void log(LLError::ELevel level,
|
|||
}
|
||||
}
|
||||
|
||||
void logdebugs(const char* p1, const char* p2, const char* p3, const char* p4)
|
||||
{
|
||||
log(LLError::LEVEL_DEBUG, p1, p2, p3, p4);
|
||||
}
|
||||
} // anonymous namespace
|
||||
|
||||
//static
|
||||
|
|
@ -515,6 +493,18 @@ void LLSingletonBase::logwarns(const char* p1, const char* p2, const char* p3, c
|
|||
log(LLError::LEVEL_WARN, p1, p2, p3, p4);
|
||||
}
|
||||
|
||||
//static
|
||||
void LLSingletonBase::loginfos(const char* p1, const char* p2, const char* p3, const char* p4)
|
||||
{
|
||||
log(LLError::LEVEL_INFO, p1, p2, p3, p4);
|
||||
}
|
||||
|
||||
//static
|
||||
void LLSingletonBase::logdebugs(const char* p1, const char* p2, const char* p3, const char* p4)
|
||||
{
|
||||
log(LLError::LEVEL_DEBUG, p1, p2, p3, p4);
|
||||
}
|
||||
|
||||
//static
|
||||
void LLSingletonBase::logerrs(const char* p1, const char* p2, const char* p3, const char* p4)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -30,18 +30,10 @@
|
|||
#include <list>
|
||||
#include <vector>
|
||||
#include <typeinfo>
|
||||
|
||||
#if LL_WINDOWS
|
||||
#pragma warning (push)
|
||||
#pragma warning (disable:4265)
|
||||
#endif
|
||||
// warning C4265: 'std::_Pad' : class has virtual functions, but destructor is not virtual
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#if LL_WINDOWS
|
||||
#pragma warning (pop)
|
||||
#endif
|
||||
#include "mutex.h"
|
||||
#include "lockstatic.h"
|
||||
#include "llthread.h" // on_main_thread()
|
||||
#include "llmainthreadtask.h"
|
||||
|
||||
class LLSingletonBase: private boost::noncopyable
|
||||
{
|
||||
|
|
@ -66,8 +58,8 @@ protected:
|
|||
typedef enum e_init_state
|
||||
{
|
||||
UNINITIALIZED = 0, // must be default-initialized state
|
||||
QUEUED, // construction queued, not yet executing
|
||||
CONSTRUCTING, // within DERIVED_TYPE constructor
|
||||
CONSTRUCTED, // finished DERIVED_TYPE constructor
|
||||
INITIALIZING, // within DERIVED_TYPE::initSingleton()
|
||||
INITIALIZED, // normal case
|
||||
DELETED // deleteSingleton() or deleteAll() called
|
||||
|
|
@ -116,14 +108,17 @@ protected:
|
|||
protected:
|
||||
// If a given call to B::getInstance() happens during either A::A() or
|
||||
// A::initSingleton(), record that A directly depends on B.
|
||||
void capture_dependency(EInitState);
|
||||
void capture_dependency();
|
||||
|
||||
// delegate LL_ERRS() logging to llsingleton.cpp
|
||||
// delegate logging calls to llsingleton.cpp
|
||||
static void logerrs(const char* p1, const char* p2="",
|
||||
const char* p3="", const char* p4="");
|
||||
// delegate LL_WARNS() logging to llsingleton.cpp
|
||||
static void logwarns(const char* p1, const char* p2="",
|
||||
const char* p3="", const char* p4="");
|
||||
static void loginfos(const char* p1, const char* p2="",
|
||||
const char* p3="", const char* p4="");
|
||||
static void logdebugs(const char* p1, const char* p2="",
|
||||
const char* p3="", const char* p4="");
|
||||
static std::string demangle(const char* mangled);
|
||||
template <typename T>
|
||||
static std::string classname() { return demangle(typeid(T).name()); }
|
||||
|
|
@ -190,9 +185,9 @@ struct LLSingleton_manage_master
|
|||
{
|
||||
return LLSingletonBase::get_initializing_size();
|
||||
}
|
||||
void capture_dependency(LLSingletonBase* sb, LLSingletonBase::EInitState state)
|
||||
void capture_dependency(LLSingletonBase* sb)
|
||||
{
|
||||
sb->capture_dependency(state);
|
||||
sb->capture_dependency();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -207,13 +202,13 @@ struct LLSingleton_manage_master<LLSingletonBase::MasterList>
|
|||
// since we never pushed, no need to clean up
|
||||
void reset_initializing(LLSingletonBase::list_t::size_type size) {}
|
||||
LLSingletonBase::list_t::size_type get_initializing_size() { return 0; }
|
||||
void capture_dependency(LLSingletonBase*, LLSingletonBase::EInitState) {}
|
||||
void capture_dependency(LLSingletonBase*) {}
|
||||
};
|
||||
|
||||
// Now we can implement LLSingletonBase's template constructor.
|
||||
template <typename DERIVED_TYPE>
|
||||
LLSingletonBase::LLSingletonBase(tag<DERIVED_TYPE>):
|
||||
mDeleteSingleton(NULL)
|
||||
mDeleteSingleton(nullptr)
|
||||
{
|
||||
// This is the earliest possible point at which we can push this new
|
||||
// instance onto the init stack. LLSingleton::constructSingleton() can't
|
||||
|
|
@ -283,11 +278,38 @@ class LLParamSingleton;
|
|||
* initSingleton() method explicitly depends on some other LLSingleton
|
||||
* subclass, you may continue to rely on that other subclass in your
|
||||
* cleanupSingleton() method.
|
||||
*
|
||||
* We introduce a special cleanupSingleton() method because cleanupSingleton()
|
||||
* operations can involve nontrivial realtime, or might throw an exception. A
|
||||
* destructor should do neither!
|
||||
*
|
||||
* If your cleanupSingleton() method throws an exception, we log that
|
||||
* exception but proceed with the remaining cleanupSingleton() calls.
|
||||
*
|
||||
* Similarly, if at some point you call LLSingletonBase::deleteAll(), all
|
||||
* remaining LLSingleton instances will be destroyed in dependency order. (Or
|
||||
* call MySubclass::deleteSingleton() to specifically destroy the canonical
|
||||
* MySubclass instance.)
|
||||
*/
|
||||
template <typename DERIVED_TYPE>
|
||||
class LLSingleton : public LLSingletonBase
|
||||
{
|
||||
private:
|
||||
// LLSingleton<DERIVED_TYPE> must have a distinct instance of
|
||||
// SingletonData for every distinct DERIVED_TYPE. It's tempting to
|
||||
// consider hoisting SingletonData up into LLSingletonBase. Don't do it.
|
||||
struct SingletonData
|
||||
{
|
||||
// Use a recursive_mutex in case of constructor circularity. With a
|
||||
// non-recursive mutex, that would result in deadlock.
|
||||
typedef std::recursive_mutex mutex_t;
|
||||
mutex_t mMutex; // LockStatic looks for mMutex
|
||||
|
||||
EInitState mInitState{UNINITIALIZED};
|
||||
DERIVED_TYPE* mInstance{nullptr};
|
||||
};
|
||||
typedef llthread::LockStatic<SingletonData> LockStatic;
|
||||
|
||||
// Allow LLParamSingleton subclass -- but NOT DERIVED_TYPE itself -- to
|
||||
// access our private members.
|
||||
friend class LLParamSingleton<DERIVED_TYPE>;
|
||||
|
|
@ -337,17 +359,17 @@ private:
|
|||
// purpose for its subclass LLParamSingleton is to support Singletons
|
||||
// requiring constructor arguments. constructSingleton() supports both use
|
||||
// cases.
|
||||
// Accepting LockStatic& requires that the caller has already locked our
|
||||
// static data before calling.
|
||||
template <typename... Args>
|
||||
static void constructSingleton(Args&&... args)
|
||||
static void constructSingleton(LockStatic& lk, Args&&... args)
|
||||
{
|
||||
auto prev_size = LLSingleton_manage_master<DERIVED_TYPE>().get_initializing_size();
|
||||
// getInstance() calls are from within constructor
|
||||
sData.mInitState = CONSTRUCTING;
|
||||
// Any getInstance() calls after this point are from within constructor
|
||||
lk->mInitState = CONSTRUCTING;
|
||||
try
|
||||
{
|
||||
sData.mInstance = new DERIVED_TYPE(std::forward<Args>(args)...);
|
||||
// we have called constructor, have not yet called initSingleton()
|
||||
sData.mInitState = CONSTRUCTED;
|
||||
lk->mInstance = new DERIVED_TYPE(std::forward<Args>(args)...);
|
||||
}
|
||||
catch (const std::exception& err)
|
||||
{
|
||||
|
|
@ -361,58 +383,56 @@ private:
|
|||
// There isn't a separate EInitState value meaning "we attempted
|
||||
// to construct this LLSingleton subclass but could not," so use
|
||||
// DELETED. That seems slightly more appropriate than UNINITIALIZED.
|
||||
sData.mInitState = DELETED;
|
||||
lk->mInitState = DELETED;
|
||||
// propagate the exception
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
static void finishInitializing()
|
||||
{
|
||||
// getInstance() calls are from within initSingleton()
|
||||
sData.mInitState = INITIALIZING;
|
||||
// Any getInstance() calls after this point are from within initSingleton()
|
||||
lk->mInitState = INITIALIZING;
|
||||
try
|
||||
{
|
||||
// initialize singleton after constructing it so that it can
|
||||
// reference other singletons which in turn depend on it, thus
|
||||
// breaking cyclic dependencies
|
||||
sData.mInstance->initSingleton();
|
||||
sData.mInitState = INITIALIZED;
|
||||
lk->mInstance->initSingleton();
|
||||
lk->mInitState = INITIALIZED;
|
||||
|
||||
// pop this off stack of initializing singletons
|
||||
pop_initializing();
|
||||
pop_initializing(lk->mInstance);
|
||||
}
|
||||
catch (const std::exception& err)
|
||||
{
|
||||
// pop this off stack of initializing singletons here, too --
|
||||
// BEFORE logging, so log-machinery LLSingletons don't record a
|
||||
// dependency on DERIVED_TYPE!
|
||||
pop_initializing();
|
||||
pop_initializing(lk->mInstance);
|
||||
logwarns("Error in ", classname<DERIVED_TYPE>().c_str(),
|
||||
"::initSingleton(): ", err.what());
|
||||
// and get rid of the instance entirely
|
||||
// Get rid of the instance entirely. This call depends on our
|
||||
// recursive_mutex. We could have a deleteSingleton(LockStatic&)
|
||||
// overload and pass lk, but we don't strictly need it.
|
||||
deleteSingleton();
|
||||
// propagate the exception
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
static void pop_initializing()
|
||||
static void pop_initializing(LLSingletonBase* sb)
|
||||
{
|
||||
// route through LLSingleton_manage_master so we Do The Right Thing
|
||||
// (namely, nothing) for MasterList
|
||||
LLSingleton_manage_master<DERIVED_TYPE>().pop_initializing(sData.mInstance);
|
||||
LLSingleton_manage_master<DERIVED_TYPE>().pop_initializing(sb);
|
||||
}
|
||||
|
||||
static void capture_dependency()
|
||||
static void capture_dependency(LLSingletonBase* sb)
|
||||
{
|
||||
// By this point, if DERIVED_TYPE was pushed onto the initializing
|
||||
// stack, it has been popped off. So the top of that stack, if any, is
|
||||
// an LLSingleton that directly depends on DERIVED_TYPE. If
|
||||
// getInstance() was called by another LLSingleton, rather than from
|
||||
// vanilla application code, record the dependency.
|
||||
LLSingleton_manage_master<DERIVED_TYPE>().capture_dependency(
|
||||
sData.mInstance, sData.mInitState);
|
||||
LLSingleton_manage_master<DERIVED_TYPE>().capture_dependency(sb);
|
||||
}
|
||||
|
||||
// We know of no way to instruct the compiler that every subclass
|
||||
|
|
@ -442,14 +462,16 @@ protected:
|
|||
protected:
|
||||
virtual ~LLSingleton()
|
||||
{
|
||||
// In case racing threads call getInstance() at the same moment as
|
||||
// this destructor, serialize the calls.
|
||||
Locker lk;
|
||||
// This phase of cleanup is performed in the destructor rather than in
|
||||
// deleteSingleton() to defend against manual deletion. When we moved
|
||||
// cleanup to deleteSingleton(), we hit crashes due to dangling
|
||||
// pointers in the MasterList.
|
||||
LockStatic lk;
|
||||
lk->mInstance = nullptr;
|
||||
lk->mInitState = DELETED;
|
||||
|
||||
// remove this instance from the master list
|
||||
// Remove this instance from the master list.
|
||||
LLSingleton_manage_master<DERIVED_TYPE>().remove(this);
|
||||
sData.mInstance = NULL;
|
||||
sData.mInitState = DELETED;
|
||||
}
|
||||
|
||||
public:
|
||||
|
|
@ -469,71 +491,144 @@ public:
|
|||
*/
|
||||
static void deleteSingleton()
|
||||
{
|
||||
// first call cleanupSingleton()
|
||||
if (sData.mInstance)
|
||||
// Hold the lock while we call cleanupSingleton() and the destructor.
|
||||
// Our destructor also instantiates LockStatic, requiring a recursive
|
||||
// mutex.
|
||||
LockStatic lk;
|
||||
// of course, only cleanup and delete if there's something there
|
||||
if (lk->mInstance)
|
||||
{
|
||||
sData.mInstance->cleanup_();
|
||||
lk->mInstance->cleanup_();
|
||||
delete lk->mInstance;
|
||||
// destructor clears mInstance (and mInitState)
|
||||
}
|
||||
// capture the instance and clear SingletonData
|
||||
auto lameduck = sData.mInstance;
|
||||
sData.mInstance = NULL;
|
||||
sData.mInitState = DELETED;
|
||||
// Now delete the instance. This sequence guards against the chance
|
||||
// that the destructor throws, somebody catches it and there's a
|
||||
// subsequent call to getInstance().
|
||||
delete lameduck;
|
||||
}
|
||||
|
||||
static DERIVED_TYPE* getInstance()
|
||||
{
|
||||
// In case racing threads call getInstance() at the same moment,
|
||||
// serialize the calls.
|
||||
Locker lk;
|
||||
// We know the viewer has LLSingleton dependency circularities. If you
|
||||
// feel strongly motivated to eliminate them, cheers and good luck.
|
||||
// (At that point we could consider a much simpler locking mechanism.)
|
||||
|
||||
switch (sData.mInitState)
|
||||
{
|
||||
case CONSTRUCTING:
|
||||
// here if DERIVED_TYPE's constructor (directly or indirectly)
|
||||
// calls DERIVED_TYPE::getInstance()
|
||||
logerrs("Tried to access singleton ",
|
||||
classname<DERIVED_TYPE>().c_str(),
|
||||
" from singleton constructor!");
|
||||
return NULL;
|
||||
// If A and B depend on each other, and thread T1 requests A at the
|
||||
// same moment thread T2 requests B, you could get a sequence like this:
|
||||
// - T1 locks A
|
||||
// - T2 locks B
|
||||
// - T1, having constructed A, calls A::initSingleton(), which calls
|
||||
// B::getInstance() and blocks on B's lock
|
||||
// - T2, having constructed B, calls B::initSingleton(), which calls
|
||||
// A::getInstance() and blocks on A's lock
|
||||
// In other words, classic deadlock.
|
||||
|
||||
case UNINITIALIZED:
|
||||
constructSingleton();
|
||||
// fall through...
|
||||
// Avoid that by constructing and initializing every LLSingleton on
|
||||
// the main thread. In that scenario:
|
||||
// - T1 locks A
|
||||
// - T2 locks B
|
||||
// - T1 discovers A is UNINITIALIZED, so it queues a task for the main
|
||||
// thread, unlocks A and blocks on the std::future.
|
||||
// - T2 discovers B is UNINITIALIZED, so it queues a task for the main
|
||||
// thread, unlocks B and blocks on the std::future.
|
||||
// - The main thread executes T1's request for A. It locks A and
|
||||
// starts to construct it.
|
||||
// - A::initSingleton() calls B::getInstance(). Fine: nobody's holding
|
||||
// B's lock.
|
||||
// - The main thread locks B, constructs B, calls B::initSingleton(),
|
||||
// which calls A::getInstance(), which returns A.
|
||||
// - B::getInstance() returns B to A::initSingleton(), unlocking B.
|
||||
// - A::getInstance() returns A to the task wrapper, unlocking A.
|
||||
// - The task wrapper passes A to T1 via the future. T1 resumes.
|
||||
// - The main thread executes T2's request for B. Oh look, B already
|
||||
// exists. The task wrapper passes B to T2 via the future. T2
|
||||
// resumes.
|
||||
// This still works even if one of T1 or T2 *is* the main thread.
|
||||
// This still works even if thread T3 requests B at the same moment as
|
||||
// T2. Finding B still UNINITIALIZED, T3 also queues a task for the
|
||||
// main thread, unlocks B and blocks on a (distinct) std::future. By
|
||||
// the time the main thread executes T3's request for B, B already
|
||||
// exists, and is simply delivered via the future.
|
||||
|
||||
case CONSTRUCTED:
|
||||
// still have to call initSingleton()
|
||||
finishInitializing();
|
||||
break;
|
||||
{ // nested scope for 'lk'
|
||||
// In case racing threads call getInstance() at the same moment,
|
||||
// serialize the calls.
|
||||
LockStatic lk;
|
||||
|
||||
case INITIALIZING:
|
||||
// here if DERIVED_TYPE::initSingleton() (directly or indirectly)
|
||||
// calls DERIVED_TYPE::getInstance(): go ahead and allow it
|
||||
case INITIALIZED:
|
||||
// normal subsequent calls
|
||||
break;
|
||||
switch (lk->mInitState)
|
||||
{
|
||||
case CONSTRUCTING:
|
||||
// here if DERIVED_TYPE's constructor (directly or indirectly)
|
||||
// calls DERIVED_TYPE::getInstance()
|
||||
logerrs("Tried to access singleton ",
|
||||
classname<DERIVED_TYPE>().c_str(),
|
||||
" from singleton constructor!");
|
||||
return nullptr;
|
||||
|
||||
case DELETED:
|
||||
// called after deleteSingleton()
|
||||
logwarns("Trying to access deleted singleton ",
|
||||
classname<DERIVED_TYPE>().c_str(),
|
||||
" -- creating new instance");
|
||||
constructSingleton();
|
||||
finishInitializing();
|
||||
break;
|
||||
}
|
||||
case INITIALIZING:
|
||||
// here if DERIVED_TYPE::initSingleton() (directly or indirectly)
|
||||
// calls DERIVED_TYPE::getInstance(): go ahead and allow it
|
||||
case INITIALIZED:
|
||||
// normal subsequent calls
|
||||
// record the dependency, if any: check if we got here from another
|
||||
// LLSingleton's constructor or initSingleton() method
|
||||
capture_dependency(lk->mInstance);
|
||||
return lk->mInstance;
|
||||
|
||||
// record the dependency, if any: check if we got here from another
|
||||
// LLSingleton's constructor or initSingleton() method
|
||||
capture_dependency();
|
||||
return sData.mInstance;
|
||||
case DELETED:
|
||||
// called after deleteSingleton()
|
||||
logwarns("Trying to access deleted singleton ",
|
||||
classname<DERIVED_TYPE>().c_str(),
|
||||
" -- creating new instance");
|
||||
// fall through
|
||||
case UNINITIALIZED:
|
||||
case QUEUED:
|
||||
// QUEUED means some secondary thread has already requested an
|
||||
// instance, but for present purposes that's semantically
|
||||
// identical to UNINITIALIZED: either way, we must ourselves
|
||||
// request an instance.
|
||||
break;
|
||||
}
|
||||
|
||||
// Here we need to construct a new instance.
|
||||
if (on_main_thread())
|
||||
{
|
||||
// On the main thread, directly construct the instance while
|
||||
// holding the lock.
|
||||
constructSingleton(lk);
|
||||
capture_dependency(lk->mInstance);
|
||||
return lk->mInstance;
|
||||
}
|
||||
|
||||
// Here we need to construct a new instance, but we're on a secondary
|
||||
// thread.
|
||||
lk->mInitState = QUEUED;
|
||||
} // unlock 'lk'
|
||||
|
||||
// Per the comment block above, dispatch to the main thread.
|
||||
loginfos(classname<DERIVED_TYPE>().c_str(),
|
||||
"::getInstance() dispatching to main thread");
|
||||
auto instance = LLMainThreadTask::dispatch(
|
||||
[](){
|
||||
// VERY IMPORTANT to call getInstance() on the main thread,
|
||||
// rather than going straight to constructSingleton()!
|
||||
// During the time window before mInitState is INITIALIZED,
|
||||
// multiple requests might be queued. It's essential that, as
|
||||
// the main thread processes them, only the FIRST such request
|
||||
// actually constructs the instance -- every subsequent one
|
||||
// simply returns the existing instance.
|
||||
loginfos(classname<DERIVED_TYPE>().c_str(),
|
||||
"::getInstance() on main thread");
|
||||
return getInstance();
|
||||
});
|
||||
// record the dependency chain tracked on THIS thread, not the main
|
||||
// thread (consider a getInstance() overload with a tag param that
|
||||
// suppresses dep tracking when dispatched to the main thread)
|
||||
capture_dependency(instance);
|
||||
loginfos(classname<DERIVED_TYPE>().c_str(),
|
||||
"::getInstance() returning on requesting thread");
|
||||
return instance;
|
||||
}
|
||||
|
||||
// Reference version of getInstance()
|
||||
// Preferred over getInstance() as it disallows checking for NULL
|
||||
// Preferred over getInstance() as it disallows checking for nullptr
|
||||
static DERIVED_TYPE& instance()
|
||||
{
|
||||
return *getInstance();
|
||||
|
|
@ -544,8 +639,8 @@ public:
|
|||
static bool instanceExists()
|
||||
{
|
||||
// defend any access to sData from racing threads
|
||||
Locker lk;
|
||||
return sData.mInitState == INITIALIZED;
|
||||
LockStatic lk;
|
||||
return lk->mInitState == INITIALIZED;
|
||||
}
|
||||
|
||||
// Has this singleton been deleted? This can be useful during shutdown
|
||||
|
|
@ -554,24 +649,11 @@ public:
|
|||
static bool wasDeleted()
|
||||
{
|
||||
// defend any access to sData from racing threads
|
||||
Locker lk;
|
||||
return sData.mInitState == DELETED;
|
||||
LockStatic lk;
|
||||
return lk->mInitState == DELETED;
|
||||
}
|
||||
|
||||
private:
|
||||
struct SingletonData
|
||||
{
|
||||
// explicitly has a default constructor so that member variables are zero initialized in BSS
|
||||
// and only changed by singleton logic, not constructor running during startup
|
||||
EInitState mInitState;
|
||||
DERIVED_TYPE* mInstance;
|
||||
};
|
||||
static SingletonData sData;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
typename LLSingleton<T>::SingletonData LLSingleton<T>::sData;
|
||||
|
||||
|
||||
/**
|
||||
* LLParamSingleton<T> is like LLSingleton<T>, except in the following ways:
|
||||
|
|
@ -596,44 +678,86 @@ class LLParamSingleton : public LLSingleton<DERIVED_TYPE>
|
|||
{
|
||||
private:
|
||||
typedef LLSingleton<DERIVED_TYPE> super;
|
||||
using typename super::Locker;
|
||||
using typename super::LockStatic;
|
||||
|
||||
// Passes arguments to DERIVED_TYPE's constructor and sets appropriate
|
||||
// states, returning a pointer to the new instance.
|
||||
template <typename... Args>
|
||||
static DERIVED_TYPE* initParamSingleton_(Args&&... args)
|
||||
{
|
||||
// In case racing threads both call initParamSingleton() at the same
|
||||
// time, serialize them. One should initialize; the other should see
|
||||
// mInitState already set.
|
||||
LockStatic lk;
|
||||
// For organizational purposes this function shouldn't be called twice
|
||||
if (lk->mInitState != super::UNINITIALIZED)
|
||||
{
|
||||
super::logerrs("Tried to initialize singleton ",
|
||||
super::template classname<DERIVED_TYPE>().c_str(),
|
||||
" twice!");
|
||||
return nullptr;
|
||||
}
|
||||
else if (on_main_thread())
|
||||
{
|
||||
// on the main thread, simply construct instance while holding lock
|
||||
super::logdebugs(super::template classname<DERIVED_TYPE>().c_str(),
|
||||
"::initParamSingleton()");
|
||||
super::constructSingleton(lk, std::forward<Args>(args)...);
|
||||
return lk->mInstance;
|
||||
}
|
||||
else
|
||||
{
|
||||
// on secondary thread, dispatch to main thread --
|
||||
// set state so we catch any other calls before the main thread
|
||||
// picks up the task
|
||||
lk->mInitState = super::QUEUED;
|
||||
// very important to unlock here so main thread can actually process
|
||||
lk.unlock();
|
||||
super::loginfos(super::template classname<DERIVED_TYPE>().c_str(),
|
||||
"::initParamSingleton() dispatching to main thread");
|
||||
// Normally it would be the height of folly to reference-bind
|
||||
// 'args' into a lambda to be executed on some other thread! By
|
||||
// the time that thread executed the lambda, the references would
|
||||
// all be dangling, and Bad Things would result. But
|
||||
// LLMainThreadTask::dispatch() promises to block until the passed
|
||||
// task has completed. So in this case we know the references will
|
||||
// remain valid until the lambda has run, so we dare to bind
|
||||
// references.
|
||||
auto instance = LLMainThreadTask::dispatch(
|
||||
[&](){
|
||||
super::loginfos(super::template classname<DERIVED_TYPE>().c_str(),
|
||||
"::initParamSingleton() on main thread");
|
||||
return initParamSingleton_(std::forward<Args>(args)...);
|
||||
});
|
||||
super::loginfos(super::template classname<DERIVED_TYPE>().c_str(),
|
||||
"::initParamSingleton() returning on requesting thread");
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
using super::deleteSingleton;
|
||||
using super::instanceExists;
|
||||
using super::wasDeleted;
|
||||
|
||||
// Passes arguments to DERIVED_TYPE's constructor and sets appropriate states
|
||||
/// initParamSingleton() constructs the instance, returning a reference.
|
||||
/// Pass whatever arguments are required to construct DERIVED_TYPE.
|
||||
template <typename... Args>
|
||||
static void initParamSingleton(Args&&... args)
|
||||
static DERIVED_TYPE& initParamSingleton(Args&&... args)
|
||||
{
|
||||
// In case racing threads both call initParamSingleton() at the same
|
||||
// time, serialize them. One should initialize; the other should see
|
||||
// mInitState already set.
|
||||
Locker lk;
|
||||
// For organizational purposes this function shouldn't be called twice
|
||||
if (super::sData.mInitState != super::UNINITIALIZED)
|
||||
{
|
||||
super::logerrs("Tried to initialize singleton ",
|
||||
super::template classname<DERIVED_TYPE>().c_str(),
|
||||
" twice!");
|
||||
}
|
||||
else
|
||||
{
|
||||
super::constructSingleton(std::forward<Args>(args)...);
|
||||
super::finishInitializing();
|
||||
}
|
||||
return *initParamSingleton_(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
static DERIVED_TYPE* getInstance()
|
||||
{
|
||||
// In case racing threads call getInstance() at the same moment as
|
||||
// initParamSingleton(), serialize the calls.
|
||||
Locker lk;
|
||||
LockStatic lk;
|
||||
|
||||
switch (super::sData.mInitState)
|
||||
switch (lk->mInitState)
|
||||
{
|
||||
case super::UNINITIALIZED:
|
||||
case super::QUEUED:
|
||||
super::logerrs("Uninitialized param singleton ",
|
||||
super::template classname<DERIVED_TYPE>().c_str());
|
||||
break;
|
||||
|
|
@ -644,25 +768,13 @@ public:
|
|||
" from singleton constructor!");
|
||||
break;
|
||||
|
||||
case super::CONSTRUCTED:
|
||||
// Should never happen!? The CONSTRUCTED state is specifically to
|
||||
// navigate through LLSingleton::SingletonInitializer getting
|
||||
// constructed (once) before LLSingleton::getInstance()'s switch
|
||||
// on mInitState. But our initParamSingleton() method calls
|
||||
// constructSingleton() and then calls finishInitializing(), which
|
||||
// immediately sets INITIALIZING. Why are we here?
|
||||
super::logerrs("Param singleton ",
|
||||
super::template classname<DERIVED_TYPE>().c_str(),
|
||||
"::initSingleton() not yet called");
|
||||
break;
|
||||
|
||||
case super::INITIALIZING:
|
||||
// As with LLSingleton, explicitly permit circular calls from
|
||||
// within initSingleton()
|
||||
case super::INITIALIZED:
|
||||
// for any valid call, capture dependencies
|
||||
super::capture_dependency();
|
||||
return super::sData.mInstance;
|
||||
super::capture_dependency(lk->mInstance);
|
||||
return lk->mInstance;
|
||||
|
||||
case super::DELETED:
|
||||
super::logerrs("Trying to access deleted param singleton ",
|
||||
|
|
@ -706,9 +818,9 @@ public:
|
|||
using super::instanceExists;
|
||||
using super::wasDeleted;
|
||||
|
||||
static void construct()
|
||||
static DT* construct()
|
||||
{
|
||||
super::initParamSingleton();
|
||||
return super::initParamSingleton();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -92,26 +92,39 @@ void set_thread_name( DWORD dwThreadID, const char* threadName)
|
|||
// }
|
||||
//
|
||||
//----------------------------------------------------------------------------
|
||||
namespace
|
||||
{
|
||||
|
||||
U32 LL_THREAD_LOCAL sThreadID = 0;
|
||||
LLThread::id_t main_thread()
|
||||
{
|
||||
// Using a function-static variable to identify the main thread
|
||||
// requires that control reach here from the main thread before it
|
||||
// reaches here from any other thread. We simply trust that whichever
|
||||
// thread gets here first is the main thread.
|
||||
static LLThread::id_t s_thread_id = LLThread::currentID();
|
||||
return s_thread_id;
|
||||
}
|
||||
|
||||
U32 LLThread::sIDIter = 0;
|
||||
} // anonymous namespace
|
||||
|
||||
LL_COMMON_API bool on_main_thread()
|
||||
{
|
||||
return (LLThread::currentID() == main_thread());
|
||||
}
|
||||
|
||||
LL_COMMON_API void assert_main_thread()
|
||||
{
|
||||
static U32 s_thread_id = LLThread::currentID();
|
||||
if (LLThread::currentID() != s_thread_id)
|
||||
auto curr = LLThread::currentID();
|
||||
auto main = main_thread();
|
||||
if (curr != main)
|
||||
{
|
||||
LL_WARNS() << "Illegal execution from thread id " << (S32) LLThread::currentID()
|
||||
<< " outside main thread " << (S32) s_thread_id << LL_ENDL;
|
||||
LL_WARNS() << "Illegal execution from thread id " << curr
|
||||
<< " outside main thread " << main << LL_ENDL;
|
||||
}
|
||||
}
|
||||
|
||||
void LLThread::registerThreadID()
|
||||
{
|
||||
sThreadID = ++sIDIter;
|
||||
}
|
||||
// this function has become moot
|
||||
void LLThread::registerThreadID() {}
|
||||
|
||||
//
|
||||
// Handed to the APR thread creation function
|
||||
|
|
@ -122,11 +135,12 @@ void LLThread::threadRun()
|
|||
set_thread_name(-1, mName.c_str());
|
||||
#endif
|
||||
|
||||
// this is the first point at which we're actually running in the new thread
|
||||
mID = currentID();
|
||||
|
||||
// for now, hard code all LLThreads to report to single master thread recorder, which is known to be running on main thread
|
||||
mRecorder = new LLTrace::ThreadRecorder(*LLTrace::get_master_thread_recorder());
|
||||
|
||||
sThreadID = mID;
|
||||
|
||||
// Run the user supplied function
|
||||
do
|
||||
{
|
||||
|
|
@ -168,8 +182,6 @@ LLThread::LLThread(const std::string& name, apr_pool_t *poolp) :
|
|||
mStatus(STOPPED),
|
||||
mRecorder(NULL)
|
||||
{
|
||||
|
||||
mID = ++sIDIter;
|
||||
mRunCondition = new LLCondition();
|
||||
mDataLock = new LLMutex();
|
||||
mLocalAPRFilePoolp = NULL ;
|
||||
|
|
@ -347,9 +359,9 @@ void LLThread::setQuitting()
|
|||
}
|
||||
|
||||
// static
|
||||
U32 LLThread::currentID()
|
||||
LLThread::id_t LLThread::currentID()
|
||||
{
|
||||
return sThreadID;
|
||||
return std::this_thread::get_id();
|
||||
}
|
||||
|
||||
// static
|
||||
|
|
@ -376,6 +388,16 @@ void LLThread::wakeLocked()
|
|||
}
|
||||
}
|
||||
|
||||
void LLThread::lockData()
|
||||
{
|
||||
mDataLock->lock();
|
||||
}
|
||||
|
||||
void LLThread::unlockData()
|
||||
{
|
||||
mDataLock->unlock();
|
||||
}
|
||||
|
||||
//============================================================================
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -30,12 +30,9 @@
|
|||
#include "llapp.h"
|
||||
#include "llapr.h"
|
||||
#include "boost/intrusive_ptr.hpp"
|
||||
#include "llmutex.h"
|
||||
#include "llrefcount.h"
|
||||
#include <thread>
|
||||
|
||||
LL_COMMON_API void assert_main_thread();
|
||||
|
||||
namespace LLTrace
|
||||
{
|
||||
class ThreadRecorder;
|
||||
|
|
@ -45,7 +42,6 @@ class LL_COMMON_API LLThread
|
|||
{
|
||||
private:
|
||||
friend class LLMutex;
|
||||
static U32 sIDIter;
|
||||
|
||||
public:
|
||||
typedef enum e_thread_status
|
||||
|
|
@ -55,6 +51,7 @@ public:
|
|||
QUITTING= 2, // Someone wants this thread to quit
|
||||
CRASHED = -1 // An uncaught exception was thrown by the thread
|
||||
} EThreadStatus;
|
||||
typedef std::thread::id id_t;
|
||||
|
||||
LLThread(const std::string& name, apr_pool_t *poolp = NULL);
|
||||
virtual ~LLThread(); // Warning! You almost NEVER want to destroy a thread unless it's in the STOPPED state.
|
||||
|
|
@ -64,7 +61,7 @@ public:
|
|||
bool isStopped() const { return (STOPPED == mStatus) || (CRASHED == mStatus); }
|
||||
bool isCrashed() const { return (CRASHED == mStatus); }
|
||||
|
||||
static U32 currentID(); // Return ID of current thread
|
||||
static id_t currentID(); // Return ID of current thread
|
||||
static void yield(); // Static because it can be called by the main thread, which doesn't have an LLThread data structure.
|
||||
|
||||
public:
|
||||
|
|
@ -88,7 +85,7 @@ public:
|
|||
|
||||
LLVolatileAPRPool* getLocalAPRFilePool() { return mLocalAPRFilePoolp ; }
|
||||
|
||||
U32 getID() const { return mID; }
|
||||
id_t getID() const { return mID; }
|
||||
|
||||
// Called by threads *not* created via LLThread to register some
|
||||
// internal state used by LLMutex. You must call this once early
|
||||
|
|
@ -109,7 +106,7 @@ protected:
|
|||
|
||||
std::thread *mThreadp;
|
||||
EThreadStatus mStatus;
|
||||
U32 mID;
|
||||
id_t mID;
|
||||
LLTrace::ThreadRecorder* mRecorder;
|
||||
|
||||
//a local apr_pool for APRFile operations in this thread. If it exists, LLAPRFile::sAPRFilePoolp should not be used.
|
||||
|
|
@ -126,8 +123,8 @@ protected:
|
|||
virtual bool runCondition(void);
|
||||
|
||||
// Lock/Unlock Run Condition -- use around modification of any variable used in runCondition()
|
||||
inline void lockData();
|
||||
inline void unlockData();
|
||||
void lockData();
|
||||
void unlockData();
|
||||
|
||||
// This is the predicate that decides whether the thread should sleep.
|
||||
// It should only be called with mDataLock locked, since the virtual runCondition() function may need to access
|
||||
|
|
@ -142,17 +139,6 @@ protected:
|
|||
};
|
||||
|
||||
|
||||
void LLThread::lockData()
|
||||
{
|
||||
mDataLock->lock();
|
||||
}
|
||||
|
||||
void LLThread::unlockData()
|
||||
{
|
||||
mDataLock->unlock();
|
||||
}
|
||||
|
||||
|
||||
//============================================================================
|
||||
|
||||
// Simple responder for self destructing callbacks
|
||||
|
|
@ -168,5 +154,6 @@ public:
|
|||
//============================================================================
|
||||
|
||||
extern LL_COMMON_API void assert_main_thread();
|
||||
extern LL_COMMON_API bool on_main_thread();
|
||||
|
||||
#endif // LL_LLTHREAD_H
|
||||
|
|
|
|||
|
|
@ -93,11 +93,9 @@ void LLThreadLocalPointerBase::initAllThreadLocalStorage()
|
|||
{
|
||||
if (!sInitialized)
|
||||
{
|
||||
for (LLInstanceTracker<LLThreadLocalPointerBase>::instance_iter it = beginInstances(), end_it = endInstances();
|
||||
it != end_it;
|
||||
++it)
|
||||
for (auto& base : instance_snapshot())
|
||||
{
|
||||
(*it).initStorage();
|
||||
base.initStorage();
|
||||
}
|
||||
sInitialized = true;
|
||||
}
|
||||
|
|
@ -108,11 +106,9 @@ void LLThreadLocalPointerBase::destroyAllThreadLocalStorage()
|
|||
{
|
||||
if (sInitialized)
|
||||
{
|
||||
//for (LLInstanceTracker<LLThreadLocalPointerBase>::instance_iter it = beginInstances(), end_it = endInstances();
|
||||
// it != end_it;
|
||||
// ++it)
|
||||
//for (auto& base : instance_snapshot())
|
||||
//{
|
||||
// (*it).destroyStorage();
|
||||
// base.destroyStorage();
|
||||
//}
|
||||
sInitialized = false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,19 +30,9 @@
|
|||
#include "llexception.h"
|
||||
#include <deque>
|
||||
#include <string>
|
||||
|
||||
#if LL_WINDOWS
|
||||
#pragma warning (push)
|
||||
#pragma warning (disable:4265)
|
||||
#endif
|
||||
// 'std::_Pad' : class has virtual functions, but destructor is not virtual
|
||||
#include <mutex> // std::unique_lock
|
||||
#include "mutex.h" // std::unique_lock
|
||||
#include <boost/fiber/condition_variable.hpp>
|
||||
|
||||
#if LL_WINDOWS
|
||||
#pragma warning (pop)
|
||||
#endif
|
||||
|
||||
//
|
||||
// A general queue exception.
|
||||
//
|
||||
|
|
@ -88,7 +78,7 @@ public:
|
|||
// Add an element to the front of queue (will block if the queue has
|
||||
// reached capacity).
|
||||
//
|
||||
// This call will raise an interrupt error if the queue is deleted while
|
||||
// This call will raise an interrupt error if the queue is closed while
|
||||
// the caller is blocked.
|
||||
void pushFront(ElementT const & element);
|
||||
|
||||
|
|
@ -99,7 +89,7 @@ public:
|
|||
// Pop the element at the end of the queue (will block if the queue is
|
||||
// empty).
|
||||
//
|
||||
// This call will raise an interrupt error if the queue is deleted while
|
||||
// This call will raise an interrupt error if the queue is closed while
|
||||
// the caller is blocked.
|
||||
ElementT popBack(void);
|
||||
|
||||
|
|
@ -110,11 +100,27 @@ public:
|
|||
// Returns the size of the queue.
|
||||
size_t size();
|
||||
|
||||
// closes the queue:
|
||||
// - every subsequent pushFront() call will throw LLThreadSafeQueueInterrupt
|
||||
// - every subsequent tryPushFront() call will return false
|
||||
// - popBack() calls will return normally until the queue is drained, then
|
||||
// every subsequent popBack() will throw LLThreadSafeQueueInterrupt
|
||||
// - tryPopBack() calls will return normally until the queue is drained,
|
||||
// then every subsequent tryPopBack() call will return false
|
||||
void close();
|
||||
|
||||
// detect closed state
|
||||
bool isClosed();
|
||||
// inverse of isClosed()
|
||||
explicit operator bool();
|
||||
|
||||
private:
|
||||
std::deque< ElementT > mStorage;
|
||||
U32 mCapacity;
|
||||
bool mClosed;
|
||||
|
||||
boost::fibers::mutex mLock;
|
||||
typedef std::unique_lock<decltype(mLock)> lock_t;
|
||||
boost::fibers::condition_variable mCapacityCond;
|
||||
boost::fibers::condition_variable mEmptyCond;
|
||||
};
|
||||
|
|
@ -124,7 +130,8 @@ private:
|
|||
|
||||
template<typename ElementT>
|
||||
LLThreadSafeQueue<ElementT>::LLThreadSafeQueue(U32 capacity) :
|
||||
mCapacity(capacity)
|
||||
mCapacity(capacity),
|
||||
mClosed(false)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -132,12 +139,18 @@ mCapacity(capacity)
|
|||
template<typename ElementT>
|
||||
void LLThreadSafeQueue<ElementT>::pushFront(ElementT const & element)
|
||||
{
|
||||
std::unique_lock<decltype(mLock)> lock1(mLock);
|
||||
lock_t lock1(mLock);
|
||||
while (true)
|
||||
{
|
||||
if (mClosed)
|
||||
{
|
||||
LLTHROW(LLThreadSafeQueueInterrupt());
|
||||
}
|
||||
|
||||
if (mStorage.size() < mCapacity)
|
||||
{
|
||||
mStorage.push_front(element);
|
||||
lock1.unlock();
|
||||
mEmptyCond.notify_one();
|
||||
return;
|
||||
}
|
||||
|
|
@ -151,14 +164,18 @@ void LLThreadSafeQueue<ElementT>::pushFront(ElementT const & element)
|
|||
template<typename ElementT>
|
||||
bool LLThreadSafeQueue<ElementT>::tryPushFront(ElementT const & element)
|
||||
{
|
||||
std::unique_lock<decltype(mLock)> lock1(mLock, std::defer_lock);
|
||||
lock_t lock1(mLock, std::defer_lock);
|
||||
if (!lock1.try_lock())
|
||||
return false;
|
||||
|
||||
if (mClosed)
|
||||
return false;
|
||||
|
||||
if (mStorage.size() >= mCapacity)
|
||||
return false;
|
||||
|
||||
mStorage.push_front(element);
|
||||
lock1.unlock();
|
||||
mEmptyCond.notify_one();
|
||||
return true;
|
||||
}
|
||||
|
|
@ -167,17 +184,23 @@ bool LLThreadSafeQueue<ElementT>::tryPushFront(ElementT const & element)
|
|||
template<typename ElementT>
|
||||
ElementT LLThreadSafeQueue<ElementT>::popBack(void)
|
||||
{
|
||||
std::unique_lock<decltype(mLock)> lock1(mLock);
|
||||
lock_t lock1(mLock);
|
||||
while (true)
|
||||
{
|
||||
if (!mStorage.empty())
|
||||
{
|
||||
ElementT value = mStorage.back();
|
||||
mStorage.pop_back();
|
||||
lock1.unlock();
|
||||
mCapacityCond.notify_one();
|
||||
return value;
|
||||
}
|
||||
|
||||
if (mClosed)
|
||||
{
|
||||
LLTHROW(LLThreadSafeQueueInterrupt());
|
||||
}
|
||||
|
||||
// Storage empty. Wait for signal.
|
||||
mEmptyCond.wait(lock1);
|
||||
}
|
||||
|
|
@ -187,15 +210,18 @@ ElementT LLThreadSafeQueue<ElementT>::popBack(void)
|
|||
template<typename ElementT>
|
||||
bool LLThreadSafeQueue<ElementT>::tryPopBack(ElementT & element)
|
||||
{
|
||||
std::unique_lock<decltype(mLock)> lock1(mLock, std::defer_lock);
|
||||
lock_t lock1(mLock, std::defer_lock);
|
||||
if (!lock1.try_lock())
|
||||
return false;
|
||||
|
||||
// no need to check mClosed: tryPopBack() behavior when the queue is
|
||||
// closed is implemented by simple inability to push any new elements
|
||||
if (mStorage.empty())
|
||||
return false;
|
||||
|
||||
element = mStorage.back();
|
||||
mStorage.pop_back();
|
||||
lock1.unlock();
|
||||
mCapacityCond.notify_one();
|
||||
return true;
|
||||
}
|
||||
|
|
@ -204,8 +230,34 @@ bool LLThreadSafeQueue<ElementT>::tryPopBack(ElementT & element)
|
|||
template<typename ElementT>
|
||||
size_t LLThreadSafeQueue<ElementT>::size(void)
|
||||
{
|
||||
std::lock_guard<decltype(mLock)> lock(mLock);
|
||||
lock_t lock(mLock);
|
||||
return mStorage.size();
|
||||
}
|
||||
|
||||
template<typename ElementT>
|
||||
void LLThreadSafeQueue<ElementT>::close()
|
||||
{
|
||||
lock_t lock(mLock);
|
||||
mClosed = true;
|
||||
lock.unlock();
|
||||
// wake up any blocked popBack() calls
|
||||
mEmptyCond.notify_all();
|
||||
// wake up any blocked pushFront() calls
|
||||
mCapacityCond.notify_all();
|
||||
}
|
||||
|
||||
template<typename ElementT>
|
||||
bool LLThreadSafeQueue<ElementT>::isClosed()
|
||||
{
|
||||
lock_t lock(mLock);
|
||||
return mClosed;
|
||||
}
|
||||
|
||||
template<typename ElementT>
|
||||
LLThreadSafeQueue<ElementT>::operator bool()
|
||||
{
|
||||
lock_t lock(mLock);
|
||||
return ! mClosed;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ class StatBase
|
|||
{
|
||||
public:
|
||||
StatBase(const char* name, const char* description);
|
||||
virtual ~StatBase() LLINSTANCETRACKER_DTOR_NOEXCEPT {}
|
||||
virtual ~StatBase() {}
|
||||
virtual const char* getUnitLabel() const;
|
||||
|
||||
const std::string& getName() const { return mName; }
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@
|
|||
#include "lltracethreadrecorder.h"
|
||||
#include "llfasttimer.h"
|
||||
#include "lltrace.h"
|
||||
#include "llstl.h"
|
||||
|
||||
namespace LLTrace
|
||||
{
|
||||
|
|
@ -64,16 +65,15 @@ void ThreadRecorder::init()
|
|||
activate(&mThreadRecordingBuffers);
|
||||
|
||||
// initialize time block parent pointers
|
||||
for (BlockTimerStatHandle::instance_tracker_t::instance_iter it = BlockTimerStatHandle::instance_tracker_t::beginInstances(), end_it = BlockTimerStatHandle::instance_tracker_t::endInstances();
|
||||
it != end_it;
|
||||
++it)
|
||||
for (auto& base : BlockTimerStatHandle::instance_snapshot())
|
||||
{
|
||||
BlockTimerStatHandle& time_block = static_cast<BlockTimerStatHandle&>(*it);
|
||||
TimeBlockTreeNode& tree_node = mTimeBlockTreeNodes[it->getIndex()];
|
||||
// because of indirect derivation from LLInstanceTracker, have to downcast
|
||||
BlockTimerStatHandle& time_block = static_cast<BlockTimerStatHandle&>(base);
|
||||
TimeBlockTreeNode& tree_node = mTimeBlockTreeNodes[time_block.getIndex()];
|
||||
tree_node.mBlock = &time_block;
|
||||
tree_node.mParent = &root_time_block;
|
||||
|
||||
it->getCurrentAccumulator().mParent = &root_time_block;
|
||||
time_block.getCurrentAccumulator().mParent = &root_time_block;
|
||||
}
|
||||
|
||||
mRootTimer = new BlockTimer(root_time_block);
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@
|
|||
#include "llstring.h"
|
||||
#include "lltimer.h"
|
||||
#include "llthread.h"
|
||||
#include "llmutex.h"
|
||||
|
||||
const LLUUID LLUUID::null;
|
||||
const LLTransactionID LLTransactionID::tnull;
|
||||
|
|
@ -739,7 +740,7 @@ void LLUUID::getCurrentTime(uuid_time_t *timestamp)
|
|||
getSystemTime(&time_last);
|
||||
uuids_this_tick = uuids_per_tick;
|
||||
init = TRUE;
|
||||
mMutex = new LLMutex();
|
||||
mMutex = new LLMutex();
|
||||
}
|
||||
|
||||
uuid_time_t time_now = {0,0};
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@
|
|||
|
||||
#include "llqueuedthread.h"
|
||||
#include "llatomic.h"
|
||||
#include "llmutex.h"
|
||||
|
||||
#define USE_FRAME_CALLBACK_MANAGER 0
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
/**
|
||||
* @file lockstatic.h
|
||||
* @author Nat Goodspeed
|
||||
* @date 2019-12-03
|
||||
* @brief LockStatic class provides mutex-guarded access to the specified
|
||||
* static data.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2019&license=viewerlgpl$
|
||||
* Copyright (c) 2019, Linden Research, Inc.
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
#if ! defined(LL_LOCKSTATIC_H)
|
||||
#define LL_LOCKSTATIC_H
|
||||
|
||||
#include "mutex.h" // std::unique_lock
|
||||
|
||||
namespace llthread
|
||||
{
|
||||
|
||||
// Instantiate this template to obtain a pointer to the canonical static
|
||||
// instance of Static while holding a lock on that instance. Use of
|
||||
// Static::mMutex presumes that Static declares some suitable mMutex.
|
||||
template <typename Static>
|
||||
class LockStatic
|
||||
{
|
||||
typedef std::unique_lock<decltype(Static::mMutex)> lock_t;
|
||||
public:
|
||||
LockStatic():
|
||||
mData(getStatic()),
|
||||
mLock(mData->mMutex)
|
||||
{}
|
||||
Static* get() const { return mData; }
|
||||
operator Static*() const { return get(); }
|
||||
Static* operator->() const { return get(); }
|
||||
// sometimes we must explicitly unlock...
|
||||
void unlock()
|
||||
{
|
||||
// but once we do, access is no longer permitted
|
||||
mData = nullptr;
|
||||
mLock.unlock();
|
||||
}
|
||||
protected:
|
||||
Static* mData;
|
||||
lock_t mLock;
|
||||
private:
|
||||
Static* getStatic()
|
||||
{
|
||||
// Static::mMutex must be function-local static rather than class-
|
||||
// static. Some of our consumers must function properly (therefore
|
||||
// lock properly) even when the containing module's static variables
|
||||
// have not yet been runtime-initialized. A mutex requires
|
||||
// construction. A static class member might not yet have been
|
||||
// constructed.
|
||||
//
|
||||
// We could store a dumb mutex_t*, notice when it's NULL and allocate a
|
||||
// heap mutex -- but that's vulnerable to race conditions. And we can't
|
||||
// defend the dumb pointer with another mutex.
|
||||
//
|
||||
// We could store a std::atomic<mutex_t*> -- but a default-constructed
|
||||
// std::atomic<T> does not contain a valid T, even a default-constructed
|
||||
// T! Which means std::atomic, too, requires runtime initialization.
|
||||
//
|
||||
// But a function-local static is guaranteed to be initialized exactly
|
||||
// once: the first time control reaches that declaration.
|
||||
static Static sData;
|
||||
return &sData;
|
||||
}
|
||||
};
|
||||
|
||||
} // llthread namespace
|
||||
|
||||
#endif /* ! defined(LL_LOCKSTATIC_H) */
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
/**
|
||||
* @file mutex.h
|
||||
* @author Nat Goodspeed
|
||||
* @date 2019-12-03
|
||||
* @brief Wrap <mutex> in odious boilerplate
|
||||
*
|
||||
* $LicenseInfo:firstyear=2019&license=viewerlgpl$
|
||||
* Copyright (c) 2019, Linden Research, Inc.
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
#if LL_WINDOWS
|
||||
#pragma warning (push)
|
||||
#pragma warning (disable:4265)
|
||||
#endif
|
||||
// warning C4265: 'std::_Pad' : class has virtual functions, but destructor is not virtual
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#if LL_WINDOWS
|
||||
#pragma warning (pop)
|
||||
#endif
|
||||
|
|
@ -41,7 +41,6 @@
|
|||
#include <boost/scoped_ptr.hpp>
|
||||
// other Linden headers
|
||||
#include "../test/lltut.h"
|
||||
#include "wrapllerrs.h"
|
||||
|
||||
struct Badness: public std::runtime_error
|
||||
{
|
||||
|
|
@ -112,24 +111,22 @@ namespace tut
|
|||
void object::test<2>()
|
||||
{
|
||||
ensure_equals(Unkeyed::instanceCount(), 0);
|
||||
Unkeyed* dangling = NULL;
|
||||
std::weak_ptr<Unkeyed> dangling;
|
||||
{
|
||||
Unkeyed one;
|
||||
ensure_equals(Unkeyed::instanceCount(), 1);
|
||||
Unkeyed* found = Unkeyed::getInstance(&one);
|
||||
ensure_equals(found, &one);
|
||||
std::weak_ptr<Unkeyed> found = one.getWeak();
|
||||
ensure(! found.expired());
|
||||
{
|
||||
boost::scoped_ptr<Unkeyed> two(new Unkeyed);
|
||||
ensure_equals(Unkeyed::instanceCount(), 2);
|
||||
Unkeyed* found = Unkeyed::getInstance(two.get());
|
||||
ensure_equals(found, two.get());
|
||||
}
|
||||
ensure_equals(Unkeyed::instanceCount(), 1);
|
||||
// store an unwise pointer to a temp Unkeyed instance
|
||||
dangling = &one;
|
||||
// store a weak pointer to a temp Unkeyed instance
|
||||
dangling = found;
|
||||
} // make that instance vanish
|
||||
// check the now-invalid pointer to the destroyed instance
|
||||
ensure("getInstance(T*) failed to track destruction", ! Unkeyed::getInstance(dangling));
|
||||
ensure("weak_ptr<Unkeyed> failed to track destruction", dangling.expired());
|
||||
ensure_equals(Unkeyed::instanceCount(), 0);
|
||||
}
|
||||
|
||||
|
|
@ -142,7 +139,8 @@ namespace tut
|
|||
// reimplement LLInstanceTracker using, say, a hash map instead of a
|
||||
// std::map. We DO insist that every key appear exactly once.
|
||||
typedef std::vector<std::string> StringVector;
|
||||
StringVector keys(Keyed::beginKeys(), Keyed::endKeys());
|
||||
auto snap = Keyed::key_snapshot();
|
||||
StringVector keys(snap.begin(), snap.end());
|
||||
std::sort(keys.begin(), keys.end());
|
||||
StringVector::const_iterator ki(keys.begin());
|
||||
ensure_equals(*ki++, "one");
|
||||
|
|
@ -153,17 +151,15 @@ namespace tut
|
|||
ensure("didn't reach end", ki == keys.end());
|
||||
|
||||
// Use a somewhat different approach to order independence with
|
||||
// beginInstances(): explicitly capture the instances we know in a
|
||||
// instance_snapshot(): explicitly capture the instances we know in a
|
||||
// set, and delete them as we iterate through.
|
||||
typedef std::set<Keyed*> InstanceSet;
|
||||
InstanceSet instances;
|
||||
instances.insert(&one);
|
||||
instances.insert(&two);
|
||||
instances.insert(&three);
|
||||
for (Keyed::instance_iter ii(Keyed::beginInstances()), iend(Keyed::endInstances());
|
||||
ii != iend; ++ii)
|
||||
for (auto& ref : Keyed::instance_snapshot())
|
||||
{
|
||||
Keyed& ref = *ii;
|
||||
ensure_equals("spurious instance", instances.erase(&ref), 1);
|
||||
}
|
||||
ensure_equals("unreported instance", instances.size(), 0);
|
||||
|
|
@ -180,63 +176,62 @@ namespace tut
|
|||
instances.insert(&two);
|
||||
instances.insert(&three);
|
||||
|
||||
for (Unkeyed::instance_iter ii(Unkeyed::beginInstances()), iend(Unkeyed::endInstances()); ii != iend; ++ii)
|
||||
{
|
||||
Unkeyed& ref = *ii;
|
||||
ensure_equals("spurious instance", instances.erase(&ref), 1);
|
||||
}
|
||||
for (auto& ref : Unkeyed::instance_snapshot())
|
||||
{
|
||||
ensure_equals("spurious instance", instances.erase(&ref), 1);
|
||||
}
|
||||
|
||||
ensure_equals("unreported instance", instances.size(), 0);
|
||||
}
|
||||
/*
|
||||
|
||||
template<> template<>
|
||||
void object::test<5>()
|
||||
{
|
||||
set_test_name("delete Keyed with outstanding instance_iter");
|
||||
std::string what;
|
||||
Keyed* keyed = new Keyed("delete Keyed with outstanding instance_iter");
|
||||
{
|
||||
WrapLLErrs wrapper;
|
||||
Keyed::instance_iter i(Keyed::beginInstances());
|
||||
what = wrapper.catch_llerrs([&keyed](){
|
||||
delete keyed;
|
||||
});
|
||||
}
|
||||
ensure(! what.empty());
|
||||
std::string desc("delete Keyed with outstanding instance_snapshot");
|
||||
set_test_name(desc);
|
||||
Keyed* keyed = new Keyed(desc);
|
||||
// capture a snapshot but do not yet traverse it
|
||||
auto snapshot = Keyed::instance_snapshot();
|
||||
// delete the one instance
|
||||
delete keyed;
|
||||
// traversing the snapshot should reflect the deletion
|
||||
// avoid ensure_equals() because it requires the ability to stream the
|
||||
// two values to std::ostream
|
||||
ensure(snapshot.begin() == snapshot.end());
|
||||
}
|
||||
|
||||
|
||||
template<> template<>
|
||||
void object::test<6>()
|
||||
{
|
||||
set_test_name("delete Keyed with outstanding key_iter");
|
||||
std::string what;
|
||||
Keyed* keyed = new Keyed("delete Keyed with outstanding key_it");
|
||||
{
|
||||
WrapLLErrs wrapper;
|
||||
Keyed::key_iter i(Keyed::beginKeys());
|
||||
what = wrapper.catch_llerrs([&keyed](){
|
||||
delete keyed;
|
||||
});
|
||||
}
|
||||
ensure(! what.empty());
|
||||
std::string desc("delete Keyed with outstanding key_snapshot");
|
||||
set_test_name(desc);
|
||||
Keyed* keyed = new Keyed(desc);
|
||||
// capture a snapshot but do not yet traverse it
|
||||
auto snapshot = Keyed::key_snapshot();
|
||||
// delete the one instance
|
||||
delete keyed;
|
||||
// traversing the snapshot should reflect the deletion
|
||||
// avoid ensure_equals() because it requires the ability to stream the
|
||||
// two values to std::ostream
|
||||
ensure(snapshot.begin() == snapshot.end());
|
||||
}
|
||||
|
||||
template<> template<>
|
||||
void object::test<7>()
|
||||
{
|
||||
set_test_name("delete Unkeyed with outstanding instance_iter");
|
||||
set_test_name("delete Unkeyed with outstanding instance_snapshot");
|
||||
std::string what;
|
||||
Unkeyed* unkeyed = new Unkeyed;
|
||||
{
|
||||
WrapLLErrs wrapper;
|
||||
Unkeyed::instance_iter i(Unkeyed::beginInstances());
|
||||
what = wrapper.catch_llerrs([&unkeyed](){
|
||||
delete unkeyed;
|
||||
});
|
||||
}
|
||||
ensure(! what.empty());
|
||||
// capture a snapshot but do not yet traverse it
|
||||
auto snapshot = Unkeyed::instance_snapshot();
|
||||
// delete the one instance
|
||||
delete unkeyed;
|
||||
// traversing the snapshot should reflect the deletion
|
||||
// avoid ensure_equals() because it requires the ability to stream the
|
||||
// two values to std::ostream
|
||||
ensure(snapshot.begin() == snapshot.end());
|
||||
}
|
||||
|
||||
|
||||
template<> template<>
|
||||
void object::test<8>()
|
||||
{
|
||||
|
|
@ -246,11 +241,9 @@ namespace tut
|
|||
// We can't use the iterator-range InstanceSet constructor because
|
||||
// beginInstances() returns an iterator that dereferences to an
|
||||
// Unkeyed&, not an Unkeyed*.
|
||||
for (Unkeyed::instance_iter uki(Unkeyed::beginInstances()),
|
||||
ukend(Unkeyed::endInstances());
|
||||
uki != ukend; ++uki)
|
||||
for (auto& ref : Unkeyed::instance_snapshot())
|
||||
{
|
||||
existing.insert(&*uki);
|
||||
existing.insert(&ref);
|
||||
}
|
||||
try
|
||||
{
|
||||
|
|
@ -273,11 +266,9 @@ namespace tut
|
|||
// instances was also present in the original set. If that's not true,
|
||||
// it's because our new Unkeyed ended up in the updated set despite
|
||||
// its constructor exception.
|
||||
for (Unkeyed::instance_iter uki(Unkeyed::beginInstances()),
|
||||
ukend(Unkeyed::endInstances());
|
||||
uki != ukend; ++uki)
|
||||
for (auto& ref : Unkeyed::instance_snapshot())
|
||||
{
|
||||
ensure("failed to remove instance", existing.find(&*uki) != existing.end());
|
||||
ensure("failed to remove instance", existing.find(&ref) != existing.end());
|
||||
}
|
||||
}*/
|
||||
}
|
||||
} // namespace tut
|
||||
|
|
|
|||
|
|
@ -49,24 +49,28 @@ const size_t BUFFERED_LENGTH = 1023*1024; // try wrangling just under a megabyte
|
|||
|
||||
#endif
|
||||
|
||||
void waitfor(const std::vector<LLLeap*>& instances, int timeout=60)
|
||||
// capture std::weak_ptrs to LLLeap instances so we can tell when they expire
|
||||
typedef std::vector<std::weak_ptr<LLLeap>> LLLeapVector;
|
||||
|
||||
void waitfor(const LLLeapVector& instances, int timeout=60)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < timeout; ++i)
|
||||
{
|
||||
// Every iteration, test whether any of the passed LLLeap instances
|
||||
// still exist (are still running).
|
||||
std::vector<LLLeap*>::const_iterator vli(instances.begin()), vlend(instances.end());
|
||||
for ( ; vli != vlend; ++vli)
|
||||
bool found = false;
|
||||
for (auto& ptr : instances)
|
||||
{
|
||||
// getInstance() returns NULL if it's terminated/gone, non-NULL if
|
||||
// it's still running
|
||||
if (LLLeap::getInstance(*vli))
|
||||
if (! ptr.expired())
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// If we made it through all of 'instances' without finding one that's
|
||||
// still running, we're done.
|
||||
if (vli == vlend)
|
||||
if (! found)
|
||||
{
|
||||
/*==========================================================================*|
|
||||
std::cout << instances.size() << " LLLeap instances terminated in "
|
||||
|
|
@ -86,8 +90,8 @@ void waitfor(const std::vector<LLLeap*>& instances, int timeout=60)
|
|||
|
||||
void waitfor(LLLeap* instance, int timeout=60)
|
||||
{
|
||||
std::vector<LLLeap*> instances;
|
||||
instances.push_back(instance);
|
||||
LLLeapVector instances;
|
||||
instances.push_back(instance->getWeak());
|
||||
waitfor(instances, timeout);
|
||||
}
|
||||
|
||||
|
|
@ -218,11 +222,11 @@ namespace tut
|
|||
NamedTempFile script("py",
|
||||
"import time\n"
|
||||
"time.sleep(1)\n");
|
||||
std::vector<LLLeap*> instances;
|
||||
LLLeapVector instances;
|
||||
instances.push_back(LLLeap::create(get_test_name(),
|
||||
sv(list_of(PYTHON)(script.getName()))));
|
||||
sv(list_of(PYTHON)(script.getName())))->getWeak());
|
||||
instances.push_back(LLLeap::create(get_test_name(),
|
||||
sv(list_of(PYTHON)(script.getName()))));
|
||||
sv(list_of(PYTHON)(script.getName())))->getWeak());
|
||||
// In this case we're simply establishing that two LLLeap instances
|
||||
// can coexist without throwing exceptions or bombing in any other
|
||||
// way. Wait for them to terminate.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,136 @@
|
|||
/**
|
||||
* @file llmainthreadtask_test.cpp
|
||||
* @author Nat Goodspeed
|
||||
* @date 2019-12-05
|
||||
* @brief Test for llmainthreadtask.
|
||||
*
|
||||
* $LicenseInfo:firstyear=2019&license=viewerlgpl$
|
||||
* Copyright (c) 2019, Linden Research, Inc.
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
// Precompiled header
|
||||
#include "linden_common.h"
|
||||
// associated header
|
||||
#include "llmainthreadtask.h"
|
||||
// STL headers
|
||||
// std headers
|
||||
#include <atomic>
|
||||
// external library headers
|
||||
// other Linden headers
|
||||
#include "../test/lltut.h"
|
||||
#include "../test/sync.h"
|
||||
#include "llthread.h" // on_main_thread()
|
||||
#include "lleventtimer.h"
|
||||
#include "lockstatic.h"
|
||||
|
||||
/*****************************************************************************
|
||||
* TUT
|
||||
*****************************************************************************/
|
||||
namespace tut
|
||||
{
|
||||
struct llmainthreadtask_data
|
||||
{
|
||||
// 2-second timeout
|
||||
Sync mSync{F32Milliseconds(2000.0f)};
|
||||
|
||||
llmainthreadtask_data()
|
||||
{
|
||||
// we're not testing the result; this is just to cache the
|
||||
// initial thread as the main thread.
|
||||
on_main_thread();
|
||||
}
|
||||
};
|
||||
typedef test_group<llmainthreadtask_data> llmainthreadtask_group;
|
||||
typedef llmainthreadtask_group::object object;
|
||||
llmainthreadtask_group llmainthreadtaskgrp("llmainthreadtask");
|
||||
|
||||
template<> template<>
|
||||
void object::test<1>()
|
||||
{
|
||||
set_test_name("inline");
|
||||
bool ran = false;
|
||||
bool result = LLMainThreadTask::dispatch(
|
||||
[&ran]()->bool{
|
||||
ran = true;
|
||||
return true;
|
||||
});
|
||||
ensure("didn't run lambda", ran);
|
||||
ensure("didn't return result", result);
|
||||
}
|
||||
|
||||
struct StaticData
|
||||
{
|
||||
std::mutex mMutex; // LockStatic looks for mMutex
|
||||
bool ran{false};
|
||||
};
|
||||
typedef llthread::LockStatic<StaticData> LockStatic;
|
||||
|
||||
template<> template<>
|
||||
void object::test<2>()
|
||||
{
|
||||
set_test_name("cross-thread");
|
||||
std::atomic_bool result(false);
|
||||
// wrapping our thread lambda in a packaged_task will catch any
|
||||
// exceptions it might throw and deliver them via future
|
||||
std::packaged_task<void()> thread_work(
|
||||
[this, &result](){
|
||||
// unblock test<2>()'s yield_until(1)
|
||||
mSync.set(1);
|
||||
// dispatch work to main thread -- should block here
|
||||
bool on_main(
|
||||
LLMainThreadTask::dispatch(
|
||||
[]()->bool{
|
||||
// have to lock static mutex to set static data
|
||||
LockStatic()->ran = true;
|
||||
// indicate whether task was run on the main thread
|
||||
return on_main_thread();
|
||||
}));
|
||||
// wait for test<2>() to unblock us again
|
||||
mSync.yield_until(3);
|
||||
result = on_main;
|
||||
});
|
||||
auto thread_result = thread_work.get_future();
|
||||
std::thread thread;
|
||||
try
|
||||
{
|
||||
// run thread_work
|
||||
thread = std::thread(std::move(thread_work));
|
||||
// wait for thread to set(1)
|
||||
mSync.yield_until(1);
|
||||
// try to acquire the lock, should block because thread has it
|
||||
LockStatic lk;
|
||||
// wake up when dispatch() unlocks the static mutex
|
||||
ensure("shouldn't have run yet", !lk->ran);
|
||||
ensure("shouldn't have returned yet", !result);
|
||||
// unlock so the task can acquire the lock
|
||||
lk.unlock();
|
||||
// run the task -- should unblock thread, which will immediately block
|
||||
// on mSync
|
||||
LLEventTimer::updateClass();
|
||||
// 'lk', having unlocked, can no longer be used to access; relock with
|
||||
// a new LockStatic instance
|
||||
ensure("should now have run", LockStatic()->ran);
|
||||
ensure("returned too early", !result);
|
||||
// okay, let thread perform the assignment
|
||||
mSync.set(3);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// A test failure exception anywhere in the try block can cause
|
||||
// the test program to terminate without explanation when
|
||||
// ~thread() finds that 'thread' is still joinable. We could
|
||||
// either join() or detach() it -- but since it might be blocked
|
||||
// waiting for something from the main thread that now can never
|
||||
// happen, it's safer to detach it.
|
||||
thread.detach();
|
||||
throw;
|
||||
}
|
||||
// 'thread' should be all done now
|
||||
thread.join();
|
||||
// deliver any exception thrown by thread_work
|
||||
thread_result.get();
|
||||
ensure("ran changed", LockStatic()->ran);
|
||||
ensure("didn't run on main thread", result);
|
||||
}
|
||||
} // namespace tut
|
||||
|
|
@ -40,6 +40,7 @@
|
|||
#include <sstream>
|
||||
#if SAFE_SSL
|
||||
#include <openssl/crypto.h>
|
||||
#include <functional> // std::hash
|
||||
#endif
|
||||
|
||||
|
||||
|
|
@ -369,7 +370,8 @@ void ssl_locking_callback(int mode, int type, const char *file, int line)
|
|||
//static
|
||||
unsigned long ssl_thread_id(void)
|
||||
{
|
||||
return LLThread::currentID();
|
||||
// std::thread::id is very deliberately opaque, but we can hash it
|
||||
return std::hash<LLThread::id_t>()(LLThread::currentID());
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
|
|||
|
|
@ -44,16 +44,19 @@ using namespace kdu_core;
|
|||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
||||
// stream kdu_dims to std::ostream
|
||||
// Turns out this must NOT be in the anonymous namespace!
|
||||
// It must also precede #include "stringize.h".
|
||||
namespace kdu_core
|
||||
{
|
||||
// stream kdu_dims to std::ostream
|
||||
inline
|
||||
std::ostream& operator<<(std::ostream& out, const kdu_dims& dims)
|
||||
{
|
||||
return out << "(" << dims.pos.x << "," << dims.pos.y << "),"
|
||||
"[" << dims.size.x << "x" << dims.size.y << "]";
|
||||
}
|
||||
} // namespace kdu_core
|
||||
|
||||
// operator<<(std::ostream&, const kdu_dims&) must precede #include "stringize.h"
|
||||
#include "stringize.h"
|
||||
|
||||
namespace {
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@
|
|||
#include "llmath.h"
|
||||
#include "llstl.h"
|
||||
#include "llthread.h"
|
||||
#include "llmutex.h"
|
||||
#include <iterator>
|
||||
|
||||
#define ASSERT_LLBUFFERARRAY_MUTEX_LOCKED() llassert(!mMutexp || mMutexp->isSelfLocked())
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@
|
|||
|
||||
#include "llbuffer.h"
|
||||
#include "llthread.h"
|
||||
#include "llmutex.h"
|
||||
|
||||
static const S32 DEFAULT_OUTPUT_SEGMENT_SIZE = 1024 * 4;
|
||||
|
||||
|
|
|
|||
|
|
@ -291,7 +291,7 @@ LLCoprocedurePool::LLCoprocedurePool(const std::string &poolName, size_t size):
|
|||
<< LL_ENDL;
|
||||
// This should ensure that all waiting coprocedures in this
|
||||
// pool will wake up and terminate.
|
||||
pendingCoprocs->pushFront({});
|
||||
pendingCoprocs->close();
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
|
@ -323,7 +323,7 @@ LLUUID LLCoprocedurePool::enqueueCoprocedure(const std::string &name, LLCoproced
|
|||
LL_INFOS("CoProcMgr") << "Coprocedure(" << name << ") enqueuing with id=" << id.asString() << " in pool \"" << mPoolName << "\" at " << mPending << LL_ENDL;
|
||||
auto pushed = mPendingCoprocs->tryPushFront(boost::make_shared<QueuedCoproc>(name, id, proc));
|
||||
// We don't really have a lot of good options if tryPushFront() failed,
|
||||
// perhaps because the consuming coroutine is gummed up or something. This
|
||||
// perhaps because the consuming coroutines are gummed up or something. This
|
||||
// method is probably called from code called by mainloop. If we toss an
|
||||
// llcoro::suspend() call here, we'll circle back for another mainloop
|
||||
// iteration, possibly resulting in being re-entered here. Let's avoid that.
|
||||
|
|
@ -341,13 +341,14 @@ void LLCoprocedurePool::coprocedureInvokerCoro(
|
|||
QueuedCoproc::ptr_t coproc;
|
||||
for (;;)
|
||||
{
|
||||
try
|
||||
{
|
||||
LLCoros::TempStatus st("waiting for work");
|
||||
coproc = pendingCoprocs->popBack();
|
||||
}
|
||||
if (! coproc)
|
||||
catch (const LLThreadSafeQueueError&)
|
||||
{
|
||||
// close() pushes an empty pointer to signal done
|
||||
// queue is closed
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -369,7 +370,7 @@ void LLCoprocedurePool::coprocedureInvokerCoro(
|
|||
<< ") in pool '" << mPoolName << "'"));
|
||||
// must NOT omit this or we deplete the pool
|
||||
mActiveCoprocs.erase(itActive);
|
||||
throw;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Nicky: This is super spammy. Consider using LL_DEBUGS here?
|
||||
|
|
@ -381,5 +382,5 @@ void LLCoprocedurePool::coprocedureInvokerCoro(
|
|||
|
||||
void LLCoprocedurePool::close()
|
||||
{
|
||||
mPendingCoprocs->pushFront({});
|
||||
mPendingCoprocs->close();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@
|
|||
#include "llmemory.h"
|
||||
#include "llsingleton.h"
|
||||
#include "llthread.h"
|
||||
#include "llmutex.h"
|
||||
#include <curl/curl.h>
|
||||
#include <string>
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@
|
|||
|
||||
#include "lliosocket.h"
|
||||
#include "llthread.h"
|
||||
#include "llmutex.h"
|
||||
|
||||
class LLPluginMessagePipe;
|
||||
|
||||
|
|
|
|||
|
|
@ -2452,9 +2452,8 @@ void LLGLNamePool::release(GLuint name)
|
|||
//static
|
||||
void LLGLNamePool::upkeepPools()
|
||||
{
|
||||
for (tracker_t::instance_iter iter = beginInstances(); iter != endInstances(); ++iter)
|
||||
for (auto& pool : instance_snapshot())
|
||||
{
|
||||
LLGLNamePool & pool = *iter;
|
||||
pool.upkeep();
|
||||
}
|
||||
}
|
||||
|
|
@ -2462,9 +2461,8 @@ void LLGLNamePool::upkeepPools()
|
|||
//static
|
||||
void LLGLNamePool::cleanupPools()
|
||||
{
|
||||
for (tracker_t::instance_iter iter = beginInstances(); iter != endInstances(); ++iter)
|
||||
for (auto& pool : instance_snapshot())
|
||||
{
|
||||
LLGLNamePool & pool = *iter;
|
||||
pool.cleanup();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -723,9 +723,9 @@ void LLConsole::onUrlLabelCallback(const LLUUID& paragraph_id, const std::string
|
|||
// static
|
||||
void LLConsole::updateClass()
|
||||
{
|
||||
for (instance_iter it = beginInstances(); it != endInstances(); ++it)
|
||||
for (auto& con : instance_snapshot())
|
||||
{
|
||||
it->update();
|
||||
con.update();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -713,10 +713,10 @@ void LLLayoutStack::createResizeBar(LLLayoutPanel* panelp)
|
|||
//static
|
||||
void LLLayoutStack::updateClass()
|
||||
{
|
||||
for (instance_iter it = beginInstances(); it != endInstances(); ++it)
|
||||
for (auto& layout : instance_snapshot())
|
||||
{
|
||||
it->updateLayout();
|
||||
it->mAnimatedThisFrame = false;
|
||||
layout.updateLayout();
|
||||
layout.mAnimatedThisFrame = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -127,18 +127,16 @@ void LLNotificationsListener::listChannels(const LLSD& params) const
|
|||
{
|
||||
LLReqID reqID(params);
|
||||
LLSD response(reqID.makeResponse());
|
||||
for (LLNotificationChannel::instance_iter cmi(LLNotificationChannel::beginInstances()),
|
||||
cmend(LLNotificationChannel::endInstances());
|
||||
cmi != cmend; ++cmi)
|
||||
for (auto& cm : LLNotificationChannel::instance_snapshot())
|
||||
{
|
||||
LLSD channelInfo, parents;
|
||||
BOOST_FOREACH(const std::string& parent, cmi->getParents())
|
||||
for (const std::string& parent : cm.getParents())
|
||||
{
|
||||
parents.append(parent);
|
||||
}
|
||||
channelInfo["parents"] = parents;
|
||||
channelInfo["parent"] = parents.size()? parents[0] : "";
|
||||
response[cmi->getName()] = channelInfo;
|
||||
response[cm.getName()] = channelInfo;
|
||||
}
|
||||
LLEventPumps::instance().obtain(params["reply"]).post(response);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@
|
|||
#include "lluuid.h"
|
||||
#include "llassettype.h"
|
||||
#include "llthread.h"
|
||||
#include "llmutex.h"
|
||||
|
||||
enum EVFSValid
|
||||
{
|
||||
|
|
|
|||
|
|
@ -471,9 +471,8 @@ LLCoordCommon LL_COORD_TYPE_WINDOW::convertToCommon() const
|
|||
{
|
||||
const LLCoordWindow& self = LLCoordWindow::getTypedCoords(*this);
|
||||
|
||||
LLWindow* windowp = &(*LLWindow::beginInstances());
|
||||
LLCoordGL out;
|
||||
windowp->convertCoords(self, &out);
|
||||
LLWindow::instance_snapshot().begin()->convertCoords(self, &out);
|
||||
return out.convert();
|
||||
}
|
||||
|
||||
|
|
@ -481,18 +480,16 @@ void LL_COORD_TYPE_WINDOW::convertFromCommon(const LLCoordCommon& from)
|
|||
{
|
||||
LLCoordWindow& self = LLCoordWindow::getTypedCoords(*this);
|
||||
|
||||
LLWindow* windowp = &(*LLWindow::beginInstances());
|
||||
LLCoordGL from_gl(from);
|
||||
windowp->convertCoords(from_gl, &self);
|
||||
LLWindow::instance_snapshot().begin()->convertCoords(from_gl, &self);
|
||||
}
|
||||
|
||||
LLCoordCommon LL_COORD_TYPE_SCREEN::convertToCommon() const
|
||||
{
|
||||
const LLCoordScreen& self = LLCoordScreen::getTypedCoords(*this);
|
||||
|
||||
LLWindow* windowp = &(*LLWindow::beginInstances());
|
||||
LLCoordGL out;
|
||||
windowp->convertCoords(self, &out);
|
||||
LLWindow::instance_snapshot().begin()->convertCoords(self, &out);
|
||||
return out.convert();
|
||||
}
|
||||
|
||||
|
|
@ -500,7 +497,6 @@ void LL_COORD_TYPE_SCREEN::convertFromCommon(const LLCoordCommon& from)
|
|||
{
|
||||
LLCoordScreen& self = LLCoordScreen::getTypedCoords(*this);
|
||||
|
||||
LLWindow* windowp = &(*LLWindow::beginInstances());
|
||||
LLCoordGL from_gl(from);
|
||||
windowp->convertCoords(from_gl, &self);
|
||||
LLWindow::instance_snapshot().begin()->convertCoords(from_gl, &self);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -240,8 +240,6 @@ public:
|
|||
LLControlGroup(const std::string& name);
|
||||
~LLControlGroup();
|
||||
void cleanup();
|
||||
|
||||
typedef LLInstanceTracker<LLControlGroup, std::string>::instance_iter instance_iter;
|
||||
|
||||
LLControlVariablePtr getControl(const std::string& name);
|
||||
|
||||
|
|
|
|||
|
|
@ -27,8 +27,9 @@
|
|||
*/
|
||||
|
||||
#include "volume_catcher.h"
|
||||
#include <windows.h>
|
||||
#include "llsingleton.h"
|
||||
#include <windows.h>
|
||||
#include <mmeapi.h>
|
||||
class VolumeCatcherImpl : public LLSingleton<VolumeCatcherImpl>
|
||||
{
|
||||
LLSINGLETON(VolumeCatcherImpl);
|
||||
|
|
|
|||
|
|
@ -1562,14 +1562,12 @@ void FSChatHistory::appendMessage(const LLChat& chat, const LLSD &args, const LL
|
|||
// We don't want multiple friendship offers to appear, this code checks if there are previous offers
|
||||
// by iterating though all panels.
|
||||
// Note: it might be better to simply add a "pending offer" flag somewhere
|
||||
for (LLToastNotifyPanel::instance_iter ti(LLToastNotifyPanel::beginInstances())
|
||||
, tend(LLToastNotifyPanel::endInstances()); ti != tend; ++ti)
|
||||
for (auto& ti : LLToastNotifyPanel::instance_snapshot())
|
||||
{
|
||||
LLToastNotifyPanel& panel = *ti;
|
||||
LLIMToastNotifyPanel * imtoastp = dynamic_cast<LLIMToastNotifyPanel *>(&panel);
|
||||
const std::string& notification_name = panel.getNotificationName();
|
||||
LLIMToastNotifyPanel * imtoastp = dynamic_cast<LLIMToastNotifyPanel *>(&ti);
|
||||
const std::string& notification_name = ti.getNotificationName();
|
||||
if (notification_name == "OfferFriendship"
|
||||
&& panel.isControlPanelEnabled()
|
||||
&& ti.isControlPanelEnabled()
|
||||
&& imtoastp)
|
||||
{
|
||||
create_toast = false;
|
||||
|
|
|
|||
|
|
@ -1972,24 +1972,9 @@ bool LLAppViewer::cleanup()
|
|||
gDirUtilp->deleteFilesInDir(logdir, "*-*-*-*-*.dmp");
|
||||
}
|
||||
|
||||
{
|
||||
// Kill off LLLeap objects. We can find them all because LLLeap is derived
|
||||
// from LLInstanceTracker. But collect instances first: LLInstanceTracker
|
||||
// specifically forbids adding/deleting instances while iterating.
|
||||
std::vector<LLLeap*> leaps;
|
||||
leaps.reserve(LLLeap::instanceCount());
|
||||
for (LLLeap::instance_iter li(LLLeap::beginInstances()), lend(LLLeap::endInstances());
|
||||
li != lend; ++li)
|
||||
{
|
||||
leaps.push_back(&*li);
|
||||
}
|
||||
// Okay, now trash them all. We don't have to NULL or erase the entry
|
||||
// in 'leaps' because the whole vector is going away momentarily.
|
||||
BOOST_FOREACH(LLLeap* leap, leaps)
|
||||
{
|
||||
delete leap;
|
||||
}
|
||||
} // destroy 'leaps'
|
||||
// Kill off LLLeap objects. We can find them all because LLLeap is derived
|
||||
// from LLInstanceTracker.
|
||||
LLLeap::instance_snapshot().deleteAll();
|
||||
|
||||
//flag all elements as needing to be destroyed immediately
|
||||
// to ensure shutdown order
|
||||
|
|
@ -3397,12 +3382,11 @@ bool LLAppViewer::initConfiguration()
|
|||
|
||||
// Let anyone else who cares know that we've populated our settings
|
||||
// variables.
|
||||
for (LLControlGroup::key_iter ki(LLControlGroup::beginKeys()), kend(LLControlGroup::endKeys());
|
||||
ki != kend; ++ki)
|
||||
for (const auto& key : LLControlGroup::key_snapshot())
|
||||
{
|
||||
// For each named instance of LLControlGroup, send an event saying
|
||||
// we've initialized an LLControlGroup instance by that name.
|
||||
LLEventPumps::instance().obtain("LLControlGroup").post(LLSDMap("init", *ki));
|
||||
LLEventPumps::instance().obtain("LLControlGroup").post(LLSDMap("init", key));
|
||||
}
|
||||
|
||||
// [RLVa:KB] - Patch: RLVa-2.1.0
|
||||
|
|
|
|||
|
|
@ -48,11 +48,18 @@ LLChannelManager::LLChannelManager()
|
|||
LLAppViewer::instance()->setOnLoginCompletedCallback(boost::bind(&LLChannelManager::onLoginCompleted, this));
|
||||
mChannelList.clear();
|
||||
mStartUpChannel = NULL;
|
||||
|
||||
|
||||
if(!gViewerWindow)
|
||||
{
|
||||
LL_ERRS() << "LLChannelManager::LLChannelManager() - viwer window is not initialized yet" << LL_ENDL;
|
||||
}
|
||||
|
||||
// We don't actually need this instance right now, but our
|
||||
// cleanupSingleton() method deletes LLScreenChannels, which need to
|
||||
// unregister from LLUI. Calling LLUI::instance() here establishes the
|
||||
// dependency so LLSingletonBase::deleteAll() calls our deleteSingleton()
|
||||
// before LLUI::deleteSingleton().
|
||||
LLUI::instance();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -1410,10 +1410,8 @@ void LLChatHistory::appendMessage(const LLChat& chat, const LLSD &args, const LL
|
|||
// We don't want multiple friendship offers to appear, this code checks if there are previous offers
|
||||
// by iterating though all panels.
|
||||
// Note: it might be better to simply add a "pending offer" flag somewhere
|
||||
for (LLToastNotifyPanel::instance_iter ti(LLToastNotifyPanel::beginInstances())
|
||||
, tend(LLToastNotifyPanel::endInstances()); ti != tend; ++ti)
|
||||
for (auto& panel : LLToastNotifyPanel::instance_snapshot())
|
||||
{
|
||||
LLToastNotifyPanel& panel = *ti;
|
||||
LLIMToastNotifyPanel * imtoastp = dynamic_cast<LLIMToastNotifyPanel *>(&panel);
|
||||
const std::string& notification_name = panel.getNotificationName();
|
||||
if (notification_name == "OfferFriendship"
|
||||
|
|
|
|||
|
|
@ -2054,10 +2054,8 @@ void LLIMProcessing::processNewMessage(LLUUID from_id,
|
|||
payload["sender"] = sender.getIPandPort();
|
||||
|
||||
bool add_notification = true;
|
||||
for (LLToastNotifyPanel::instance_iter ti(LLToastNotifyPanel::beginInstances())
|
||||
, tend(LLToastNotifyPanel::endInstances()); ti != tend; ++ti)
|
||||
for (auto& panel : LLToastNotifyPanel::instance_snapshot())
|
||||
{
|
||||
LLToastNotifyPanel& panel = *ti;
|
||||
const std::string& notification_name = panel.getNotificationName();
|
||||
if (notification_name == "OfferFriendship" && panel.isControlPanelEnabled())
|
||||
{
|
||||
|
|
|
|||
|
|
@ -559,16 +559,14 @@ void LLSceneMonitor::dumpToFile(std::string file_name)
|
|||
|
||||
|
||||
typedef StatType<CountAccumulator> trace_count;
|
||||
for (trace_count::instance_iter it = trace_count::beginInstances(), end_it = trace_count::endInstances();
|
||||
it != end_it;
|
||||
++it)
|
||||
for (auto& it : trace_count::instance_snapshot())
|
||||
{
|
||||
std::ostringstream row;
|
||||
row << std::setprecision(10);
|
||||
|
||||
row << it->getName();
|
||||
row << it.getName();
|
||||
|
||||
const char* unit_label = it->getUnitLabel();
|
||||
const char* unit_label = it.getUnitLabel();
|
||||
if(unit_label[0])
|
||||
{
|
||||
row << "(" << unit_label << ")";
|
||||
|
|
@ -579,8 +577,8 @@ void LLSceneMonitor::dumpToFile(std::string file_name)
|
|||
for (S32 frame = 1; frame <= frame_count; frame++)
|
||||
{
|
||||
Recording& recording = scene_load_recording.getPrevRecording(frame_count - frame);
|
||||
samples += recording.getSampleCount(*it);
|
||||
row << ", " << recording.getSum(*it);
|
||||
samples += recording.getSampleCount(it);
|
||||
row << ", " << recording.getSum(it);
|
||||
}
|
||||
|
||||
row << '\n';
|
||||
|
|
@ -593,15 +591,13 @@ void LLSceneMonitor::dumpToFile(std::string file_name)
|
|||
|
||||
typedef StatType<EventAccumulator> trace_event;
|
||||
|
||||
for (trace_event::instance_iter it = trace_event::beginInstances(), end_it = trace_event::endInstances();
|
||||
it != end_it;
|
||||
++it)
|
||||
for (auto& it : trace_event::instance_snapshot())
|
||||
{
|
||||
std::ostringstream row;
|
||||
row << std::setprecision(10);
|
||||
row << it->getName();
|
||||
row << it.getName();
|
||||
|
||||
const char* unit_label = it->getUnitLabel();
|
||||
const char* unit_label = it.getUnitLabel();
|
||||
if(unit_label[0])
|
||||
{
|
||||
row << "(" << unit_label << ")";
|
||||
|
|
@ -612,8 +608,8 @@ void LLSceneMonitor::dumpToFile(std::string file_name)
|
|||
for (S32 frame = 1; frame <= frame_count; frame++)
|
||||
{
|
||||
Recording& recording = scene_load_recording.getPrevRecording(frame_count - frame);
|
||||
samples += recording.getSampleCount(*it);
|
||||
F64 mean = recording.getMean(*it);
|
||||
samples += recording.getSampleCount(it);
|
||||
F64 mean = recording.getMean(it);
|
||||
if (llisnan(mean))
|
||||
{
|
||||
row << ", n/a";
|
||||
|
|
@ -634,15 +630,13 @@ void LLSceneMonitor::dumpToFile(std::string file_name)
|
|||
|
||||
typedef StatType<SampleAccumulator> trace_sample;
|
||||
|
||||
for (trace_sample::instance_iter it = trace_sample::beginInstances(), end_it = trace_sample::endInstances();
|
||||
it != end_it;
|
||||
++it)
|
||||
for (auto& it : trace_sample::instance_snapshot())
|
||||
{
|
||||
std::ostringstream row;
|
||||
row << std::setprecision(10);
|
||||
row << it->getName();
|
||||
row << it.getName();
|
||||
|
||||
const char* unit_label = it->getUnitLabel();
|
||||
const char* unit_label = it.getUnitLabel();
|
||||
if(unit_label[0])
|
||||
{
|
||||
row << "(" << unit_label << ")";
|
||||
|
|
@ -653,8 +647,8 @@ void LLSceneMonitor::dumpToFile(std::string file_name)
|
|||
for (S32 frame = 1; frame <= frame_count; frame++)
|
||||
{
|
||||
Recording& recording = scene_load_recording.getPrevRecording(frame_count - frame);
|
||||
samples += recording.getSampleCount(*it);
|
||||
F64 mean = recording.getMean(*it);
|
||||
samples += recording.getSampleCount(it);
|
||||
F64 mean = recording.getMean(it);
|
||||
if (llisnan(mean))
|
||||
{
|
||||
row << ", n/a";
|
||||
|
|
@ -674,15 +668,13 @@ void LLSceneMonitor::dumpToFile(std::string file_name)
|
|||
}
|
||||
|
||||
typedef StatType<MemAccumulator> trace_mem;
|
||||
for (trace_mem::instance_iter it = trace_mem::beginInstances(), end_it = trace_mem::endInstances();
|
||||
it != end_it;
|
||||
++it)
|
||||
for (auto& it : trace_mem::instance_snapshot())
|
||||
{
|
||||
os << it->getName() << "(KiB)";
|
||||
os << it.getName() << "(KiB)";
|
||||
|
||||
for (S32 frame = 1; frame <= frame_count; frame++)
|
||||
{
|
||||
os << ", " << scene_load_recording.getPrevRecording(frame_count - frame).getMax(*it).valueInUnits<LLUnits::Kilobytes>();
|
||||
os << ", " << scene_load_recording.getPrevRecording(frame_count - frame).getMax(it).valueInUnits<LLUnits::Kilobytes>();
|
||||
}
|
||||
|
||||
os << '\n';
|
||||
|
|
|
|||
|
|
@ -634,16 +634,8 @@ S32 LLToast::notifyParent(const LLSD& info)
|
|||
//static
|
||||
void LLToast::updateClass()
|
||||
{
|
||||
// <FS:ND> Minimize calls to getInstances per frame
|
||||
//for (LLInstanceTracker<LLToast>::instance_iter iter = LLInstanceTracker<LLToast>::beginInstances();
|
||||
// iter != LLInstanceTracker<LLToast>::endInstances(); )
|
||||
|
||||
LLInstanceTracker<LLToast>::instance_iter end = LLInstanceTracker<LLToast>::endInstances();
|
||||
for (LLInstanceTracker<LLToast>::instance_iter iter = LLInstanceTracker<LLToast>::beginInstances(); iter != end; )
|
||||
// </FS:ND>
|
||||
for (auto& toast : LLInstanceTracker<LLToast>::instance_snapshot())
|
||||
{
|
||||
LLToast& toast = *iter++;
|
||||
|
||||
toast.updateHoveredState();
|
||||
}
|
||||
}
|
||||
|
|
@ -651,22 +643,6 @@ void LLToast::updateClass()
|
|||
// static
|
||||
void LLToast::cleanupToasts()
|
||||
{
|
||||
LLToast * toastp = NULL;
|
||||
|
||||
while (LLInstanceTracker<LLToast>::instanceCount() > 0)
|
||||
{
|
||||
{ // Need to scope iter to allow deletion
|
||||
LLInstanceTracker<LLToast>::instance_iter iter = LLInstanceTracker<LLToast>::beginInstances();
|
||||
toastp = &(*iter);
|
||||
}
|
||||
|
||||
//LL_INFOS() << "Cleaning up toast id " << toastp->getNotificationID() << LL_ENDL;
|
||||
|
||||
// LLToast destructor will remove it from the LLInstanceTracker.
|
||||
if (!toastp)
|
||||
break; // Don't get stuck in the loop if a null pointer somehow got on the list
|
||||
|
||||
delete toastp;
|
||||
}
|
||||
LLInstanceTracker<LLToast>::instance_snapshot().deleteAll();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -50,11 +50,9 @@ LLViewerControlListener::LLViewerControlListener()
|
|||
std::ostringstream groupnames;
|
||||
groupnames << "[\"group\"] is one of ";
|
||||
const char* delim = "";
|
||||
for (LLControlGroup::key_iter cgki(LLControlGroup::beginKeys()),
|
||||
cgkend(LLControlGroup::endKeys());
|
||||
cgki != cgkend; ++cgki)
|
||||
for (const auto& key : LLControlGroup::key_snapshot())
|
||||
{
|
||||
groupnames << delim << '"' << *cgki << '"';
|
||||
groupnames << delim << '"' << key << '"';
|
||||
delim = ", ";
|
||||
}
|
||||
groupnames << '\n';
|
||||
|
|
@ -181,11 +179,9 @@ void LLViewerControlListener::groups(LLSD const & request)
|
|||
{
|
||||
// No Info, we're not looking up either a group or a control name.
|
||||
Response response(LLSD(), request);
|
||||
for (LLControlGroup::key_iter cgki(LLControlGroup::beginKeys()),
|
||||
cgkend(LLControlGroup::endKeys());
|
||||
cgki != cgkend; ++cgki)
|
||||
for (const auto& key : LLControlGroup::key_snapshot())
|
||||
{
|
||||
response["groups"].append(*cgki);
|
||||
response["groups"].append(key);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -41,18 +41,49 @@ public:
|
|||
mTimeout(timeout)
|
||||
{}
|
||||
|
||||
/// Bump mCond by n steps -- ideally, do this every time a participating
|
||||
/// coroutine wakes up from any suspension. The choice to bump() after
|
||||
/// resumption rather than just before suspending is worth calling out:
|
||||
/// this practice relies on the fact that condition_variable::notify_all()
|
||||
/// merely marks a suspended coroutine ready to run, rather than
|
||||
/// immediately resuming it. This way, though, even if a coroutine exits
|
||||
/// before reaching its next suspend point, the other coroutine isn't
|
||||
/// left waiting forever.
|
||||
/**
|
||||
* Bump mCond by n steps -- ideally, do this every time a participating
|
||||
* coroutine wakes up from any suspension. The choice to bump() after
|
||||
* resumption rather than just before suspending is worth calling out:
|
||||
* this practice relies on the fact that condition_variable::notify_all()
|
||||
* merely marks a suspended coroutine ready to run, rather than
|
||||
* immediately resuming it. This way, though, even if a coroutine exits
|
||||
* before reaching its next suspend point, the other coroutine isn't
|
||||
* left waiting forever.
|
||||
*/
|
||||
void bump(int n=1)
|
||||
{
|
||||
LL_DEBUGS() << llcoro::logname() << " bump(" << n << ") -> " << (mCond.get() + n) << LL_ENDL;
|
||||
mCond.set_all(mCond.get() + n);
|
||||
// Calling mCond.set_all(mCond.get() + n) would be great for
|
||||
// coroutines -- but not so good between kernel threads -- it would be
|
||||
// racy. Make the increment atomic by calling update_all(), which runs
|
||||
// the passed lambda within a mutex lock.
|
||||
int updated;
|
||||
mCond.update_all(
|
||||
[&n, &updated](int& data)
|
||||
{
|
||||
data += n;
|
||||
// Capture the new value for possible logging purposes.
|
||||
updated = data;
|
||||
});
|
||||
// In the multi-threaded case, this log message could be a bit
|
||||
// misleading, as it will be emitted after waiting threads have
|
||||
// already awakened. But emitting the log message within the lock
|
||||
// would seem to hold the lock longer than we really ought.
|
||||
LL_DEBUGS() << llcoro::logname() << " bump(" << n << ") -> " << updated << LL_ENDL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set mCond to a specific n. Use of bump() and yield() is nicely
|
||||
* maintainable, since you can insert or delete matching operations in a
|
||||
* test function and have the rest of the Sync operations continue to
|
||||
* line up as before. But sometimes you need to get very specific, which
|
||||
* is where set() and yield_until() come in handy: less maintainable,
|
||||
* more precise.
|
||||
*/
|
||||
void set(int n)
|
||||
{
|
||||
LL_DEBUGS() << llcoro::logname() << " set(" << n << ")" << LL_ENDL;
|
||||
mCond.set_all(n);
|
||||
}
|
||||
|
||||
/// suspend until "somebody else" has bumped mCond by n steps
|
||||
|
|
|
|||
Loading…
Reference in New Issue