summaryrefslogtreecommitdiff
path: root/indra
diff options
context:
space:
mode:
authorAndrey Lihatskiy <alihatskiy@productengine.com>2020-07-21 11:54:11 +0300
committerAndrey Lihatskiy <alihatskiy@productengine.com>2020-07-21 11:54:11 +0300
commitae48c7c8b353d44287db06c3ad52b9ccfcf1b4e4 (patch)
treed6e97f71afbc7e31e9e16f929cb6a6070318dffc /indra
parentad3f7252229e476f6e85f7b5d274aa6ee362fce1 (diff)
parent72423372d6cd7f763a5567ad75752fa4e7131d60 (diff)
Merge branch 'master' into DRTVWR-513-maint
# Conflicts: # indra/llcommon/llerror.cpp # indra/newview/llappviewerwin32.cpp # indra/newview/llimprocessing.cpp # indra/newview/llviewerjoystick.cpp
Diffstat (limited to 'indra')
-rw-r--r--indra/CMakeLists.txt5
-rw-r--r--indra/cmake/00-Common.cmake5
-rw-r--r--indra/cmake/Boost.cmake110
-rw-r--r--indra/cmake/BuildPackagesInfo.cmake3
-rw-r--r--indra/cmake/CMakeLists.txt1
-rw-r--r--indra/cmake/Copy3rdPartyLibs.cmake171
-rw-r--r--indra/cmake/DirectX.cmake48
-rw-r--r--indra/cmake/LLAddBuildTest.cmake5
-rw-r--r--indra/cmake/LLAppearance.cmake2
-rw-r--r--indra/cmake/LLCommon.cmake4
-rw-r--r--indra/cmake/LLCoreHttp.cmake2
-rwxr-xr-xindra/cmake/run_build_test.py19
-rw-r--r--indra/integration_tests/llimage_libtest/CMakeLists.txt1
-rw-r--r--indra/integration_tests/llui_libtest/CMakeLists.txt1
-rw-r--r--indra/linux_crash_logger/CMakeLists.txt2
-rw-r--r--indra/llappearance/llwearabletype.cpp67
-rw-r--r--indra/llappearance/llwearabletype.h1
-rw-r--r--indra/llcommon/CMakeLists.txt15
-rw-r--r--indra/llcommon/StackWalker.cpp9
-rw-r--r--indra/llcommon/StackWalker.h2
-rw-r--r--indra/llcommon/llapp.cpp36
-rw-r--r--indra/llcommon/llapr.h12
-rw-r--r--indra/llcommon/llcond.h405
-rw-r--r--indra/llcommon/llcoro_get_id.cpp32
-rw-r--r--indra/llcommon/llcoro_get_id.h30
-rw-r--r--indra/llcommon/llcoros.cpp384
-rw-r--r--indra/llcommon/llcoros.h275
-rw-r--r--indra/llcommon/llerror.cpp553
-rw-r--r--indra/llcommon/llerror.h78
-rw-r--r--indra/llcommon/llerrorcontrol.h1
-rw-r--r--indra/llcommon/lleventcoro.cpp419
-rw-r--r--indra/llcommon/lleventcoro.h204
-rw-r--r--indra/llcommon/lleventfilter.cpp82
-rw-r--r--indra/llcommon/lleventfilter.h162
-rw-r--r--indra/llcommon/llevents.cpp163
-rw-r--r--indra/llcommon/llevents.h507
-rw-r--r--indra/llcommon/lleventtimer.cpp16
-rw-r--r--indra/llcommon/lleventtimer.h69
-rw-r--r--indra/llcommon/llexception.cpp32
-rw-r--r--indra/llcommon/llexception.h26
-rw-r--r--indra/llcommon/llfasttimer.cpp31
-rw-r--r--indra/llcommon/llfasttimer.h6
-rw-r--r--indra/llcommon/llfile.h63
-rw-r--r--indra/llcommon/llinstancetracker.cpp20
-rw-r--r--indra/llcommon/llinstancetracker.h694
-rw-r--r--indra/llcommon/llleaplistener.cpp58
-rw-r--r--indra/llcommon/llleaplistener.h5
-rw-r--r--indra/llcommon/lllistenerwrapper.h198
-rw-r--r--indra/llcommon/llmainthreadtask.cpp22
-rw-r--r--indra/llcommon/llmainthreadtask.h99
-rw-r--r--indra/llcommon/llmake.h52
-rw-r--r--indra/llcommon/llmutex.cpp13
-rw-r--r--indra/llcommon/llmutex.h25
-rw-r--r--indra/llcommon/llpreprocessor.h7
-rw-r--r--indra/llcommon/llprocess.cpp4
-rw-r--r--indra/llcommon/llrefcount.h3
-rw-r--r--indra/llcommon/llsdserialize.cpp78
-rw-r--r--indra/llcommon/llsdserialize.h100
-rw-r--r--indra/llcommon/llsdserialize_xml.cpp10
-rw-r--r--indra/llcommon/llsdutil.cpp68
-rw-r--r--indra/llcommon/llsdutil.h105
-rw-r--r--indra/llcommon/llsingleton.cpp267
-rw-r--r--indra/llcommon/llsingleton.h586
-rw-r--r--indra/llcommon/llstacktrace.cpp5
-rw-r--r--indra/llcommon/llstring.cpp16
-rw-r--r--indra/llcommon/llstring.h26
-rw-r--r--indra/llcommon/lltempredirect.cpp138
-rw-r--r--indra/llcommon/lltempredirect.h91
-rw-r--r--indra/llcommon/llthread.cpp54
-rw-r--r--indra/llcommon/llthread.h27
-rw-r--r--indra/llcommon/llthreadlocalstorage.cpp12
-rw-r--r--indra/llcommon/llthreadsafequeue.h156
-rw-r--r--indra/llcommon/lltrace.h2
-rw-r--r--indra/llcommon/lltraceaccumulators.cpp4
-rw-r--r--indra/llcommon/lltraceaccumulators.h8
-rw-r--r--indra/llcommon/lltracethreadrecorder.cpp12
-rw-r--r--indra/llcommon/lluuid.cpp3
-rw-r--r--indra/llcommon/llworkerthread.h1
-rw-r--r--indra/llcommon/lockstatic.h73
-rw-r--r--indra/llcommon/mutex.h22
-rw-r--r--indra/llcommon/tests/llcond_test.cpp67
-rw-r--r--indra/llcommon/tests/lleventcoro_test.cpp728
-rw-r--r--indra/llcommon/tests/lleventdispatcher_test.cpp123
-rw-r--r--indra/llcommon/tests/lleventfilter_test.cpp75
-rw-r--r--indra/llcommon/tests/llexception_test.cpp15
-rw-r--r--indra/llcommon/tests/llinstancetracker_test.cpp107
-rw-r--r--indra/llcommon/tests/llleap_test.cpp28
-rw-r--r--indra/llcommon/tests/llmainthreadtask_test.cpp137
-rw-r--r--indra/llcommon/tests/llprocess_test.cpp8
-rw-r--r--indra/llcommon/tests/llsdserialize_test.cpp29
-rw-r--r--indra/llcommon/tests/llsingleton_test.cpp14
-rw-r--r--indra/llcorehttp/CMakeLists.txt13
-rw-r--r--indra/llcorehttp/_httpreplyqueue.h1
-rw-r--r--indra/llcorehttp/examples/http_texture_load.cpp10
-rw-r--r--indra/llcorehttp/httpcommon.cpp4
-rw-r--r--indra/llcorehttp/httpcommon.h1
-rwxr-xr-xindra/llcorehttp/tests/llcorehttp_test.cpp17
-rw-r--r--indra/llcorehttp/tests/test_allocator.cpp18
-rw-r--r--indra/llcorehttp/tests/test_allocator.h13
-rw-r--r--indra/llcorehttp/tests/test_bufferarray.hpp52
-rw-r--r--indra/llcorehttp/tests/test_bufferstream.hpp52
-rw-r--r--indra/llcorehttp/tests/test_httpheaders.hpp40
-rw-r--r--indra/llcorehttp/tests/test_httpoperation.hpp26
-rw-r--r--indra/llcorehttp/tests/test_httprequest.hpp215
-rw-r--r--indra/llcorehttp/tests/test_httprequestqueue.hpp31
-rw-r--r--indra/llcorehttp/tests/test_refcounted.hpp37
-rw-r--r--indra/llcrashlogger/llcrashlogger.cpp8
-rw-r--r--indra/llcrashlogger/llcrashlogger.h7
-rw-r--r--indra/llimage/llimagejpeg.cpp1
-rw-r--r--indra/llinventory/llsettingsdaycycle.cpp4
-rw-r--r--indra/llkdu/llimagej2ckdu.cpp7
-rw-r--r--indra/llmath/tests/v3dmath_test.cpp5
-rw-r--r--indra/llmessage/CMakeLists.txt9
-rw-r--r--indra/llmessage/llavatarnamecache.cpp4
-rw-r--r--indra/llmessage/llbuffer.cpp1
-rw-r--r--indra/llmessage/llbufferstream.cpp1
-rw-r--r--indra/llmessage/llcoproceduremanager.cpp342
-rw-r--r--indra/llmessage/llcoproceduremanager.h10
-rw-r--r--indra/llmessage/llexperiencecache.cpp6
-rw-r--r--indra/llmessage/lliosocket.cpp15
-rw-r--r--indra/llmessage/llproxy.cpp4
-rw-r--r--indra/llmessage/llproxy.h1
-rw-r--r--indra/llmessage/message.cpp41
-rw-r--r--indra/llmessage/message.h132
-rw-r--r--indra/llmessage/tests/llcoproceduremanager_test.cpp178
-rw-r--r--indra/llplugin/llpluginmessagepipe.h1
-rw-r--r--indra/llplugin/slplugin/CMakeLists.txt1
-rw-r--r--indra/llprimitive/CMakeLists.txt2
-rw-r--r--indra/llprimitive/llmodel.cpp2
-rw-r--r--indra/llrender/llgl.cpp6
-rw-r--r--indra/llrender/llvertexbuffer.cpp9
-rw-r--r--indra/llui/CMakeLists.txt2
-rw-r--r--indra/llui/llaccordionctrl.cpp2
-rw-r--r--indra/llui/llconsole.cpp4
-rw-r--r--indra/llui/lllayoutstack.cpp6
-rw-r--r--indra/llui/llnotifications.h34
-rw-r--r--indra/llui/llnotificationslistener.cpp8
-rw-r--r--indra/llvfs/lldir.cpp2
-rw-r--r--indra/llvfs/llvfs.h1
-rw-r--r--indra/llwindow/CMakeLists.txt2
-rw-r--r--indra/llwindow/llwindow.cpp12
-rw-r--r--indra/llxml/llcontrol.h2
-rw-r--r--indra/mac_crash_logger/CMakeLists.txt2
-rw-r--r--indra/media_plugins/cef/windows_volume_catcher.cpp3
-rw-r--r--indra/newview/CMakeLists.txt28
-rw-r--r--indra/newview/VIEWER_VERSION.txt2
-rw-r--r--indra/newview/llaccountingcostmanager.cpp4
-rw-r--r--indra/newview/llappviewer.cpp152
-rw-r--r--indra/newview/llappviewerwin32.cpp100
-rw-r--r--indra/newview/llchannelmanager.cpp9
-rw-r--r--indra/newview/llchathistory.cpp4
-rw-r--r--indra/newview/llcurrencyuimanager.cpp20
-rw-r--r--indra/newview/llfloaterregioninfo.cpp4
-rw-r--r--indra/newview/llfloaterreporter.cpp4
-rw-r--r--indra/newview/llimprocessing.cpp4
-rw-r--r--indra/newview/lllogininstance.cpp30
-rw-r--r--indra/newview/llpaneleditwearable.cpp6
-rw-r--r--indra/newview/llpanellogin.cpp12
-rw-r--r--indra/newview/llscenemonitor.cpp44
-rw-r--r--indra/newview/llstartup.cpp72
-rw-r--r--indra/newview/llstartup.h1
-rw-r--r--indra/newview/llstartuplistener.cpp26
-rw-r--r--indra/newview/llstartuplistener.h1
-rw-r--r--indra/newview/lltexturestats.cpp4
-rw-r--r--indra/newview/lltoast.cpp23
-rw-r--r--indra/newview/lltranslate.cpp20
-rw-r--r--indra/newview/llversioninfo.cpp107
-rw-r--r--indra/newview/llversioninfo.h68
-rw-r--r--indra/newview/llviewercontrollistener.cpp12
-rw-r--r--indra/newview/llviewerjoystick.cpp66
-rw-r--r--indra/newview/llviewermedia.cpp4
-rw-r--r--indra/newview/llviewerprecompiledheaders.h2
-rw-r--r--indra/newview/llviewerstats.cpp2
-rw-r--r--indra/newview/llviewerwindow.cpp2
-rw-r--r--indra/newview/llvoicevivox.cpp142
-rw-r--r--indra/newview/llvovolume.cpp2
-rw-r--r--indra/newview/llwatchdog.cpp6
-rw-r--r--indra/newview/llweb.cpp12
-rw-r--r--indra/newview/llwindebug.h4
-rw-r--r--indra/newview/llxmlrpclistener.cpp3
-rw-r--r--indra/newview/skins/default/xui/en/notifications.xml5
-rw-r--r--indra/newview/tests/lllogininstance_test.cpp2
-rw-r--r--indra/newview/tests/llversioninfo_test.cpp24
-rwxr-xr-xindra/newview/viewer_manifest.py39
-rw-r--r--indra/test/CMakeLists.txt4
-rw-r--r--indra/test/chained_callback.h107
-rw-r--r--indra/test/debug.h42
-rw-r--r--indra/test/llevents_tut.cpp303
-rw-r--r--indra/test/lltestapp.h34
-rw-r--r--indra/test/print.h42
-rw-r--r--indra/test/setenv.h66
-rw-r--r--indra/test/sync.h116
-rw-r--r--indra/test/test.cpp98
-rwxr-xr-xindra/tools/vstool/VSTool.exebin24576 -> 24576 bytes
-rwxr-xr-xindra/tools/vstool/main.cs6
-rw-r--r--indra/viewer_components/login/CMakeLists.txt4
-rw-r--r--indra/viewer_components/login/lllogin.cpp284
-rw-r--r--indra/viewer_components/login/tests/lllogin_test.cpp152
-rw-r--r--indra/win_crash_logger/CMakeLists.txt8
199 files changed, 6527 insertions, 5672 deletions
diff --git a/indra/CMakeLists.txt b/indra/CMakeLists.txt
index 62a8f3f003..53e5d7b6a5 100644
--- a/indra/CMakeLists.txt
+++ b/indra/CMakeLists.txt
@@ -15,6 +15,11 @@ set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
include(Variables)
include(BuildVersion)
+set(LEGACY_STDIO_LIBS)
+if (WINDOWS)
+ set(LEGACY_STDIO_LIBS legacy_stdio_definitions)
+endif (WINDOWS)
+
if (NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING
"Build type. One of: Debug Release RelWithDebInfo" FORCE)
diff --git a/indra/cmake/00-Common.cmake b/indra/cmake/00-Common.cmake
index 03da30649a..865c057e33 100644
--- a/indra/cmake/00-Common.cmake
+++ b/indra/cmake/00-Common.cmake
@@ -60,7 +60,10 @@ if (WINDOWS)
# http://www.cmake.org/pipermail/cmake/2009-September/032143.html
string(REPLACE "/Zm1000" " " CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS})
- set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP")
+ # Without PreferredToolArchitecture=x64, as of 2020-06-26 the 32-bit
+ # compiler on our TeamCity build hosts has started running out of virtual
+ # memory for the precompiled header file.
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP /p:PreferredToolArchitecture=x64")
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO
"${CMAKE_CXX_FLAGS_RELWITHDEBINFO} /Zo"
diff --git a/indra/cmake/Boost.cmake b/indra/cmake/Boost.cmake
index 180a84dbcf..06a7ab6d75 100644
--- a/indra/cmake/Boost.cmake
+++ b/indra/cmake/Boost.cmake
@@ -8,7 +8,7 @@ if (USESYSTEMLIBS)
include(FindBoost)
set(BOOST_CONTEXT_LIBRARY boost_context-mt)
- set(BOOST_COROUTINE_LIBRARY boost_coroutine-mt)
+ set(BOOST_FIBER_LIBRARY boost_fiber-mt)
set(BOOST_FILESYSTEM_LIBRARY boost_filesystem-mt)
set(BOOST_PROGRAM_OPTIONS_LIBRARY boost_program_options-mt)
set(BOOST_REGEX_LIBRARY boost_regex-mt)
@@ -18,11 +18,15 @@ if (USESYSTEMLIBS)
else (USESYSTEMLIBS)
use_prebuilt_binary(boost)
set(Boost_INCLUDE_DIRS ${LIBS_PREBUILT_DIR}/include)
- set(BOOST_VERSION "1.55")
+
+ # As of sometime between Boost 1.67 and 1.72, Boost libraries are suffixed
+ # with the address size.
+ set(addrsfx "-x${ADDRESS_SIZE}")
if (WINDOWS)
if(MSVC80)
# This should be obsolete at this point
+ set(BOOST_VERSION "1.55")
set(BOOST_CONTEXT_LIBRARY
optimized libboost_context-vc80-mt-${BOOST_VERSION}
debug libboost_context-vc80-mt-gd-${BOOST_VERSION})
@@ -47,80 +51,80 @@ else (USESYSTEMLIBS)
else(MSVC80)
# MSVC 10.0 config
set(BOOST_CONTEXT_LIBRARY
- optimized libboost_context-mt
- debug libboost_context-mt-gd)
- set(BOOST_COROUTINE_LIBRARY
- optimized libboost_coroutine-mt
- debug libboost_coroutine-mt-gd)
+ optimized libboost_context-mt${addrsfx}
+ debug libboost_context-mt${addrsfx}-gd)
+ set(BOOST_FIBER_LIBRARY
+ optimized libboost_fiber-mt${addrsfx}
+ debug libboost_fiber-mt${addrsfx}-gd)
set(BOOST_FILESYSTEM_LIBRARY
- optimized libboost_filesystem-mt
- debug libboost_filesystem-mt-gd)
+ optimized libboost_filesystem-mt${addrsfx}
+ debug libboost_filesystem-mt${addrsfx}-gd)
set(BOOST_PROGRAM_OPTIONS_LIBRARY
- optimized libboost_program_options-mt
- debug libboost_program_options-mt-gd)
+ optimized libboost_program_options-mt${addrsfx}
+ debug libboost_program_options-mt${addrsfx}-gd)
set(BOOST_REGEX_LIBRARY
- optimized libboost_regex-mt
- debug libboost_regex-mt-gd)
+ optimized libboost_regex-mt${addrsfx}
+ debug libboost_regex-mt${addrsfx}-gd)
set(BOOST_SIGNALS_LIBRARY
- optimized libboost_signals-mt
- debug libboost_signals-mt-gd)
+ optimized libboost_signals-mt${addrsfx}
+ debug libboost_signals-mt${addrsfx}-gd)
set(BOOST_SYSTEM_LIBRARY
- optimized libboost_system-mt
- debug libboost_system-mt-gd)
+ optimized libboost_system-mt${addrsfx}
+ debug libboost_system-mt${addrsfx}-gd)
set(BOOST_THREAD_LIBRARY
- optimized libboost_thread-mt
- debug libboost_thread-mt-gd)
+ optimized libboost_thread-mt${addrsfx}
+ debug libboost_thread-mt${addrsfx}-gd)
endif (MSVC80)
elseif (LINUX)
set(BOOST_CONTEXT_LIBRARY
- optimized boost_context-mt
- debug boost_context-mt-d)
- set(BOOST_COROUTINE_LIBRARY
- optimized boost_coroutine-mt
- debug boost_coroutine-mt-d)
+ optimized boost_context-mt${addrsfx}
+ debug boost_context-mt${addrsfx}-d)
+ set(BOOST_FIBER_LIBRARY
+ optimized boost_fiber-mt${addrsfx}
+ debug boost_fiber-mt${addrsfx}-d)
set(BOOST_FILESYSTEM_LIBRARY
- optimized boost_filesystem-mt
- debug boost_filesystem-mt-d)
+ optimized boost_filesystem-mt${addrsfx}
+ debug boost_filesystem-mt${addrsfx}-d)
set(BOOST_PROGRAM_OPTIONS_LIBRARY
- optimized boost_program_options-mt
- debug boost_program_options-mt-d)
+ optimized boost_program_options-mt${addrsfx}
+ debug boost_program_options-mt${addrsfx}-d)
set(BOOST_REGEX_LIBRARY
- optimized boost_regex-mt
- debug boost_regex-mt-d)
+ optimized boost_regex-mt${addrsfx}
+ debug boost_regex-mt${addrsfx}-d)
set(BOOST_SIGNALS_LIBRARY
- optimized boost_signals-mt
- debug boost_signals-mt-d)
+ optimized boost_signals-mt${addrsfx}
+ debug boost_signals-mt${addrsfx}-d)
set(BOOST_SYSTEM_LIBRARY
- optimized boost_system-mt
- debug boost_system-mt-d)
+ optimized boost_system-mt${addrsfx}
+ debug boost_system-mt${addrsfx}-d)
set(BOOST_THREAD_LIBRARY
- optimized boost_thread-mt
- debug boost_thread-mt-d)
+ optimized boost_thread-mt${addrsfx}
+ debug boost_thread-mt${addrsfx}-d)
elseif (DARWIN)
set(BOOST_CONTEXT_LIBRARY
- optimized boost_context-mt
- debug boost_context-mt-d)
- set(BOOST_COROUTINE_LIBRARY
- optimized boost_coroutine-mt
- debug boost_coroutine-mt-d)
+ optimized boost_context-mt${addrsfx}
+ debug boost_context-mt${addrsfx}-d)
+ set(BOOST_FIBER_LIBRARY
+ optimized boost_fiber-mt${addrsfx}
+ debug boost_fiber-mt${addrsfx}-d)
set(BOOST_FILESYSTEM_LIBRARY
- optimized boost_filesystem-mt
- debug boost_filesystem-mt-d)
+ optimized boost_filesystem-mt${addrsfx}
+ debug boost_filesystem-mt${addrsfx}-d)
set(BOOST_PROGRAM_OPTIONS_LIBRARY
- optimized boost_program_options-mt
- debug boost_program_options-mt-d)
+ optimized boost_program_options-mt${addrsfx}
+ debug boost_program_options-mt${addrsfx}-d)
set(BOOST_REGEX_LIBRARY
- optimized boost_regex-mt
- debug boost_regex-mt-d)
+ optimized boost_regex-mt${addrsfx}
+ debug boost_regex-mt${addrsfx}-d)
set(BOOST_SIGNALS_LIBRARY
- optimized boost_signals-mt
- debug boost_signals-mt-d)
+ optimized boost_signals-mt${addrsfx}
+ debug boost_signals-mt${addrsfx}-d)
set(BOOST_SYSTEM_LIBRARY
- optimized boost_system-mt
- debug boost_system-mt-d)
+ optimized boost_system-mt${addrsfx}
+ debug boost_system-mt${addrsfx}-d)
set(BOOST_THREAD_LIBRARY
- optimized boost_thread-mt
- debug boost_thread-mt-d)
+ optimized boost_thread-mt${addrsfx}
+ debug boost_thread-mt${addrsfx}-d)
endif (WINDOWS)
endif (USESYSTEMLIBS)
diff --git a/indra/cmake/BuildPackagesInfo.cmake b/indra/cmake/BuildPackagesInfo.cmake
index 4314cca33d..8f8b6b2330 100644
--- a/indra/cmake/BuildPackagesInfo.cmake
+++ b/indra/cmake/BuildPackagesInfo.cmake
@@ -1,6 +1,7 @@
# -*- cmake -*-
# Construct the version and copyright information based on package data.
include(Python)
+include(FindAutobuild)
# packages-formatter.py runs autobuild install --versions, which needs to know
# the build_directory, which (on Windows) depends on AUTOBUILD_ADDRSIZE.
@@ -13,7 +14,7 @@ add_custom_command(OUTPUT packages-info.txt
DEPENDS ${CMAKE_SOURCE_DIR}/../scripts/packages-formatter.py
${CMAKE_SOURCE_DIR}/../autobuild.xml
COMMAND ${PYTHON_EXECUTABLE}
- ${CMAKE_SOURCE_DIR}/cmake/run_build_test.py -DAUTOBUILD_ADDRSIZE=${ADDRESS_SIZE}
+ ${CMAKE_SOURCE_DIR}/cmake/run_build_test.py -DAUTOBUILD_ADDRSIZE=${ADDRESS_SIZE} -DAUTOBUILD=${AUTOBUILD_EXECUTABLE}
${PYTHON_EXECUTABLE}
${CMAKE_SOURCE_DIR}/../scripts/packages-formatter.py "${VIEWER_CHANNEL}" "${VIEWER_SHORT_VERSION}.${VIEWER_VERSION_REVISION}" > packages-info.txt
)
diff --git a/indra/cmake/CMakeLists.txt b/indra/cmake/CMakeLists.txt
index 3a14bf522f..a17e37cd32 100644
--- a/indra/cmake/CMakeLists.txt
+++ b/indra/cmake/CMakeLists.txt
@@ -22,7 +22,6 @@ set(cmake_SOURCE_FILES
Copy3rdPartyLibs.cmake
DBusGlib.cmake
DeploySharedLibs.cmake
- DirectX.cmake
DragDrop.cmake
EXPAT.cmake
FindAPR.cmake
diff --git a/indra/cmake/Copy3rdPartyLibs.cmake b/indra/cmake/Copy3rdPartyLibs.cmake
index 82cd5d62e8..7f84ec146a 100644
--- a/indra/cmake/Copy3rdPartyLibs.cmake
+++ b/indra/cmake/Copy3rdPartyLibs.cmake
@@ -7,6 +7,21 @@
include(CMakeCopyIfDifferent)
include(Linking)
+# When we copy our dependent libraries, we almost always want to copy them to
+# both the Release and the RelWithDebInfo staging directories. This has
+# resulted in duplicate (or worse, erroneous attempted duplicate)
+# copy_if_different commands. Encapsulate that usage.
+# Pass FROM_DIR, TARGETS and the files to copy. TO_DIR is implicit.
+# to_staging_dirs diverges from copy_if_different in that it appends to TARGETS.
+MACRO(to_staging_dirs from_dir targets)
+ foreach(staging_dir
+ "${SHARED_LIB_STAGING_DIR_RELEASE}"
+ "${SHARED_LIB_STAGING_DIR_RELWITHDEBINFO}")
+ copy_if_different("${from_dir}" "${staging_dir}" out_targets ${ARGN})
+ list(APPEND "${targets}" "${out_targets}")
+ endforeach()
+ENDMACRO(to_staging_dirs from_dir to_dir targets)
+
###################################################################
# set up platform specific lists of files that need to be copied
###################################################################
@@ -69,95 +84,54 @@ if(WINDOWS)
#*******************************
# Copy MS C runtime dlls, required for packaging.
- # *TODO - Adapt this to support VC9
if (MSVC80)
- list(APPEND LMSVC_VER 80)
- list(APPEND LMSVC_VERDOT 8.0)
+ set(MSVC_VER 80)
elseif (MSVC_VERSION EQUAL 1600) # VisualStudio 2010
MESSAGE(STATUS "MSVC_VERSION ${MSVC_VERSION}")
elseif (MSVC_VERSION EQUAL 1800) # VisualStudio 2013, which is (sigh) VS 12
- list(APPEND LMSVC_VER 120)
- list(APPEND LMSVC_VERDOT 12.0)
+ set(MSVC_VER 120)
+ elseif (MSVC_VERSION GREATER_EQUAL 1910 AND MSVC_VERSION LESS 1920) # Visual Studio 2017
+ set(MSVC_VER 140)
else (MSVC80)
MESSAGE(WARNING "New MSVC_VERSION ${MSVC_VERSION} of MSVC: adapt Copy3rdPartyLibs.cmake")
endif (MSVC80)
- # try to copy VS2010 redist independently of system version
- # maint-7360 CP
- # list(APPEND LMSVC_VER 100)
- # list(APPEND LMSVC_VERDOT 10.0)
-
- list(LENGTH LMSVC_VER count)
- math(EXPR count "${count}-1")
- foreach(i RANGE ${count})
- list(GET LMSVC_VER ${i} MSVC_VER)
- list(GET LMSVC_VERDOT ${i} MSVC_VERDOT)
- MESSAGE(STATUS "Copying redist libs for VC ${MSVC_VERDOT}")
- FIND_PATH(debug_msvc_redist_path NAME msvcr${MSVC_VER}d.dll
- PATHS
- [HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\VisualStudio\\${MSVC_VERDOT}\\Setup\\VC;ProductDir]/redist/Debug_NonRedist/x86/Microsoft.VC${MSVC_VER}.DebugCRT
- [HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Windows;Directory]/SysWOW64
- [HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Windows;Directory]/System32
- ${MSVC_DEBUG_REDIST_PATH}
- NO_DEFAULT_PATH
+ if(ADDRESS_SIZE EQUAL 32)
+ # this folder contains the 32bit DLLs.. (yes really!)
+ set(registry_find_path "[HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Windows;Directory]/SysWOW64")
+ else(ADDRESS_SIZE EQUAL 32)
+ # this folder contains the 64bit DLLs.. (yes really!)
+ set(registry_find_path "[HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Windows;Directory]/System32")
+ endif(ADDRESS_SIZE EQUAL 32)
+
+ # Having a string containing the system registry path is a start, but to
+ # get CMake to actually read the registry, we must engage some other
+ # operation.
+ get_filename_component(registry_path "${registry_find_path}" ABSOLUTE)
+
+ # These are candidate DLL names. Empirically, VS versions before 2015 have
+ # msvcp*.dll and msvcr*.dll. VS 2017 has msvcp*.dll and vcruntime*.dll.
+ # Check each of them.
+ foreach(release_msvc_file
+ msvcp${MSVC_VER}.dll
+ msvcr${MSVC_VER}.dll
+ vcruntime${MSVC_VER}.dll
)
-
- if(EXISTS ${debug_msvc_redist_path})
- set(debug_msvc_files
- msvcr${MSVC_VER}d.dll
- msvcp${MSVC_VER}d.dll
- )
-
- copy_if_different(
- ${debug_msvc_redist_path}
- "${SHARED_LIB_STAGING_DIR_DEBUG}"
- out_targets
- ${debug_msvc_files}
- )
- set(third_party_targets ${third_party_targets} ${out_targets})
-
- unset(debug_msvc_redist_path CACHE)
- endif()
-
- if(ADDRESS_SIZE EQUAL 32)
- # this folder contains the 32bit DLLs.. (yes really!)
- set(registry_find_path "[HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Windows;Directory]/SysWOW64")
- else(ADDRESS_SIZE EQUAL 32)
- # this folder contains the 64bit DLLs.. (yes really!)
- set(registry_find_path "[HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Windows;Directory]/System32")
- endif(ADDRESS_SIZE EQUAL 32)
-
- FIND_PATH(release_msvc_redist_path NAME msvcr${MSVC_VER}.dll
- PATHS
- ${registry_find_path}
- NO_DEFAULT_PATH
- )
-
- if(EXISTS ${release_msvc_redist_path})
- set(release_msvc_files
- msvcr${MSVC_VER}.dll
- msvcp${MSVC_VER}.dll
- )
-
- copy_if_different(
- ${release_msvc_redist_path}
- "${SHARED_LIB_STAGING_DIR_RELEASE}"
- out_targets
- ${release_msvc_files}
- )
- set(third_party_targets ${third_party_targets} ${out_targets})
-
- copy_if_different(
- ${release_msvc_redist_path}
- "${SHARED_LIB_STAGING_DIR_RELWITHDEBINFO}"
- out_targets
- ${release_msvc_files}
- )
- set(third_party_targets ${third_party_targets} ${out_targets})
-
- unset(release_msvc_redist_path CACHE)
+ if(EXISTS "${registry_path}/${release_msvc_file}")
+ to_staging_dirs(
+ ${registry_path}
+ third_party_targets
+ ${release_msvc_file})
+ else()
+ # This isn't a WARNING because, as noted above, every VS version
+ # we've observed has only a subset of the specified DLL names.
+ MESSAGE(STATUS "Redist lib ${release_msvc_file} not found")
endif()
endforeach()
+ MESSAGE(STATUS "Will copy redist files for MSVC ${MSVC_VER}:")
+ foreach(target ${third_party_targets})
+ MESSAGE(STATUS "${target}")
+ endforeach()
elseif(DARWIN)
set(SHARED_LIB_STAGING_DIR_DEBUG "${SHARED_LIB_STAGING_DIR}/Debug/Resources")
@@ -182,6 +156,7 @@ elseif(DARWIN)
libexception_handler.dylib
${EXPAT_COPY}
libGLOD.dylib
+ libhunspell-1.3.0.dylib
libndofdev.dylib
libnghttp2.dylib
libnghttp2.14.dylib
@@ -268,52 +243,28 @@ endif(WINDOWS)
# Done building the file lists, now set up the copy commands.
################################################################
-copy_if_different(
- ${vivox_lib_dir}
- "${SHARED_LIB_STAGING_DIR_DEBUG}"
- out_targets
- ${vivox_libs}
- )
-set(third_party_targets ${third_party_targets} ${out_targets})
-
+# Curiously, slvoice_files are only copied to SHARED_LIB_STAGING_DIR_RELEASE.
+# It's unclear whether this is oversight or intentional, but anyway leave the
+# single copy_if_different command rather than using to_staging_dirs.
copy_if_different(
${slvoice_src_dir}
"${SHARED_LIB_STAGING_DIR_RELEASE}"
out_targets
${slvoice_files}
)
-copy_if_different(
- ${vivox_lib_dir}
- "${SHARED_LIB_STAGING_DIR_RELEASE}"
- out_targets
- ${vivox_libs}
- )
-
-set(third_party_targets ${third_party_targets} ${out_targets})
+list(APPEND third_party_targets ${out_targets})
-copy_if_different(
+to_staging_dirs(
${vivox_lib_dir}
- "${SHARED_LIB_STAGING_DIR_RELWITHDEBINFO}"
- out_targets
+ third_party_targets
${vivox_libs}
)
-set(third_party_targets ${third_party_targets} ${out_targets})
-copy_if_different(
+to_staging_dirs(
${release_src_dir}
- "${SHARED_LIB_STAGING_DIR_RELEASE}"
- out_targets
- ${release_files}
- )
-set(third_party_targets ${third_party_targets} ${out_targets})
-
-copy_if_different(
- ${release_src_dir}
- "${SHARED_LIB_STAGING_DIR_RELWITHDEBINFO}"
- out_targets
+ third_party_targets
${release_files}
)
-set(third_party_targets ${third_party_targets} ${out_targets})
if(NOT USESYSTEMLIBS)
add_custom_target(
diff --git a/indra/cmake/DirectX.cmake b/indra/cmake/DirectX.cmake
deleted file mode 100644
index 25163d0322..0000000000
--- a/indra/cmake/DirectX.cmake
+++ /dev/null
@@ -1,48 +0,0 @@
-# -*- cmake -*-
-
-if (WINDOWS)
- find_path(DIRECTX_INCLUDE_DIR dxdiag.h
- "$ENV{DXSDK_DIR}/Include"
- "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (June 2010)/Include"
- "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (August 2009)/Include"
- "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (March 2009)/Include"
- "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (August 2008)/Include"
- "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (June 2008)/Include"
- "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (March 2008)/Include"
- "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (November 2007)/Include"
- "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (August 2007)/Include"
- "C:/DX90SDK/Include"
- "$ENV{PROGRAMFILES}/DX90SDK/Include"
- )
- if (DIRECTX_INCLUDE_DIR)
- include_directories(${DIRECTX_INCLUDE_DIR})
- if (DIRECTX_FIND_QUIETLY)
- message(STATUS "Found DirectX include: ${DIRECTX_INCLUDE_DIR}")
- endif (DIRECTX_FIND_QUIETLY)
- else (DIRECTX_INCLUDE_DIR)
- message(FATAL_ERROR "Could not find DirectX SDK Include")
- endif (DIRECTX_INCLUDE_DIR)
-
-
- find_path(DIRECTX_LIBRARY_DIR dxguid.lib
- "$ENV{DXSDK_DIR}/Lib/x86"
- "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (June 2010)/Lib/x86"
- "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (August 2009)/Lib/x86"
- "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (March 2009)/Lib/x86"
- "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (August 2008)/Lib/x86"
- "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (June 2008)/Lib/x86"
- "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (March 2008)/Lib/x86"
- "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (November 2007)/Lib/x86"
- "$ENV{PROGRAMFILES}/Microsoft DirectX SDK (August 2007)/Lib/x86"
- "C:/DX90SDK/Lib"
- "$ENV{PROGRAMFILES}/DX90SDK/Lib"
- )
- if (DIRECTX_LIBRARY_DIR)
- if (DIRECTX_FIND_QUIETLY)
- message(STATUS "Found DirectX include: ${DIRECTX_LIBRARY_DIR}")
- endif (DIRECTX_FIND_QUIETLY)
- else (DIRECTX_LIBRARY_DIR)
- message(FATAL_ERROR "Could not find DirectX SDK Libraries")
- endif (DIRECTX_LIBRARY_DIR)
-
-endif (WINDOWS)
diff --git a/indra/cmake/LLAddBuildTest.cmake b/indra/cmake/LLAddBuildTest.cmake
index b3f42c1a5e..4932e9044f 100644
--- a/indra/cmake/LLAddBuildTest.cmake
+++ b/indra/cmake/LLAddBuildTest.cmake
@@ -53,7 +53,7 @@ INCLUDE(GoogleMock)
${GOOGLEMOCK_INCLUDE_DIRS}
)
SET(alltest_LIBRARIES
- ${BOOST_COROUTINE_LIBRARY}
+ ${BOOST_FIBER_LIBRARY}
${BOOST_CONTEXT_LIBRARY}
${BOOST_SYSTEM_LIBRARY}
${GOOGLEMOCK_LIBRARIES}
@@ -200,8 +200,9 @@ FUNCTION(LL_ADD_INTEGRATION_TEST
)
SET(libraries
+ ${LEGACY_STDIO_LIBS}
${library_dependencies}
- ${BOOST_COROUTINE_LIBRARY}
+ ${BOOST_FIBER_LIBRARY}
${BOOST_CONTEXT_LIBRARY}
${BOOST_SYSTEM_LIBRARY}
${GOOGLEMOCK_LIBRARIES}
diff --git a/indra/cmake/LLAppearance.cmake b/indra/cmake/LLAppearance.cmake
index ae265d07e3..675330ec72 100644
--- a/indra/cmake/LLAppearance.cmake
+++ b/indra/cmake/LLAppearance.cmake
@@ -18,7 +18,7 @@ endif (BUILD_HEADLESS)
set(LLAPPEARANCE_LIBRARIES llappearance
llmessage
llcorehttp
- ${BOOST_COROUTINE_LIBRARY}
+ ${BOOST_FIBER_LIBRARY}
${BOOST_CONTEXT_LIBRARY}
${BOOST_SYSTEM_LIBRARY}
)
diff --git a/indra/cmake/LLCommon.cmake b/indra/cmake/LLCommon.cmake
index 3e29297c58..8900419f9b 100644
--- a/indra/cmake/LLCommon.cmake
+++ b/indra/cmake/LLCommon.cmake
@@ -19,7 +19,7 @@ if (LINUX)
# specify all libraries that llcommon uses.
# llcommon uses `clock_gettime' which is provided by librt on linux.
set(LLCOMMON_LIBRARIES llcommon
- ${BOOST_COROUTINE_LIBRARY}
+ ${BOOST_FIBER_LIBRARY}
${BOOST_CONTEXT_LIBRARY}
${BOOST_THREAD_LIBRARY}
${BOOST_SYSTEM_LIBRARY}
@@ -27,7 +27,7 @@ if (LINUX)
)
else (LINUX)
set(LLCOMMON_LIBRARIES llcommon
- ${BOOST_COROUTINE_LIBRARY}
+ ${BOOST_FIBER_LIBRARY}
${BOOST_CONTEXT_LIBRARY}
${BOOST_THREAD_LIBRARY}
${BOOST_SYSTEM_LIBRARY} )
diff --git a/indra/cmake/LLCoreHttp.cmake b/indra/cmake/LLCoreHttp.cmake
index 379ae207de..613453ab5d 100644
--- a/indra/cmake/LLCoreHttp.cmake
+++ b/indra/cmake/LLCoreHttp.cmake
@@ -12,6 +12,6 @@ set(LLCOREHTTP_INCLUDE_DIRS
)
set(LLCOREHTTP_LIBRARIES llcorehttp
- ${BOOST_COROUTINE_LIBRARY}
+ ${BOOST_FIBER_LIBRARY}
${BOOST_CONTEXT_LIBRARY}
${BOOST_SYSTEM_LIBRARY})
diff --git a/indra/cmake/run_build_test.py b/indra/cmake/run_build_test.py
index 210e43b232..ec5d33f902 100755
--- a/indra/cmake/run_build_test.py
+++ b/indra/cmake/run_build_test.py
@@ -87,7 +87,6 @@ def main(command, arguments=[], libpath=[], vars={}):
# might not exist; instead of KeyError, just use an empty string.
dirs = os.environ.get(var, "").split(os.pathsep)
# Append the sequence in libpath
- log.info("%s += %r" % (var, libpath))
for dir in libpath:
# append system paths at the end
if dir in ('/lib', '/usr/lib'):
@@ -105,16 +104,21 @@ def main(command, arguments=[], libpath=[], vars={}):
# Now rebuild the path string. This way we use a minimum of separators
# -- and we avoid adding a pointless separator when libpath is empty.
os.environ[var] = os.pathsep.join(clean_dirs)
- log.info("%s = %r" % (var, os.environ[var]))
+ # This output format is intended to make it straightforward to copy
+ # the variable settings and the command itself from the build output
+ # and paste the whole thing at a command prompt to rerun it manually.
+ log.info("%s='%s' \\" % (var, os.environ[var]))
# Now handle arbitrary environment variables. The tricky part is ensuring
# that all the keys and values we try to pass are actually strings.
if vars:
- log.info("Setting: %s" % ("\n".join(["%s=%s" % (key, value) for key, value in vars.iteritems()])))
+ for key, value in vars.items():
+ # As noted a few lines above, facilitate copy-paste rerunning.
+ log.info("%s='%s' \\" % (key, value))
os.environ.update(dict([(str(key), str(value)) for key, value in vars.iteritems()]))
# Run the child process.
command_list = [command]
command_list.extend(arguments)
- log.info("Running: %s" % " ".join(command_list))
+ log.info(" ".join((("'%s'" % w) if ' ' in w else w) for w in command_list))
# Make sure we see all relevant output *before* child-process output.
sys.stdout.flush()
try:
@@ -305,8 +309,11 @@ def get_windows_table():
return _windows_table
-log=logging.getLogger(__name__)
-logging.basicConfig()
+# Use this instead of logging.basicConfig() because the latter prefixes
+# every line of output with INFO:__main__:...
+log=logging.getLogger()
+log.setLevel(logging.INFO)
+log.addHandler(logging.StreamHandler())
if __name__ == "__main__":
import argparse
diff --git a/indra/integration_tests/llimage_libtest/CMakeLists.txt b/indra/integration_tests/llimage_libtest/CMakeLists.txt
index d9353f904c..5787d4d600 100644
--- a/indra/integration_tests/llimage_libtest/CMakeLists.txt
+++ b/indra/integration_tests/llimage_libtest/CMakeLists.txt
@@ -64,6 +64,7 @@ endif (DARWIN)
# Libraries on which this application depends on
# Sort by high-level to low-level
target_link_libraries(llimage_libtest
+ ${LEGACY_STDIO_LIBS}
${LLCOMMON_LIBRARIES}
${LLVFS_LIBRARIES}
${LLMATH_LIBRARIES}
diff --git a/indra/integration_tests/llui_libtest/CMakeLists.txt b/indra/integration_tests/llui_libtest/CMakeLists.txt
index 34e34c7e47..1cec660eb0 100644
--- a/indra/integration_tests/llui_libtest/CMakeLists.txt
+++ b/indra/integration_tests/llui_libtest/CMakeLists.txt
@@ -75,6 +75,7 @@ endif (DARWIN)
# Libraries on which this library depends, needed for Linux builds
# Sort by high-level to low-level
target_link_libraries(llui_libtest
+ ${LEGACY_STDIO_LIBS}
llui
llinventory
llmessage
diff --git a/indra/linux_crash_logger/CMakeLists.txt b/indra/linux_crash_logger/CMakeLists.txt
index 315aed8d11..d789c850a0 100644
--- a/indra/linux_crash_logger/CMakeLists.txt
+++ b/indra/linux_crash_logger/CMakeLists.txt
@@ -69,7 +69,7 @@ target_link_libraries(linux-crash-logger
${LLMATH_LIBRARIES}
${LLCOREHTTP_LIBRARIES}
${LLCOMMON_LIBRARIES}
- ${BOOST_COROUTINE_LIBRARY}
+ ${BOOST_FIBER_LIBRARY}
${BOOST_CONTEXT_LIBRARY}
${UI_LIBRARIES}
${DB_LIBRARIES}
diff --git a/indra/llappearance/llwearabletype.cpp b/indra/llappearance/llwearabletype.cpp
index d6ff28e2d2..281060d01d 100644
--- a/indra/llappearance/llwearabletype.cpp
+++ b/indra/llappearance/llwearabletype.cpp
@@ -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,41 +57,35 @@ 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&);
};
-LLWearableDictionary::LLWearableDictionary()
+LLWearableDictionary::LLWearableDictionary(LLWearableType& wtype)
{
- if (!LLWearableType::instanceExists())
- {
- // LLWearableType is effectively a wrapper around LLWearableDictionary and is used as storage for LLTranslationBridge
- // 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_PHYSICS, new WearableEntry("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_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));
+
+ 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(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));
}
@@ -107,6 +102,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)
{
diff --git a/indra/llappearance/llwearabletype.h b/indra/llappearance/llwearabletype.h
index 148ccafdd8..57f3ef160d 100644
--- a/indra/llappearance/llwearabletype.h
+++ b/indra/llappearance/llwearabletype.h
@@ -37,6 +37,7 @@ class LLWearableType : public LLParamSingleton<LLWearableType>
{
LLSINGLETON(LLWearableType, LLTranslationBridge* trans);
~LLWearableType();
+ void initSingleton();
friend struct WearableEntry;
public:
enum EType
diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt
index af41b9e460..eeb315ead6 100644
--- a/indra/llcommon/CMakeLists.txt
+++ b/indra/llcommon/CMakeLists.txt
@@ -1,4 +1,3 @@
-
# -*- cmake -*-
project(llcommon)
@@ -44,7 +43,6 @@ set(llcommon_SOURCE_FILES
llcleanup.cpp
llcommon.cpp
llcommonutils.cpp
- llcoro_get_id.cpp
llcoros.cpp
llcrc.cpp
llcriticaldamp.cpp
@@ -106,6 +104,7 @@ set(llcommon_SOURCE_FILES
llstring.cpp
llstringtable.cpp
llsys.cpp
+ lltempredirect.cpp
llthread.cpp
llthreadlocalstorage.cpp
llthreadsafequeue.cpp
@@ -146,7 +145,7 @@ set(llcommon_HEADER_FILES
llcleanup.h
llcommon.h
llcommonutils.h
- llcoro_get_id.h
+ llcond.h
llcoros.h
llcrc.h
llcriticaldamp.h
@@ -186,9 +185,9 @@ set(llcommon_HEADER_FILES
llkeythrottle.h
llleap.h
llleaplistener.h
- lllistenerwrapper.h
llliveappconfig.h
lllivefile.h
+ llmainthreadtask.h
llmd5.h
llmemory.h
llmemorystream.h
@@ -230,6 +229,7 @@ set(llcommon_HEADER_FILES
llstaticstringtable.h
llstatsaccumulator.h
llsys.h
+ lltempredirect.h
llthread.h
llthreadlocalstorage.h
llthreadsafequeue.h
@@ -247,6 +247,7 @@ set(llcommon_HEADER_FILES
llwin32headers.h
llwin32headerslean.h
llworkerthread.h
+ lockstatic.h
stdtypes.h
stringize.h
timer.h
@@ -291,7 +292,7 @@ target_link_libraries(
${JSONCPP_LIBRARIES}
${ZLIB_LIBRARIES}
${WINDOWS_LIBRARIES}
- ${BOOST_COROUTINE_LIBRARY}
+ ${BOOST_FIBER_LIBRARY}
${BOOST_CONTEXT_LIBRARY}
${BOOST_PROGRAM_OPTIONS_LIBRARY}
${BOOST_REGEX_LIBRARY}
@@ -320,13 +321,14 @@ if (LL_TESTS)
${LLCOMMON_LIBRARIES}
${WINDOWS_LIBRARIES}
${GOOGLEMOCK_LIBRARIES}
- ${BOOST_COROUTINE_LIBRARY}
+ ${BOOST_FIBER_LIBRARY}
${BOOST_CONTEXT_LIBRARY}
${BOOST_THREAD_LIBRARY}
${BOOST_SYSTEM_LIBRARY})
LL_ADD_INTEGRATION_TEST(commonmisc "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(bitpack "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(llbase64 "" "${test_libs}")
+ LL_ADD_INTEGRATION_TEST(llcond "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(lldate "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(lldeadmantimer "" "${test_libs}")
LL_ADD_INTEGRATION_TEST(lldependencies "" "${test_libs}")
@@ -338,6 +340,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}")
diff --git a/indra/llcommon/StackWalker.cpp b/indra/llcommon/StackWalker.cpp
index c0d3104099..56defc6465 100644
--- a/indra/llcommon/StackWalker.cpp
+++ b/indra/llcommon/StackWalker.cpp
@@ -98,7 +98,10 @@
// If VC7 and later, then use the shipped 'dbghelp.h'-file
#pragma pack(push,8)
#if _MSC_VER >= 1300
+#pragma warning (push)
+#pragma warning (disable:4091) // a microsoft header has warnings. Very nice.
#include <dbghelp.h>
+#pragma warning (pop)
#else
// inline the important dbghelp.h-declarations...
typedef enum {
@@ -422,7 +425,7 @@ public:
LPSTR m_szSymPath;
#pragma pack(push,8)
-typedef struct IMAGEHLP_MODULE64_V3 {
+struct IMAGEHLP_MODULE64_V3 {
DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_MODULE64)
DWORD64 BaseOfImage; // base load address of module
DWORD ImageSize; // virtual size of the loaded module
@@ -450,7 +453,7 @@ typedef struct IMAGEHLP_MODULE64_V3 {
BOOL Publics; // contains public symbols
};
-typedef struct IMAGEHLP_MODULE64_V2 {
+struct IMAGEHLP_MODULE64_V2 {
DWORD SizeOfStruct; // set to sizeof(IMAGEHLP_MODULE64)
DWORD64 BaseOfImage; // base load address of module
DWORD ImageSize; // virtual size of the loaded module
@@ -657,7 +660,7 @@ private:
pGMI = (tGMI) GetProcAddress( hPsapi, "GetModuleInformation" );
if ( (pEPM == NULL) || (pGMFNE == NULL) || (pGMBN == NULL) || (pGMI == NULL) )
{
- // we couldn´t find all functions
+ // we couldn't find all functions
FreeLibrary(hPsapi);
return FALSE;
}
diff --git a/indra/llcommon/StackWalker.h b/indra/llcommon/StackWalker.h
index 834f89c471..4634765d0b 100644
--- a/indra/llcommon/StackWalker.h
+++ b/indra/llcommon/StackWalker.h
@@ -148,7 +148,7 @@ protected:
CHAR loadedImageName[STACKWALK_MAX_NAMELEN];
} CallstackEntry;
- typedef enum CallstackEntryType {firstEntry, nextEntry, lastEntry};
+ enum CallstackEntryType {firstEntry, nextEntry, lastEntry};
virtual void OnSymInit(LPCSTR szSearchPath, DWORD symOptions, LPCSTR szUserName);
virtual void OnLoadModule(LPCSTR img, LPCSTR mod, DWORD64 baseAddr, DWORD size, DWORD result, LPCSTR symType, LPCSTR pdbName, ULONGLONG fileVersion);
diff --git a/indra/llcommon/llapp.cpp b/indra/llcommon/llapp.cpp
index 421af3006e..3dab632aef 100644
--- a/indra/llcommon/llapp.cpp
+++ b/indra/llcommon/llapp.cpp
@@ -49,6 +49,8 @@
#include "google_breakpad/exception_handler.h"
#include "stringize.h"
#include "llcleanup.h"
+#include "llevents.h"
+#include "llsdutil.h"
//
// Signal handling
@@ -561,10 +563,42 @@ void LLApp::runErrorHandler()
LLApp::setStopped();
}
+namespace
+{
+
+static std::map<LLApp::EAppStatus, const char*> statusDesc
+{
+ { LLApp::APP_STATUS_RUNNING, "running" },
+ { LLApp::APP_STATUS_QUITTING, "quitting" },
+ { LLApp::APP_STATUS_STOPPED, "stopped" },
+ { LLApp::APP_STATUS_ERROR, "error" }
+};
+
+} // anonymous namespace
+
// static
void LLApp::setStatus(EAppStatus status)
{
- sStatus = status;
+ sStatus = status;
+
+ // This can also happen very late in the application lifecycle -- don't
+ // resurrect a deleted LLSingleton
+ if (! LLEventPumps::wasDeleted())
+ {
+ // notify interested parties of status change
+ LLSD statsd;
+ auto found = statusDesc.find(status);
+ if (found != statusDesc.end())
+ {
+ statsd = found->second;
+ }
+ else
+ {
+ // unknown status? at least report value
+ statsd = LLSD::Integer(status);
+ }
+ LLEventPumps::instance().obtain("LLApp").post(llsd::map("status", statsd));
+ }
}
diff --git a/indra/llcommon/llapr.h b/indra/llcommon/llapr.h
index da50dda103..3c07976f42 100644
--- a/indra/llcommon/llapr.h
+++ b/indra/llcommon/llapr.h
@@ -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;
/**
diff --git a/indra/llcommon/llcond.h b/indra/llcommon/llcond.h
new file mode 100644
index 0000000000..e31b67d893
--- /dev/null
+++ b/indra/llcommon/llcond.h
@@ -0,0 +1,405 @@
+/**
+ * @file llcond.h
+ * @author Nat Goodspeed
+ * @date 2019-07-10
+ * @brief LLCond is a wrapper around condition_variable to encapsulate the
+ * obligatory condition_variable usage pattern. We also provide
+ * simplified versions LLScalarCond, LLBoolCond and LLOneShotCond.
+ *
+ * $LicenseInfo:firstyear=2019&license=viewerlgpl$
+ * Copyright (c) 2019, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if ! defined(LL_LLCOND_H)
+#define LL_LLCOND_H
+
+#include "llunits.h"
+#include "llcoros.h"
+#include LLCOROS_MUTEX_HEADER
+#include "mutex.h"
+#include <chrono>
+
+/**
+ * LLCond encapsulates the pattern required to use a condition_variable. It
+ * bundles subject data, a mutex and a condition_variable: the three required
+ * data objects. It provides wait() methods analogous to condition_variable,
+ * but using the contained condition_variable and the contained mutex. It
+ * provides modify() methods accepting an invocable to safely modify the
+ * contained data and notify waiters. These methods implicitly perform the
+ * required locking.
+ *
+ * The generic LLCond template assumes that DATA might be a struct or class.
+ * For a scalar DATA type, consider LLScalarCond instead. For specifically
+ * bool, consider LLBoolCond.
+ *
+ * Use of LLCoros::ConditionVariable makes LLCond work between
+ * coroutines as well as between threads.
+ */
+template <typename DATA>
+class LLCond
+{
+public:
+ typedef DATA value_type;
+
+private:
+ // This is the DATA controlled by the condition_variable.
+ value_type mData;
+ // condition_variable must be used in conjunction with a mutex. Use
+ // LLCoros::Mutex instead of std::mutex because the latter blocks
+ // the entire calling thread, whereas the former blocks only the current
+ // coroutine within the calling thread. Yet LLCoros::Mutex is safe to
+ // use across threads as well: it subsumes std::mutex functionality.
+ LLCoros::Mutex mMutex;
+ // Use LLCoros::ConditionVariable for the same reason.
+ LLCoros::ConditionVariable mCond;
+
+public:
+ /// LLCond can be explicitly initialized with a specific value for mData if
+ /// desired.
+ LLCond(const value_type& init=value_type()):
+ mData(init)
+ {}
+
+ /// LLCond is move-only
+ LLCond(const LLCond&) = delete;
+ LLCond& operator=(const LLCond&) = delete;
+
+ /// get() returns a const reference to the stored DATA. The only way to
+ /// get a non-const reference -- to modify the stored DATA -- is via
+ /// update_one() or update_all().
+ const value_type& get() const { return mData; }
+
+ /**
+ * Pass update_one() an invocable accepting non-const (DATA&). The
+ * invocable will presumably modify the referenced DATA. update_one()
+ * will lock the mutex, call the invocable and then call notify_one() on
+ * the condition_variable.
+ *
+ * For scalar DATA, it's simpler to use LLScalarCond::set_one(). Use
+ * update_one() when DATA is a struct or class.
+ */
+ template <typename MODIFY>
+ void update_one(MODIFY modify)
+ {
+ { // scope of lock can/should end before notify_one()
+ LLCoros::LockType lk(mMutex);
+ modify(mData);
+ }
+ mCond.notify_one();
+ }
+
+ /**
+ * Pass update_all() an invocable accepting non-const (DATA&). The
+ * invocable will presumably modify the referenced DATA. update_all()
+ * will lock the mutex, call the invocable and then call notify_all() on
+ * the condition_variable.
+ *
+ * For scalar DATA, it's simpler to use LLScalarCond::set_all(). Use
+ * update_all() when DATA is a struct or class.
+ */
+ template <typename MODIFY>
+ void update_all(MODIFY modify)
+ {
+ { // scope of lock can/should end before notify_all()
+ LLCoros::LockType lk(mMutex);
+ modify(mData);
+ }
+ mCond.notify_all();
+ }
+
+ /**
+ * Pass wait() a predicate accepting (const DATA&), returning bool. The
+ * predicate returns true when the condition for which it is waiting has
+ * been satisfied, presumably determined by examining the referenced DATA.
+ * wait() locks the mutex and, until the predicate returns true, calls
+ * wait() on the condition_variable.
+ */
+ template <typename Pred>
+ void wait(Pred pred)
+ {
+ LLCoros::LockType lk(mMutex);
+ // We must iterate explicitly since the predicate accepted by
+ // condition_variable::wait() requires a different signature:
+ // condition_variable::wait() calls its predicate with no arguments.
+ // Fortunately, the loop is straightforward.
+ // We advise the caller to pass a predicate accepting (const DATA&).
+ // But what if they instead pass a predicate accepting non-const
+ // (DATA&)? Such a predicate could modify mData, which would be Bad.
+ // Forbid that.
+ while (! pred(const_cast<const value_type&>(mData)))
+ {
+ mCond.wait(lk);
+ }
+ }
+
+ /**
+ * Pass wait_for() a chrono::duration, indicating how long we're willing
+ * to wait, and a predicate accepting (const DATA&), returning bool. The
+ * predicate returns true when the condition for which it is waiting has
+ * been satisfied, presumably determined by examining the referenced DATA.
+ * wait_for() locks the mutex and, until the predicate returns true, calls
+ * wait_for() on the condition_variable. wait_for() returns false if
+ * condition_variable::wait_for() timed out without the predicate
+ * returning true.
+ */
+ template <typename Rep, typename Period, typename Pred>
+ bool wait_for(const std::chrono::duration<Rep, Period>& timeout_duration, Pred pred)
+ {
+ // Instead of replicating wait_until() logic, convert duration to
+ // time_point and just call wait_until().
+ // An implementation in which we repeatedly called
+ // condition_variable::wait_for() with our passed duration would be
+ // wrong! We'd keep pushing the timeout time farther and farther into
+ // the future. This way, we establish a definite timeout time and
+ // stick to it.
+ return wait_until(std::chrono::steady_clock::now() + timeout_duration, pred);
+ }
+
+ /**
+ * This wait_for() overload accepts F32Milliseconds as the duration. Any
+ * duration unit defined in llunits.h is implicitly convertible to
+ * F32Milliseconds. The semantics of this method are the same as the
+ * generic wait_for() method.
+ */
+ template <typename Pred>
+ bool wait_for(F32Milliseconds timeout_duration, Pred pred)
+ {
+ return wait_for(convert(timeout_duration), pred);
+ }
+
+protected:
+ // convert F32Milliseconds to a chrono::duration
+ auto convert(F32Milliseconds duration)
+ {
+ // std::chrono::milliseconds doesn't like to be constructed from a
+ // float (F32), rubbing our nose in the thought that
+ // std::chrono::duration::rep is probably integral. Therefore
+ // converting F32Milliseconds to std::chrono::milliseconds would lose
+ // precision. Use std::chrono::microseconds instead. Extract the F32
+ // milliseconds from F32Milliseconds, scale to microseconds, construct
+ // std::chrono::microseconds from that value.
+ return std::chrono::microseconds{ std::chrono::microseconds::rep(duration.value() * 1000) };
+ }
+
+private:
+ /**
+ * Pass wait_until() a chrono::time_point, indicating the time at which we
+ * should stop waiting, and a predicate accepting (const DATA&), returning
+ * bool. The predicate returns true when the condition for which it is
+ * waiting has been satisfied, presumably determined by examining the
+ * referenced DATA. wait_until() locks the mutex and, until the predicate
+ * returns true, calls wait_until() on the condition_variable.
+ * wait_until() returns false if condition_variable::wait_until() timed
+ * out without the predicate returning true.
+ *
+ * Originally this class and its subclasses published wait_until() methods
+ * corresponding to each wait_for() method. But that raised all sorts of
+ * fascinating questions about the time zone of the passed time_point:
+ * local time? server time? UTC? The bottom line is that for LLCond
+ * timeout purposes, we really shouldn't have to care -- timeout duration
+ * is all we need. This private method remains because it's the simplest
+ * way to support iteratively waiting across spurious wakeups while
+ * honoring a fixed timeout.
+ */
+ template <typename Clock, typename Duration, typename Pred>
+ bool wait_until(const std::chrono::time_point<Clock, Duration>& timeout_time, Pred pred)
+ {
+ LLCoros::LockType lk(mMutex);
+ // We advise the caller to pass a predicate accepting (const DATA&).
+ // But what if they instead pass a predicate accepting non-const
+ // (DATA&)? Such a predicate could modify mData, which would be Bad.
+ // Forbid that.
+ while (! pred(const_cast<const value_type&>(mData)))
+ {
+ if (LLCoros::cv_status::timeout == mCond.wait_until(lk, timeout_time))
+ {
+ // It's possible that wait_until() timed out AND the predicate
+ // became true more or less simultaneously. Even though
+ // wait_until() timed out, check the predicate one more time.
+ return pred(const_cast<const value_type&>(mData));
+ }
+ }
+ return true;
+ }
+};
+
+template <typename DATA>
+class LLScalarCond: public LLCond<DATA>
+{
+ using super = LLCond<DATA>;
+
+public:
+ using typename super::value_type;
+ using super::get;
+ using super::wait;
+ using super::wait_for;
+
+ /// LLScalarCond can be explicitly initialized with a specific value for
+ /// mData if desired.
+ LLScalarCond(const value_type& init=value_type()):
+ super(init)
+ {}
+
+ /// Pass set_one() a new value to which to update mData. set_one() will
+ /// lock the mutex, update mData and then call notify_one() on the
+ /// condition_variable.
+ void set_one(const value_type& value)
+ {
+ super::update_one([&value](value_type& data){ data = value; });
+ }
+
+ /// Pass set_all() a new value to which to update mData. set_all() will
+ /// lock the mutex, update mData and then call notify_all() on the
+ /// condition_variable.
+ void set_all(const value_type& value)
+ {
+ super::update_all([&value](value_type& data){ data = value; });
+ }
+
+ /**
+ * Pass wait_equal() a value for which to wait. wait_equal() locks the
+ * mutex and, until the stored DATA equals that value, calls wait() on the
+ * condition_variable.
+ */
+ void wait_equal(const value_type& value)
+ {
+ super::wait([&value](const value_type& data){ return (data == value); });
+ }
+
+ /**
+ * Pass wait_for_equal() a chrono::duration, indicating how long we're
+ * willing to wait, and a value for which to wait. wait_for_equal() locks
+ * the mutex and, until the stored DATA equals that value, calls
+ * wait_for() on the condition_variable. wait_for_equal() returns false if
+ * condition_variable::wait_for() timed out without the stored DATA being
+ * equal to the passed value.
+ */
+ template <typename Rep, typename Period>
+ bool wait_for_equal(const std::chrono::duration<Rep, Period>& timeout_duration,
+ const value_type& value)
+ {
+ return super::wait_for(timeout_duration,
+ [&value](const value_type& data){ return (data == value); });
+ }
+
+ /**
+ * This wait_for_equal() overload accepts F32Milliseconds as the duration.
+ * Any duration unit defined in llunits.h is implicitly convertible to
+ * F32Milliseconds. The semantics of this method are the same as the
+ * generic wait_for_equal() method.
+ */
+ bool wait_for_equal(F32Milliseconds timeout_duration, const value_type& value)
+ {
+ return wait_for_equal(super::convert(timeout_duration), value);
+ }
+
+ /**
+ * Pass wait_unequal() a value from which to move away. wait_unequal()
+ * locks the mutex and, until the stored DATA no longer equals that value,
+ * calls wait() on the condition_variable.
+ */
+ void wait_unequal(const value_type& value)
+ {
+ super::wait([&value](const value_type& data){ return (data != value); });
+ }
+
+ /**
+ * Pass wait_for_unequal() a chrono::duration, indicating how long we're
+ * willing to wait, and a value from which to move away.
+ * wait_for_unequal() locks the mutex and, until the stored DATA no longer
+ * equals that value, calls wait_for() on the condition_variable.
+ * wait_for_unequal() returns false if condition_variable::wait_for()
+ * timed out with the stored DATA still being equal to the passed value.
+ */
+ template <typename Rep, typename Period>
+ bool wait_for_unequal(const std::chrono::duration<Rep, Period>& timeout_duration,
+ const value_type& value)
+ {
+ return super::wait_for(timeout_duration,
+ [&value](const value_type& data){ return (data != value); });
+ }
+
+ /**
+ * This wait_for_unequal() overload accepts F32Milliseconds as the duration.
+ * Any duration unit defined in llunits.h is implicitly convertible to
+ * F32Milliseconds. The semantics of this method are the same as the
+ * generic wait_for_unequal() method.
+ */
+ bool wait_for_unequal(F32Milliseconds timeout_duration, const value_type& value)
+ {
+ return wait_for_unequal(super::convert(timeout_duration), value);
+ }
+
+protected:
+ using super::convert;
+};
+
+/// Using bool as LLScalarCond's DATA seems like a particularly useful case
+using LLBoolCond = LLScalarCond<bool>;
+
+/// LLOneShotCond -- init false, set (and wait for) true
+class LLOneShotCond: public LLBoolCond
+{
+ using super = LLBoolCond;
+
+public:
+ using typename super::value_type;
+ using super::get;
+ using super::wait;
+ using super::wait_for;
+ using super::wait_equal;
+ using super::wait_for_equal;
+ using super::wait_unequal;
+ using super::wait_for_unequal;
+
+ /// The bool stored in LLOneShotCond is initially false
+ LLOneShotCond(): super(false) {}
+
+ /// LLOneShotCond assumes that nullary set_one() means to set its bool true
+ void set_one(bool value=true)
+ {
+ super::set_one(value);
+ }
+
+ /// LLOneShotCond assumes that nullary set_all() means to set its bool true
+ void set_all(bool value=true)
+ {
+ super::set_all(value);
+ }
+
+ /**
+ * wait() locks the mutex and, until the stored bool is true, calls wait()
+ * on the condition_variable.
+ */
+ void wait()
+ {
+ super::wait_unequal(false);
+ }
+
+ /**
+ * Pass wait_for() a chrono::duration, indicating how long we're willing
+ * to wait. wait_for() locks the mutex and, until the stored bool is true,
+ * calls wait_for() on the condition_variable. wait_for() returns false if
+ * condition_variable::wait_for() timed out without the stored bool being
+ * true.
+ */
+ template <typename Rep, typename Period>
+ bool wait_for(const std::chrono::duration<Rep, Period>& timeout_duration)
+ {
+ return super::wait_for_unequal(timeout_duration, false);
+ }
+
+ /**
+ * This wait_for() overload accepts F32Milliseconds as the duration.
+ * Any duration unit defined in llunits.h is implicitly convertible to
+ * F32Milliseconds. The semantics of this method are the same as the
+ * generic wait_for() method.
+ */
+ bool wait_for(F32Milliseconds timeout_duration)
+ {
+ return wait_for(super::convert(timeout_duration));
+ }
+};
+
+#endif /* ! defined(LL_LLCOND_H) */
diff --git a/indra/llcommon/llcoro_get_id.cpp b/indra/llcommon/llcoro_get_id.cpp
deleted file mode 100644
index 24ed1fe0c9..0000000000
--- a/indra/llcommon/llcoro_get_id.cpp
+++ /dev/null
@@ -1,32 +0,0 @@
-/**
- * @file llcoro_get_id.cpp
- * @author Nat Goodspeed
- * @date 2016-09-03
- * @brief Implementation for llcoro_get_id.
- *
- * $LicenseInfo:firstyear=2016&license=viewerlgpl$
- * Copyright (c) 2016, Linden Research, Inc.
- * $/LicenseInfo$
- */
-
-// Precompiled header
-#include "linden_common.h"
-// associated header
-#include "llcoro_get_id.h"
-// STL headers
-// std headers
-// external library headers
-// other Linden headers
-#include "llcoros.h"
-
-namespace llcoro
-{
-
-id get_id()
-{
- // An instance of Current can convert to LLCoros::CoroData*, which can
- // implicitly convert to void*, which is an llcoro::id.
- return LLCoros::Current();
-}
-
-} // llcoro
diff --git a/indra/llcommon/llcoro_get_id.h b/indra/llcommon/llcoro_get_id.h
deleted file mode 100644
index 4c1dca6f19..0000000000
--- a/indra/llcommon/llcoro_get_id.h
+++ /dev/null
@@ -1,30 +0,0 @@
-/**
- * @file llcoro_get_id.h
- * @author Nat Goodspeed
- * @date 2016-09-03
- * @brief Supplement the functionality in llcoro.h.
- *
- * This is broken out as a separate header file to resolve
- * circularity: LLCoros isa LLSingleton, yet LLSingleton machinery
- * requires llcoro::get_id().
- *
- * Be very suspicious of anyone else #including this header.
- *
- * $LicenseInfo:firstyear=2016&license=viewerlgpl$
- * Copyright (c) 2016, Linden Research, Inc.
- * $/LicenseInfo$
- */
-
-#if ! defined(LL_LLCORO_GET_ID_H)
-#define LL_LLCORO_GET_ID_H
-
-namespace llcoro
-{
-
-/// Get an opaque, distinct token for the running coroutine (or main).
-typedef void* id;
-id get_id();
-
-} // llcoro
-
-#endif /* ! defined(LL_LLCORO_GET_ID_H) */
diff --git a/indra/llcommon/llcoros.cpp b/indra/llcommon/llcoros.cpp
index cc775775bf..262929006d 100644
--- a/indra/llcommon/llcoros.cpp
+++ b/indra/llcommon/llcoros.cpp
@@ -26,15 +26,30 @@
* $/LicenseInfo$
*/
+#include "llwin32headers.h"
+
// Precompiled header
#include "linden_common.h"
// associated header
#include "llcoros.h"
// STL headers
// std headers
+#include <atomic>
// external library headers
#include <boost/bind.hpp>
+#include <boost/fiber/fiber.hpp>
+#ifndef BOOST_DISABLE_ASSERTS
+#define UNDO_BOOST_DISABLE_ASSERTS
+// with Boost 1.65.1, needed for Mac with this specific header
+#define BOOST_DISABLE_ASSERTS
+#endif
+#include <boost/fiber/protected_fixedsize_stack.hpp>
+#ifdef UNDO_BOOST_DISABLE_ASSERTS
+#undef UNDO_BOOST_DISABLE_ASSERTS
+#undef BOOST_DISABLE_ASSERTS
+#endif
// other Linden headers
+#include "llapp.h"
#include "lltimer.h"
#include "llevents.h"
#include "llerror.h"
@@ -45,85 +60,43 @@
#include <excpt.h>
#endif
-namespace {
-void no_op() {}
-} // anonymous namespace
-
-// Do nothing, when we need nothing done. This is a static member of LLCoros
-// because CoroData is a private nested class.
-void LLCoros::no_cleanup(CoroData*) {}
-
-// CoroData for the currently-running coroutine. Use a thread_specific_ptr
-// because each thread potentially has its own distinct pool of coroutines.
-LLCoros::Current::Current()
+// static
+LLCoros::CoroData& LLCoros::get_CoroData(const std::string& caller)
{
- // Use a function-static instance so this thread_specific_ptr is
- // instantiated on demand. Since we happen to know it's consumed by
- // LLSingleton, this is likely to happen before the runtime has finished
- // initializing module-static data. For the same reason, we can't package
- // this pointer in an LLSingleton.
-
- // This thread_specific_ptr does NOT own the CoroData object! That's owned
- // by LLCoros::mCoros. It merely identifies it. For this reason we
- // instantiate it with a no-op cleanup function.
- static boost::thread_specific_ptr<LLCoros::CoroData> sCurrent(LLCoros::no_cleanup);
-
- // If this is the first time we're accessing sCurrent for the running
- // thread, its get() will be NULL. This could be a problem, in that
- // llcoro::get_id() would return the same (NULL) token value for the "main
- // coroutine" in every thread, whereas what we really want is a distinct
- // value for every distinct stack in the process. So if get() is NULL,
- // give it a heap CoroData: this ensures that llcoro::get_id() will return
- // distinct values.
- // This tactic is "leaky": sCurrent explicitly does not destroy any
- // CoroData to which it points, and we do NOT enter these "main coroutine"
- // CoroData instances in the LLCoros::mCoros map. They are dummy entries,
- // and they will leak at process shutdown: one CoroData per thread.
- if (! sCurrent.get())
+ CoroData* current{ nullptr };
+ // be careful about attempted accesses in the final throes of app shutdown
+ if (! wasDeleted())
{
- // 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 --
- // and truthfully the additional (thread-safe!) machinery to ensure
- // uniqueness just doesn't feel worth the trouble.
- // We use a no-op callable and a minimal stack size because, although
- // CoroData's constructor in fact initializes its mCoro with a
- // coroutine with that stack size, no one ever actually enters it by
- // calling mCoro().
- sCurrent.reset(new CoroData(0, // no prev
- "", // not a named coroutine
- no_op, // no-op callable
- 1024)); // stacksize moot
+ current = instance().mCurrent.get();
+ }
+ // For the main() coroutine, the one NOT explicitly launched by launch(),
+ // we never explicitly set mCurrent. Use a static CoroData instance with
+ // canonical values.
+ if (! current)
+ {
+ 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;
}
-
- mCurrent = &sCurrent;
-}
-
-//static
-LLCoros::CoroData& LLCoros::get_CoroData(const std::string& caller)
-{
- CoroData* current = Current();
- // With the dummy CoroData set in LLCoros::Current::Current(), this
- // pointer should never be NULL.
- llassert_always(current);
return *current;
}
//static
-LLCoros::coro::self& LLCoros::get_self()
+LLCoros::coro::id LLCoros::get_self()
{
- CoroData& current = get_CoroData("get_self()");
- if (! current.mSelf)
- {
- LL_ERRS("LLCoros") << "Calling get_self() from non-coroutine context!" << LL_ENDL;
- }
- return *current.mSelf;
+ return boost::this_fiber::get_id();
}
//static
void LLCoros::set_consuming(bool consuming)
{
- get_CoroData("set_consuming()").mConsuming = consuming;
+ CoroData& data(get_CoroData("set_consuming()"));
+ // DO NOT call this on the main() coroutine.
+ llassert_always(! data.mName.empty());
+ data.mConsuming = consuming;
}
//static
@@ -132,89 +105,59 @@ bool LLCoros::get_consuming()
return get_CoroData("get_consuming()").mConsuming;
}
-llcoro::Suspending::Suspending()
+// static
+void LLCoros::setStatus(const std::string& status)
{
- LLCoros::Current current;
- // Remember currently-running coroutine: we're about to suspend it.
- mSuspended = current;
- // Revert Current to the value it had at the moment we last switched
- // into this coroutine.
- current.reset(mSuspended->mPrev);
+ get_CoroData("setStatus()").mStatus = status;
}
-llcoro::Suspending::~Suspending()
+// static
+std::string LLCoros::getStatus()
{
- LLCoros::Current current;
- // Okay, we're back, update our mPrev
- mSuspended->mPrev = current;
- // and reinstate our Current.
- current.reset(mSuspended);
+ return get_CoroData("getStatus()").mStatus;
}
LLCoros::LLCoros():
// MAINT-2724: default coroutine stack size too small on Windows.
// Previously we used
// boost::context::guarded_stack_allocator::default_stacksize();
- // empirically this is 64KB on Windows and Linux. Try quadrupling.
+ // empirically this is insufficient.
#if ADDRESS_SIZE == 64
- mStackSize(512*1024)
+ mStackSize(512*1024),
#else
- mStackSize(256*1024)
+ mStackSize(256*1024),
#endif
+ // mCurrent does NOT own the current CoroData instance -- it simply
+ // points to it. So initialize it with a no-op deleter.
+ mCurrent{ [](CoroData*){} }
{
- // Register our cleanup() method for "mainloop" ticks
- LLEventPumps::instance().obtain("mainloop").listen(
- "LLCoros", boost::bind(&LLCoros::cleanup, this, _1));
}
-bool LLCoros::cleanup(const LLSD&)
+LLCoros::~LLCoros()
{
- static std::string previousName;
- static int previousCount = 0;
- // Walk the mCoros map, checking and removing completed coroutines.
- for (CoroMap::iterator mi(mCoros.begin()), mend(mCoros.end()); mi != mend; )
+ printActiveCoroutines("at entry to ~LLCoros()");
+ // Other LLApp status-change listeners do things like close
+ // work queues and inject the Stop exception into pending
+ // promises, to force coroutines waiting on those things to
+ // notice and terminate. The only problem is that by the time
+ // LLApp sets "quitting" status, the main loop has stopped
+ // pumping the fiber scheduler with yield() calls. A waiting
+ // coroutine still might not wake up until after resources on
+ // which it depends have been freed. Pump it a few times
+ // ourselves. Of course, stop pumping as soon as the last of
+ // the coroutines has terminated.
+ for (size_t count = 0; count < 10 && CoroData::instanceCount() > 0; ++count)
{
- // Has this coroutine exited (normal return, exception, exit() call)
- // since last tick?
- if (mi->second->mCoro.exited())
- {
- if (previousName != mi->first)
- {
- previousName = mi->first;
- previousCount = 1;
- }
- else
- {
- ++previousCount;
- }
-
- if ((previousCount < 5) || !(previousCount % 50))
- {
- if (previousCount < 5)
- LL_DEBUGS("LLCoros") << "LLCoros: cleaning up coroutine " << mi->first << LL_ENDL;
- else
- LL_DEBUGS("LLCoros") << "LLCoros: cleaning up coroutine " << mi->first << "("<< previousCount << ")" << LL_ENDL;
-
- }
- // The erase() call will invalidate its passed iterator value --
- // so increment mi FIRST -- but pass its original value to
- // erase(). This is what postincrement is all about.
- mCoros.erase(mi++);
- }
- else
- {
- // Still live, just skip this entry as if incrementing at the top
- // of the loop as usual.
- ++mi;
- }
+ // don't use llcoro::suspend() because that module depends
+ // on this one
+ boost::this_fiber::yield();
}
- return false;
+ printActiveCoroutines("after pumping");
}
std::string LLCoros::generateDistinctName(const std::string& prefix) const
{
- static std::string previousName;
- static int previousCount = 0;
+ static int unique = 0;
// Allowing empty name would make getName()'s not-found return ambiguous.
if (prefix.empty())
@@ -225,37 +168,15 @@ std::string LLCoros::generateDistinctName(const std::string& prefix) const
// If the specified name isn't already in the map, just use that.
std::string name(prefix);
- // Find the lowest numeric suffix that doesn't collide with an existing
- // entry. Start with 2 just to make it more intuitive for any interested
- // parties: e.g. "joe", "joe2", "joe3"...
- for (int i = 2; ; name = STRINGIZE(prefix << i++))
+ // Until we find an unused name, append a numeric suffix for uniqueness.
+ while (CoroData::getInstance(name))
{
- if (mCoros.find(name) == mCoros.end())
- {
- if (previousName != name)
- {
- previousName = name;
- previousCount = 1;
- }
- else
- {
- ++previousCount;
- }
-
- if ((previousCount < 5) || !(previousCount % 50))
- {
- if (previousCount < 5)
- LL_DEBUGS("LLCoros") << "LLCoros: launching coroutine " << name << LL_ENDL;
- else
- LL_DEBUGS("LLCoros") << "LLCoros: launching coroutine " << name << "(" << previousCount << ")" << LL_ENDL;
-
- }
-
- return name;
- }
+ name = STRINGIZE(prefix << unique++);
}
+ return name;
}
+/*==========================================================================*|
bool LLCoros::kill(const std::string& name)
{
CoroMap::iterator found = mCoros.find(name);
@@ -269,10 +190,19 @@ bool LLCoros::kill(const std::string& name)
mCoros.erase(found);
return true;
}
+|*==========================================================================*/
-std::string LLCoros::getName() const
+//static
+std::string LLCoros::getName()
{
- return Current()->mName;
+ 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)
@@ -281,25 +211,46 @@ void LLCoros::setStackSize(S32 stacksize)
mStackSize = stacksize;
}
-void LLCoros::printActiveCoroutines()
+void LLCoros::printActiveCoroutines(const std::string& when)
{
- LL_INFOS("LLCoros") << "Number of active coroutines: " << (S32)mCoros.size() << LL_ENDL;
- if (mCoros.size() > 0)
+ LL_INFOS("LLCoros") << "Number of active coroutines " << when
+ << ": " << CoroData::instanceCount() << LL_ENDL;
+ if (CoroData::instanceCount() > 0)
{
LL_INFOS("LLCoros") << "-------------- List of active coroutines ------------";
- CoroMap::iterator iter;
- CoroMap::iterator end = mCoros.end();
F64 time = LLTimer::getTotalSeconds();
- for (iter = mCoros.begin(); iter != end; iter++)
+ for (auto& cd : CoroData::instance_snapshot())
{
- F64 life_time = time - iter->second->mCreationTime;
- LL_CONT << LL_NEWLINE << "Name: " << iter->first << " life: " << life_time;
+ F64 life_time = time - cd.mCreationTime;
+ LL_CONT << LL_NEWLINE
+ << cd.getKey() << ' ' << cd.mStatus << " life: " << life_time;
}
LL_CONT << LL_ENDL;
LL_INFOS("LLCoros") << "-----------------------------------------------------" << LL_ENDL;
}
}
+std::string LLCoros::launch(const std::string& prefix, const callable_t& callable)
+{
+ std::string name(generateDistinctName(prefix));
+ // 'dispatch' means: enter the new fiber immediately, returning here only
+ // when the fiber yields for whatever reason.
+ // std::allocator_arg is a flag to indicate that the following argument is
+ // a StackAllocator.
+ // protected_fixedsize_stack sets a guard page past the end of the new
+ // stack so that stack underflow will result in an access violation
+ // instead of weird, subtle, possibly undiagnosed memory stomps.
+ boost::fibers::fiber newCoro(boost::fibers::launch::dispatch,
+ std::allocator_arg,
+ boost::fibers::protected_fixedsize_stack(mStackSize),
+ [this, &name, &callable](){ toplevel(name, callable); });
+ // You have two choices with a fiber instance: you can join() it or you
+ // can detach() it. If you try to destroy the instance before doing
+ // either, the program silently terminates. We don't need this handle.
+ newCoro.detach();
+ return name;
+}
+
#if LL_WINDOWS
static const U32 STATUS_MSC_EXCEPTION = 0xE06D7363; // compiler specific
@@ -337,13 +288,16 @@ void LLCoros::winlevel(const callable_t& callable)
#endif
-// Top-level wrapper around caller's coroutine callable. This function accepts
-// the coroutine library's implicit coro::self& parameter and saves it, but
-// does not pass it down to the caller's callable.
-void LLCoros::toplevel(coro::self& self, CoroData* data, const callable_t& callable)
+// Top-level wrapper around caller's coroutine callable.
+// Normally we like to pass strings and such by const reference -- but in this
+// case, we WANT to copy both the name and the callable to our local stack!
+void LLCoros::toplevel(std::string name, callable_t callable)
{
- // capture the 'self' param in CoroData
- data->mSelf = &self;
+ // keep the CoroData on this top-level function's stack frame
+ CoroData corodata(name);
+ // set it as current
+ mCurrent.reset(&corodata);
+
// run the code the caller actually wants in the coroutine
try
{
@@ -353,75 +307,69 @@ void LLCoros::toplevel(coro::self& self, CoroData* data, const callable_t& calla
callable();
#endif
}
+ catch (const Stop& exc)
+ {
+ LL_INFOS("LLCoros") << "coroutine " << name << " terminating because "
+ << exc.what() << LL_ENDL;
+ }
catch (const LLContinueError&)
{
// Any uncaught exception derived from LLContinueError will be caught
// here and logged. This coroutine will terminate but the rest of the
// viewer will carry on.
- LOG_UNHANDLED_EXCEPTION(STRINGIZE("coroutine " << data->mName));
+ LOG_UNHANDLED_EXCEPTION(STRINGIZE("coroutine " << name));
}
catch (...)
{
// Any OTHER kind of uncaught exception will cause the viewer to
// crash, hopefully informatively.
- CRASH_ON_UNHANDLED_EXCEPTION(STRINGIZE("coroutine " << data->mName));
+ CRASH_ON_UNHANDLED_EXCEPTION(STRINGIZE("coroutine " << name));
}
- // This cleanup isn't perfectly symmetrical with the way we initially set
- // data->mPrev, but this is our last chance to reset Current.
- Current().reset(data->mPrev);
}
-/*****************************************************************************
-* MUST BE LAST
-*****************************************************************************/
-// Turn off MSVC optimizations for just LLCoros::launch() -- see
-// DEV-32777. But MSVC doesn't support push/pop for optimization flags as it
-// does for warning suppression, and we really don't want to force
-// optimization ON for other code even in Debug or RelWithDebInfo builds.
-
-#if LL_MSVC
-// work around broken optimizations
-#pragma warning(disable: 4748)
-#pragma warning(disable: 4355) // 'this' used in initializer list: yes, intentionally
-#pragma optimize("", off)
-#endif // LL_MSVC
+//static
+void LLCoros::checkStop()
+{
+ if (wasDeleted())
+ {
+ LLTHROW(Shutdown("LLCoros was deleted"));
+ }
+ // do this AFTER the check above, because getName() depends on
+ // get_CoroData(), which depends on the local_ptr in our instance().
+ if (getName().empty())
+ {
+ // Our Stop exception and its subclasses are intended to stop loitering
+ // coroutines. Don't throw it from the main coroutine.
+ return;
+ }
+ if (LLApp::isStopped())
+ {
+ LLTHROW(Stopped("viewer is stopped"));
+ }
+ if (! LLApp::isRunning())
+ {
+ LLTHROW(Stopping("viewer is stopping"));
+ }
+}
-LLCoros::CoroData::CoroData(CoroData* prev, const std::string& name,
- const callable_t& callable, S32 stacksize):
- mPrev(prev),
+LLCoros::CoroData::CoroData(const std::string& name):
+ LLInstanceTracker<CoroData, std::string>(name),
mName(name),
- // Wrap the caller's callable in our toplevel() function so we can manage
- // Current appropriately at startup and shutdown of each coroutine.
- mCoro(boost::bind(toplevel, _1, this, callable), stacksize),
// don't consume events unless specifically directed
mConsuming(false),
- mSelf(0),
mCreationTime(LLTimer::getTotalSeconds())
{
}
-std::string LLCoros::launch(const std::string& prefix, const callable_t& callable)
+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())
{
- std::string name(generateDistinctName(prefix));
- Current current;
- // pass the current value of Current as previous context
- CoroData* newCoro = new(std::nothrow) CoroData(current, name, callable, mStackSize);
- if (newCoro == NULL)
- {
- // Out of memory?
- printActiveCoroutines();
- LL_ERRS("LLCoros") << "Failed to start coroutine: " << name << " Stacksize: " << mStackSize << " Total coroutines: " << mCoros.size() << LL_ENDL;
- }
- // Store it in our pointer map
- mCoros.insert(name, newCoro);
- // also set it as current
- current.reset(newCoro);
- /* Run the coroutine until its first wait, then return here */
- (newCoro->mCoro)(std::nothrow);
- return name;
}
-
-#if LL_MSVC
-// reenable optimizations
-#pragma optimize("", on)
-#endif // LL_MSVC
diff --git a/indra/llcommon/llcoros.h b/indra/llcommon/llcoros.h
index c551413811..38c2356c99 100644
--- a/indra/llcommon/llcoros.h
+++ b/indra/llcommon/llcoros.h
@@ -29,21 +29,26 @@
#if ! defined(LL_LLCOROS_H)
#define LL_LLCOROS_H
-#include <boost/dcoroutine/coroutine.hpp>
-#include <boost/dcoroutine/future.hpp>
+#include "llexception.h"
+#include <boost/fiber/fss.hpp>
+#include <boost/fiber/future/promise.hpp>
+#include <boost/fiber/future/future.hpp>
+#include "mutex.h"
#include "llsingleton.h"
-#include <boost/ptr_container/ptr_map.hpp>
+#include "llinstancetracker.h"
#include <boost/function.hpp>
-#include <boost/thread/tss.hpp>
-#include <boost/noncopyable.hpp>
#include <string>
-#include <stdexcept>
-#include "llcoro_get_id.h" // for friend declaration
-// forward-declare helper class
-namespace llcoro
-{
-class Suspending;
+// e.g. #include LLCOROS_MUTEX_HEADER
+#define LLCOROS_MUTEX_HEADER <boost/fiber/mutex.hpp>
+#define LLCOROS_CONDVAR_HEADER <boost/fiber/condition_variable.hpp>
+
+namespace boost {
+ namespace fibers {
+ class mutex;
+ enum class cv_status;
+ class condition_variable;
+ }
}
/**
@@ -76,19 +81,21 @@ class Suspending;
* name prefix; from your prefix it generates a distinct name, registers the
* new coroutine and returns the actual name.
*
- * The name can be used to kill off the coroutine prematurely, if needed. It
- * can also provide diagnostic info: we can look up the name of the
+ * The name
+ * can provide diagnostic info: we can look up the name of the
* currently-running coroutine.
- *
- * Finally, the next frame ("mainloop" event) after the coroutine terminates,
- * LLCoros will notice its demise and destroy it.
*/
class LL_COMMON_API LLCoros: public LLSingleton<LLCoros>
{
LLSINGLETON(LLCoros);
+ ~LLCoros();
public:
- /// Canonical boost::dcoroutines::coroutine signature we use
- typedef boost::dcoroutines::coroutine<void()> coro;
+ /// The viewer's use of the term "coroutine" became deeply embedded before
+ /// the industry term "fiber" emerged to distinguish userland threads from
+ /// simpler, more transient kinds of coroutines. Semantically they've
+ /// always been fibers. But at this point in history, we're pretty much
+ /// stuck with the term "coroutine."
+ typedef boost::fibers::fiber coro;
/// Canonical callable type
typedef boost::function<void()> callable_t;
@@ -119,10 +126,10 @@ public:
* DEV-32777 comments for an explanation.
*
* Pass a nullary callable. It works to directly pass a nullary free
- * function (or static method); for all other cases use boost::bind(). Of
- * course, for a non-static class method, the first parameter must be the
- * class instance. Any other parameters should be passed via the bind()
- * expression.
+ * function (or static method); for other cases use a lambda expression,
+ * std::bind() or boost::bind(). Of course, for a non-static class method,
+ * the first parameter must be the class instance. Any other parameters
+ * should be passed via the enclosing expression.
*
* launch() tweaks the suggested name so it won't collide with any
* existing coroutine instance, creates the coroutine instance, registers
@@ -138,7 +145,7 @@ public:
* one prematurely. Returns @c true if the specified name was found and
* still running at the time.
*/
- bool kill(const std::string& name);
+// bool kill(const std::string& name);
/**
* From within a coroutine, look up the (tweaked) name string by which
@@ -146,16 +153,27 @@ public:
* (e.g. if the coroutine was launched by hand rather than using
* LLCoros::launch()).
*/
- std::string getName() const;
+ static std::string getName();
- /// for delayed initialization
+ /**
+ * 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
+ * provides no way to alter the stack size of any running coroutine.
+ */
void setStackSize(S32 stacksize);
- /// for delayed initialization
- void printActiveCoroutines();
+ /// diagnostic
+ void printActiveCoroutines(const std::string& when=std::string());
- /// get the current coro::self& for those who really really care
- static coro::self& get_self();
+ /// get the current coro::id for those who really really care
+ static coro::id get_self();
/**
* Most coroutines, most of the time, don't "consume" the events for which
@@ -180,6 +198,7 @@ public:
{
set_consuming(consuming);
}
+ OverrideConsuming(const OverrideConsuming&) = delete;
~OverrideConsuming()
{
set_consuming(mPrevConsuming);
@@ -189,142 +208,124 @@ public:
bool mPrevConsuming;
};
+ /// set string coroutine status for diagnostic purposes
+ static void setStatus(const std::string& status);
+ static std::string getStatus();
+
+ /// RAII control of status
+ class TempStatus
+ {
+ public:
+ TempStatus(const std::string& status):
+ mOldStatus(getStatus())
+ {
+ setStatus(status);
+ }
+ TempStatus(const TempStatus&) = delete;
+ ~TempStatus()
+ {
+ setStatus(mOldStatus);
+ }
+
+ private:
+ std::string mOldStatus;
+ };
+
+ /// thrown by checkStop()
+ // It may sound ironic that Stop is derived from LLContinueError, but the
+ // point is that LLContinueError is the category of exception that should
+ // not immediately crash the viewer. Stop and its subclasses are to notify
+ // coroutines that the viewer intends to shut down. The expected response
+ // is to terminate the coroutine, rather than abort the viewer.
+ struct Stop: public LLContinueError
+ {
+ Stop(const std::string& what): LLContinueError(what) {}
+ };
+
+ /// early stages
+ struct Stopping: public Stop
+ {
+ Stopping(const std::string& what): Stop(what) {}
+ };
+
+ /// cleaning up
+ struct Stopped: public Stop
+ {
+ Stopped(const std::string& what): Stop(what) {}
+ };
+
+ /// cleaned up -- not much survives!
+ struct Shutdown: public Stop
+ {
+ Shutdown(const std::string& what): Stop(what) {}
+ };
+
+ /// Call this intermittently if there's a chance your coroutine might
+ /// continue running into application shutdown. Throws Stop if LLCoros has
+ /// been cleaned up.
+ static void checkStop();
+
/**
- * Please do NOT directly use boost::dcoroutines::future! It is essential
- * to maintain the "current" coroutine at every context switch. This
- * Future wraps the essential boost::dcoroutines::future functionality
- * with that maintenance.
+ * Aliases for promise and future. An older underlying future implementation
+ * required us to wrap future; that's no longer needed. However -- if it's
+ * important to restore kill() functionality, we might need to provide a
+ * proxy, so continue using the aliases.
*/
template <typename T>
- class Future;
+ using Promise = boost::fibers::promise<T>;
+ template <typename T>
+ using Future = boost::fibers::future<T>;
+ template <typename T>
+ static Future<T> getFuture(Promise<T>& promise) { return promise.get_future(); }
+
+ // use mutex, lock, condition_variable suitable for coroutines
+ using Mutex = boost::fibers::mutex;
+ using LockType = std::unique_lock<Mutex>;
+ using cv_status = boost::fibers::cv_status;
+ using ConditionVariable = boost::fibers::condition_variable;
+
+ /// for data local to each running coroutine
+ template <typename T>
+ using local_ptr = boost::fibers::fiber_specific_ptr<T>;
private:
- friend class llcoro::Suspending;
- friend llcoro::id llcoro::get_id();
std::string generateDistinctName(const std::string& prefix) const;
- bool cleanup(const LLSD&);
+ void toplevel(std::string name, callable_t callable);
struct CoroData;
- static void no_cleanup(CoroData*);
#if LL_WINDOWS
static void winlevel(const callable_t& callable);
#endif
- static void toplevel(coro::self& self, CoroData* data, const callable_t& callable);
static CoroData& get_CoroData(const std::string& caller);
S32 mStackSize;
// coroutine-local storage, as it were: one per coro we track
- struct CoroData
+ struct CoroData: public LLInstanceTracker<CoroData, std::string>
{
- CoroData(CoroData* prev, const std::string& name,
- const callable_t& callable, S32 stacksize);
-
- // The boost::dcoroutines library supports asymmetric coroutines. Every
- // time we context switch out of a coroutine, we pass control to the
- // previously-active one (or to the non-coroutine stack owned by the
- // thread). So our management of the "current" coroutine must be able to
- // restore the previous value when we're about to switch away.
- CoroData* mPrev;
+ CoroData(const std::string& name);
+ CoroData(int n);
+
// tweaked name of the current coroutine
const std::string mName;
- // the actual coroutine instance
- LLCoros::coro mCoro;
// set_consuming() state
bool mConsuming;
- // When the dcoroutine library calls a top-level callable, it implicitly
- // passes coro::self& as the first parameter. All our consumer code used
- // to explicitly pass coro::self& down through all levels of call stack,
- // because at the leaf level we need it for context-switching. But since
- // coroutines are based on cooperative switching, we can cause the
- // top-level entry point to stash a pointer to the currently-running
- // coroutine, and manage it appropriately as we switch out and back in.
- // That eliminates the need to pass it as an explicit parameter down
- // through every level, which is unfortunately viral in nature. Finding it
- // implicitly rather than explicitly allows minor maintenance in which a
- // leaf-level function adds a new async I/O call that suspends the calling
- // coroutine, WITHOUT having to propagate coro::self& through every
- // function signature down to that point -- and of course through every
- // other caller of every such function.
- LLCoros::coro::self* mSelf;
+ // setStatus() state
+ std::string mStatus;
F64 mCreationTime; // since epoch
};
- typedef boost::ptr_map<std::string, CoroData> CoroMap;
- CoroMap mCoros;
- // Identify the current coroutine's CoroData. Use a little helper class so
- // a caller can either use a temporary instance, or instantiate a named
- // variable and access it multiple times.
- class Current
- {
- public:
- Current();
-
- operator LLCoros::CoroData*() { return get(); }
- LLCoros::CoroData* operator->() { return get(); }
- LLCoros::CoroData* get() { return mCurrent->get(); }
- void reset(LLCoros::CoroData* ptr) { mCurrent->reset(ptr); }
-
- private:
- boost::thread_specific_ptr<LLCoros::CoroData>* mCurrent;
- };
+ // Identify the current coroutine's CoroData. This local_ptr isn't static
+ // because it's a member of an LLSingleton, and we rely on it being
+ // cleaned up in proper dependency order.
+ local_ptr<CoroData> mCurrent;
};
namespace llcoro
{
-/// Instantiate one of these in a block surrounding any leaf point when
-/// control literally switches away from this coroutine.
-class Suspending: boost::noncopyable
-{
-public:
- Suspending();
- ~Suspending();
-
-private:
- LLCoros::CoroData* mSuspended;
-};
-
-} // namespace llcoro
-
-template <typename T>
-class LLCoros::Future
-{
- typedef boost::dcoroutines::future<T> dfuture;
-
-public:
- Future():
- mFuture(get_self())
- {}
-
- typedef typename boost::dcoroutines::make_callback_result<dfuture>::type callback_t;
-
- callback_t make_callback()
- {
- return boost::dcoroutines::make_callback(mFuture);
- }
-
-#ifndef LL_LINUX
- explicit
-#endif
- operator bool() const
- {
- return bool(mFuture);
- }
-
- bool operator!() const
- {
- return ! mFuture;
- }
+inline
+std::string logname() { return LLCoros::logname(); }
- T get()
- {
- // instantiate Suspending to manage the "current" coroutine
- llcoro::Suspending suspended;
- return *mFuture;
- }
-
-private:
- dfuture mFuture;
-};
+} // llcoro
#endif /* ! defined(LL_LLCOROS_H) */
diff --git a/indra/llcommon/llerror.cpp b/indra/llcommon/llerror.cpp
index 0a83c4a3d7..411412c883 100644
--- a/indra/llcommon/llerror.cpp
+++ b/indra/llcommon/llerror.cpp
@@ -39,6 +39,7 @@
#if !LL_WINDOWS
# include <syslog.h>
# include <unistd.h>
+# include <sys/stat.h>
#else
# include <io.h>
#endif // !LL_WINDOWS
@@ -55,6 +56,13 @@
#include "llstl.h"
#include "lltimer.h"
+// On Mac, got:
+// #error "Boost.Stacktrace requires `_Unwind_Backtrace` function. Define
+// `_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
+#include <boost/stacktrace.hpp>
+
namespace {
#if LL_WINDOWS
void debugger_print(const std::string& s)
@@ -120,27 +128,28 @@ namespace {
class RecordToFile : public LLError::Recorder
{
public:
- RecordToFile(const std::string& filename)
+ RecordToFile(const std::string& filename):
+ mName(filename)
{
mFile.open(filename.c_str(), std::ios_base::out | std::ios_base::app);
if (!mFile)
{
LL_INFOS() << "Error setting log file to " << filename << LL_ENDL;
}
- else
- {
- if (!LLError::getAlwaysFlush())
- {
- mFile.sync_with_stdio(false);
- }
- }
+ else
+ {
+ if (!LLError::getAlwaysFlush())
+ {
+ mFile.sync_with_stdio(false);
+ }
+ }
}
-
+
~RecordToFile()
{
mFile.close();
}
-
+
virtual bool enabled() override
{
#ifdef LL_RELEASE_FOR_DOWNLOAD
@@ -150,11 +159,13 @@ namespace {
#endif
}
- bool okay() { return mFile.good(); }
-
- virtual void recordMessage(LLError::ELevel level,
- const std::string& message) override
- {
+ bool okay() const { return mFile.good(); }
+
+ std::string getFilename() const { return mName; }
+
+ virtual void recordMessage(LLError::ELevel level,
+ const std::string& message) override
+ {
if (LLError::getAlwaysFlush())
{
mFile << message << std::endl;
@@ -163,9 +174,10 @@ namespace {
{
mFile << message << "\n";
}
- }
-
+ }
+
private:
+ const std::string mName;
llofstream mFile;
};
@@ -173,7 +185,7 @@ namespace {
class RecordToStderr : public LLError::Recorder
{
public:
- RecordToStderr(bool timestamp) : mUseANSI(ANSI_PROBE)
+ RecordToStderr(bool timestamp) : mUseANSI(checkANSI())
{
this->showMultiline(true);
}
@@ -195,14 +207,12 @@ namespace {
virtual void recordMessage(LLError::ELevel level,
const std::string& message) override
- {
+ {
static std::string s_ansi_error = createANSI("31"); // red
static std::string s_ansi_warn = createANSI("34"); // blue
static std::string s_ansi_debug = createANSI("35"); // magenta
- mUseANSI = (ANSI_PROBE == mUseANSI) ? (checkANSI() ? ANSI_YES : ANSI_NO) : mUseANSI;
-
- if (ANSI_YES == mUseANSI)
+ if (mUseANSI)
{
writeANSI((level == LLError::LEVEL_ERROR) ? s_ansi_error :
(level == LLError::LEVEL_WARN) ? s_ansi_warn :
@@ -215,12 +225,7 @@ namespace {
}
private:
- enum ANSIState
- {
- ANSI_PROBE,
- ANSI_YES,
- ANSI_NO
- } mUseANSI;
+ bool mUseANSI;
LL_FORCE_INLINE void writeANSI(const std::string& ansi_code, const std::string& message)
{
@@ -231,7 +236,7 @@ namespace {
fprintf(stderr, "%s%s%s\n%s", s_ansi_bold.c_str(), ansi_code.c_str(), message.c_str(), s_ansi_reset.c_str() );
}
- bool checkANSI(void)
+ static bool checkANSI(void)
{
// Check whether it's okay to use ANSI; if stderr is
// a tty then we assume yes. Can be turned off with
@@ -307,28 +312,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;
-#elif LL_WINDOWS
- // DevStudio: type_info::name() includes the text "class " at the start
+ std::string result(name ? name : mangled);
+ free(name);
+ return result;
- static const std::string class_prefix = "class ";
+#elif LL_WINDOWS
+ // 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 " })
+ {
+ 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())
{
- LL_DEBUGS() << "Did not see '" << class_prefix << "' prefix on '"
- << name << "'" << LL_ENDL;
- return name;
+ // 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
}
@@ -407,7 +419,7 @@ namespace
return false;
}
- if (configuration.isUndefined() || !configuration.isMap() || configuration.emptyMap())
+ if (! configuration || !configuration.isMap())
{
LL_WARNS() << filename() << " missing, ill-formed, or simply undefined"
" content; not changing configuration"
@@ -489,14 +501,11 @@ namespace LLError
LLError::FatalFunction mCrashFunction;
LLError::TimeFunction mTimeFunction;
-
+
Recorders mRecorders;
- RecorderPtr mFileRecorder;
- RecorderPtr mFixedBufferRecorder;
- std::string mFileRecorderFileName;
-
- int mShouldLogCallCounter;
-
+
+ int mShouldLogCallCounter;
+
private:
SettingsConfig();
};
@@ -530,9 +539,6 @@ namespace LLError
mCrashFunction(NULL),
mTimeFunction(NULL),
mRecorders(),
- mFileRecorder(),
- mFixedBufferRecorder(),
- mFileRecorderFileName(),
mShouldLogCallCounter(0)
{
}
@@ -655,22 +661,38 @@ namespace LLError
namespace
{
- bool shouldLogToStderr()
- {
+ bool shouldLogToStderr()
+ {
#if LL_DARWIN
- // On Mac OS X, stderr from apps launched from the Finder goes to the
- // console log. It's generally considered bad form to spam too much
- // there.
-
- // If stdin is a tty, assume the user launched from the command line and
- // therefore wants to see stderr. Otherwise, assume we've been launched
- // from the finder and shouldn't spam stderr.
- return isatty(0);
+ // On Mac OS X, stderr from apps launched from the Finder goes to the
+ // console log. It's generally considered bad form to spam too much
+ // there. That scenario can be detected by noticing that stderr is a
+ // character device (S_IFCHR).
+
+ // If stderr is a tty or a pipe, assume the user launched from the
+ // command line or debugger and therefore wants to see stderr.
+ if (isatty(STDERR_FILENO))
+ return true;
+ // not a tty, but might still be a pipe -- check
+ struct stat st;
+ if (fstat(STDERR_FILENO, &st) < 0)
+ {
+ // capture errno right away, before engaging any other operations
+ auto errno_save = errno;
+ // this gets called during log-system setup -- can't log yet!
+ std::cerr << "shouldLogToStderr: fstat(" << STDERR_FILENO << ") failed, errno "
+ << errno_save << std::endl;
+ // if we can't tell, err on the safe side and don't write stderr
+ return false;
+ }
+
+ // fstat() worked: return true only if stderr is a pipe
+ return ((st.st_mode & S_IFMT) == S_IFIFO);
#else
- return true;
+ return true;
#endif
- }
-
+ }
+
bool stderrLogWantsTime()
{
#if LL_WINDOWS
@@ -684,20 +706,19 @@ namespace
void commonInit(const std::string& user_dir, const std::string& app_dir, bool log_to_stderr = true)
{
LLError::Settings::getInstance()->reset();
-
+
LLError::setDefaultLevel(LLError::LEVEL_INFO);
- LLError::setAlwaysFlush(true);
- LLError::setEnabledLogTypesMask(0xFFFFFFFF);
+ LLError::setAlwaysFlush(true);
+ LLError::setEnabledLogTypesMask(0xFFFFFFFF);
LLError::setFatalFunction(LLError::crashAndLoop);
LLError::setTimeFunction(LLError::utcTime);
// log_to_stderr is only false in the unit and integration tests to keep builds quieter
if (log_to_stderr && shouldLogToStderr())
{
- LLError::RecorderPtr recordToStdErr(new RecordToStderr(stderrLogWantsTime()));
- LLError::addRecorder(recordToStdErr);
+ LLError::logToStderr();
}
-
+
#if LL_WINDOWS
LLError::RecorderPtr recordToWinDebug(new RecordToWinDebug());
LLError::addRecorder(recordToWinDebug);
@@ -995,49 +1016,110 @@ namespace LLError
s->mRecorders.erase(std::remove(s->mRecorders.begin(), s->mRecorders.end(), recorder),
s->mRecorders.end());
}
+
+ // Find an entry in SettingsConfig::mRecorders whose RecorderPtr points to
+ // a Recorder subclass of type RECORDER. Return, not a RecorderPtr (which
+ // points to the Recorder base class), but a shared_ptr<RECORDER> which
+ // specifically points to the concrete RECORDER subclass instance, along
+ // with a Recorders::iterator indicating the position of that entry in
+ // mRecorders. The shared_ptr might be empty (operator!() returns true) if
+ // there was no such RECORDER subclass instance in mRecorders.
+ template <typename RECORDER>
+ std::pair<boost::shared_ptr<RECORDER>, Recorders::iterator>
+ findRecorderPos()
+ {
+ SettingsConfigPtr s = Settings::instance().getSettingsConfig();
+ // Since we promise to return an iterator, use a classic iterator
+ // loop.
+ auto end{s->mRecorders.end()};
+ for (Recorders::iterator it{s->mRecorders.begin()}; it != end; ++it)
+ {
+ // *it is a RecorderPtr, a shared_ptr<Recorder>. Use a
+ // dynamic_pointer_cast to try to downcast to test if it's also a
+ // shared_ptr<RECORDER>.
+ auto ptr = boost::dynamic_pointer_cast<RECORDER>(*it);
+ if (ptr)
+ {
+ // found the entry we want
+ return { ptr, it };
+ }
+ }
+ // dropped out of the loop without finding any such entry -- instead
+ // of default-constructing Recorders::iterator (which might or might
+ // not be valid), return a value that is valid but not dereferenceable.
+ return { {}, end };
+ }
+
+ // Find an entry in SettingsConfig::mRecorders whose RecorderPtr points to
+ // a Recorder subclass of type RECORDER. Return, not a RecorderPtr (which
+ // points to the Recorder base class), but a shared_ptr<RECORDER> which
+ // specifically points to the concrete RECORDER subclass instance. The
+ // shared_ptr might be empty (operator!() returns true) if there was no
+ // such RECORDER subclass instance in mRecorders.
+ template <typename RECORDER>
+ boost::shared_ptr<RECORDER> findRecorder()
+ {
+ return findRecorderPos<RECORDER>().first;
+ }
+
+ // Remove an entry from SettingsConfig::mRecorders whose RecorderPtr
+ // points to a Recorder subclass of type RECORDER. Return true if there
+ // was one and we removed it, false if there wasn't one to start with.
+ template <typename RECORDER>
+ bool removeRecorder()
+ {
+ auto found = findRecorderPos<RECORDER>();
+ if (found.first)
+ {
+ SettingsConfigPtr s = Settings::instance().getSettingsConfig();
+ s->mRecorders.erase(found.second);
+ }
+ return bool(found.first);
+ }
}
namespace LLError
{
void logToFile(const std::string& file_name)
{
- SettingsConfigPtr s = Settings::getInstance()->getSettingsConfig();
+ // remove any previous Recorder filling this role
+ removeRecorder<RecordToFile>();
- removeRecorder(s->mFileRecorder);
- s->mFileRecorder.reset();
- s->mFileRecorderFileName.clear();
-
if (!file_name.empty())
{
- RecorderPtr recordToFile(new RecordToFile(file_name));
- if (boost::dynamic_pointer_cast<RecordToFile>(recordToFile)->okay())
- {
- s->mFileRecorderFileName = file_name;
- s->mFileRecorder = recordToFile;
- addRecorder(recordToFile);
- }
+ boost::shared_ptr<RecordToFile> recordToFile(new RecordToFile(file_name));
+ if (recordToFile->okay())
+ {
+ addRecorder(recordToFile);
+ }
}
}
-
- void logToFixedBuffer(LLLineBuffer* fixedBuffer)
+
+ std::string logFileName()
{
- SettingsConfigPtr s = Settings::getInstance()->getSettingsConfig();
+ auto found = findRecorder<RecordToFile>();
+ return found? found->getFilename() : std::string();
+ }
- removeRecorder(s->mFixedBufferRecorder);
- s->mFixedBufferRecorder.reset();
-
- if (fixedBuffer)
- {
- RecorderPtr recordToFixedBuffer(new RecordToFixedBuffer(fixedBuffer));
- s->mFixedBufferRecorder = recordToFixedBuffer;
- addRecorder(recordToFixedBuffer);
+ void logToStderr()
+ {
+ if (! findRecorder<RecordToStderr>())
+ {
+ RecorderPtr recordToStdErr(new RecordToStderr(stderrLogWantsTime()));
+ addRecorder(recordToStdErr);
}
- }
+ }
- std::string logFileName()
+ void logToFixedBuffer(LLLineBuffer* fixedBuffer)
{
- SettingsConfigPtr s = Settings::getInstance()->getSettingsConfig();
- return s->mFileRecorderFileName;
+ // remove any previous Recorder filling this role
+ removeRecorder<RecordToFixedBuffer>();
+
+ if (fixedBuffer)
+ {
+ RecorderPtr recordToFixedBuffer(new RecordToFixedBuffer(fixedBuffer));
+ addRecorder(recordToFixedBuffer);
+ }
}
}
@@ -1153,8 +1235,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)
@@ -1202,7 +1301,7 @@ namespace LLError
bool Log::shouldLog(CallSite& site)
{
- LLMutexTrylock lock(&gLogMutex, 5);
+ LLMutexTrylock lock(getMutex<LOG_MUTEX>(), 5);
if (!lock.isLocked())
{
return false;
@@ -1253,7 +1352,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.
@@ -1273,7 +1372,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;
@@ -1313,7 +1412,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;
@@ -1485,129 +1584,133 @@ 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::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);
- if (!lock.isLocked())
- {
- return;
- }
-
- if(sBuffer == NULL)
- {
- allocateStackBuffer();
- }
-
- if(sIndex > 511)
- {
- clear() ;
- }
-
- strcpy(sBuffer[sIndex], function) ;
- sprintf(sBuffer[sIndex] + strlen(function), " line: %d ", line) ;
- sIndex++ ;
-
- return ;
- }
+ 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 ;
+ }
+ }
- //static
- std::ostringstream* LLCallStacks::insert(const char* function, const int line)
- {
- std::ostringstream* _out = LLError::Log::out();
- *_out << function << " line " << line << " " ;
-
- return _out ;
- }
-
- //static
- void LLCallStacks::end(std::ostringstream* _out)
- {
- LLMutexTrylock lock(&gCallStacksLogMutex, 5);
- if (!lock.isLocked())
- {
- return;
- }
-
- if(sBuffer == NULL)
- {
- allocateStackBuffer();
- }
-
- if(sIndex > 511)
- {
- clear() ;
- }
-
- LLError::Log::flush(_out, sBuffer[sIndex++]) ;
- }
-
- //static
- void LLCallStacks::print()
- {
- LLMutexTrylock lock(&gCallStacksLogMutex, 5);
- if (!lock.isLocked())
- {
- return;
- }
-
- if(sIndex > 0)
- {
- LL_INFOS() << " ************* PRINT OUT LL CALL STACKS ************* " << LL_ENDL;
- while(sIndex > 0)
- {
- sIndex-- ;
- LL_INFOS() << sBuffer[sIndex] << LL_ENDL;
- }
- LL_INFOS() << " *************** END OF LL CALL STACKS *************** " << LL_ENDL;
- }
-
- if(sBuffer != NULL)
- {
- freeStackBuffer();
- }
- }
-
- //static
- void LLCallStacks::clear()
- {
- sIndex = 0 ;
- }
-
- //static
- void LLCallStacks::cleanup()
- {
- freeStackBuffer();
- }
+ 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(getMutex<STACKS_MUTEX>(), 5);
+ if (!lock.isLocked())
+ {
+ return;
+ }
+
+ if(sBuffer == NULL)
+ {
+ allocateStackBuffer();
+ }
+
+ if(sIndex > 511)
+ {
+ clear() ;
+ }
+
+ strcpy(sBuffer[sIndex], function) ;
+ sprintf(sBuffer[sIndex] + strlen(function), " line: %d ", line) ;
+ sIndex++ ;
+
+ return ;
+ }
+
+ //static
+ std::ostringstream* LLCallStacks::insert(const char* function, const int line)
+ {
+ std::ostringstream* _out = LLError::Log::out();
+ *_out << function << " line " << line << " " ;
+ return _out ;
+ }
+
+ //static
+ void LLCallStacks::end(std::ostringstream* _out)
+ {
+ LLMutexTrylock lock(getMutex<STACKS_MUTEX>(), 5);
+ if (!lock.isLocked())
+ {
+ return;
+ }
+
+ if(sBuffer == NULL)
+ {
+ allocateStackBuffer();
+ }
+
+ if(sIndex > 511)
+ {
+ clear() ;
+ }
+
+ LLError::Log::flush(_out, sBuffer[sIndex++]) ;
+ }
+
+ //static
+ void LLCallStacks::print()
+ {
+ LLMutexTrylock lock(getMutex<STACKS_MUTEX>(), 5);
+ if (!lock.isLocked())
+ {
+ return;
+ }
+
+ if(sIndex > 0)
+ {
+ LL_INFOS() << " ************* PRINT OUT LL CALL STACKS ************* " << LL_ENDL;
+ while(sIndex > 0)
+ {
+ sIndex-- ;
+ LL_INFOS() << sBuffer[sIndex] << LL_ENDL;
+ }
+ LL_INFOS() << " *************** END OF LL CALL STACKS *************** " << LL_ENDL;
+ }
+
+ if(sBuffer != NULL)
+ {
+ freeStackBuffer();
+ }
+ }
+
+ //static
+ void LLCallStacks::clear()
+ {
+ sIndex = 0 ;
+ }
+
+ //static
+ void LLCallStacks::cleanup()
+ {
+ freeStackBuffer();
+ }
+
+ std::ostream& operator<<(std::ostream& out, const LLStacktrace&)
+ {
+ return out << boost::stacktrace::stacktrace();
+ }
}
bool debugLoggingEnabled(const std::string& tag)
{
- LLMutexTrylock lock(&gLogMutex, 5);
+ LLMutexTrylock lock(getMutex<LOG_MUTEX>(), 5);
if (!lock.isLocked())
{
return false;
diff --git a/indra/llcommon/llerror.h b/indra/llcommon/llerror.h
index 0a78229555..ffaa464d77 100644
--- a/indra/llcommon/llerror.h
+++ b/indra/llcommon/llerror.h
@@ -191,9 +191,9 @@ namespace LLError
The classes CallSite and Log are used by the logging macros below.
They are not intended for general use.
*/
-
+
struct CallSite;
-
+
class LL_COMMON_API Log
{
public:
@@ -202,8 +202,17 @@ namespace LLError
static void flush(std::ostringstream* out, char* message);
static void flush(std::ostringstream*, const CallSite&);
static std::string demangle(const char* mangled);
+ /// classname<TYPE>()
+ template <typename T>
+ static std::string classname() { return demangle(typeid(T).name()); }
+ /// classname(some_pointer)
+ template <typename T>
+ static std::string classname(T* const ptr) { return ptr? demangle(typeid(*ptr).name()) : "nullptr"; }
+ /// classname(some_reference)
+ template <typename T>
+ static std::string classname(const T& obj) { return demangle(typeid(obj).name()); }
};
-
+
struct LL_COMMON_API CallSite
{
// Represents a specific place in the code where a message is logged
@@ -262,30 +271,36 @@ namespace LLError
class LL_COMMON_API NoClassInfo { };
// used to indicate no class info known for logging
- //LLCallStacks keeps track of call stacks and output the call stacks to log file
- //when LLAppViewer::handleViewerCrash() is triggered.
- //
- //Note: to be simple, efficient and necessary to keep track of correct call stacks,
- //LLCallStacks is designed not to be thread-safe.
- //so try not to use it in multiple parallel threads at same time.
- //Used in a single thread at a time is fine.
- class LL_COMMON_API LLCallStacks
- {
- private:
- static char** sBuffer ;
- static S32 sIndex ;
-
- static void allocateStackBuffer();
- static void freeStackBuffer();
-
- public:
- static void push(const char* function, const int line) ;
- static std::ostringstream* insert(const char* function, const int line) ;
- static void print() ;
- static void clear() ;
- static void end(std::ostringstream* _out) ;
- static void cleanup();
- };
+ //LLCallStacks keeps track of call stacks and output the call stacks to log file
+ //when LLAppViewer::handleViewerCrash() is triggered.
+ //
+ //Note: to be simple, efficient and necessary to keep track of correct call stacks,
+ //LLCallStacks is designed not to be thread-safe.
+ //so try not to use it in multiple parallel threads at same time.
+ //Used in a single thread at a time is fine.
+ class LL_COMMON_API LLCallStacks
+ {
+ private:
+ static char** sBuffer ;
+ static S32 sIndex ;
+
+ static void allocateStackBuffer();
+ static void freeStackBuffer();
+
+ public:
+ static void push(const char* function, const int line) ;
+ static std::ostringstream* insert(const char* function, const int line) ;
+ static void print() ;
+ static void clear() ;
+ static void end(std::ostringstream* _out) ;
+ static void cleanup();
+ };
+
+ // class which, when streamed, inserts the current stack trace
+ struct LLStacktrace
+ {
+ friend std::ostream& operator<<(std::ostream& out, const LLStacktrace&);
+ };
}
//this is cheaper than llcallstacks if no need to output other variables to call stacks.
@@ -381,8 +396,13 @@ typedef LLError::NoClassInfo _LL_CLASS_TO_LOG;
#define LL_WARNS(...) lllog(LLError::LEVEL_WARN, false, ##__VA_ARGS__)
#define LL_ERRS(...) lllog(LLError::LEVEL_ERROR, false, ##__VA_ARGS__)
// alternative to llassert_always that prints explanatory message
-#define LL_WARNS_IF(exp, ...) if (exp) LL_WARNS(##__VA_ARGS__) << "(" #exp ")"
-#define LL_ERRS_IF(exp, ...) if (exp) LL_ERRS(##__VA_ARGS__) << "(" #exp ")"
+// note ## token paste operator hack used above will only work in gcc following
+// a comma and is completely unnecessary in VS since the comma is automatically
+// suppressed
+// https://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html
+// https://docs.microsoft.com/en-us/cpp/preprocessor/variadic-macros?view=vs-2015
+#define LL_WARNS_IF(exp, ...) if (exp) LL_WARNS(__VA_ARGS__) << "(" #exp ")"
+#define LL_ERRS_IF(exp, ...) if (exp) LL_ERRS(__VA_ARGS__) << "(" #exp ")"
// Only print the log message once (good for warnings or infos that would otherwise
// spam the log file over and over, such as tighter loops).
diff --git a/indra/llcommon/llerrorcontrol.h b/indra/llcommon/llerrorcontrol.h
index 276d22fc36..bfa2269025 100644
--- a/indra/llcommon/llerrorcontrol.h
+++ b/indra/llcommon/llerrorcontrol.h
@@ -183,6 +183,7 @@ namespace LLError
// each error message is passed to each recorder via recordMessage()
LL_COMMON_API void logToFile(const std::string& filename);
+ LL_COMMON_API void logToStderr();
LL_COMMON_API void logToFixedBuffer(LLLineBuffer*);
// Utilities to add recorders for logging to a file or a fixed buffer
// A second call to the same function will remove the logger added
diff --git a/indra/llcommon/lleventcoro.cpp b/indra/llcommon/lleventcoro.cpp
index 56367b8f54..995356dc52 100644
--- a/indra/llcommon/lleventcoro.cpp
+++ b/indra/llcommon/lleventcoro.cpp
@@ -31,17 +31,17 @@
// associated header
#include "lleventcoro.h"
// STL headers
-#include <map>
+#include <chrono>
+#include <exception>
// std headers
// external library headers
+#include <boost/fiber/operations.hpp>
// other Linden headers
#include "llsdserialize.h"
+#include "llsdutil.h"
#include "llerror.h"
#include "llcoros.h"
-#include "llmake.h"
-#include "llexception.h"
-
-#include "lleventfilter.h"
+#include "stringize.h"
namespace
{
@@ -62,7 +62,7 @@ namespace
std::string listenerNameForCoro()
{
// If this coroutine was launched by LLCoros::launch(), find that name.
- std::string name(LLCoros::instance().getName());
+ std::string name(LLCoros::getName());
if (! name.empty())
{
return name;
@@ -92,137 +92,173 @@ std::string listenerNameForCoro()
* In the degenerate case in which @a path is an empty array, @a dest will
* @em become @a value rather than @em containing it.
*/
-void storeToLLSDPath(LLSD& dest, const LLSD& rawPath, const LLSD& value)
+void storeToLLSDPath(LLSD& dest, const LLSD& path, const LLSD& value)
{
- if (rawPath.isUndefined())
+ if (path.isUndefined())
{
// no-op case
return;
}
- // Arrange to treat rawPath uniformly as an array. If it's not already an
- // array, store it as the only entry in one.
- LLSD path;
- if (rawPath.isArray())
- {
- path = rawPath;
- }
- else
- {
- path.append(rawPath);
- }
-
- // Need to indicate a current destination -- but that current destination
- // needs to change as we step through the path array. Where normally we'd
- // use an LLSD& to capture a subscripted LLSD lvalue, this time we must
- // instead use a pointer -- since it must be reassigned.
- LLSD* pdest = &dest;
-
- // Now loop through that array
- for (LLSD::Integer i = 0; i < path.size(); ++i)
- {
- if (path[i].isString())
- {
- // *pdest is an LLSD map
- pdest = &((*pdest)[path[i].asString()]);
- }
- else if (path[i].isInteger())
- {
- // *pdest is an LLSD array
- pdest = &((*pdest)[path[i].asInteger()]);
- }
- else
- {
- // What do we do with Real or Array or Map or ...?
- // As it's a coder error -- not a user error -- rub the coder's
- // face in it so it gets fixed.
- LL_ERRS("lleventcoro") << "storeToLLSDPath(" << dest << ", " << rawPath << ", " << value
- << "): path[" << i << "] bad type " << path[i].type() << LL_ENDL;
- }
- }
-
- // Here *pdest is where we should store value.
- *pdest = value;
+ // Drill down to where we should store 'value'.
+ llsd::drill(dest, path) = value;
}
-/// For LLCoros::Future<LLSD>::make_callback(), the callback has a signature
-/// like void callback(LLSD), which isn't a valid LLEventPump listener: such
-/// listeners must return bool.
-template <typename LISTENER>
-class FutureListener
-{
-public:
- // FutureListener is instantiated on the coroutine stack: the stack, in
- // other words, that wants to suspend.
- FutureListener(const LISTENER& listener):
- mListener(listener),
- // Capture the suspending coroutine's flag as a consuming or
- // non-consuming listener.
- mConsume(LLCoros::get_consuming())
- {}
-
- // operator()() is called on the main stack: the stack on which the
- // expected event is fired.
- bool operator()(const LLSD& event)
- {
- mListener(event);
- // tell upstream LLEventPump whether listener consumed
- return mConsume;
- }
-
-protected:
- LISTENER mListener;
- bool mConsume;
-};
-
} // anonymous
void llcoro::suspend()
{
- // By viewer convention, we post an event on the "mainloop" LLEventPump
- // each iteration of the main event-handling loop. So waiting for a single
- // event on "mainloop" gives us a one-frame suspend.
- suspendUntilEventOn("mainloop");
+ LLCoros::checkStop();
+ LLCoros::TempStatus st("waiting one tick");
+ boost::this_fiber::yield();
}
void llcoro::suspendUntilTimeout(float seconds)
{
- LLEventTimeout timeout;
-
- timeout.eventAfter(seconds, LLSD());
- llcoro::suspendUntilEventOn(timeout);
+ LLCoros::checkStop();
+ // We used to call boost::this_fiber::sleep_for(). But some coroutines
+ // (e.g. LLExperienceCache::idleCoro()) sit in a suspendUntilTimeout()
+ // loop, in which case a sleep_for() call risks sleeping through shutdown.
+ // So instead, listen for "LLApp" state-changing events -- which
+ // fortunately is handled for us by suspendUntilEventOnWithTimeout().
+ // Wait for an event on a bogus LLEventPump on which nobody ever posts
+ // events. Don't make it static because that would force instantiation of
+ // the LLEventPumps LLSingleton registry at static initialization time.
+ // DO allow tweaking the name for uniqueness, this definitely gets
+ // re-entered on multiple coroutines!
+ // We could use an LLUUID if it were important to actively prohibit anyone
+ // from ever posting on this LLEventPump.
+ LLEventStream bogus("xyzzy", true);
+ // Timeout is the NORMAL case for this call!
+ static LLSD timedout;
+ // Deliver, but ignore, timedout when (as usual) we did not receive any
+ // "LLApp" event. The point is that suspendUntilEventOnWithTimeout() will
+ // itself throw Stopping when "LLApp" starts broadcasting shutdown events.
+ suspendUntilEventOnWithTimeout(bogus, seconds, timedout);
}
-LLSD llcoro::postAndSuspend(const LLSD& event, const LLEventPumpOrPumpName& requestPump,
- const LLEventPumpOrPumpName& replyPump, const LLSD& replyPumpNamePath)
+namespace
{
- // declare the future
- LLCoros::Future<LLSD> future;
- // make a callback that will assign a value to the future, and listen on
- // the specified LLEventPump with that callback
- std::string listenerName(listenerNameForCoro());
- LLTempBoundListener connection(
- replyPump.getPump().listen(listenerName,
- llmake<FutureListener>(future.make_callback())));
+
+// returns a listener on replyPumpP, also on "mainloop" -- both should be
+// stored in LLTempBoundListeners on the caller's stack frame
+std::pair<LLBoundListener, LLBoundListener>
+postAndSuspendSetup(const std::string& callerName,
+ const std::string& listenerName,
+ LLCoros::Promise<LLSD>& promise,
+ const LLSD& event,
+ const LLEventPumpOrPumpName& requestPumpP,
+ const LLEventPumpOrPumpName& replyPumpP,
+ const LLSD& replyPumpNamePath)
+{
+ // Before we get any farther -- should we be stopping instead of
+ // suspending?
+ LLCoros::checkStop();
+ // Get the consuming attribute for THIS coroutine, the one that's about to
+ // suspend. Don't call get_consuming() in the lambda body: that would
+ // return the consuming attribute for some other coroutine, most likely
+ // the main routine.
+ bool consuming(LLCoros::get_consuming());
+ // listen on the specified LLEventPump with a lambda that will assign a
+ // value to the promise, thus fulfilling its future
+ llassert_always_msg(replyPumpP, ("replyPump required for " + callerName));
+ LLEventPump& replyPump(replyPumpP.getPump());
+ // The relative order of the two listen() calls below would only matter if
+ // "LLApp" were an LLEventMailDrop. But if we ever go there, we'd want to
+ // notice the pending LLApp status first.
+ LLBoundListener stopper(
+ LLEventPumps::instance().obtain("LLApp").listen(
+ listenerName,
+ [&promise, listenerName](const LLSD& status)
+ {
+ // anything except "running" should wake up the waiting
+ // coroutine
+ auto& statsd = status["status"];
+ if (statsd.asString() != "running")
+ {
+ LL_DEBUGS("lleventcoro") << listenerName
+ << " spotted status " << statsd
+ << ", throwing Stopping" << LL_ENDL;
+ try
+ {
+ promise.set_exception(
+ std::make_exception_ptr(
+ LLCoros::Stopping("status " + statsd.asString())));
+ }
+ catch (const boost::fibers::promise_already_satisfied&)
+ {
+ LL_WARNS("lleventcoro") << listenerName
+ << " couldn't throw Stopping "
+ "because promise already set" << LL_ENDL;
+ }
+ }
+ // do not consume -- every listener must see status
+ return false;
+ }));
+ LLBoundListener connection(
+ replyPump.listen(
+ listenerName,
+ [&promise, consuming, listenerName](const LLSD& result)
+ {
+ try
+ {
+ promise.set_value(result);
+ // We did manage to propagate the result value to the
+ // (real) listener. If we're supposed to indicate that
+ // we've consumed it, do so.
+ return consuming;
+ }
+ catch(boost::fibers::promise_already_satisfied & ex)
+ {
+ LL_DEBUGS("lleventcoro") << "promise already satisfied in '"
+ << listenerName << "': " << ex.what() << LL_ENDL;
+ // We could not propagate the result value to the
+ // listener.
+ return false;
+ }
+ }));
+
// skip the "post" part if requestPump is default-constructed
- if (requestPump)
+ if (requestPumpP)
{
+ LLEventPump& requestPump(requestPumpP.getPump());
// If replyPumpNamePath is non-empty, store the replyPump name in the
// request event.
LLSD modevent(event);
- storeToLLSDPath(modevent, replyPumpNamePath, replyPump.getPump().getName());
- LL_DEBUGS("lleventcoro") << "postAndSuspend(): coroutine " << listenerName
- << " posting to " << requestPump.getPump().getName()
+ storeToLLSDPath(modevent, replyPumpNamePath, replyPump.getName());
+ LL_DEBUGS("lleventcoro") << callerName << ": coroutine " << listenerName
+ << " posting to " << requestPump.getName()
<< LL_ENDL;
// *NOTE:Mani - Removed because modevent could contain user's hashed passwd.
// << ": " << modevent << LL_ENDL;
- requestPump.getPump().post(modevent);
+ requestPump.post(modevent);
}
- LL_DEBUGS("lleventcoro") << "postAndSuspend(): coroutine " << listenerName
- << " about to wait on LLEventPump " << replyPump.getPump().getName()
+ LL_DEBUGS("lleventcoro") << callerName << ": coroutine " << listenerName
+ << " about to wait on LLEventPump " << replyPump.getName()
<< LL_ENDL;
+ return { connection, stopper };
+}
+
+} // anonymous
+
+LLSD llcoro::postAndSuspend(const LLSD& event, const LLEventPumpOrPumpName& requestPump,
+ const LLEventPumpOrPumpName& replyPump, const LLSD& replyPumpNamePath)
+{
+ LLCoros::Promise<LLSD> promise;
+ std::string listenerName(listenerNameForCoro());
+
+ // Store both connections into LLTempBoundListeners so we implicitly
+ // disconnect on return from this function.
+ auto connections =
+ postAndSuspendSetup("postAndSuspend()", listenerName, promise,
+ event, requestPump, replyPump, replyPumpNamePath);
+ LLTempBoundListener connection(connections.first), stopper(connections.second);
+
+ // declare the future
+ LLCoros::Future<LLSD> future = LLCoros::getFuture(promise);
// calling get() on the future makes us wait for it
+ LLCoros::TempStatus st(STRINGIZE("waiting for " << replyPump.getPump().getName()));
LLSD value(future.get());
LL_DEBUGS("lleventcoro") << "postAndSuspend(): coroutine " << listenerName
<< " resuming with " << value << LL_ENDL;
@@ -230,147 +266,52 @@ LLSD llcoro::postAndSuspend(const LLSD& event, const LLEventPumpOrPumpName& requ
return value;
}
-LLSD llcoro::suspendUntilEventOnWithTimeout(const LLEventPumpOrPumpName& suspendPumpOrName,
- F32 timeoutin, const LLSD &timeoutResult)
-{
- /**
- * The timeout pump is attached upstream of of the waiting pump and will
- * pass the timeout event through it. We CAN NOT attach downstream since
- * doing so will cause the suspendPump to fire any waiting events immediately
- * and they will be lost. This becomes especially problematic with the
- * LLEventTimeout(pump) constructor which will also attempt to fire those
- * events using the virtual listen_impl method in the not yet fully constructed
- * timeoutPump.
- */
- LLEventTimeout timeoutPump;
- LLEventPump &suspendPump = suspendPumpOrName.getPump();
-
- LLTempBoundListener timeoutListener(timeoutPump.listen(suspendPump.getName(),
- boost::bind(&LLEventPump::post, &suspendPump, _1)));
-
- timeoutPump.eventAfter(timeoutin, timeoutResult);
- return llcoro::suspendUntilEventOn(suspendPump);
-}
-
-namespace
-{
-
-/**
- * This helper is specifically for postAndSuspend2(). We use a single future
- * object, but we want to listen on two pumps with it. Since we must still
- * adapt from the callable constructed by boost::dcoroutines::make_callback()
- * (void return) to provide an event listener (bool return), we've adapted
- * FutureListener for the purpose. The basic idea is that we construct a
- * distinct instance of FutureListener2 -- binding different instance data --
- * for each of the pumps. Then, when a pump delivers an LLSD value to either
- * FutureListener2, it can combine that LLSD with its discriminator to feed
- * the future object.
- *
- * DISCRIM is a template argument so we can use llmake() rather than
- * having to write our own argument-deducing helper function.
- */
-template <typename LISTENER, typename DISCRIM>
-class FutureListener2: public FutureListener<LISTENER>
+LLSD llcoro::postAndSuspendWithTimeout(const LLSD& event,
+ const LLEventPumpOrPumpName& requestPump,
+ const LLEventPumpOrPumpName& replyPump,
+ const LLSD& replyPumpNamePath,
+ F32 timeout, const LLSD& timeoutResult)
{
- typedef FutureListener<LISTENER> super;
-
-public:
- // instantiated on coroutine stack: the stack about to suspend
- FutureListener2(const LISTENER& listener, DISCRIM discriminator):
- super(listener),
- mDiscrim(discriminator)
- {}
-
- // called on main stack: the stack on which event is fired
- bool operator()(const LLSD& event)
- {
- // our future object is defined to accept LLEventWithID
- super::mListener(LLEventWithID(event, mDiscrim));
- // tell LLEventPump whether or not event was consumed
- return super::mConsume;
- }
-
-private:
- const DISCRIM mDiscrim;
-};
+ LLCoros::Promise<LLSD> promise;
+ std::string listenerName(listenerNameForCoro());
-} // anonymous
+ // Store both connections into LLTempBoundListeners so we implicitly
+ // disconnect on return from this function.
+ auto connections =
+ postAndSuspendSetup("postAndSuspendWithTimeout()", listenerName, promise,
+ event, requestPump, replyPump, replyPumpNamePath);
+ LLTempBoundListener connection(connections.first), stopper(connections.second);
-namespace llcoro
-{
-
-LLEventWithID postAndSuspend2(const LLSD& event,
- const LLEventPumpOrPumpName& requestPump,
- const LLEventPumpOrPumpName& replyPump0,
- const LLEventPumpOrPumpName& replyPump1,
- const LLSD& replyPump0NamePath,
- const LLSD& replyPump1NamePath)
-{
// declare the future
- LLCoros::Future<LLEventWithID> future;
- // either callback will assign a value to this future; listen on
- // each specified LLEventPump with a callback
- std::string name(listenerNameForCoro());
- LLTempBoundListener connection0(
- replyPump0.getPump().listen(
- name + "a",
- llmake<FutureListener2>(future.make_callback(), 0)));
- LLTempBoundListener connection1(
- replyPump1.getPump().listen(
- name + "b",
- llmake<FutureListener2>(future.make_callback(), 1)));
- // skip the "post" part if requestPump is default-constructed
- if (requestPump)
+ LLCoros::Future<LLSD> future = LLCoros::getFuture(promise);
+ // wait for specified timeout
+ boost::fibers::future_status status;
{
- // If either replyPumpNamePath is non-empty, store the corresponding
- // replyPump name in the request event.
- LLSD modevent(event);
- storeToLLSDPath(modevent, replyPump0NamePath,
- replyPump0.getPump().getName());
- storeToLLSDPath(modevent, replyPump1NamePath,
- replyPump1.getPump().getName());
- LL_DEBUGS("lleventcoro") << "postAndSuspend2(): coroutine " << name
- << " posting to " << requestPump.getPump().getName()
- << ": " << modevent << LL_ENDL;
- requestPump.getPump().post(modevent);
+ LLCoros::TempStatus st(STRINGIZE("waiting for " << replyPump.getPump().getName()
+ << " for " << timeout << "s"));
+ // The fact that we accept non-integer seconds means we should probably
+ // use granularity finer than one second. However, given the overhead of
+ // the rest of our processing, it seems silly to use granularity finer
+ // than a millisecond.
+ status = future.wait_for(std::chrono::milliseconds(long(timeout * 1000)));
}
- LL_DEBUGS("lleventcoro") << "postAndSuspend2(): coroutine " << name
- << " about to wait on LLEventPumps " << replyPump0.getPump().getName()
- << ", " << replyPump1.getPump().getName() << LL_ENDL;
- // calling get() on the future makes us wait for it
- LLEventWithID value(future.get());
- LL_DEBUGS("lleventcoro") << "postAndSuspend(): coroutine " << name
- << " resuming with (" << value.first << ", " << value.second << ")"
- << LL_ENDL;
- // returning should disconnect both connections
- return value;
-}
-
-LLSD errorException(const LLEventWithID& result, const std::string& desc)
-{
- // If the result arrived on the error pump (pump 1), instead of
- // returning it, deliver it via exception.
- if (result.second)
+ // if the future is NOT yet ready, return timeoutResult instead
+ if (status == boost::fibers::future_status::timeout)
{
- LLTHROW(LLErrorEvent(desc, result.first));
+ LL_DEBUGS("lleventcoro") << "postAndSuspendWithTimeout(): coroutine " << listenerName
+ << " timed out after " << timeout << " seconds,"
+ << " resuming with " << timeoutResult << LL_ENDL;
+ return timeoutResult;
}
- // That way, our caller knows a simple return must be from the reply
- // pump (pump 0).
- return result.first;
-}
-
-LLSD errorLog(const LLEventWithID& result, const std::string& desc)
-{
- // If the result arrived on the error pump (pump 1), log it as a fatal
- // error.
- if (result.second)
+ else
{
- LL_ERRS("errorLog") << desc << ":" << std::endl;
- LLSDSerialize::toPrettyXML(result.first, LL_CONT);
- LL_CONT << LL_ENDL;
+ llassert_always(status == boost::fibers::future_status::ready);
+
+ // future is now ready, no more waiting
+ LLSD value(future.get());
+ LL_DEBUGS("lleventcoro") << "postAndSuspendWithTimeout(): coroutine " << listenerName
+ << " resuming with " << value << LL_ENDL;
+ // returning should disconnect the connection
+ return value;
}
- // A simple return must therefore be from the reply pump (pump 0).
- return result.first;
}
-
-} // namespace llcoro
diff --git a/indra/llcommon/lleventcoro.h b/indra/llcommon/lleventcoro.h
index 84827aab4a..c0fe8b094f 100644
--- a/indra/llcommon/lleventcoro.h
+++ b/indra/llcommon/lleventcoro.h
@@ -29,12 +29,8 @@
#if ! defined(LL_LLEVENTCORO_H)
#define LL_LLEVENTCORO_H
-#include <boost/optional.hpp>
#include <string>
-#include <utility> // std::pair
#include "llevents.h"
-#include "llerror.h"
-#include "llexception.h"
/**
* Like LLListenerOrPumpName, this is a class intended for parameter lists:
@@ -147,117 +143,29 @@ LLSD suspendUntilEventOn(const LLEventPumpOrPumpName& pump)
return postAndSuspend(LLSD(), LLEventPumpOrPumpName(), pump);
}
+/// Like postAndSuspend(), but if we wait longer than @a timeout seconds,
+/// stop waiting and return @a timeoutResult instead.
+LLSD postAndSuspendWithTimeout(const LLSD& event,
+ const LLEventPumpOrPumpName& requestPump,
+ const LLEventPumpOrPumpName& replyPump,
+ const LLSD& replyPumpNamePath,
+ F32 timeout, const LLSD& timeoutResult);
+
/// Suspend the coroutine until an event is fired on the identified pump
/// or the timeout duration has elapsed. If the timeout duration
/// elapses the specified LLSD is returned.
-LLSD suspendUntilEventOnWithTimeout(const LLEventPumpOrPumpName& suspendPumpOrName, F32 timeoutin, const LLSD &timeoutResult);
-
-} // namespace llcoro
-
-/// return type for two-pump variant of suspendUntilEventOn()
-typedef std::pair<LLSD, int> LLEventWithID;
-
-namespace llcoro
-{
-
-/**
- * This function waits for a reply on either of two specified LLEventPumps.
- * Otherwise, it closely resembles postAndSuspend(); please see the documentation
- * for that function for detailed parameter info.
- *
- * While we could have implemented the single-pump variant in terms of this
- * one, there's enough added complexity here to make it worthwhile to give the
- * single-pump variant its own straightforward implementation. Conversely,
- * though we could use preprocessor logic to generate n-pump overloads up to
- * BOOST_COROUTINE_WAIT_MAX, we don't foresee a use case. This two-pump
- * overload exists because certain event APIs are defined in terms of a reply
- * LLEventPump and an error LLEventPump.
- *
- * The LLEventWithID return value provides not only the received event, but
- * the index of the pump on which it arrived (0 or 1).
- *
- * @note
- * I'd have preferred to overload the name postAndSuspend() for both signatures.
- * But consider the following ambiguous call:
- * @code
- * postAndSuspend(LLSD(), requestPump, replyPump, "someString");
- * @endcode
- * "someString" could be converted to either LLSD (@a replyPumpNamePath for
- * the single-pump function) or LLEventOrPumpName (@a replyPump1 for two-pump
- * function).
- *
- * It seems less burdensome to write postAndSuspend2() than to write either
- * LLSD("someString") or LLEventOrPumpName("someString").
- */
-LLEventWithID postAndSuspend2(const LLSD& event,
- const LLEventPumpOrPumpName& requestPump,
- const LLEventPumpOrPumpName& replyPump0,
- const LLEventPumpOrPumpName& replyPump1,
- const LLSD& replyPump0NamePath=LLSD(),
- const LLSD& replyPump1NamePath=LLSD());
-
-/**
- * Wait for the next event on either of two specified LLEventPumps.
- */
inline
-LLEventWithID
-suspendUntilEventOn(const LLEventPumpOrPumpName& pump0, const LLEventPumpOrPumpName& pump1)
+LLSD suspendUntilEventOnWithTimeout(const LLEventPumpOrPumpName& suspendPumpOrName,
+ F32 timeoutin, const LLSD &timeoutResult)
{
- // This is now a convenience wrapper for postAndSuspend2().
- return postAndSuspend2(LLSD(), LLEventPumpOrPumpName(), pump0, pump1);
+ return postAndSuspendWithTimeout(LLSD(), // event
+ LLEventPumpOrPumpName(), // requestPump
+ suspendPumpOrName, // replyPump
+ LLSD(), // replyPumpNamePath
+ timeoutin,
+ timeoutResult);
}
-/**
- * Helper for the two-pump variant of suspendUntilEventOn(), e.g.:
- *
- * @code
- * LLSD reply = errorException(suspendUntilEventOn(replyPump, errorPump),
- * "error response from login.cgi");
- * @endcode
- *
- * Examines an LLEventWithID, assuming that the second pump (pump 1) is
- * listening for an error indication. If the incoming data arrived on pump 1,
- * throw an LLErrorEvent exception. If the incoming data arrived on pump 0,
- * just return it. Since a normal return can only be from pump 0, we no longer
- * need the LLEventWithID's discriminator int; we can just return the LLSD.
- *
- * @note I'm not worried about introducing the (fairly generic) name
- * errorException() into global namespace, because how many other overloads of
- * the same name are going to accept an LLEventWithID parameter?
- */
-LLSD errorException(const LLEventWithID& result, const std::string& desc);
-
-} // namespace llcoro
-
-/**
- * Exception thrown by errorException(). We don't call this LLEventError
- * because it's not an error in event processing: rather, this exception
- * announces an event that bears error information (for some other API).
- */
-class LL_COMMON_API LLErrorEvent: public LLException
-{
-public:
- LLErrorEvent(const std::string& what, const LLSD& data):
- LLException(what),
- mData(data)
- {}
- virtual ~LLErrorEvent() throw() {}
-
- LLSD getData() const { return mData; }
-
-private:
- LLSD mData;
-};
-
-namespace llcoro
-{
-
-/**
- * Like errorException(), save that this trips a fatal error using LL_ERRS
- * rather than throwing an exception.
- */
-LL_COMMON_API LLSD errorLog(const LLEventWithID& result, const std::string& desc);
-
} // namespace llcoro
/**
@@ -304,84 +212,4 @@ private:
LLEventStream mPump;
};
-/**
- * Other event APIs require the names of two different LLEventPumps: one for
- * success response, the other for error response. Extend LLCoroEventPump
- * for the two-pump use case.
- */
-class LL_COMMON_API LLCoroEventPumps
-{
-public:
- LLCoroEventPumps(const std::string& name="coro",
- const std::string& suff0="Reply",
- const std::string& suff1="Error"):
- mPump0(name + suff0, true), // allow tweaking the pump instance name
- mPump1(name + suff1, true)
- {}
- /// request pump 0's name
- std::string getName0() const { return mPump0.getName(); }
- /// request pump 1's name
- std::string getName1() const { return mPump1.getName(); }
- /// request both names
- std::pair<std::string, std::string> getNames() const
- {
- return std::pair<std::string, std::string>(mPump0.getName(), mPump1.getName());
- }
-
- /// request pump 0
- LLEventPump& getPump0() { return mPump0; }
- /// request pump 1
- LLEventPump& getPump1() { return mPump1; }
-
- /// suspendUntilEventOn(either of our two LLEventPumps)
- LLEventWithID suspend()
- {
- return llcoro::suspendUntilEventOn(mPump0, mPump1);
- }
-
- /// errorException(suspend())
- LLSD suspendWithException()
- {
- return llcoro::errorException(suspend(), std::string("Error event on ") + getName1());
- }
-
- /// errorLog(suspend())
- LLSD suspendWithLog()
- {
- return llcoro::errorLog(suspend(), std::string("Error event on ") + getName1());
- }
-
- LLEventWithID postAndSuspend(const LLSD& event,
- const LLEventPumpOrPumpName& requestPump,
- const LLSD& replyPump0NamePath=LLSD(),
- const LLSD& replyPump1NamePath=LLSD())
- {
- return llcoro::postAndSuspend2(event, requestPump, mPump0, mPump1,
- replyPump0NamePath, replyPump1NamePath);
- }
-
- LLSD postAndSuspendWithException(const LLSD& event,
- const LLEventPumpOrPumpName& requestPump,
- const LLSD& replyPump0NamePath=LLSD(),
- const LLSD& replyPump1NamePath=LLSD())
- {
- return llcoro::errorException(postAndSuspend(event, requestPump,
- replyPump0NamePath, replyPump1NamePath),
- std::string("Error event on ") + getName1());
- }
-
- LLSD postAndSuspendWithLog(const LLSD& event,
- const LLEventPumpOrPumpName& requestPump,
- const LLSD& replyPump0NamePath=LLSD(),
- const LLSD& replyPump1NamePath=LLSD())
- {
- return llcoro::errorLog(postAndSuspend(event, requestPump,
- replyPump0NamePath, replyPump1NamePath),
- std::string("Error event on ") + getName1());
- }
-
-private:
- LLEventStream mPump0, mPump1;
-};
-
#endif /* ! defined(LL_LLEVENTCORO_H) */
diff --git a/indra/llcommon/lleventfilter.cpp b/indra/llcommon/lleventfilter.cpp
index 9fb18dc67d..4cded7f88e 100644
--- a/indra/llcommon/lleventfilter.cpp
+++ b/indra/llcommon/lleventfilter.cpp
@@ -37,6 +37,9 @@
// other Linden headers
#include "llerror.h" // LL_ERRS
#include "llsdutil.h" // llsd_matches()
+#include "stringize.h"
+#include "lleventtimer.h"
+#include "lldate.h"
/*****************************************************************************
* LLEventFilter
@@ -182,6 +185,27 @@ bool LLEventTimeout::countdownElapsed() const
return mTimer.hasExpired();
}
+LLEventTimer* LLEventTimeout::post_every(F32 period, const std::string& pump, const LLSD& data)
+{
+ return LLEventTimer::run_every(
+ period,
+ [pump, data](){ LLEventPumps::instance().obtain(pump).post(data); });
+}
+
+LLEventTimer* LLEventTimeout::post_at(const LLDate& time, const std::string& pump, const LLSD& data)
+{
+ return LLEventTimer::run_at(
+ time,
+ [pump, data](){ LLEventPumps::instance().obtain(pump).post(data); });
+}
+
+LLEventTimer* LLEventTimeout::post_after(F32 interval, const std::string& pump, const LLSD& data)
+{
+ return LLEventTimer::run_after(
+ interval,
+ [pump, data](){ LLEventPumps::instance().obtain(pump).post(data); });
+}
+
/*****************************************************************************
* LLEventBatch
*****************************************************************************/
@@ -409,3 +433,61 @@ void LLEventBatchThrottle::setSize(std::size_t size)
flush();
}
}
+
+/*****************************************************************************
+* LLEventLogProxy
+*****************************************************************************/
+LLEventLogProxy::LLEventLogProxy(LLEventPump& source, const std::string& name, bool tweak):
+ // note: we are NOT using the constructor that implicitly connects!
+ LLEventFilter(name, tweak),
+ // instead we simply capture a reference to the subject LLEventPump
+ mPump(source)
+{
+}
+
+bool LLEventLogProxy::post(const LLSD& event) /* override */
+{
+ auto counter = mCounter++;
+ auto eventplus = event;
+ if (eventplus.type() == LLSD::TypeMap)
+ {
+ eventplus["_cnt"] = counter;
+ }
+ std::string hdr{STRINGIZE(getName() << ": post " << counter)};
+ LL_INFOS("LogProxy") << hdr << ": " << event << LL_ENDL;
+ bool result = mPump.post(eventplus);
+ LL_INFOS("LogProxy") << hdr << " => " << result << LL_ENDL;
+ return result;
+}
+
+LLBoundListener LLEventLogProxy::listen_impl(const std::string& name,
+ const LLEventListener& target,
+ const NameList& after,
+ const NameList& before)
+{
+ LL_DEBUGS("LogProxy") << "LLEventLogProxy('" << getName() << "').listen('"
+ << name << "')" << LL_ENDL;
+ return mPump.listen(name,
+ [this, name, target](const LLSD& event)->bool
+ { return listener(name, target, event); },
+ after,
+ before);
+}
+
+bool LLEventLogProxy::listener(const std::string& name,
+ const LLEventListener& target,
+ const LLSD& event) const
+{
+ auto eventminus = event;
+ std::string counter{"**"};
+ if (eventminus.has("_cnt"))
+ {
+ counter = stringize(eventminus["_cnt"].asInteger());
+ eventminus.erase("_cnt");
+ }
+ std::string hdr{STRINGIZE(getName() << " to " << name << " " << counter)};
+ LL_INFOS("LogProxy") << hdr << ": " << eventminus << LL_ENDL;
+ bool result = target(eventminus);
+ LL_INFOS("LogProxy") << hdr << " => " << result << LL_ENDL;
+ return result;
+}
diff --git a/indra/llcommon/lleventfilter.h b/indra/llcommon/lleventfilter.h
index ff8fc9bc7f..48c2570732 100644
--- a/indra/llcommon/lleventfilter.h
+++ b/indra/llcommon/lleventfilter.h
@@ -32,8 +32,12 @@
#include "llevents.h"
#include "stdtypes.h"
#include "lltimer.h"
+#include "llsdutil.h"
#include <boost/function.hpp>
+class LLEventTimer;
+class LLDate;
+
/**
* Generic base class
*/
@@ -210,6 +214,19 @@ public:
LLEventTimeout();
LLEventTimeout(LLEventPump& source);
+ /// using LLEventTimeout as namespace for free functions
+ /// Post event to specified LLEventPump every period seconds. Delete
+ /// returned LLEventTimer* to cancel.
+ static LLEventTimer* post_every(F32 period, const std::string& pump, const LLSD& data);
+ /// Post event to specified LLEventPump at specified future time. Call
+ /// LLEventTimer::getInstance(returned pointer) to check whether it's still
+ /// pending; if so, delete the pointer to cancel.
+ static LLEventTimer* post_at(const LLDate& time, const std::string& pump, const LLSD& data);
+ /// Post event to specified LLEventPump after specified interval. Call
+ /// LLEventTimer::getInstance(returned pointer) to check whether it's still
+ /// pending; if so, delete the pointer to cancel.
+ static LLEventTimer* post_after(F32 interval, const std::string& pump, const LLSD& data);
+
protected:
virtual void setCountdown(F32 seconds);
virtual bool countdownElapsed() const;
@@ -376,4 +393,149 @@ private:
std::size_t mBatchSize;
};
+/**
+ * LLStoreListener self-registers on the LLEventPump of interest, and
+ * unregisters on destruction. As long as it exists, a particular element is
+ * extracted from every event that comes through the upstream LLEventPump and
+ * stored into the target variable.
+ *
+ * This is implemented as a subclass of LLEventFilter, though strictly
+ * speaking it isn't really a "filter" at all: it never passes incoming events
+ * to its own listeners, if any.
+ *
+ * TBD: A variant based on output iterators that stores and then increments
+ * the iterator. Useful with boost::coroutine2!
+ */
+template <typename T>
+class LLStoreListener: public LLEventFilter
+{
+public:
+ // pass target and optional path to element
+ LLStoreListener(T& target, const LLSD& path=LLSD(), bool consume=false):
+ LLEventFilter("store"),
+ mTarget(target),
+ mPath(path),
+ mConsume(consume)
+ {}
+ // construct and connect
+ LLStoreListener(LLEventPump& source, T& target, const LLSD& path=LLSD(), bool consume=false):
+ LLEventFilter(source, "store"),
+ mTarget(target),
+ mPath(path),
+ mConsume(consume)
+ {}
+
+ // Calling post() with an LLSD event extracts the element indicated by
+ // path, then stores it to mTarget.
+ virtual bool post(const LLSD& event)
+ {
+ // Extract the element specified by 'mPath' from 'event'. To perform a
+ // generic type-appropriate store through mTarget, construct an
+ // LLSDParam<T> and store that, thus engaging LLSDParam's custom
+ // conversions.
+ mTarget = LLSDParam<T>(llsd::drill(event, mPath));
+ return mConsume;
+ }
+
+private:
+ T& mTarget;
+ const LLSD mPath;
+ const bool mConsume;
+};
+
+/*****************************************************************************
+* LLEventLogProxy
+*****************************************************************************/
+/**
+ * LLEventLogProxy is a little different than the other LLEventFilter
+ * subclasses declared in this header file, in that it completely wraps the
+ * passed LLEventPump (both input and output) instead of simply processing its
+ * output. Of course, if someone directly posts to the wrapped LLEventPump by
+ * looking up its string name in LLEventPumps, LLEventLogProxy can't intercept
+ * that post() call. But as long as consuming code is willing to access the
+ * LLEventLogProxy instance instead of the wrapped LLEventPump, all event data
+ * both post()ed and received is logged.
+ *
+ * The proxy role means that LLEventLogProxy intercepts more of LLEventPump's
+ * API than a typical LLEventFilter subclass.
+ */
+class LLEventLogProxy: public LLEventFilter
+{
+ typedef LLEventFilter super;
+public:
+ /**
+ * Construct LLEventLogProxy, wrapping the specified LLEventPump.
+ * Unlike a typical LLEventFilter subclass, the name parameter is @emph
+ * not optional because typically you want LLEventLogProxy to completely
+ * replace the wrapped LLEventPump. So you give the subject LLEventPump
+ * some other name and give the LLEventLogProxy the name that would have
+ * been used for the subject LLEventPump.
+ */
+ LLEventLogProxy(LLEventPump& source, const std::string& name, bool tweak=false);
+
+ /// register a new listener
+ LLBoundListener listen_impl(const std::string& name, const LLEventListener& target,
+ const NameList& after, const NameList& before);
+
+ /// Post an event to all listeners
+ virtual bool post(const LLSD& event) /* override */;
+
+private:
+ /// This method intercepts each call to any target listener. We pass it
+ /// the listener name and the caller's intended target listener plus the
+ /// posted LLSD event.
+ bool listener(const std::string& name,
+ const LLEventListener& target,
+ const LLSD& event) const;
+
+ LLEventPump& mPump;
+ LLSD::Integer mCounter{0};
+};
+
+/**
+ * LLEventPumpHolder<T> is a helper for LLEventLogProxyFor<T>. It simply
+ * stores an instance of T, presumably a subclass of LLEventPump. We derive
+ * LLEventLogProxyFor<T> from LLEventPumpHolder<T>, ensuring that
+ * LLEventPumpHolder's contained mWrappedPump is fully constructed before
+ * passing it to LLEventLogProxyFor's LLEventLogProxy base class constructor.
+ * But since LLEventPumpHolder<T> presents none of the LLEventPump API,
+ * LLEventLogProxyFor<T> inherits its methods unambiguously from
+ * LLEventLogProxy.
+ */
+template <class T>
+class LLEventPumpHolder
+{
+protected:
+ LLEventPumpHolder(const std::string& name, bool tweak=false):
+ mWrappedPump(name, tweak)
+ {}
+ T mWrappedPump;
+};
+
+/**
+ * LLEventLogProxyFor<T> is a wrapper around any of the LLEventPump subclasses.
+ * Instantiating an LLEventLogProxy<T> instantiates an internal T. Otherwise
+ * it behaves like LLEventLogProxy.
+ */
+template <class T>
+class LLEventLogProxyFor: private LLEventPumpHolder<T>, public LLEventLogProxy
+{
+ // We derive privately from LLEventPumpHolder because it's an
+ // implementation detail of LLEventLogProxyFor. The only reason it's a
+ // base class at all is to guarantee that it's constructed first so we can
+ // pass it to our LLEventLogProxy base class constructor.
+ typedef LLEventPumpHolder<T> holder;
+ typedef LLEventLogProxy super;
+
+public:
+ LLEventLogProxyFor(const std::string& name, bool tweak=false):
+ // our wrapped LLEventPump subclass instance gets a name suffix
+ // because that's not the LLEventPump we want consumers to obtain when
+ // they ask LLEventPumps for this name
+ holder(name + "-", tweak),
+ // it's our LLEventLogProxy that gets the passed name
+ super(holder::mWrappedPump, name, tweak)
+ {}
+};
+
#endif /* ! defined(LL_LLEVENTFILTER_H) */
diff --git a/indra/llcommon/llevents.cpp b/indra/llcommon/llevents.cpp
index eedd8c92b5..64fb985951 100644
--- a/indra/llcommon/llevents.cpp
+++ b/indra/llcommon/llevents.cpp
@@ -45,6 +45,7 @@
#include <cctype>
// external library headers
#include <boost/range/iterator_range.hpp>
+#include <boost/make_shared.hpp>
#if LL_WINDOWS
#pragma warning (push)
#pragma warning (disable : 4701) // compiler thinks might use uninitialized var, but no
@@ -63,51 +64,23 @@
#endif
/*****************************************************************************
-* queue_names: specify LLEventPump names that should be instantiated as
-* LLEventQueue
-*****************************************************************************/
-/**
- * At present, we recognize particular requested LLEventPump names as needing
- * LLEventQueues. Later on we'll migrate this information to an external
- * configuration file.
- */
-const char* queue_names[] =
-{
- "placeholder - replace with first real name string"
-};
-
-/*****************************************************************************
-* If there's a "mainloop" pump, listen on that to flush all LLEventQueues
+* LLEventPumps
*****************************************************************************/
-struct RegisterFlush : public LLEventTrackable
-{
- RegisterFlush():
- pumps(LLEventPumps::instance())
+LLEventPumps::LLEventPumps():
+ mFactories
{
- pumps.obtain("mainloop").listen("flushLLEventQueues", boost::bind(&RegisterFlush::flush, this, _1));
- }
- bool flush(const LLSD&)
+ { "LLEventStream", [](const std::string& name, bool tweak)
+ { return new LLEventStream(name, tweak); } },
+ { "LLEventMailDrop", [](const std::string& name, bool tweak)
+ { return new LLEventMailDrop(name, tweak); } }
+ },
+ mTypes
{
- pumps.flush();
- return false;
+ // LLEventStream is the default for obtain(), so even if somebody DOES
+ // call obtain("placeholder"), this sample entry won't break anything.
+ { "placeholder", "LLEventStream" }
}
- ~RegisterFlush()
- {
- // LLEventTrackable handles stopListening for us.
- }
- LLEventPumps& pumps;
-};
-static RegisterFlush registerFlush;
-
-/*****************************************************************************
-* LLEventPumps
-*****************************************************************************/
-LLEventPumps::LLEventPumps():
- // Until we migrate this information to an external config file,
- // initialize mQueueNames from the static queue_names array.
- mQueueNames(boost::begin(queue_names), boost::end(queue_names))
-{
-}
+{}
LLEventPump& LLEventPumps::obtain(const std::string& name)
{
@@ -118,14 +91,31 @@ LLEventPump& LLEventPumps::obtain(const std::string& name)
// name.
return *found->second;
}
- // Here we must instantiate an LLEventPump subclass.
- LLEventPump* newInstance;
- // Should this name be an LLEventQueue?
- PumpNames::const_iterator nfound = mQueueNames.find(name);
- if (nfound != mQueueNames.end())
- newInstance = new LLEventQueue(name);
- else
- newInstance = new LLEventStream(name);
+
+ // Here we must instantiate an LLEventPump subclass. Is there a
+ // preregistered class name override for this specific instance name?
+ auto nfound = mTypes.find(name);
+ std::string type;
+ if (nfound != mTypes.end())
+ {
+ type = nfound->second;
+ }
+ // pass tweak=false: we already know there's no existing instance with
+ // this name
+ return make(name, false, type);
+}
+
+LLEventPump& LLEventPumps::make(const std::string& name, bool tweak,
+ const std::string& type)
+{
+ // find the relevant factory for this (or default) type
+ auto found = mFactories.find(type.empty()? "LLEventStream" : type);
+ if (found == mFactories.end())
+ {
+ // Passing an unrecognized type name is a no-no
+ LLTHROW(BadType(type));
+ }
+ auto newInstance = (found->second)(name, tweak);
// LLEventPump's constructor implicitly registers each new instance in
// mPumpMap. But remember that we instantiated it (in mOurPumps) so we'll
// delete it later.
@@ -143,14 +133,23 @@ bool LLEventPumps::post(const std::string&name, const LLSD&message)
return (*found).second->post(message);
}
-
void LLEventPumps::flush()
{
// Flush every known LLEventPump instance. Leave it up to each instance to
// decide what to do with the flush() call.
- for (PumpMap::iterator pmi = mPumpMap.begin(), pmend = mPumpMap.end(); pmi != pmend; ++pmi)
+ for (PumpMap::value_type& pair : mPumpMap)
+ {
+ pair.second->flush();
+ }
+}
+
+void LLEventPumps::clear()
+{
+ // Clear every known LLEventPump instance. Leave it up to each instance to
+ // decide what to do with the clear() call.
+ for (PumpMap::value_type& pair : mPumpMap)
{
- pmi->second->flush();
+ pair.second->clear();
}
}
@@ -158,9 +157,9 @@ void LLEventPumps::reset()
{
// Reset every known LLEventPump instance. Leave it up to each instance to
// decide what to do with the reset() call.
- for (PumpMap::iterator pmi = mPumpMap.begin(), pmend = mPumpMap.end(); pmi != pmend; ++pmi)
+ for (PumpMap::value_type& pair : mPumpMap)
{
- pmi->second->reset();
+ pair.second->reset();
}
}
@@ -267,6 +266,9 @@ LLEventPumps::~LLEventPumps()
{
delete *mOurPumps.begin();
}
+ // Reset every remaining registered LLEventPump subclass instance: those
+ // we DIDN'T instantiate using either make() or obtain().
+ reset();
}
/*****************************************************************************
@@ -283,7 +285,7 @@ LLEventPump::LLEventPump(const std::string& name, bool tweak):
// Register every new instance with LLEventPumps
mRegistry(LLEventPumps::instance().getHandle()),
mName(mRegistry.get()->registerNew(*this, name, tweak)),
- mSignal(new LLStandardSignal()),
+ mSignal(boost::make_shared<LLStandardSignal>()),
mEnabled(true)
{}
@@ -311,6 +313,14 @@ std::string LLEventPump::inventName(const std::string& pfx)
return STRINGIZE(pfx << suffix++);
}
+void LLEventPump::clear()
+{
+ // Destroy the original LLStandardSignal instance, replacing it with a
+ // whole new one.
+ mSignal = boost::make_shared<LLStandardSignal>();
+ mConnections.clear();
+}
+
void LLEventPump::reset()
{
mSignal.reset();
@@ -553,7 +563,7 @@ bool LLEventMailDrop::post(const LLSD& event)
// be posted to any future listeners when they attach.
mEventHistory.push_back(event);
}
-
+
return posted;
}
@@ -583,46 +593,9 @@ LLBoundListener LLEventMailDrop::listen_impl(const std::string& name,
return LLEventStream::listen_impl(name, listener, after, before);
}
-
-/*****************************************************************************
-* LLEventQueue
-*****************************************************************************/
-bool LLEventQueue::post(const LLSD& event)
-{
- if (mEnabled)
- {
- // Defer sending this event by queueing it until flush()
- mEventQueue.push_back(event);
- }
- // Unconditionally return false. We won't know until flush() whether a
- // listener claims to have handled the event -- meanwhile, don't block
- // other listeners.
- return false;
-}
-
-void LLEventQueue::flush()
+void LLEventMailDrop::discard()
{
- if(!mSignal) return;
-
- // Consider the case when a given listener on this LLEventQueue posts yet
- // another event on the same queue. If we loop over mEventQueue directly,
- // we'll end up processing all those events during the same flush() call
- // -- rather like an EventStream. Instead, copy mEventQueue and clear it,
- // so that any new events posted to this LLEventQueue during flush() will
- // be processed in the *next* flush() call.
- EventQueue queue(mEventQueue);
- mEventQueue.clear();
- // NOTE NOTE NOTE: Any new access to member data beyond this point should
- // cause us to move our LLStandardSignal object to a pimpl class along
- // with said member data. Then the local shared_ptr will preserve both.
-
- // DEV-43463: capture a local copy of mSignal. See LLEventStream::post()
- // for detailed comments.
- boost::shared_ptr<LLStandardSignal> signal(mSignal);
- for ( ; ! queue.empty(); queue.pop_front())
- {
- (*signal)(queue.front());
- }
+ mEventHistory.clear();
}
/*****************************************************************************
diff --git a/indra/llcommon/llevents.h b/indra/llcommon/llevents.h
index 62d97007ac..e380c108f4 100644
--- a/indra/llcommon/llevents.h
+++ b/indra/llcommon/llevents.h
@@ -37,6 +37,7 @@
#include <set>
#include <vector>
#include <deque>
+#include <functional>
#if LL_WINDOWS
#pragma warning (push)
#pragma warning (disable : 4263) // boost::signals2::expired_slot::what() has const mismatch
@@ -55,7 +56,6 @@
#include <boost/visit_each.hpp>
#include <boost/ref.hpp> // reference_wrapper
#include <boost/type_traits/is_pointer.hpp>
-#include <boost/function.hpp>
#include <boost/static_assert.hpp>
#include "llsd.h"
#include "llsingleton.h"
@@ -211,8 +211,7 @@ public:
/// exception if you try to call when empty
struct Empty: public LLException
{
- Empty(const std::string& what):
- LLException(std::string("LLListenerOrPumpName::Empty: ") + what) {}
+ Empty(const std::string& what): LLException("LLListenerOrPumpName::Empty: " + what) {}
};
private:
@@ -247,6 +246,30 @@ public:
*/
LLEventPump& obtain(const std::string& name);
+ /// exception potentially thrown by make()
+ struct BadType: public LLException
+ {
+ BadType(const std::string& what): LLException("BadType: " + what) {}
+ };
+
+ /**
+ * Create an LLEventPump with suggested name (optionally of specified
+ * LLEventPump subclass type). As with obtain(), LLEventPumps owns the new
+ * instance.
+ *
+ * As with LLEventPump's constructor, make() could throw
+ * LLEventPump::DupPumpName unless you pass tweak=true.
+ *
+ * As with a hand-constructed LLEventPump subclass, if you pass
+ * tweak=true, the tweaked name can be obtained by LLEventPump::getName().
+ *
+ * Pass empty type to get the default LLEventStream.
+ *
+ * If you pass an unrecognized type string, make() throws BadType.
+ */
+ LLEventPump& make(const std::string& name, bool tweak=false,
+ const std::string& type=std::string());
+
/**
* Find the named LLEventPump instance. If it exists post the message to it.
* If the pump does not exist, do nothing.
@@ -264,6 +287,11 @@ public:
void flush();
/**
+ * Disconnect listeners from all known LLEventPump instances
+ */
+ void clear();
+
+ /**
* Reset all known LLEventPump instances
* workaround for DEV-35406 crash on shutdown
*/
@@ -298,44 +326,22 @@ testable:
// destroyed.
typedef std::set<LLEventPump*> PumpSet;
PumpSet mOurPumps;
- // LLEventPump names that should be instantiated as LLEventQueue rather
- // than as LLEventStream
- typedef std::set<std::string> PumpNames;
- PumpNames mQueueNames;
+ // for make(), map string type name to LLEventPump subclass factory function
+ typedef std::map<std::string, std::function<LLEventPump*(const std::string&, bool)>> PumpFactories;
+ // Data used by make().
+ // One might think mFactories and mTypes could reasonably be static. So
+ // they could -- if not for the fact that make() or obtain() might be
+ // called before this module's static variables have been initialized.
+ // This is why we use singletons in the first place.
+ PumpFactories mFactories;
+
+ // for obtain(), map desired string instance name to string type when
+ // obtain() must create the instance
+ typedef std::map<std::string, std::string> InstanceTypes;
+ InstanceTypes mTypes;
};
/*****************************************************************************
-* details
-*****************************************************************************/
-namespace LLEventDetail
-{
- /// Any callable capable of connecting an LLEventListener to an
- /// LLStandardSignal to produce an LLBoundListener can be mapped to this
- /// signature.
- typedef boost::function<LLBoundListener(const LLEventListener&)> ConnectFunc;
-
- /// overload of visit_and_connect() when we have a string identifier available
- template <typename LISTENER>
- LLBoundListener visit_and_connect(const std::string& name,
- const LISTENER& listener,
- const ConnectFunc& connect_func);
- /**
- * Utility template function to use Visitor appropriately
- *
- * @param listener Callable to connect, typically a boost::bind()
- * expression. This will be visited by Visitor using boost::visit_each().
- * @param connect_func Callable that will connect() @a listener to an
- * LLStandardSignal, returning LLBoundListener.
- */
- template <typename LISTENER>
- LLBoundListener visit_and_connect(const LISTENER& listener,
- const ConnectFunc& connect_func)
- {
- return visit_and_connect("", listener, connect_func);
- }
-} // namespace LLEventDetail
-
-/*****************************************************************************
* LLEventTrackable
*****************************************************************************/
/**
@@ -369,11 +375,6 @@ namespace LLEventDetail
* instance, it attempts to dereference the <tt>Foo*</tt> pointer that was
* <tt>delete</tt>d but not zeroed.)
* - Undefined behavior results.
- * If you suspect you may encounter any such scenario, you're better off
- * managing the lifespan of your object with <tt>boost::shared_ptr</tt>.
- * Passing <tt>LLEventPump::listen()</tt> a <tt>boost::bind()</tt> expression
- * involving a <tt>boost::weak_ptr<Foo></tt> is recognized specially, engaging
- * thread-safe Boost.Signals2 machinery.
*/
typedef boost::signals2::trackable LLEventTrackable;
@@ -382,7 +383,7 @@ typedef boost::signals2::trackable LLEventTrackable;
*****************************************************************************/
/**
* LLEventPump is the base class interface through which we access the
- * concrete subclasses LLEventStream and LLEventQueue.
+ * concrete subclasses such as LLEventStream.
*
* @NOTE
* LLEventPump derives from LLEventTrackable so that when you "chain"
@@ -403,8 +404,7 @@ public:
*/
struct DupPumpName: public LLException
{
- DupPumpName(const std::string& what):
- LLException(std::string("DupPumpName: ") + what) {}
+ DupPumpName(const std::string& what): LLException("DupPumpName: " + what) {}
};
/**
@@ -440,9 +440,7 @@ public:
*/
struct DupListenerName: public ListenError
{
- DupListenerName(const std::string& what):
- ListenError(std::string("DupListenerName: ") + what)
- {}
+ DupListenerName(const std::string& what): ListenError("DupListenerName: " + what) {}
};
/**
* exception thrown by listen(). The order dependencies specified for your
@@ -454,7 +452,7 @@ public:
*/
struct Cycle: public ListenError
{
- Cycle(const std::string& what): ListenError(std::string("Cycle: ") + what) {}
+ Cycle(const std::string& what): ListenError("Cycle: " + what) {}
};
/**
* exception thrown by listen(). This one means that your new listener
@@ -475,7 +473,7 @@ public:
*/
struct OrderChange: public ListenError
{
- OrderChange(const std::string& what): ListenError(std::string("OrderChange: ") + what) {}
+ OrderChange(const std::string& what): ListenError("OrderChange: " + what) {}
};
/// used by listen()
@@ -512,44 +510,13 @@ public:
* the result be assigned to a LLTempBoundListener or the listener is
* manually disconnected when no longer needed since there will be no
* way to later find and disconnect this listener manually.
- *
- * If (as is typical) you pass a <tt>boost::bind()</tt> expression as @a
- * listener, listen() will inspect the components of that expression. If a
- * bound object matches any of several cases, the connection will
- * automatically be disconnected when that object is destroyed.
- *
- * * You bind a <tt>boost::weak_ptr</tt>.
- * * Binding a <tt>boost::shared_ptr</tt> that way would ensure that the
- * referenced object would @em never be destroyed, since the @c
- * shared_ptr stored in the LLEventPump would remain an outstanding
- * reference. Use the weaken() function to convert your @c shared_ptr to
- * @c weak_ptr. Because this is easy to forget, binding a @c shared_ptr
- * will produce a compile error (@c BOOST_STATIC_ASSERT failure).
- * * You bind a simple pointer or reference to an object derived from
- * <tt>boost::enable_shared_from_this</tt>. (UNDER CONSTRUCTION)
- * * You bind a simple pointer or reference to an object derived from
- * LLEventTrackable. Unlike the cases described above, though, this is
- * vulnerable to a couple of cross-thread race conditions, as described
- * in the LLEventTrackable documentation.
*/
- template <typename LISTENER>
- LLBoundListener listen(const std::string& name, const LISTENER& listener,
+ LLBoundListener listen(const std::string& name,
+ const LLEventListener& listener,
const NameList& after=NameList(),
const NameList& before=NameList())
{
- // Examine listener, using our listen_impl() method to make the
- // actual connection.
- // This is why listen() is a template. Conversion from boost::bind()
- // to LLEventListener performs type erasure, so it's important to look
- // at the boost::bind object itself before that happens.
- return LLEventDetail::visit_and_connect(name,
- listener,
- boost::bind(&LLEventPump::listen_invoke,
- this,
- name,
- _1,
- after,
- before));
+ return listen_impl(name, listener, after, before);
}
/// Get the LLBoundListener associated with the passed name (dummy
@@ -587,19 +554,12 @@ public:
private:
friend class LLEventPumps;
-
+ virtual void clear();
virtual void reset();
private:
- LLBoundListener listen_invoke(const std::string& name, const LLEventListener& listener,
- const NameList& after,
- const NameList& before)
- {
- return this->listen_impl(name, listener, after, before);
- }
-
// must precede mName; see LLEventPump::LLEventPump()
LLHandle<LLEventPumps> mRegistry;
@@ -663,11 +623,10 @@ public:
* event *must* eventually reach a listener that will consume it, else the
* queue will grow to arbitrary length.
*
- * @NOTE: When using an LLEventMailDrop (or LLEventQueue) with a LLEventTimeout or
+ * @NOTE: When using an LLEventMailDrop with an LLEventTimeout or
* LLEventFilter attaching the filter downstream, using Timeout's constructor will
* cause the MailDrop to discharge any of its stored events. The timeout should
* instead be connected upstream using its listen() method.
- * See llcoro::suspendUntilEventOnWithTimeout() for an example.
*/
class LL_COMMON_API LLEventMailDrop : public LLEventStream
{
@@ -679,7 +638,8 @@ public:
virtual bool post(const LLSD& event) override;
/// Remove any history stored in the mail drop.
- virtual void flush() override { mEventHistory.clear(); LLEventStream::flush(); };
+ void discard();
+
protected:
virtual LLBoundListener listen_impl(const std::string& name, const LLEventListener&,
const NameList& after,
@@ -691,30 +651,6 @@ private:
};
/*****************************************************************************
-* LLEventQueue
-*****************************************************************************/
-/**
- * LLEventQueue is a LLEventPump whose post() method defers calling registered
- * listeners until flush() is called.
- */
-class LL_COMMON_API LLEventQueue: public LLEventPump
-{
-public:
- LLEventQueue(const std::string& name, bool tweak=false): LLEventPump(name, tweak) {}
- virtual ~LLEventQueue() {}
-
- /// Post an event to all listeners
- virtual bool post(const LLSD& event);
-
- /// flush queued events
- virtual void flush();
-
-private:
- typedef std::deque<LLSD> EventQueue;
- EventQueue mEventQueue;
-};
-
-/*****************************************************************************
* LLReqID
*****************************************************************************/
/**
@@ -809,329 +745,6 @@ private:
LL_COMMON_API bool sendReply(const LLSD& reply, const LLSD& request,
const std::string& replyKey="reply");
-/**
- * Base class for LLListenerWrapper. See visit_and_connect() and llwrap(). We
- * provide virtual @c accept_xxx() methods, customization points allowing a
- * subclass access to certain data visible at LLEventPump::listen() time.
- * Example subclass usage:
- *
- * @code
- * myEventPump.listen("somename",
- * llwrap<MyListenerWrapper>(boost::bind(&MyClass::method, instance, _1)));
- * @endcode
- *
- * Because of the anticipated usage (note the anonymous temporary
- * MyListenerWrapper instance in the example above), the @c accept_xxx()
- * methods must be @c const.
- */
-class LL_COMMON_API LLListenerWrapperBase
-{
-public:
- /// New instance. The accept_xxx() machinery makes it important to use
- /// shared_ptrs for our data. Many copies of this object are made before
- /// the instance that actually ends up in the signal, yet accept_xxx()
- /// will later be called on the @em original instance. All copies of the
- /// same original instance must share the same data.
- LLListenerWrapperBase():
- mName(new std::string),
- mConnection(new LLBoundListener)
- {
- }
-
- /// Copy constructor. Copy shared_ptrs to original instance data.
- LLListenerWrapperBase(const LLListenerWrapperBase& that):
- mName(that.mName),
- mConnection(that.mConnection)
- {
- }
- virtual ~LLListenerWrapperBase() {}
-
- /// Ask LLEventPump::listen() for the listener name
- virtual void accept_name(const std::string& name) const
- {
- *mName = name;
- }
-
- /// Ask LLEventPump::listen() for the new connection
- virtual void accept_connection(const LLBoundListener& connection) const
- {
- *mConnection = connection;
- }
-
-protected:
- /// Listener name.
- boost::shared_ptr<std::string> mName;
- /// Connection.
- boost::shared_ptr<LLBoundListener> mConnection;
-};
-
-/*****************************************************************************
-* Underpinnings
-*****************************************************************************/
-/**
- * We originally provided a suite of overloaded
- * LLEventTrackable::listenTo(LLEventPump&, ...) methods that would call
- * LLEventPump::listen(...) and then pass the returned LLBoundListener to
- * LLEventTrackable::track(). This was workable but error-prone: the coder
- * must remember to call listenTo() rather than the more straightforward
- * listen() method.
- *
- * Now we publish only the single canonical listen() method, so there's a
- * uniform mechanism. Having a single way to do this is good, in that there's
- * no question in the coder's mind which of several alternatives to choose.
- *
- * To support automatic connection management, we use boost::visit_each
- * (http://www.boost.org/doc/libs/1_37_0/doc/html/boost/visit_each.html) to
- * inspect each argument of a boost::bind expression. (Although the visit_each
- * mechanism was first introduced with the original Boost.Signals library, it
- * was only later documented.)
- *
- * Cases:
- * * At least one of the function's arguments is a boost::weak_ptr<T>. Pass
- * the corresponding shared_ptr to slot_type::track(). Ideally that would be
- * the object whose method we want to call, but in fact we do the same for
- * any weak_ptr we might find among the bound arguments. If we're passing
- * our bound method a weak_ptr to some object, wouldn't the destruction of
- * that object invalidate the call? So we disconnect automatically when any
- * such object is destroyed. This is the mechanism preferred by boost::
- * signals2.
- * * One of the functions's arguments is a boost::shared_ptr<T>. This produces
- * a compile error: the bound copy of the shared_ptr stored in the
- * boost_bind object stored in the signal object would make the referenced
- * T object immortal. We provide a weaken() function. Pass
- * weaken(your_shared_ptr) instead. (We can inspect, but not modify, the
- * boost::bind object. Otherwise we'd replace the shared_ptr with weak_ptr
- * implicitly and just proceed.)
- * * One of the function's arguments is a plain pointer/reference to an object
- * derived from boost::enable_shared_from_this. We assume that this object
- * is managed using boost::shared_ptr, so we implicitly extract a shared_ptr
- * and track that. (UNDER CONSTRUCTION)
- * * One of the function's arguments is derived from LLEventTrackable. Pass
- * the LLBoundListener to its LLEventTrackable::track(). This is vulnerable
- * to a couple different race conditions, as described in LLEventTrackable
- * documentation. (NOTE: Now that LLEventTrackable is a typedef for
- * boost::signals2::trackable, the Signals2 library handles this itself, so
- * our visitor needs no special logic for this case.)
- * * Any other argument type is irrelevant to automatic connection management.
- */
-
-namespace LLEventDetail
-{
- template <typename F>
- const F& unwrap(const F& f) { return f; }
-
- template <typename F>
- const F& unwrap(const boost::reference_wrapper<F>& f) { return f.get(); }
-
- // Most of the following is lifted from the Boost.Signals use of
- // visit_each.
- template<bool Cond> struct truth {};
-
- /**
- * boost::visit_each() Visitor, used on a template argument <tt>const F&
- * f</tt> as follows (see visit_and_connect()):
- * @code
- * LLEventListener listener(f);
- * Visitor visitor(listener); // bind listener so it can track() shared_ptrs
- * using boost::visit_each; // allow unqualified visit_each() call for ADL
- * visit_each(visitor, unwrap(f));
- * @endcode
- */
- class Visitor
- {
- public:
- /**
- * Visitor binds a reference to LLEventListener so we can track() any
- * shared_ptrs we find in the argument list.
- */
- Visitor(LLEventListener& listener):
- mListener(listener)
- {
- }
-
- /**
- * boost::visit_each() calls this method for each component of a
- * boost::bind() expression.
- */
- template <typename T>
- void operator()(const T& t) const
- {
- decode(t, 0);
- }
-
- private:
- // decode() decides between a reference wrapper and anything else
- // boost::ref() variant
- template<typename T>
- void decode(const boost::reference_wrapper<T>& t, int) const
- {
-// add_if_trackable(t.get_pointer());
- }
-
- // decode() anything else
- template<typename T>
- void decode(const T& t, long) const
- {
- typedef truth<(boost::is_pointer<T>::value)> is_a_pointer;
- maybe_get_pointer(t, is_a_pointer());
- }
-
- // maybe_get_pointer() decides between a pointer and a non-pointer
- // plain pointer variant
- template<typename T>
- void maybe_get_pointer(const T& t, truth<true>) const
- {
-// add_if_trackable(t);
- }
-
- // shared_ptr variant
- template<typename T>
- void maybe_get_pointer(const boost::shared_ptr<T>& t, truth<false>) const
- {
- // If we have a shared_ptr to this object, it doesn't matter
- // whether the object is derived from LLEventTrackable, so no
- // further analysis of T is needed.
-// mListener.track(t);
-
- // Make this case illegal. Passing a bound shared_ptr to
- // slot_type::track() is useless, since the bound shared_ptr will
- // keep the object alive anyway! Force the coder to cast to weak_ptr.
-
- // Trivial as it is, make the BOOST_STATIC_ASSERT() condition
- // dependent on template param so the macro is only evaluated if
- // this method is in fact instantiated, as described here:
- // http://www.boost.org/doc/libs/1_34_1/doc/html/boost_staticassert.html
-
- // ATTENTION: Don't bind a shared_ptr<anything> using
- // LLEventPump::listen(boost::bind()). Doing so captures a copy of
- // the shared_ptr, making the referenced object effectively
- // immortal. Use the weaken() function, e.g.:
- // somepump.listen(boost::bind(...weaken(my_shared_ptr)...));
- // This lets us automatically disconnect when the referenced
- // object is destroyed.
- BOOST_STATIC_ASSERT(sizeof(T) == 0);
- }
-
- // weak_ptr variant
- template<typename T>
- void maybe_get_pointer(const boost::weak_ptr<T>& t, truth<false>) const
- {
- // If we have a weak_ptr to this object, it doesn't matter
- // whether the object is derived from LLEventTrackable, so no
- // further analysis of T is needed.
- mListener.track(t);
-// std::cout << "Found weak_ptr<" << typeid(T).name() << ">!\n";
- }
-
-#if 0
- // reference to anything derived from boost::enable_shared_from_this
- template <typename T>
- inline void maybe_get_pointer(const boost::enable_shared_from_this<T>& ct,
- truth<false>) const
- {
- // Use the slot_type::track(shared_ptr) mechanism. Cast away
- // const-ness because (in our code base anyway) it's unusual
- // to find shared_ptr<const T>.
- boost::enable_shared_from_this<T>&
- t(const_cast<boost::enable_shared_from_this<T>&>(ct));
- std::cout << "Capturing shared_from_this()" << std::endl;
- boost::shared_ptr<T> sp(t.shared_from_this());
-/*==========================================================================*|
- std::cout << "Capturing weak_ptr" << std::endl;
- boost::weak_ptr<T> wp(sp);
-|*==========================================================================*/
- std::cout << "Tracking shared__ptr" << std::endl;
- mListener.track(sp);
- }
-#endif
-
- // non-pointer variant
- template<typename T>
- void maybe_get_pointer(const T& t, truth<false>) const
- {
- // Take the address of this object, because the object itself may be
- // trackable
-// add_if_trackable(boost::addressof(t));
- }
-
-/*==========================================================================*|
- // add_if_trackable() adds LLEventTrackable objects to mTrackables
- inline void add_if_trackable(const LLEventTrackable* t) const
- {
- if (t)
- {
- }
- }
-
- // pointer to anything not an LLEventTrackable subclass
- inline void add_if_trackable(const void*) const
- {
- }
-
- // pointer to free function
- // The following construct uses the preprocessor to generate
- // add_if_trackable() overloads accepting pointer-to-function taking
- // 0, 1, ..., LLEVENTS_LISTENER_ARITY parameters of arbitrary type.
-#define BOOST_PP_LOCAL_MACRO(n) \
- template <typename R \
- BOOST_PP_COMMA_IF(n) \
- BOOST_PP_ENUM_PARAMS(n, typename T)> \
- inline void \
- add_if_trackable(R (*)(BOOST_PP_ENUM_PARAMS(n, T))) const \
- { \
- }
-#define BOOST_PP_LOCAL_LIMITS (0, LLEVENTS_LISTENER_ARITY)
-#include BOOST_PP_LOCAL_ITERATE()
-#undef BOOST_PP_LOCAL_MACRO
-#undef BOOST_PP_LOCAL_LIMITS
-|*==========================================================================*/
-
- /// Bind a reference to the LLEventListener to call its track() method.
- LLEventListener& mListener;
- };
-
- /**
- * Utility template function to use Visitor appropriately
- *
- * @param raw_listener Callable to connect, typically a boost::bind()
- * expression. This will be visited by Visitor using boost::visit_each().
- * @param connect_funct Callable that will connect() @a raw_listener to an
- * LLStandardSignal, returning LLBoundListener.
- */
- template <typename LISTENER>
- LLBoundListener visit_and_connect(const std::string& name,
- const LISTENER& raw_listener,
- const ConnectFunc& connect_func)
- {
- // Capture the listener
- LLEventListener listener(raw_listener);
- // Define our Visitor, binding the listener so we can call
- // listener.track() if we discover any shared_ptr<Foo>.
- LLEventDetail::Visitor visitor(listener);
- // Allow unqualified visit_each() call for ADL
- using boost::visit_each;
- // Visit each component of a boost::bind() expression. Pass
- // 'raw_listener', our template argument, rather than 'listener' from
- // which type details have been erased. unwrap() comes from
- // Boost.Signals, in case we were passed a boost::ref().
- visit_each(visitor, LLEventDetail::unwrap(raw_listener));
- // Make the connection using passed function.
- LLBoundListener connection(connect_func(listener));
- // If the LISTENER is an LLListenerWrapperBase subclass, pass it the
- // desired information. It's important that we pass the raw_listener
- // so the compiler can make decisions based on its original type.
- const LLListenerWrapperBase* lwb =
- ll_template_cast<const LLListenerWrapperBase*>(&raw_listener);
- if (lwb)
- {
- lwb->accept_name(name);
- lwb->accept_connection(connection);
- }
- // In any case, show new connection to caller.
- return connection;
- }
-} // namespace LLEventDetail
-
// Somewhat to my surprise, passing boost::bind(...boost::weak_ptr<T>...) to
// listen() fails in Boost code trying to instantiate LLEventListener (i.e.
// LLStandardSignal::slot_type) because the boost::get_pointer() utility function isn't
@@ -1142,12 +755,4 @@ namespace boost
T* get_pointer(const weak_ptr<T>& ptr) { return shared_ptr<T>(ptr).get(); }
}
-/// Since we forbid use of listen(boost::bind(...shared_ptr<T>...)), provide an
-/// easy way to cast to the corresponding weak_ptr.
-template <typename T>
-boost::weak_ptr<T> weaken(const boost::shared_ptr<T>& ptr)
-{
- return boost::weak_ptr<T>(ptr);
-}
-
#endif /* ! defined(LL_LLEVENTS_H) */
diff --git a/indra/llcommon/lleventtimer.cpp b/indra/llcommon/lleventtimer.cpp
index 0d96e03da4..f575a7b6bf 100644
--- a/indra/llcommon/lleventtimer.cpp
+++ b/indra/llcommon/lleventtimer.cpp
@@ -57,29 +57,17 @@ LLEventTimer::~LLEventTimer()
//static
void LLEventTimer::updateClass()
{
- std::list<LLEventTimer*> completed_timers;
- for (instance_iter iter = beginInstances(); iter != endInstances(); )
+ 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;
- }
- }
}
diff --git a/indra/llcommon/lleventtimer.h b/indra/llcommon/lleventtimer.h
index dc918121e1..dbbfe0c6e6 100644
--- a/indra/llcommon/lleventtimer.h
+++ b/indra/llcommon/lleventtimer.h
@@ -40,16 +40,83 @@ public:
LLEventTimer(F32 period); // period is the amount of time between each call to tick() in seconds
LLEventTimer(const LLDate& time);
virtual ~LLEventTimer();
-
+
//function to be called at the supplied frequency
// Normally return FALSE; TRUE will delete the timer after the function returns.
virtual BOOL tick() = 0;
static void updateClass();
+ /// Schedule recurring calls to generic callable every period seconds.
+ /// Returns a pointer; if you delete it, cancels the recurring calls.
+ template <typename CALLABLE>
+ static LLEventTimer* run_every(F32 period, const CALLABLE& callable);
+
+ /// Schedule a future call to generic callable. Returns a pointer.
+ /// CAUTION: The object referenced by that pointer WILL BE DELETED once
+ /// the callback has been called! LLEventTimer::getInstance(pointer) (NOT
+ /// pointer->getInstance(pointer)!) can be used to test whether the
+ /// pointer is still valid. If it is, deleting it will cancel the
+ /// callback.
+ template <typename CALLABLE>
+ static LLEventTimer* run_at(const LLDate& time, const CALLABLE& callable);
+
+ /// Like run_at(), but after a time delta rather than at a timestamp.
+ /// Same CAUTION.
+ template <typename CALLABLE>
+ static LLEventTimer* run_after(F32 interval, const CALLABLE& callable);
+
protected:
LLTimer mEventTimer;
F32 mPeriod;
+
+private:
+ template <typename CALLABLE>
+ class Generic;
+};
+
+template <typename CALLABLE>
+class LLEventTimer::Generic: public LLEventTimer
+{
+public:
+ // making TIME generic allows engaging either LLEventTimer constructor
+ template <typename TIME>
+ Generic(const TIME& time, bool once, const CALLABLE& callable):
+ LLEventTimer(time),
+ mOnce(once),
+ mCallable(callable)
+ {}
+ BOOL tick() override
+ {
+ mCallable();
+ // true tells updateClass() to delete this instance
+ return mOnce;
+ }
+
+private:
+ bool mOnce;
+ CALLABLE mCallable;
};
+template <typename CALLABLE>
+LLEventTimer* LLEventTimer::run_every(F32 period, const CALLABLE& callable)
+{
+ // return false to schedule recurring calls
+ return new Generic<CALLABLE>(period, false, callable);
+}
+
+template <typename CALLABLE>
+LLEventTimer* LLEventTimer::run_at(const LLDate& time, const CALLABLE& callable)
+{
+ // return true for one-shot callback
+ return new Generic<CALLABLE>(time, true, callable);
+}
+
+template <typename CALLABLE>
+LLEventTimer* LLEventTimer::run_after(F32 interval, const CALLABLE& callable)
+{
+ // one-shot callback after specified interval
+ return new Generic<CALLABLE>(interval, true, callable);
+}
+
#endif //LL_EVENTTIMER_H
diff --git a/indra/llcommon/llexception.cpp b/indra/llcommon/llexception.cpp
index b32ec2c9c9..5ce8958687 100644
--- a/indra/llcommon/llexception.cpp
+++ b/indra/llcommon/llexception.cpp
@@ -18,10 +18,28 @@
#include <typeinfo>
// external library headers
#include <boost/exception/diagnostic_information.hpp>
+#include <boost/exception/error_info.hpp>
+// On Mac, got:
+// #error "Boost.Stacktrace requires `_Unwind_Backtrace` function. Define
+// `_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"
#include "llerrorcontrol.h"
+// used to attach and extract stacktrace information to/from boost::exception,
+// see https://www.boost.org/doc/libs/release/doc/html/stacktrace/getting_started.html#stacktrace.getting_started.exceptions_with_stacktrace
+// apparently the struct passed as the first template param needs no definition?
+typedef boost::error_info<struct errinfo_stacktrace_, boost::stacktrace::stacktrace>
+ errinfo_stacktrace;
+
namespace {
// used by crash_on_unhandled_exception_() and log_unhandled_exception_()
void log_unhandled_exception_(LLError::ELevel level,
@@ -53,3 +71,17 @@ void log_unhandled_exception_(const char* file, int line, const char* pretty_fun
// routinely, but we DO expect to return from this function.
log_unhandled_exception_(LLError::LEVEL_WARN, file, line, pretty_function, context);
}
+
+void annotate_exception_(boost::exception& exc)
+{
+ // https://www.boost.org/doc/libs/release/libs/exception/doc/tutorial_transporting_data.html
+ // "Adding of Arbitrary Data to Active Exception Objects"
+ // Given a boost::exception&, we can add boost::error_info items to it
+ // without knowing its leaf type.
+ // The stacktrace constructor that lets us skip a level -- and why would
+ // we always include annotate_exception_()? -- also requires a max depth.
+ // For the nullary constructor, the stacktrace class declaration itself
+ // passes static_cast<std::size_t>(-1), but that's kind of dubious.
+ // Anyway, which of us is really going to examine more than 100 frames?
+ exc << errinfo_stacktrace(boost::stacktrace::stacktrace(1, 100));
+}
diff --git a/indra/llcommon/llexception.h b/indra/llcommon/llexception.h
index dfcb7c192f..422dd8810a 100644
--- a/indra/llcommon/llexception.h
+++ b/indra/llcommon/llexception.h
@@ -67,9 +67,29 @@ struct LLContinueError: public LLException
* enriches the exception's diagnostic_information() with the source file,
* line and containing function of the LLTHROW() macro.
*/
-// Currently we implement that using BOOST_THROW_EXCEPTION(). Wrap it in
-// LLTHROW() in case we ever want to revisit that implementation decision.
-#define LLTHROW(x) BOOST_THROW_EXCEPTION(x)
+#define LLTHROW(x) \
+do { \
+ /* Capture the exception object 'x' by value. (Exceptions must */ \
+ /* be copyable.) It might seem simpler to use */ \
+ /* BOOST_THROW_EXCEPTION(annotate_exception_(x)) instead of */ \
+ /* three separate statements, but: */ \
+ /* - We want to throw 'x' with its original type, not just a */ \
+ /* reference to boost::exception. */ \
+ /* - To return x's original type, annotate_exception_() would */ \
+ /* have to be a template function. */ \
+ /* - We want annotate_exception_() to be opaque. */ \
+ /* We also might consider embedding BOOST_THROW_EXCEPTION() in */ \
+ /* our helper function, but we want the filename and line info */ \
+ /* embedded by BOOST_THROW_EXCEPTION() to be the throw point */ \
+ /* rather than always indicating the same line in */ \
+ /* llexception.cpp. */ \
+ auto exc{x}; \
+ annotate_exception_(exc); \
+ BOOST_THROW_EXCEPTION(exc); \
+ /* Use the classic 'do { ... } while (0)' macro trick to wrap */ \
+ /* our multiple statements. */ \
+} while (0)
+void annotate_exception_(boost::exception& exc);
/// Call this macro from a catch (...) clause
#define CRASH_ON_UNHANDLED_EXCEPTION(CONTEXT) \
diff --git a/indra/llcommon/llfasttimer.cpp b/indra/llcommon/llfasttimer.cpp
index 3d28cd15b0..08ea668964 100644
--- a/indra/llcommon/llfasttimer.cpp
+++ b/indra/llcommon/llfasttimer.cpp
@@ -193,27 +193,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;
- }
+ }
}
}
@@ -306,12 +305,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;
@@ -362,12 +359,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()));
diff --git a/indra/llcommon/llfasttimer.h b/indra/llcommon/llfasttimer.h
index d463fc9d65..5628a05b00 100644
--- a/indra/llcommon/llfasttimer.h
+++ b/indra/llcommon/llfasttimer.h
@@ -31,6 +31,10 @@
#include "lltrace.h"
#include "lltreeiterators.h"
+#if LL_WINDOWS
+#include <intrin.h>
+#endif
+
#define LL_FAST_TIMER_ON 1
#define LL_FASTTIMER_USE_RDTSC 1
@@ -85,6 +89,8 @@ public:
// return __rdtsc();
//}
+
+
// shift off lower 8 bits for lower resolution but longer term timing
// on 1Ghz machine, a 32-bit word will hold ~1000 seconds of timing
#if LL_FASTTIMER_USE_RDTSC
diff --git a/indra/llcommon/llfile.h b/indra/llcommon/llfile.h
index 398938b729..9de095b45d 100644
--- a/indra/llcommon/llfile.h
+++ b/indra/llcommon/llfile.h
@@ -86,6 +86,69 @@ public:
static const char * tmpdir();
};
+/// RAII class
+class LLUniqueFile
+{
+public:
+ // empty
+ LLUniqueFile(): mFileHandle(nullptr) {}
+ // wrap (e.g.) result of LLFile::fopen()
+ LLUniqueFile(LLFILE* f): mFileHandle(f) {}
+ // no copy
+ LLUniqueFile(const LLUniqueFile&) = delete;
+ // move construction
+ LLUniqueFile(LLUniqueFile&& other)
+ {
+ mFileHandle = other.mFileHandle;
+ other.mFileHandle = nullptr;
+ }
+ // The point of LLUniqueFile is to close on destruction.
+ ~LLUniqueFile()
+ {
+ close();
+ }
+
+ // simple assignment
+ LLUniqueFile& operator=(LLFILE* f)
+ {
+ close();
+ mFileHandle = f;
+ return *this;
+ }
+ // copy assignment deleted
+ LLUniqueFile& operator=(const LLUniqueFile&) = delete;
+ // move assignment
+ LLUniqueFile& operator=(LLUniqueFile&& other)
+ {
+ close();
+ std::swap(mFileHandle, other.mFileHandle);
+ return *this;
+ }
+
+ // explicit close operation
+ void close()
+ {
+ if (mFileHandle)
+ {
+ // in case close() throws, set mFileHandle null FIRST
+ LLFILE* h{nullptr};
+ std::swap(h, mFileHandle);
+ LLFile::close(h);
+ }
+ }
+
+ // detect whether the wrapped LLFILE is open or not
+ explicit operator bool() const { return bool(mFileHandle); }
+ bool operator!() { return ! mFileHandle; }
+
+ // LLUniqueFile should be usable for any operation that accepts LLFILE*
+ // (or FILE* for that matter)
+ operator LLFILE*() const { return mFileHandle; }
+
+private:
+ LLFILE* mFileHandle;
+};
+
#if LL_WINDOWS
/**
* @brief Controlling input for files.
diff --git a/indra/llcommon/llinstancetracker.cpp b/indra/llcommon/llinstancetracker.cpp
index 3f990f4869..e7193b70b5 100644
--- a/indra/llcommon/llinstancetracker.cpp
+++ b/indra/llcommon/llinstancetracker.cpp
@@ -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()
-{
- ++sIterationNestDepth;
-}
-
-void LLInstanceTrackerBase::StaticBase::decrementDepth()
-{
- llassert(sIterationNestDepth);
- --sIterationNestDepth;
-}
-
-U32 LLInstanceTrackerBase::StaticBase::getDepth()
+void LLInstanceTrackerPrivate::logerrs(const char* cls, const std::string& arg1,
+ const std::string& arg2, const std::string& arg3)
{
- return sIterationNestDepth;
+ LL_ERRS("LLInstanceTracker") << LLError::Log::demangle(cls)
+ << arg1 << arg2 << arg3 << LL_ENDL;
}
diff --git a/indra/llcommon/llinstancetracker.h b/indra/llcommon/llinstancetracker.h
index 363d0bcbd5..402333cca7 100644
--- a/indra/llcommon/llinstancetracker.h
+++ b/indra/llcommon/llinstancetracker.h
@@ -28,354 +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 "llstringtable.h"
#include <boost/iterator/transform_iterator.hpp>
#include <boost/iterator/indirect_iterator.hpp>
+#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__
-
-#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
+#include "lockstatic.h"
+#include "stringize.h"
-/**
- * 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
{
- StaticBase():
- sIterationNestDepth(0)
- {}
-
- void incrementDepth();
- void decrementDepth();
- U32 getDepth();
- private:
-#ifdef LL_WINDOWS
- std::atomic_uint32_t sIterationNestDepth;
-#else
- std::atomic_uint sIterationNestDepth;
-#endif
- };
-};
+ // We need to be able to lock static data while manipulating it.
+ std::mutex mMutex;
+ };
-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
{
- 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)
- {
- getStatic().incrementDepth();
- }
-
- ~instance_iter()
- {
- getStatic().decrementDepth();
- }
-
-
- private:
- friend class boost::iterator_core_access;
-
- void increment() { mIterator++; }
- bool equal(instance_iter const& other) const
- {
- return mIterator == other.mIterator;
- }
-
- T& dereference() const
- {
- return *(mIterator->second);
- }
-
- typename InstanceMap::iterator mIterator;
- };
-
- 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;
-
- key_iter(typename InstanceMap::iterator it)
- : mIterator(it)
- {
- getStatic().incrementDepth();
- }
-
- key_iter(const key_iter& other)
- : mIterator(other.mIterator)
- {
- getStatic().incrementDepth();
- }
-
- ~key_iter()
- {
- getStatic().decrementDepth();
- }
-
-
- 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());
- }
-
- static S32 instanceCount()
- {
- return getMap_().size();
- }
-
- static key_iter beginKeys()
- {
- return key_iter(getMap_().begin());
- }
- static key_iter endKeys()
- {
- return key_iter(getMap_().end());
- }
+ // 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);
+ }
+
+ 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;
+
+ iterator begin() { return make_iterator(mData.begin()); }
+ iterator end() { return make_iterator(mData.end()); }
+
+ 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));
+ }
+
+ // 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;
+ };
+
+ // 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); }
+
+ void deleteAll()
+ {
+ for (auto it(snapshot::begin()), end(snapshot::end()); it != end; ++it)
+ {
+ delete it->second.get();
+ }
+ }
+ };
+
+ // 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); }
+ };
+
+ 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();
+ }
+
+ 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.
- llassert_always(getStatic().getDepth() == 0);
- remove_();
- }
- virtual void setKey(KEY key) { remove_(); add_(key); }
- virtual const KEY& getKey() const { return mInstanceKey; }
+ 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& );
-
- 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);
- }
- }
+ LLInstanceTracker( const LLInstanceTracker& ) = delete;
+ LLInstanceTracker& operator=( const LLInstanceTracker& ) = delete;
+
+ // 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>
{
- 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(); }
-
- class instance_iter : public boost::iterator_facade<instance_iter, T, boost::forward_traversal_tag>
- {
- public:
- instance_iter(const typename InstanceSet::iterator& it)
- : mIterator(it)
- {
- getStatic().incrementDepth();
- }
-
- instance_iter(const instance_iter& other)
- : mIterator(other.mIterator)
- {
- getStatic().incrementDepth();
- }
-
- ~instance_iter()
- {
- getStatic().decrementDepth();
- }
-
- private:
- friend class boost::iterator_core_access;
-
- void increment() { mIterator++; }
- bool equal(instance_iter const& other) const
- {
- return mIterator == other.mIterator;
- }
-
- T& dereference() const
- {
- return **mIterator;
- }
-
- typename InstanceSet::iterator mIterator;
- };
-
- static instance_iter beginInstances() { return instance_iter(getSet_().begin()); }
- static instance_iter endInstances() { return instance_iter(getSet_().end()); }
+ // 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);
+ }
+
+ 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();
+ }
+
+ typedef boost::transform_iterator<decltype(strengthen)*,
+ typename VectorType::iterator> strong_iterator;
+ typedef boost::filter_iterator<decltype(dead_skipper)*, strong_iterator> iterator;
+
+ iterator begin() { return make_iterator(mData.begin()); }
+ iterator end() { return make_iterator(mData.end()); }
+
+ 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));
+ }
+
+ // 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;
+ };
+
+ // 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()); }
+
+ 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.
- llassert_always(getStatic().getDepth() == 0);
- getSet_().erase(static_cast<T*>(this));
- }
-
- LLInstanceTracker(const LLInstanceTracker& other)
- {
- getSet_().insert(static_cast<T*>(this));
- }
+ 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()
+ {}
+
+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
diff --git a/indra/llcommon/llleaplistener.cpp b/indra/llcommon/llleaplistener.cpp
index fa5730f112..3e6ce9092c 100644
--- a/indra/llcommon/llleaplistener.cpp
+++ b/indra/llcommon/llleaplistener.cpp
@@ -14,6 +14,8 @@
// associated header
#include "llleaplistener.h"
// STL headers
+#include <map>
+#include <functional>
// std headers
// external library headers
#include <boost/foreach.hpp>
@@ -60,16 +62,11 @@ LLLeapListener::LLLeapListener(const ConnectFunc& connect):
LLSD need_name(LLSDMap("name", LLSD()));
add("newpump",
"Instantiate a new LLEventPump named like [\"name\"] and listen to it.\n"
- "If [\"type\"] == \"LLEventQueue\", make LLEventQueue, else LLEventStream.\n"
+ "[\"type\"] == \"LLEventStream\", \"LLEventMailDrop\" et al.\n"
"Events sent through new LLEventPump will be decorated with [\"pump\"]=name.\n"
"Returns actual name in [\"name\"] (may be different if collision).",
&LLLeapListener::newpump,
need_name);
- add("killpump",
- "Delete LLEventPump [\"name\"] created by \"newpump\".\n"
- "Returns [\"status\"] boolean indicating whether such a pump existed.",
- &LLLeapListener::killpump,
- need_name);
LLSD need_source_listener(LLSDMap("source", LLSD())("listener", LLSD()));
add("listen",
"Listen to an existing LLEventPump named [\"source\"], with listener name\n"
@@ -124,40 +121,23 @@ void LLLeapListener::newpump(const LLSD& request)
Response reply(LLSD(), request);
std::string name = request["name"];
- LLSD const & type = request["type"];
+ std::string type = request["type"];
- LLEventPump * new_pump = NULL;
- if (type.asString() == "LLEventQueue")
+ try
{
- new_pump = new LLEventQueue(name, true); // tweak name for uniqueness
+ // tweak name for uniqueness
+ LLEventPump& new_pump(LLEventPumps::instance().make(name, true, type));
+ name = new_pump.getName();
+ reply["name"] = name;
+
+ // Now listen on this new pump with our plugin listener
+ std::string myname("llleap");
+ saveListener(name, myname, mConnect(new_pump, myname));
}
- else
+ catch (const LLEventPumps::BadType& error)
{
- if (! (type.isUndefined() || type.asString() == "LLEventStream"))
- {
- reply.warn(STRINGIZE("unknown 'type' " << type << ", using LLEventStream"));
- }
- new_pump = new LLEventStream(name, true); // tweak name for uniqueness
+ reply.error(error.what());
}
-
- name = new_pump->getName();
-
- mEventPumps.insert(name, new_pump);
-
- // Now listen on this new pump with our plugin listener
- std::string myname("llleap");
- saveListener(name, myname, mConnect(*new_pump, myname));
-
- reply["name"] = name;
-}
-
-void LLLeapListener::killpump(const LLSD& request)
-{
- Response reply(LLSD(), request);
-
- std::string name = request["name"];
- // success == (nonzero number of entries were erased)
- reply["status"] = bool(mEventPumps.erase(name));
}
void LLLeapListener::listen(const LLSD& request)
@@ -228,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;
}
}
diff --git a/indra/llcommon/llleaplistener.h b/indra/llcommon/llleaplistener.h
index 2193d81b9e..0ca5893657 100644
--- a/indra/llcommon/llleaplistener.h
+++ b/indra/llcommon/llleaplistener.h
@@ -40,7 +40,6 @@ public:
private:
void newpump(const LLSD&);
- void killpump(const LLSD&);
void listen(const LLSD&);
void stoplistening(const LLSD&);
void ping(const LLSD&) const;
@@ -64,10 +63,6 @@ private:
// and listener name.
typedef std::map<std::pair<std::string, std::string>, LLBoundListener> ListenersMap;
ListenersMap mListeners;
- // Similar lifespan reasoning applies to LLEventPumps instantiated by
- // newpump() operations.
- typedef boost::ptr_map<std::string, LLEventPump> EventPumpsMap;
- EventPumpsMap mEventPumps;
};
#endif /* ! defined(LL_LLLEAPLISTENER_H) */
diff --git a/indra/llcommon/lllistenerwrapper.h b/indra/llcommon/lllistenerwrapper.h
deleted file mode 100644
index 09d074abca..0000000000
--- a/indra/llcommon/lllistenerwrapper.h
+++ /dev/null
@@ -1,198 +0,0 @@
-/**
- * @file lllistenerwrapper.h
- * @author Nat Goodspeed
- * @date 2009-11-30
- * @brief Introduce LLListenerWrapper template
- *
- * $LicenseInfo:firstyear=2009&license=viewerlgpl$
- * Second Life Viewer Source Code
- * Copyright (C) 2010, Linden Research, Inc.
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation;
- * version 2.1 of the License only.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
- * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
- * $/LicenseInfo$
- */
-
-#if ! defined(LL_LLLISTENERWRAPPER_H)
-#define LL_LLLISTENERWRAPPER_H
-
-#include "llevents.h" // LLListenerWrapperBase
-#include <boost/visit_each.hpp>
-
-/**
- * Template base class for coding wrappers for LLEventPump listeners.
- *
- * Derive your listener wrapper from LLListenerWrapper. You must use
- * LLLISTENER_WRAPPER_SUBCLASS() so your subclass will play nicely with
- * boost::visit_each (q.v.). That way boost::signals2 can still detect
- * derivation from LLEventTrackable, and so forth.
- */
-template <typename LISTENER>
-class LLListenerWrapper: public LLListenerWrapperBase
-{
-public:
- /// Wrap an arbitrary listener object
- LLListenerWrapper(const LISTENER& listener):
- mListener(listener)
- {}
-
- /// call
- virtual bool operator()(const LLSD& event)
- {
- return mListener(event);
- }
-
- /// Allow boost::visit_each() to peek at our mListener.
- template <class V>
- void accept_visitor(V& visitor) const
- {
- using boost::visit_each;
- visit_each(visitor, mListener, 0);
- }
-
-private:
- LISTENER mListener;
-};
-
-/**
- * Specialize boost::visit_each() (leveraging ADL) to peek inside an
- * LLListenerWrapper<T> to traverse its LISTENER. We borrow the
- * accept_visitor() pattern from boost::bind(), avoiding the need to make
- * mListener public.
- */
-template <class V, typename T>
-void visit_each(V& visitor, const LLListenerWrapper<T>& wrapper, int)
-{
- wrapper.accept_visitor(visitor);
-}
-
-/// use this (sigh!) for each subclass of LLListenerWrapper<T> you write
-#define LLLISTENER_WRAPPER_SUBCLASS(CLASS) \
-template <class V, typename T> \
-void visit_each(V& visitor, const CLASS<T>& wrapper, int) \
-{ \
- visit_each(visitor, static_cast<const LLListenerWrapper<T>&>(wrapper), 0); \
-} \
- \
-/* Have to state this explicitly, rather than using LL_TEMPLATE_CONVERTIBLE, */ \
-/* because the source type is itself a template. */ \
-template <typename T> \
-struct ll_template_cast_impl<const LLListenerWrapperBase*, const CLASS<T>*> \
-{ \
- const LLListenerWrapperBase* operator()(const CLASS<T>* wrapper) \
- { \
- return wrapper; \
- } \
-}
-
-/**
- * Make an instance of a listener wrapper. Every wrapper class must be a
- * template accepting a listener object of arbitrary type. In particular, the
- * type of a boost::bind() expression is deliberately undocumented. So we
- * can't just write Wrapper<CorrectType>(boost::bind(...)). Instead we must
- * write llwrap<Wrapper>(boost::bind(...)).
- */
-template <template<typename> class WRAPPER, typename T>
-WRAPPER<T> llwrap(const T& listener)
-{
- return WRAPPER<T>(listener);
-}
-
-/**
- * This LLListenerWrapper template subclass is used to report entry/exit to an
- * event listener, by changing this:
- * @code
- * someEventPump.listen("MyClass",
- * boost::bind(&MyClass::method, ptr, _1));
- * @endcode
- * to this:
- * @code
- * someEventPump.listen("MyClass",
- * llwrap<LLCoutListener>(
- * boost::bind(&MyClass::method, ptr, _1)));
- * @endcode
- */
-template <class LISTENER>
-class LLCoutListener: public LLListenerWrapper<LISTENER>
-{
- typedef LLListenerWrapper<LISTENER> super;
-
-public:
- /// Wrap an arbitrary listener object
- LLCoutListener(const LISTENER& listener):
- super(listener)
- {}
-
- /// call
- virtual bool operator()(const LLSD& event)
- {
- std::cout << "Entering listener " << *super::mName << " with " << event << std::endl;
- bool handled = super::operator()(event);
- std::cout << "Leaving listener " << *super::mName;
- if (handled)
- {
- std::cout << " (handled)";
- }
- std::cout << std::endl;
- return handled;
- }
-};
-
-LLLISTENER_WRAPPER_SUBCLASS(LLCoutListener);
-
-/**
- * This LLListenerWrapper template subclass is used to log entry/exit to an
- * event listener, by changing this:
- * @code
- * someEventPump.listen("MyClass",
- * boost::bind(&MyClass::method, ptr, _1));
- * @endcode
- * to this:
- * @code
- * someEventPump.listen("MyClass",
- * llwrap<LLLogListener>(
- * boost::bind(&MyClass::method, ptr, _1)));
- * @endcode
- */
-template <class LISTENER>
-class LLLogListener: public LLListenerWrapper<LISTENER>
-{
- typedef LLListenerWrapper<LISTENER> super;
-
-public:
- /// Wrap an arbitrary listener object
- LLLogListener(const LISTENER& listener):
- super(listener)
- {}
-
- /// call
- virtual bool operator()(const LLSD& event)
- {
- LL_DEBUGS("LLLogListener") << "Entering listener " << *super::mName << " with " << event << LL_ENDL;
- bool handled = super::operator()(event);
- LL_DEBUGS("LLLogListener") << "Leaving listener " << *super::mName;
- if (handled)
- {
- LL_CONT << " (handled)";
- }
- LL_CONT << LL_ENDL;
- return handled;
- }
-};
-
-LLLISTENER_WRAPPER_SUBCLASS(LLLogListener);
-
-#endif /* ! defined(LL_LLLISTENERWRAPPER_H) */
diff --git a/indra/llcommon/llmainthreadtask.cpp b/indra/llcommon/llmainthreadtask.cpp
new file mode 100644
index 0000000000..e0d70cacd8
--- /dev/null
+++ b/indra/llcommon/llmainthreadtask.cpp
@@ -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.
diff --git a/indra/llcommon/llmainthreadtask.h b/indra/llcommon/llmainthreadtask.h
new file mode 100644
index 0000000000..d509b687c0
--- /dev/null
+++ b/indra/llcommon/llmainthreadtask.h
@@ -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) */
diff --git a/indra/llcommon/llmake.h b/indra/llcommon/llmake.h
index 08744f90fb..02463d97ea 100644
--- a/indra/llcommon/llmake.h
+++ b/indra/llcommon/llmake.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,37 +23,43 @@
#if ! defined(LL_LLMAKE_H)
#define LL_LLMAKE_H
-/*==========================================================================*|
-// When we allow ourselves to compile with C++11 features enabled, this form
-// should generically handle an arbitrary number of arguments.
-
+/**
+ * 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)...);
}
-|*==========================================================================*/
-// As of 2015-12-18, this is what we'll use instead. Add explicit overloads
-// for different numbers of template parameters as use cases arise.
+/// dumb pointer template just in case that's what's wanted
+template <typename T>
+using dumb_pointer = T*;
/**
- * Usage: llmake<SomeTemplate>(arg)
+ * Same as llmake(), but returns a pointer to a new heap instance of
+ * SomeTemplate<T...>(args...) using the pointer of your choice.
*
- * Deduces the type T of 'arg' and returns an instance of SomeTemplate<T>
- * initialized with 'arg'. Assumes a constructor accepting T (by value,
- * reference or whatever).
+ * @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
*/
-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)
+// 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 CLASS_TEMPLATE<ARG1, ARG2>(arg1, arg2);
+ return POINTER_TEMPLATE<CLASS_TEMPLATE<ARGS...>>(
+ new CLASS_TEMPLATE<ARGS...>(std::forward<ARGS>(args)...));
}
#endif /* ! defined(LL_LLMAKE_H) */
diff --git a/indra/llcommon/llmutex.cpp b/indra/llcommon/llmutex.cpp
index 75f43a4704..4d73c04d07 100644
--- a/indra/llcommon/llmutex.cpp
+++ b/indra/llcommon/llmutex.cpp
@@ -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;
diff --git a/indra/llcommon/llmutex.h b/indra/llcommon/llmutex.h
index f841d7f950..838d7d34c0 100644
--- a/indra/llcommon/llmutex.h
+++ b/indra/llcommon/llmutex.h
@@ -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
};
diff --git a/indra/llcommon/llpreprocessor.h b/indra/llcommon/llpreprocessor.h
index e8f9981437..bae402110a 100644
--- a/indra/llcommon/llpreprocessor.h
+++ b/indra/llcommon/llpreprocessor.h
@@ -232,4 +232,11 @@
#define LL_COMPILE_TIME_MESSAGE(msg)
#endif
+// __FUNCTION__ works on all the platforms we care about, but...
+#if LL_WINDOWS
+#define LL_PRETTY_FUNCTION __FUNCSIG__
+#else
+#define LL_PRETTY_FUNCTION __PRETTY_FUNCTION__
+#endif
+
#endif // not LL_LINDEN_PREPROCESSOR_H
diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp
index 1fa53f322b..23936f0526 100644
--- a/indra/llcommon/llprocess.cpp
+++ b/indra/llcommon/llprocess.cpp
@@ -994,9 +994,9 @@ void LLProcess::handle_status(int reason, int status)
// wi->rv = apr_proc_wait(wi->child, &wi->rc, &wi->why, APR_NOWAIT);
// It's just wrong to call apr_proc_wait() here. The only way APR knows to
// call us with APR_OC_REASON_DEATH is that it's already reaped this child
- // process, so calling suspend() will only produce "huh?" from the OS. We
+ // process, so calling wait() will only produce "huh?" from the OS. We
// must rely on the status param passed in, which unfortunately comes
- // straight from the OS suspend() call, which means we have to decode it by
+ // straight from the OS wait() call, which means we have to decode it by
// hand.
mStatus = interpret_status(status);
LL_INFOS("LLProcess") << getStatusString() << LL_ENDL;
diff --git a/indra/llcommon/llrefcount.h b/indra/llcommon/llrefcount.h
index fb0411d27b..7e4af6ea66 100644
--- a/indra/llcommon/llrefcount.h
+++ b/indra/llcommon/llrefcount.h
@@ -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
diff --git a/indra/llcommon/llsdserialize.cpp b/indra/llcommon/llsdserialize.cpp
index 79934642ae..022a5d4659 100644
--- a/indra/llcommon/llsdserialize.cpp
+++ b/indra/llcommon/llsdserialize.cpp
@@ -66,7 +66,8 @@ const std::string LLSD_NOTATION_HEADER("llsd/notation");
*/
// static
-void LLSDSerialize::serialize(const LLSD& sd, std::ostream& str, ELLSD_Serialize type, U32 options)
+void LLSDSerialize::serialize(const LLSD& sd, std::ostream& str, ELLSD_Serialize type,
+ LLSDFormatter::EFormatterOptions options)
{
LLPointer<LLSDFormatter> f = NULL;
@@ -174,10 +175,10 @@ bool LLSDSerialize::deserialize(LLSD& sd, std::istream& str, S32 max_bytes)
{
p = new LLSDXMLParser;
}
- else if (header == LLSD_NOTATION_HEADER)
- {
- p = new LLSDNotationParser;
- }
+ else if (header == LLSD_NOTATION_HEADER)
+ {
+ p = new LLSDNotationParser;
+ }
else
{
LL_WARNS() << "deserialize request for unknown ELLSD_Serialize" << LL_ENDL;
@@ -1234,9 +1235,11 @@ bool LLSDBinaryParser::parseString(
/**
* LLSDFormatter
*/
-LLSDFormatter::LLSDFormatter() :
- mBoolAlpha(false)
+LLSDFormatter::LLSDFormatter(bool boolAlpha, const std::string& realFmt, EFormatterOptions options):
+ mOptions(options)
{
+ boolalpha(boolAlpha);
+ realFormat(realFmt);
}
// virtual
@@ -1253,6 +1256,17 @@ void LLSDFormatter::realFormat(const std::string& format)
mRealFormat = format;
}
+S32 LLSDFormatter::format(const LLSD& data, std::ostream& ostr) const
+{
+ // pass options captured by constructor
+ return format(data, ostr, mOptions);
+}
+
+S32 LLSDFormatter::format(const LLSD& data, std::ostream& ostr, EFormatterOptions options) const
+{
+ return format_impl(data, ostr, options, 0);
+}
+
void LLSDFormatter::formatReal(LLSD::Real real, std::ostream& ostr) const
{
std::string buffer = llformat(mRealFormat.c_str(), real);
@@ -1262,7 +1276,9 @@ void LLSDFormatter::formatReal(LLSD::Real real, std::ostream& ostr) const
/**
* LLSDNotationFormatter
*/
-LLSDNotationFormatter::LLSDNotationFormatter()
+LLSDNotationFormatter::LLSDNotationFormatter(bool boolAlpha, const std::string& realFormat,
+ EFormatterOptions options):
+ LLSDFormatter(boolAlpha, realFormat, options)
{
}
@@ -1278,14 +1294,8 @@ std::string LLSDNotationFormatter::escapeString(const std::string& in)
return ostr.str();
}
-// virtual
-S32 LLSDNotationFormatter::format(const LLSD& data, std::ostream& ostr, U32 options) const
-{
- S32 rv = format_impl(data, ostr, options, 0);
- return rv;
-}
-
-S32 LLSDNotationFormatter::format_impl(const LLSD& data, std::ostream& ostr, U32 options, U32 level) const
+S32 LLSDNotationFormatter::format_impl(const LLSD& data, std::ostream& ostr,
+ EFormatterOptions options, U32 level) const
{
S32 format_count = 1;
std::string pre;
@@ -1406,21 +1416,33 @@ S32 LLSDNotationFormatter::format_impl(const LLSD& data, std::ostream& ostr, U32
{
// *FIX: memory inefficient.
const std::vector<U8>& buffer = data.asBinary();
- ostr << "b(" << buffer.size() << ")\"";
- if(buffer.size())
+ if (options & LLSDFormatter::OPTIONS_PRETTY_BINARY)
{
- if (options & LLSDFormatter::OPTIONS_PRETTY_BINARY)
+ ostr << "b16\"";
+ if (! buffer.empty())
{
std::ios_base::fmtflags old_flags = ostr.flags();
ostr.setf( std::ios::hex, std::ios::basefield );
- ostr << "0x";
+ // It shouldn't strictly matter whether the emitted hex digits
+ // are uppercase; LLSDNotationParser handles either; but as of
+ // 2020-05-13, Python's llbase.llsd requires uppercase hex.
+ ostr << std::uppercase;
+ auto oldfill(ostr.fill('0'));
+ auto oldwidth(ostr.width());
for (int i = 0; i < buffer.size(); i++)
{
- ostr << (int) buffer[i];
+ // have to restate setw() before every conversion
+ ostr << std::setw(2) << (int) buffer[i];
}
+ ostr.width(oldwidth);
+ ostr.fill(oldfill);
ostr.flags(old_flags);
}
- else
+ }
+ else // ! OPTIONS_PRETTY_BINARY
+ {
+ ostr << "b(" << buffer.size() << ")\"";
+ if (! buffer.empty())
{
ostr.write((const char*)&buffer[0], buffer.size());
}
@@ -1437,11 +1459,12 @@ S32 LLSDNotationFormatter::format_impl(const LLSD& data, std::ostream& ostr, U32
return format_count;
}
-
/**
* LLSDBinaryFormatter
*/
-LLSDBinaryFormatter::LLSDBinaryFormatter()
+LLSDBinaryFormatter::LLSDBinaryFormatter(bool boolAlpha, const std::string& realFormat,
+ EFormatterOptions options):
+ LLSDFormatter(boolAlpha, realFormat, options)
{
}
@@ -1450,7 +1473,8 @@ LLSDBinaryFormatter::~LLSDBinaryFormatter()
{ }
// virtual
-S32 LLSDBinaryFormatter::format(const LLSD& data, std::ostream& ostr, U32 options) const
+S32 LLSDBinaryFormatter::format_impl(const LLSD& data, std::ostream& ostr,
+ EFormatterOptions options, U32 level) const
{
S32 format_count = 1;
switch(data.type())
@@ -1466,7 +1490,7 @@ S32 LLSDBinaryFormatter::format(const LLSD& data, std::ostream& ostr, U32 option
{
ostr.put('k');
formatString((*iter).first, ostr);
- format_count += format((*iter).second, ostr);
+ format_count += format_impl((*iter).second, ostr, options, level+1);
}
ostr.put('}');
break;
@@ -1481,7 +1505,7 @@ S32 LLSDBinaryFormatter::format(const LLSD& data, std::ostream& ostr, U32 option
LLSD::array_const_iterator end = data.endArray();
for(; iter != end; ++iter)
{
- format_count += format(*iter, ostr);
+ format_count += format_impl(*iter, ostr, options, level+1);
}
ostr.put(']');
break;
diff --git a/indra/llcommon/llsdserialize.h b/indra/llcommon/llsdserialize.h
index fe0f4443ef..d6079fd9fa 100644
--- a/indra/llcommon/llsdserialize.h
+++ b/indra/llcommon/llsdserialize.h
@@ -435,7 +435,8 @@ public:
/**
* @brief Constructor
*/
- LLSDFormatter();
+ LLSDFormatter(bool boolAlpha=false, const std::string& realFormat="",
+ EFormatterOptions options=OPTIONS_PRETTY_BINARY);
/**
* @brief Set the boolean serialization format.
@@ -459,16 +460,38 @@ public:
void realFormat(const std::string& format);
/**
- * @brief Call this method to format an LLSD to a stream.
+ * @brief Call this method to format an LLSD to a stream with options as
+ * set by the constructor.
+ *
+ * @param data The data to write.
+ * @param ostr The destination stream for the data.
+ * @return Returns The number of LLSD objects formatted out
+ */
+ S32 format(const LLSD& data, std::ostream& ostr) const;
+
+ /**
+ * @brief Call this method to format an LLSD to a stream, passing options
+ * explicitly.
*
* @param data The data to write.
* @param ostr The destination stream for the data.
- * @return Returns The number of LLSD objects fomatted out
+ * @param options OPTIONS_NONE to emit LLSD::Binary as raw bytes
+ * @return Returns The number of LLSD objects formatted out
*/
- virtual S32 format(const LLSD& data, std::ostream& ostr, U32 options = LLSDFormatter::OPTIONS_NONE) const = 0;
+ virtual S32 format(const LLSD& data, std::ostream& ostr, EFormatterOptions options) const;
protected:
/**
+ * @brief Implementation to format the data. This is called recursively.
+ *
+ * @param data The data to write.
+ * @param ostr The destination stream for the data.
+ * @return Returns The number of LLSD objects formatted out
+ */
+ virtual S32 format_impl(const LLSD& data, std::ostream& ostr, EFormatterOptions options,
+ U32 level) const = 0;
+
+ /**
* @brief Helper method which appropriately obeys the real format.
*
* @param real The real value to format.
@@ -476,9 +499,9 @@ protected:
*/
void formatReal(LLSD::Real real, std::ostream& ostr) const;
-protected:
bool mBoolAlpha;
std::string mRealFormat;
+ EFormatterOptions mOptions;
};
@@ -498,7 +521,8 @@ public:
/**
* @brief Constructor
*/
- LLSDNotationFormatter();
+ LLSDNotationFormatter(bool boolAlpha=false, const std::string& realFormat="",
+ EFormatterOptions options=OPTIONS_PRETTY_BINARY);
/**
* @brief Helper static method to return a notation escaped string
@@ -512,25 +536,16 @@ public:
*/
static std::string escapeString(const std::string& in);
- /**
- * @brief Call this method to format an LLSD to a stream.
- *
- * @param data The data to write.
- * @param ostr The destination stream for the data.
- * @return Returns The number of LLSD objects fomatted out
- */
- virtual S32 format(const LLSD& data, std::ostream& ostr, U32 options = LLSDFormatter::OPTIONS_NONE) const;
-
protected:
-
/**
* @brief Implementation to format the data. This is called recursively.
*
* @param data The data to write.
* @param ostr The destination stream for the data.
- * @return Returns The number of LLSD objects fomatted out
+ * @return Returns The number of LLSD objects formatted out
*/
- S32 format_impl(const LLSD& data, std::ostream& ostr, U32 options, U32 level) const;
+ S32 format_impl(const LLSD& data, std::ostream& ostr, EFormatterOptions options,
+ U32 level) const override;
};
@@ -550,7 +565,8 @@ public:
/**
* @brief Constructor
*/
- LLSDXMLFormatter();
+ LLSDXMLFormatter(bool boolAlpha=false, const std::string& realFormat="",
+ EFormatterOptions options=OPTIONS_PRETTY_BINARY);
/**
* @brief Helper static method to return an xml escaped string
@@ -565,20 +581,23 @@ public:
*
* @param data The data to write.
* @param ostr The destination stream for the data.
- * @return Returns The number of LLSD objects fomatted out
+ * @return Returns The number of LLSD objects formatted out
*/
- virtual S32 format(const LLSD& data, std::ostream& ostr, U32 options = LLSDFormatter::OPTIONS_NONE) const;
+ S32 format(const LLSD& data, std::ostream& ostr, EFormatterOptions options) const override;
-protected:
+ // also pull down base-class format() method that isn't overridden
+ using LLSDFormatter::format;
+protected:
/**
* @brief Implementation to format the data. This is called recursively.
*
* @param data The data to write.
* @param ostr The destination stream for the data.
- * @return Returns The number of LLSD objects fomatted out
+ * @return Returns The number of LLSD objects formatted out
*/
- S32 format_impl(const LLSD& data, std::ostream& ostr, U32 options, U32 level) const;
+ S32 format_impl(const LLSD& data, std::ostream& ostr, EFormatterOptions options,
+ U32 level) const override;
};
@@ -618,18 +637,20 @@ public:
/**
* @brief Constructor
*/
- LLSDBinaryFormatter();
+ LLSDBinaryFormatter(bool boolAlpha=false, const std::string& realFormat="",
+ EFormatterOptions options=OPTIONS_PRETTY_BINARY);
+protected:
/**
- * @brief Call this method to format an LLSD to a stream.
+ * @brief Implementation to format the data. This is called recursively.
*
* @param data The data to write.
* @param ostr The destination stream for the data.
- * @return Returns The number of LLSD objects fomatted out
+ * @return Returns The number of LLSD objects formatted out
*/
- virtual S32 format(const LLSD& data, std::ostream& ostr, U32 options = LLSDFormatter::OPTIONS_NONE) const;
+ S32 format_impl(const LLSD& data, std::ostream& ostr, EFormatterOptions options,
+ U32 level) const override;
-protected:
/**
* @brief Helper method to serialize strings
*
@@ -669,7 +690,8 @@ public:
/**
* @brief Constructor
*/
- LLSDOStreamer(const LLSD& data, U32 options = LLSDFormatter::OPTIONS_NONE) :
+ LLSDOStreamer(const LLSD& data,
+ LLSDFormatter::EFormatterOptions options=LLSDFormatter::OPTIONS_PRETTY_BINARY) :
mSD(data), mOptions(options) {}
/**
@@ -681,17 +703,17 @@ public:
* @return Returns the stream passed in after streaming mSD.
*/
friend std::ostream& operator<<(
- std::ostream& str,
- const LLSDOStreamer<Formatter>& formatter)
+ std::ostream& out,
+ const LLSDOStreamer<Formatter>& streamer)
{
LLPointer<Formatter> f = new Formatter;
- f->format(formatter.mSD, str, formatter.mOptions);
- return str;
+ f->format(streamer.mSD, out, streamer.mOptions);
+ return out;
}
protected:
LLSD mSD;
- U32 mOptions;
+ LLSDFormatter::EFormatterOptions mOptions;
};
typedef LLSDOStreamer<LLSDNotationFormatter> LLSDNotationStreamer;
@@ -724,7 +746,7 @@ public:
* Generic in/outs
*/
static void serialize(const LLSD& sd, std::ostream& str, ELLSD_Serialize,
- U32 options = LLSDFormatter::OPTIONS_NONE);
+ LLSDFormatter::EFormatterOptions options=LLSDFormatter::OPTIONS_PRETTY_BINARY);
/**
* @brief Examine a stream, and parse 1 sd object out based on contents.
@@ -752,9 +774,9 @@ public:
static S32 toPrettyBinaryNotation(const LLSD& sd, std::ostream& str)
{
LLPointer<LLSDNotationFormatter> f = new LLSDNotationFormatter;
- return f->format(sd, str,
- LLSDFormatter::OPTIONS_PRETTY |
- LLSDFormatter::OPTIONS_PRETTY_BINARY);
+ return f->format(sd, str,
+ LLSDFormatter::EFormatterOptions(LLSDFormatter::OPTIONS_PRETTY |
+ LLSDFormatter::OPTIONS_PRETTY_BINARY));
}
static S32 fromNotation(LLSD& sd, std::istream& str, S32 max_bytes)
{
diff --git a/indra/llcommon/llsdserialize_xml.cpp b/indra/llcommon/llsdserialize_xml.cpp
index 6d0fe862b9..0da824d694 100644
--- a/indra/llcommon/llsdserialize_xml.cpp
+++ b/indra/llcommon/llsdserialize_xml.cpp
@@ -45,7 +45,9 @@ extern "C"
/**
* LLSDXMLFormatter
*/
-LLSDXMLFormatter::LLSDXMLFormatter()
+LLSDXMLFormatter::LLSDXMLFormatter(bool boolAlpha, const std::string& realFormat,
+ EFormatterOptions options):
+ LLSDFormatter(boolAlpha, realFormat, options)
{
}
@@ -55,7 +57,8 @@ LLSDXMLFormatter::~LLSDXMLFormatter()
}
// virtual
-S32 LLSDXMLFormatter::format(const LLSD& data, std::ostream& ostr, U32 options) const
+S32 LLSDXMLFormatter::format(const LLSD& data, std::ostream& ostr,
+ EFormatterOptions options) const
{
std::streamsize old_precision = ostr.precision(25);
@@ -72,7 +75,8 @@ S32 LLSDXMLFormatter::format(const LLSD& data, std::ostream& ostr, U32 options)
return rv;
}
-S32 LLSDXMLFormatter::format_impl(const LLSD& data, std::ostream& ostr, U32 options, U32 level) const
+S32 LLSDXMLFormatter::format_impl(const LLSD& data, std::ostream& ostr,
+ EFormatterOptions options, U32 level) const
{
S32 format_count = 1;
std::string pre;
diff --git a/indra/llcommon/llsdutil.cpp b/indra/llcommon/llsdutil.cpp
index 6a23c443a0..d44387cc55 100644
--- a/indra/llcommon/llsdutil.cpp
+++ b/indra/llcommon/llsdutil.cpp
@@ -856,6 +856,74 @@ bool llsd_equals(const LLSD& lhs, const LLSD& rhs, int bits)
}
}
+/*****************************************************************************
+* llsd::drill()
+*****************************************************************************/
+namespace llsd
+{
+
+LLSD& drill(LLSD& blob, const LLSD& rawPath)
+{
+ // Treat rawPath uniformly as an array. If it's not already an array,
+ // store it as the only entry in one. (But let's say Undefined means an
+ // empty array.)
+ LLSD path;
+ if (rawPath.isArray() || rawPath.isUndefined())
+ {
+ path = rawPath;
+ }
+ else
+ {
+ path.append(rawPath);
+ }
+
+ // Need to indicate a current destination -- but that current destination
+ // must change as we step through the path array. Where normally we'd use
+ // an LLSD& to capture a subscripted LLSD lvalue, this time we must
+ // instead use a pointer -- since it must be reassigned.
+ // Start by pointing to the input blob exactly as is.
+ LLSD* located{&blob};
+
+ // Extract the element of interest by walking path. Use an explicit index
+ // so that, in case of a bogus type in path, we can identify the specific
+ // path entry that's bad.
+ for (LLSD::Integer i = 0; i < path.size(); ++i)
+ {
+ const LLSD& key{path[i]};
+ if (key.isString())
+ {
+ // a string path element is a map key
+ located = &((*located)[key.asString()]);
+ }
+ else if (key.isInteger())
+ {
+ // an integer path element is an array index
+ located = &((*located)[key.asInteger()]);
+ }
+ else
+ {
+ // What do we do with Real or Array or Map or ...?
+ // As it's a coder error -- not a user error -- rub the coder's
+ // face in it so it gets fixed.
+ LL_ERRS("llsdutil") << "drill(" << blob << ", " << rawPath
+ << "): path[" << i << "] bad type "
+ << sTypes.lookup(key.type()) << LL_ENDL;
+ }
+ }
+
+ // dereference the pointer to return a reference to the element we found
+ return *located;
+}
+
+LLSD drill(const LLSD& blob, const LLSD& path)
+{
+ // non-const drill() does exactly what we want. Temporarily cast away
+ // const-ness and use that.
+ return drill(const_cast<LLSD&>(blob), path);
+}
+
+} // namespace llsd
+
// Construct a deep partial clone of of an LLSD object. primitive types share
// references, however maps, arrays and binary objects are duplicated. An optional
// filter may be include to exclude/include keys in a map.
diff --git a/indra/llcommon/llsdutil.h b/indra/llcommon/llsdutil.h
index 863be04c8a..84be95ba54 100644
--- a/indra/llcommon/llsdutil.h
+++ b/indra/llcommon/llsdutil.h
@@ -143,6 +143,16 @@ LL_COMMON_API std::string llsd_matches(const LLSD& prototype, const LLSD& data,
/// equality rather than bitwise equality, pass @a bits as for
/// is_approx_equal_fraction().
LL_COMMON_API bool llsd_equals(const LLSD& lhs, const LLSD& rhs, int bits=-1);
+/// If you don't care about LLSD::Real equality
+inline bool operator==(const LLSD& lhs, const LLSD& rhs)
+{
+ return llsd_equals(lhs, rhs);
+}
+inline bool operator!=(const LLSD& lhs, const LLSD& rhs)
+{
+ // operator!=() should always be the negation of operator==()
+ return ! (lhs == rhs);
+}
// Simple function to copy data out of input & output iterators if
// there is no need for casting.
@@ -156,6 +166,31 @@ template<typename Input> LLSD llsd_copy_array(Input iter, Input end)
return dest;
}
+namespace llsd
+{
+
+/**
+ * Drill down to locate an element in 'blob' according to 'path', where 'path'
+ * is one of the following:
+ *
+ * - LLSD::String: 'blob' is an LLSD::Map. Find the entry with key 'path'.
+ * - LLSD::Integer: 'blob' is an LLSD::Array. Find the entry with index 'path'.
+ * - Any other 'path' type will be interpreted as LLSD::Array, and 'blob' is a
+ * nested structure. For each element of 'path':
+ * - If it's an LLSD::Integer, select the entry with that index from an
+ * LLSD::Array at that level.
+ * - If it's an LLSD::String, select the entry with that key from an
+ * LLSD::Map at that level.
+ * - Anything else is an error.
+ *
+ * By implication, if path.isUndefined() or otherwise equivalent to an empty
+ * LLSD::Array, drill() returns 'blob' as is.
+ */
+LLSD drill(const LLSD& blob, const LLSD& path);
+LLSD& drill( LLSD& blob, const LLSD& path);
+
+}
+
/*****************************************************************************
* LLSDArray
*****************************************************************************/
@@ -225,6 +260,36 @@ private:
LLSD _data;
};
+namespace llsd
+{
+
+/**
+ * Construct an LLSD::Array inline, using modern C++ variadic arguments.
+ */
+
+// recursion tail
+inline
+void array_(LLSD&) {}
+
+// recursive call
+template <typename T0, typename... Ts>
+void array_(LLSD& data, T0&& v0, Ts&&... vs)
+{
+ data.append(std::forward<T0>(v0));
+ array_(data, std::forward<Ts>(vs)...);
+}
+
+// public interface
+template <typename... Ts>
+LLSD array(Ts&&... vs)
+{
+ LLSD data;
+ array_(data, std::forward<Ts>(vs)...);
+ return data;
+}
+
+} // namespace llsd
+
/*****************************************************************************
* LLSDMap
*****************************************************************************/
@@ -269,6 +334,36 @@ private:
LLSD _data;
};
+namespace llsd
+{
+
+/**
+ * Construct an LLSD::Map inline, using modern C++ variadic arguments.
+ */
+
+// recursion tail
+inline
+void map_(LLSD&) {}
+
+// recursive call
+template <typename T0, typename... Ts>
+void map_(LLSD& data, const LLSD::String& k0, T0&& v0, Ts&&... vs)
+{
+ data[k0] = v0;
+ map_(data, std::forward<Ts>(vs)...);
+}
+
+// public interface
+template <typename... Ts>
+LLSD map(Ts&&... vs)
+{
+ LLSD data;
+ map_(data, std::forward<Ts>(vs)...);
+ return data;
+}
+
+} // namespace llsd
+
/*****************************************************************************
* LLSDParam
*****************************************************************************/
@@ -452,6 +547,16 @@ LLSD llsd_clone(LLSD value, LLSD filter = LLSD());
// the filter parameter.
LLSD llsd_shallow(LLSD value, LLSD filter = LLSD());
+namespace llsd
+{
+
+// llsd namespace aliases
+inline
+LLSD clone (LLSD value, LLSD filter=LLSD()) { return llsd_clone (value, filter); }
+inline
+LLSD shallow(LLSD value, LLSD filter=LLSD()) { return llsd_shallow(value, filter); }
+
+} // namespace llsd
// Specialization for generating a hash value from an LLSD block.
template <>
diff --git a/indra/llcommon/llsingleton.cpp b/indra/llcommon/llsingleton.cpp
index c45c144570..d3d25201b2 100644
--- a/indra/llcommon/llsingleton.cpp
+++ b/indra/llcommon/llsingleton.cpp
@@ -30,9 +30,9 @@
#include "llerror.h"
#include "llerrorcontrol.h" // LLError::is_available()
#include "lldependencies.h"
-#include "llcoro_get_id.h"
+#include "llexception.h"
+#include "llcoros.h"
#include <boost/foreach.hpp>
-#include <boost/unordered_map.hpp>
#include <algorithm>
#include <iostream> // std::cerr in dire emergency
#include <sstream>
@@ -42,8 +42,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
@@ -57,63 +55,131 @@ bool oktolog();
class LLSingletonBase::MasterList:
public LLSingleton<LLSingletonBase::MasterList>
{
+private:
LLSINGLETON_EMPTY_CTOR(MasterList);
-public:
- // No need to make this private with accessors; nobody outside this source
- // file can see it.
+ // Independently of the LLSingleton locks governing construction,
+ // destruction and other state changes of the MasterList instance itself,
+ // we must also defend each of the data structures owned by the
+ // MasterList.
+ // This must be a recursive_mutex because, while the lock is held for
+ // manipulating some data in the master list, we must also check whether
+ // it's safe to log -- which involves querying a different LLSingleton --
+ // which requires accessing the master list.
+ typedef std::recursive_mutex mutex_t;
+ typedef std::unique_lock<mutex_t> lock_t;
+ mutex_t mMutex;
+
+public:
+ // Instantiate this to both obtain a reference to MasterList::instance()
+ // and lock its mutex for the lifespan of this Lock instance.
+ class Lock
+ {
+ public:
+ Lock():
+ mMasterList(MasterList::instance()),
+ mLock(mMasterList.mMutex)
+ {}
+ Lock(const Lock&) = delete;
+ Lock& operator=(const Lock&) = delete;
+ MasterList& get() const { return mMasterList; }
+ operator MasterList&() const { return get(); }
+
+ protected:
+ MasterList& mMasterList;
+ MasterList::lock_t mLock;
+ };
+
+private:
// This is the master list of all instantiated LLSingletons (save the
// MasterList itself) in arbitrary order. You MUST call dep_sort() before
// traversing this list.
- LLSingletonBase::list_t mMaster;
+ list_t mMaster;
+
+public:
+ // Instantiate this to obtain a reference to MasterList::mMaster and to
+ // hold the MasterList lock for the lifespan of this LockedMaster
+ // instance.
+ struct LockedMaster: public Lock
+ {
+ list_t& get() const { return mMasterList.mMaster; }
+ operator list_t&() const { return get(); }
+ };
+private:
// We need to maintain a stack of LLSingletons currently being
// initialized, either in the constructor or in initSingleton(). However,
// managing that as a stack depends on having a DISTINCT 'initializing'
// stack for every C++ stack in the process! And we have a distinct C++
- // stack for every running coroutine. It would be interesting and cool to
- // implement a generic coroutine-local-storage mechanism and use that
- // here. The trouble is that LLCoros is itself an LLSingleton, so
- // depending on LLCoros functionality could dig us into infinite
- // recursion. (Moreover, when we reimplement LLCoros on top of
- // Boost.Fiber, that library already provides fiber_specific_ptr -- so
- // it's not worth a great deal of time and energy implementing a generic
- // equivalent on top of boost::dcoroutine, which is on its way out.)
- // Instead, use a map of llcoro::id to select the appropriate
- // coro-specific 'initializing' stack. llcoro::get_id() is carefully
- // implemented to avoid requiring LLCoros.
- typedef boost::unordered_map<llcoro::id, LLSingletonBase::list_t> InitializingMap;
- InitializingMap mInitializing;
-
- // non-static method, cf. LLSingletonBase::get_initializing()
+ // 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<list_t> mInitializing;
+
+public:
+ // Instantiate this to obtain a reference to the coroutine-specific
+ // initializing list and to hold the MasterList lock for the lifespan of
+ // this LockedInitializing instance.
+ struct LockedInitializing: public Lock
+ {
+ public:
+ LockedInitializing():
+ // only do the lookup once, cache the result
+ // note that the lock is already locked during this lookup
+ mList(&mMasterList.get_initializing_())
+ {}
+ list_t& get() const
+ {
+ if (! mList)
+ {
+ LLTHROW(LLException("Trying to use LockedInitializing "
+ "after cleanup_initializing()"));
+ }
+ return *mList;
+ }
+ operator list_t&() const { return get(); }
+ void log(const char* verb, const char* name);
+ void cleanup_initializing()
+ {
+ mMasterList.cleanup_initializing_();
+ mList = nullptr;
+ }
+
+ private:
+ // Store pointer since cleanup_initializing() must clear it.
+ list_t* mList;
+ };
+
+private:
list_t& get_initializing_()
{
- // map::operator[] has find-or-create semantics, exactly what we need
- // here. It returns a reference to the selected mapped_type instance.
- return mInitializing[llcoro::get_id()];
+ LLSingletonBase::list_t* current = mInitializing.get();
+ if (! current)
+ {
+ // If the running coroutine doesn't already have an initializing
+ // stack, allocate a new one and save it for future reference.
+ current = new LLSingletonBase::list_t();
+ mInitializing.reset(current);
+ }
+ return *current;
}
+ // By the time mInitializing is destroyed, its value for every coroutine
+ // except the running one must have been reset() to nullptr. So every time
+ // we pop the list to empty, reset() the running coroutine's local_ptr.
void cleanup_initializing_()
{
- InitializingMap::iterator found = mInitializing.find(llcoro::get_id());
- if (found != mInitializing.end())
- {
- mInitializing.erase(found);
- }
+ mInitializing.reset(nullptr);
}
};
-//static
-LLSingletonBase::list_t& LLSingletonBase::get_master()
-{
- return LLSingletonBase::MasterList::instance().mMaster;
-}
-
void LLSingletonBase::add_master()
{
// As each new LLSingleton is constructed, add to the master list.
- get_master().push_back(this);
+ // This temporary LockedMaster should suffice to hold the MasterList lock
+ // during the push_back() call.
+ MasterList::LockedMaster().get().push_back(this);
}
void LLSingletonBase::remove_master()
@@ -125,27 +191,32 @@ void LLSingletonBase::remove_master()
// master list, and remove this item IF FOUND. We have few enough
// LLSingletons, and they are so rarely destroyed (once per run), that the
// cost of a linear search should not be an issue.
- get_master().remove(this);
+ // This temporary LockedMaster should suffice to hold the MasterList lock
+ // during the remove() call.
+ MasterList::LockedMaster().get().remove(this);
}
//static
-LLSingletonBase::list_t& LLSingletonBase::get_initializing()
+LLSingletonBase::list_t::size_type LLSingletonBase::get_initializing_size()
{
- return LLSingletonBase::MasterList::instance().get_initializing_();
+ return MasterList::LockedInitializing().get().size();
}
LLSingletonBase::~LLSingletonBase() {}
void LLSingletonBase::push_initializing(const char* name)
{
+ MasterList::LockedInitializing locked_list;
// log BEFORE pushing so logging singletons don't cry circularity
- log_initializing("Pushing", name);
- get_initializing().push_back(this);
+ locked_list.log("Pushing", name);
+ locked_list.get().push_back(this);
}
void LLSingletonBase::pop_initializing()
{
- list_t& list(get_initializing());
+ // Lock the MasterList for the duration of this call
+ MasterList::LockedInitializing locked_list;
+ list_t& list(locked_list.get());
if (list.empty())
{
@@ -165,7 +236,7 @@ void LLSingletonBase::pop_initializing()
// entirely.
if (list.empty())
{
- MasterList::instance().cleanup_initializing_();
+ locked_list.cleanup_initializing();
}
// Now validate the newly-popped LLSingleton.
@@ -177,7 +248,7 @@ void LLSingletonBase::pop_initializing()
}
// log AFTER popping so logging singletons don't cry circularity
- log_initializing("Popping", typeid(*back).name());
+ locked_list.log("Popping", typeid(*back).name());
}
void LLSingletonBase::reset_initializing(list_t::size_type size)
@@ -191,7 +262,8 @@ void LLSingletonBase::reset_initializing(list_t::size_type size)
// push_initializing() call in LLSingletonBase's constructor. So only
// remove the stack top if in fact we've pushed something more than the
// previous size.
- list_t& list(get_initializing());
+ MasterList::LockedInitializing locked_list;
+ list_t& list(locked_list.get());
while (list.size() > size)
{
@@ -201,29 +273,32 @@ void LLSingletonBase::reset_initializing(list_t::size_type size)
// as in pop_initializing()
if (list.empty())
{
- MasterList::instance().cleanup_initializing_();
+ locked_list.cleanup_initializing();
}
}
-//static
-void LLSingletonBase::log_initializing(const char* verb, const char* name)
+void LLSingletonBase::MasterList::LockedInitializing::log(const char* verb, const char* name)
{
if (oktolog())
{
LL_DEBUGS("LLSingleton") << verb << ' ' << demangle(name) << ';';
- list_t& list(get_initializing());
- for (list_t::const_reverse_iterator ri(list.rbegin()), rend(list.rend());
- ri != rend; ++ri)
+ if (mList)
{
- LLSingletonBase* sb(*ri);
- LL_CONT << ' ' << classname(sb);
+ for (list_t::const_reverse_iterator ri(mList->rbegin()), rend(mList->rend());
+ ri != rend; ++ri)
+ {
+ LLSingletonBase* sb(*ri);
+ LL_CONT << ' ' << classname(sb);
+ }
}
LL_ENDL;
}
}
-void LLSingletonBase::capture_dependency(list_t& initializing, EInitState initState)
+void LLSingletonBase::capture_dependency()
{
+ MasterList::LockedInitializing locked_list;
+ list_t& initializing(locked_list.get());
// Did this getInstance() call come from another LLSingleton, or from
// vanilla application code? Note that although this is a nontrivial
// method, the vast majority of its calls arrive here with initializing
@@ -252,21 +327,8 @@ void LLSingletonBase::capture_dependency(list_t& initializing, EInitState initSt
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,
@@ -309,12 +371,12 @@ LLSingletonBase::vec_t LLSingletonBase::dep_sort()
// SingletonDeps through the life of the program, dynamically adding and
// removing LLSingletons as they are created and destroyed, in practice
// it's less messy to construct it on demand. The overhead of doing so
- // should happen basically twice: once for cleanupAll(), once for
- // deleteAll().
+ // should happen basically once: for deleteAll().
typedef LLDependencies<LLSingletonBase*> SingletonDeps;
SingletonDeps sdeps;
- list_t& master(get_master());
- BOOST_FOREACH(LLSingletonBase* sp, master)
+ // Lock while traversing the master list
+ MasterList::LockedMaster master;
+ for (LLSingletonBase* sp : master.get())
{
// Build the SingletonDeps structure by adding, for each
// LLSingletonBase* sp in the master list, sp itself. It has no
@@ -326,51 +388,32 @@ LLSingletonBase::vec_t LLSingletonBase::dep_sort()
SingletonDeps::KeyList(sp->mDepends.begin(), sp->mDepends.end()));
}
vec_t ret;
- ret.reserve(master.size());
+ ret.reserve(master.get().size());
// We should be able to effect this with a transform_iterator that
// 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;
}
-//static
-void LLSingletonBase::cleanupAll()
+void LLSingletonBase::cleanup_()
{
- // It's essential to traverse these in dependency order.
- BOOST_FOREACH(LLSingletonBase* sp, dep_sort())
+ logdebugs("calling ", classname(this).c_str(), "::cleanupSingleton()");
+ try
{
- // Call cleanupSingleton() only if we haven't already done so for this
- // instance.
- if (! sp->mCleaned)
- {
- sp->mCleaned = true;
-
- logdebugs("calling ",
- classname(sp).c_str(), "::cleanupSingleton()");
- try
- {
- sp->cleanupSingleton();
- }
- catch (const std::exception& e)
- {
- logwarns("Exception in ", classname(sp).c_str(),
- "::cleanupSingleton(): ", e.what());
- }
- catch (...)
- {
- logwarns("Unknown exception in ", classname(sp).c_str(),
- "::cleanupSingleton()");
- }
- }
+ cleanupSingleton();
+ }
+ catch (...)
+ {
+ LOG_UNHANDLED_EXCEPTION(classname(this) + "::cleanupSingleton()");
}
}
@@ -441,10 +484,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
@@ -454,6 +493,18 @@ void LLSingletonBase::logwarns(const char* p1, const char* p2, const char* p3, c
}
//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)
{
log(LLError::LEVEL_ERROR, p1, p2, p3, p4);
diff --git a/indra/llcommon/llsingleton.h b/indra/llcommon/llsingleton.h
index 7def9b019c..30a5b21cf8 100644
--- a/indra/llcommon/llsingleton.h
+++ b/indra/llcommon/llsingleton.h
@@ -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
{
@@ -51,15 +43,13 @@ public:
private:
// All existing LLSingleton instances are tracked in this master list.
typedef std::list<LLSingletonBase*> list_t;
- static list_t& get_master();
- // This, on the other hand, is a stack whose top indicates the LLSingleton
- // currently being initialized.
- static list_t& get_initializing();
+ // Size of stack whose top indicates the LLSingleton currently being
+ // initialized.
+ static list_t::size_type get_initializing_size();
// Produce a vector<LLSingletonBase*> of master list, in dependency order.
typedef std::vector<LLSingletonBase*> vec_t;
static vec_t dep_sort();
- bool mCleaned; // cleanupSingleton() has been called
// we directly depend on these other LLSingletons
typedef boost::unordered_set<LLSingletonBase*> set_t;
set_t mDepends;
@@ -68,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
@@ -115,21 +105,23 @@ protected:
// Remove 'this' from the init stack in case of exception in the
// LLSingleton subclass constructor.
static void reset_initializing(list_t::size_type size);
-private:
- // logging
- static void log_initializing(const char* verb, const char* name);
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(list_t& initializing, 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);
+ // these classname() declarations restate template functions declared in
+ // llerror.h because we avoid #including that here
template <typename T>
static std::string classname() { return demangle(typeid(T).name()); }
template <typename T>
@@ -139,6 +131,9 @@ protected:
virtual void initSingleton() {}
virtual void cleanupSingleton() {}
+ // internal wrapper around calls to cleanupSingleton()
+ void cleanup_();
+
// deleteSingleton() isn't -- and shouldn't be -- a virtual method. It's a
// class static. However, given only Foo*, deleteAll() does need to be
// able to reach Foo::deleteSingleton(). Make LLSingleton (which declares
@@ -148,32 +143,15 @@ protected:
public:
/**
- * Call this to call the cleanupSingleton() method for every LLSingleton
- * constructed since the start of the last cleanupAll() call. (Any
- * LLSingleton constructed DURING a cleanupAll() call won't be cleaned up
- * until the next cleanupAll() call.) cleanupSingleton() neither deletes
- * nor destroys its LLSingleton; therefore it's safe to include logic that
- * might take significant realtime or even throw an exception.
- *
- * The most important property of cleanupAll() is that cleanupSingleton()
- * methods are called in dependency order, leaf classes last. Thus, given
- * two LLSingleton subclasses A and B, if A's dependency on B is properly
- * expressed as a B::getInstance() or B::instance() call during either
- * A::A() or A::initSingleton(), B will be cleaned up after A.
- *
- * If a cleanupSingleton() method throws an exception, the exception is
- * logged, but cleanupAll() attempts to continue calling the rest of the
- * cleanupSingleton() methods.
- */
- static void cleanupAll();
- /**
- * Call this to call the deleteSingleton() method for every LLSingleton
- * constructed since the start of the last deleteAll() call. (Any
- * LLSingleton constructed DURING a deleteAll() call won't be cleaned up
- * until the next deleteAll() call.) deleteSingleton() deletes and
- * destroys its LLSingleton. Any cleanup logic that might take significant
- * realtime -- or throw an exception -- must not be placed in your
- * LLSingleton's destructor, but rather in its cleanupSingleton() method.
+ * deleteAll() calls the cleanupSingleton() and deleteSingleton() methods
+ * for every LLSingleton constructed since the start of the last
+ * deleteAll() call. (Any LLSingleton constructed DURING a deleteAll()
+ * call won't be cleaned up until the next deleteAll() call.)
+ * deleteSingleton() deletes and destroys its LLSingleton. Any cleanup
+ * logic that might take significant realtime -- or throw an exception --
+ * must not be placed in your LLSingleton's destructor, but rather in its
+ * cleanupSingleton() method, which is called implicitly by
+ * deleteSingleton().
*
* The most important property of deleteAll() is that deleteSingleton()
* methods are called in dependency order, leaf classes last. Thus, given
@@ -181,9 +159,9 @@ public:
* expressed as a B::getInstance() or B::instance() call during either
* A::A() or A::initSingleton(), B will be cleaned up after A.
*
- * If a deleteSingleton() method throws an exception, the exception is
- * logged, but deleteAll() attempts to continue calling the rest of the
- * deleteSingleton() methods.
+ * If a cleanupSingleton() or deleteSingleton() method throws an
+ * exception, the exception is logged, but deleteAll() attempts to
+ * continue calling the rest of the deleteSingleton() methods.
*/
static void deleteAll();
};
@@ -203,9 +181,16 @@ struct LLSingleton_manage_master
{
LLSingletonBase::reset_initializing(size);
}
- // For any LLSingleton subclass except the MasterList, obtain the init
- // stack from the MasterList singleton instance.
- LLSingletonBase::list_t& get_initializing() { return LLSingletonBase::get_initializing(); }
+ // For any LLSingleton subclass except the MasterList, obtain the size of
+ // the init stack from the MasterList singleton instance.
+ LLSingletonBase::list_t::size_type get_initializing_size()
+ {
+ return LLSingletonBase::get_initializing_size();
+ }
+ void capture_dependency(LLSingletonBase* sb)
+ {
+ sb->capture_dependency();
+ }
};
// But for the specific case of LLSingletonBase::MasterList, don't.
@@ -218,20 +203,14 @@ struct LLSingleton_manage_master<LLSingletonBase::MasterList>
void pop_initializing (LLSingletonBase*) {}
// since we never pushed, no need to clean up
void reset_initializing(LLSingletonBase::list_t::size_type size) {}
- LLSingletonBase::list_t& get_initializing()
- {
- // The MasterList shouldn't depend on any other LLSingletons. We'd
- // get into trouble if we tried to recursively engage that machinery.
- static LLSingletonBase::list_t sDummyList;
- return sDummyList;
- }
+ LLSingletonBase::list_t::size_type get_initializing_size() { return 0; }
+ void capture_dependency(LLSingletonBase*) {}
};
// Now we can implement LLSingletonBase's template constructor.
template <typename DERIVED_TYPE>
LLSingletonBase::LLSingletonBase(tag<DERIVED_TYPE>):
- mCleaned(false),
- 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
@@ -273,10 +252,19 @@ class LLParamSingleton;
* leading back to yours, move the instance reference from your constructor to
* your initSingleton() method.
*
- * If you override LLSingleton<T>::cleanupSingleton(), your method will be
- * called if someone calls LLSingletonBase::cleanupAll(). The significant part
- * of this promise is that cleanupAll() will call individual
- * cleanupSingleton() methods in reverse dependency order.
+ * If you override LLSingleton<T>::cleanupSingleton(), your method will
+ * implicitly be called by LLSingleton<T>::deleteSingleton() just before the
+ * instance is destroyed. We introduce a special cleanupSingleton() method
+ * because cleanupSingleton() operations can involve nontrivial realtime, or
+ * throw an exception. A destructor should do neither!
+ *
+ * If your cleanupSingleton() method throws an exception, we log that
+ * exception but carry on.
+ *
+ * If at some point you call LLSingletonBase::deleteAll(), all remaining
+ * LLSingleton<T> instances will be destroyed in reverse dependency order. (Or
+ * call MySubclass::deleteSingleton() to specifically destroy the canonical
+ * MySubclass instance.)
*
* That is, consider LLSingleton subclasses C, B and A. A depends on B, which
* in turn depends on C. These dependencies are expressed as calls to
@@ -284,33 +272,34 @@ class LLParamSingleton;
* It shouldn't matter whether these calls appear in A::A() or
* A::initSingleton(), likewise B::B() or B::initSingleton().
*
- * We promise that if you later call LLSingletonBase::cleanupAll():
- * 1. A::cleanupSingleton() will be called before
- * 2. B::cleanupSingleton(), which will be called before
- * 3. C::cleanupSingleton().
+ * We promise that if you later call LLSingletonBase::deleteAll():
+ * 1. A::deleteSingleton() will be called before
+ * 2. B::deleteSingleton(), which will be called before
+ * 3. C::deleteSingleton().
* Put differently, if your LLSingleton subclass constructor or
* 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.)
- *
- * As currently written, LLSingleton is not thread-safe.
*/
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>;
@@ -319,17 +308,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;
+ auto prev_size = LLSingleton_manage_master<DERIVED_TYPE>().get_initializing_size();
+ // 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)
{
@@ -343,62 +332,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);
}
- // Without this 'using' declaration, the static method we're declaring
- // here would hide the base-class method we want it to call.
- using LLSingletonBase::capture_dependency;
- 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.
- sData.mInstance->capture_dependency(
- LLSingleton_manage_master<DERIVED_TYPE>().get_initializing(),
- sData.mInitState);
+ LLSingleton_manage_master<DERIVED_TYPE>().capture_dependency(sb);
}
// We know of no way to instruct the compiler that every subclass
@@ -411,20 +394,6 @@ private:
// subclass body.
virtual void you_must_use_LLSINGLETON_macro() = 0;
- // The purpose of this struct is to engage the C++11 guarantee that static
- // variables declared in function scope are initialized exactly once, even
- // if multiple threads concurrently reach the same declaration.
- // https://en.cppreference.com/w/cpp/language/storage_duration#Static_local_variables
- // Since getInstance() declares a static instance of SingletonInitializer,
- // only the first call to getInstance() calls constructSingleton().
- struct SingletonInitializer
- {
- SingletonInitializer()
- {
- constructSingleton();
- }
- };
-
protected:
// Pass DERIVED_TYPE explicitly to LLSingletonBase's constructor because,
// until our subclass constructor completes, *this isn't yet a
@@ -439,97 +408,176 @@ protected:
LLSingleton_manage_master<DERIVED_TYPE>().add(this);
}
-public:
+protected:
virtual ~LLSingleton()
{
- // remove this instance from the master list
+ // 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.
LLSingleton_manage_master<DERIVED_TYPE>().remove(this);
- sData.mInstance = NULL;
- sData.mInitState = DELETED;
}
+public:
/**
- * @brief Immediately delete the singleton.
+ * @brief Cleanup and destroy the singleton instance.
*
- * A subsequent call to LLProxy::getInstance() will construct a new
- * instance of the class.
+ * deleteSingleton() calls this instance's cleanupSingleton() method and
+ * then destroys the instance.
*
- * Without an explicit call to LLSingletonBase::deleteAll(), LLSingletons
- * are implicitly destroyed after main() has exited and the C++ runtime is
- * cleaning up statically-constructed objects. Some classes derived from
- * LLSingleton have objects that are part of a runtime system that is
- * terminated before main() exits. Calling the destructor of those objects
- * after the termination of their respective systems can cause crashes and
- * other problems during termination of the project. Using this method to
- * destroy the singleton early can prevent these crashes.
+ * A subsequent call to LLSingleton<T>::getInstance() will construct a new
+ * instance of the class.
*
- * An example where this is needed is for a LLSingleton that has an APR
- * object as a member that makes APR calls on destruction. The APR system is
- * shut down explicitly before main() exits. This causes a crash on exit.
- * Using this method before the call to apr_terminate() and NOT calling
- * getInstance() again will prevent the crash.
+ * Without an explicit call to LLSingletonBase::deleteAll(), or
+ * LLSingleton<T>::deleteSingleton(), LLSingleton instances are simply
+ * leaked. (Allowing implicit destruction at shutdown caused too many
+ * problems.)
*/
static void deleteSingleton()
{
- delete sData.mInstance;
- // SingletonData state handled by destructor, above
+ // 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)
+ {
+ lk->mInstance->cleanup_();
+ delete lk->mInstance;
+ // destructor clears mInstance (and mInitState)
+ }
}
static DERIVED_TYPE* getInstance()
{
- // call constructSingleton() only the first time we get here
- static SingletonInitializer sInitializer;
-
- switch (sData.mInitState)
- {
- case UNINITIALIZED:
- // should never be uninitialized at this point
- logerrs("Uninitialized singleton ",
- classname<DERIVED_TYPE>().c_str());
- return NULL;
-
- 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;
-
- case CONSTRUCTED:
- // first time through: set to CONSTRUCTED by
- // constructSingleton(), called by sInitializer's constructor;
- // still have to call initSingleton()
- 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
- break;
-
- case DELETED:
- // called after deleteSingleton()
- logwarns("Trying to access deleted singleton ",
- classname<DERIVED_TYPE>().c_str(),
- " -- creating new instance");
- // This recovery sequence is NOT thread-safe! We would need a
- // recursive_mutex a la LLParamSingleton.
- constructSingleton();
- finishInitializing();
- break;
- }
-
- // record the dependency, if any: check if we got here from another
- // LLSingleton's constructor or initSingleton() method
- capture_dependency();
- return sData.mInstance;
+ // 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.)
+
+ // 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.
+
+ // 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.
+
+ { // nested scope for 'lk'
+ // In case racing threads call getInstance() at the same moment,
+ // serialize the calls.
+ LockStatic lk;
+
+ 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 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;
+
+ 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();
@@ -539,7 +587,9 @@ public:
// Use this to avoid accessing singletons before they can safely be constructed.
static bool instanceExists()
{
- return sData.mInitState == INITIALIZED;
+ // defend any access to sData from racing threads
+ LockStatic lk;
+ return lk->mInitState == INITIALIZED;
}
// Has this singleton been deleted? This can be useful during shutdown
@@ -547,23 +597,12 @@ public:
// cleaned up.
static bool wasDeleted()
{
- return sData.mInitState == DELETED;
+ // defend any access to sData from racing threads
+ 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:
@@ -588,47 +627,86 @@ class LLParamSingleton : public LLSingleton<DERIVED_TYPE>
{
private:
typedef LLSingleton<DERIVED_TYPE> super;
- // Use a recursive_mutex in case of constructor circularity. With a
- // non-recursive mutex, that would result in deadlock rather than the
- // logerrs() call in getInstance().
- typedef std::recursive_mutex mutex_t;
+ using typename super::LockStatic;
-public:
- using super::deleteSingleton;
- using super::instanceExists;
- using super::wasDeleted;
-
- // Passes arguments to DERIVED_TYPE's constructor and sets appropriate states
+ // Passes arguments to DERIVED_TYPE's constructor and sets appropriate
+ // states, returning a pointer to the new instance.
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.
- std::unique_lock<mutex_t> lk(getMutex());
+ LockStatic lk;
// For organizational purposes this function shouldn't be called twice
- if (super::sData.mInitState != super::UNINITIALIZED)
+ 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
{
- super::constructSingleton(std::forward<Args>(args)...);
- super::finishInitializing();
+ // 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;
+
+ /// initParamSingleton() constructs the instance, returning a reference.
+ /// Pass whatever arguments are required to construct DERIVED_TYPE.
+ template <typename... Args>
+ static DERIVED_TYPE& initParamSingleton(Args&&... args)
+ {
+ 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.
- std::unique_lock<mutex_t> lk(getMutex());
+ 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;
@@ -639,25 +717,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 ",
@@ -677,30 +743,6 @@ public:
{
return *getInstance();
}
-
-private:
- // sMutex must be a function-local static rather than a static member. One
- // of the essential features of LLSingleton and friends is that they must
- // support getInstance() 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 mutex_t& getMutex()
- {
- static mutex_t sMutex;
- return sMutex;
- }
};
/**
@@ -725,9 +767,9 @@ public:
using super::instanceExists;
using super::wasDeleted;
- static void construct()
+ static DT* construct()
{
- super::initParamSingleton();
+ return super::initParamSingleton();
}
};
diff --git a/indra/llcommon/llstacktrace.cpp b/indra/llcommon/llstacktrace.cpp
index bbf0e1e141..80057bf0f2 100644
--- a/indra/llcommon/llstacktrace.cpp
+++ b/indra/llcommon/llstacktrace.cpp
@@ -33,7 +33,10 @@
#include <sstream>
#include "llwin32headerslean.h"
-#include "Dbghelp.h"
+#pragma warning (push)
+#pragma warning (disable:4091) // a microsoft header has warnings. Very nice.
+#include <dbghelp.h>
+#pragma warning (pop)
typedef USHORT NTAPI RtlCaptureStackBackTrace_Function(
IN ULONG frames_to_skip,
diff --git a/indra/llcommon/llstring.cpp b/indra/llcommon/llstring.cpp
index 0174c411b4..0290eea143 100644
--- a/indra/llcommon/llstring.cpp
+++ b/indra/llcommon/llstring.cpp
@@ -657,22 +657,6 @@ std::string utf8str_removeCRLF(const std::string& utf8str)
}
#if LL_WINDOWS
-// documentation moved to header. Phoenix 2007-11-27
-namespace snprintf_hack
-{
- int snprintf(char *str, size_t size, const char *format, ...)
- {
- va_list args;
- va_start(args, format);
-
- int num_written = _vsnprintf(str, size, format, args); /* Flawfinder: ignore */
- va_end(args);
-
- str[size-1] = '\0'; // always null terminate
- return num_written;
- }
-}
-
std::string ll_convert_wide_to_string(const wchar_t* in)
{
return ll_convert_wide_to_string(in, CP_UTF8);
diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h
index b619a9e48c..6b1a1e0a03 100644
--- a/indra/llcommon/llstring.h
+++ b/indra/llcommon/llstring.h
@@ -707,32 +707,6 @@ LL_COMMON_API std::string utf8str_removeCRLF(const std::string& utf8str);
//@{
/**
- * @brief Implementation the expected snprintf interface.
- *
- * If the size of the passed in buffer is not large enough to hold the string,
- * two bad things happen:
- * 1. resulting formatted string is NOT null terminated
- * 2. Depending on the platform, the return value could be a) the required
- * size of the buffer to copy the entire formatted string or b) -1.
- * On Windows with VS.Net 2003, it returns -1 e.g.
- *
- * safe_snprintf always adds a NULL terminator so that the caller does not
- * need to check for return value or need to add the NULL terminator.
- * It does not, however change the return value - to let the caller know
- * that the passed in buffer size was not large enough to hold the
- * formatted string.
- *
- */
-
-// Deal with the differeneces on Windows
-namespace snprintf_hack
-{
- LL_COMMON_API int snprintf(char *str, size_t size, const char *format, ...);
-}
-
-using snprintf_hack::snprintf;
-
-/**
* @brief Convert a wide string to std::string
*
* This replaces the unsafe W2A macro from ATL.
diff --git a/indra/llcommon/lltempredirect.cpp b/indra/llcommon/lltempredirect.cpp
new file mode 100644
index 0000000000..ec194c1d29
--- /dev/null
+++ b/indra/llcommon/lltempredirect.cpp
@@ -0,0 +1,138 @@
+/**
+ * @file lltempredirect.cpp
+ * @author Nat Goodspeed
+ * @date 2019-10-31
+ * @brief Implementation for lltempredirect.
+ *
+ * $LicenseInfo:firstyear=2019&license=viewerlgpl$
+ * Copyright (c) 2019, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+// Precompiled header
+#include "linden_common.h"
+// associated header
+#include "lltempredirect.h"
+// STL headers
+// std headers
+#if !LL_WINDOWS
+# include <unistd.h>
+#else
+# include <io.h>
+#endif // !LL_WINDOWS
+// external library headers
+// other Linden headers
+
+/*****************************************************************************
+* llfd
+*****************************************************************************/
+// We could restate the implementation of each of llfd::close(), etc., but
+// this is way more succinct.
+#if LL_WINDOWS
+#define fhclose _close
+#define fhdup _dup
+#define fhdup2 _dup2
+#define fhfdopen _fdopen
+#define fhfileno _fileno
+#else
+#define fhclose ::close
+#define fhdup ::dup
+#define fhdup2 ::dup2
+#define fhfdopen ::fdopen
+#define fhfileno ::fileno
+#endif
+
+int llfd::close(int fd)
+{
+ return fhclose(fd);
+}
+
+int llfd::dup(int target)
+{
+ return fhdup(target);
+}
+
+int llfd::dup2(int target, int reference)
+{
+ return fhdup2(target, reference);
+}
+
+FILE* llfd::open(int fd, const char* mode)
+{
+ return fhfdopen(fd, mode);
+}
+
+int llfd::fileno(FILE* stream)
+{
+ return fhfileno(stream);
+}
+
+/*****************************************************************************
+* LLTempRedirect
+*****************************************************************************/
+LLTempRedirect::LLTempRedirect():
+ mOrigTarget(-1), // -1 is an invalid file descriptor
+ mReference(-1)
+{}
+
+LLTempRedirect::LLTempRedirect(FILE* target, FILE* reference):
+ LLTempRedirect((target? fhfileno(target) : -1),
+ (reference? fhfileno(reference) : -1))
+{}
+
+LLTempRedirect::LLTempRedirect(int target, int reference):
+ // capture a duplicate file descriptor for the file originally targeted by
+ // 'reference'
+ mOrigTarget((reference >= 0)? fhdup(reference) : -1),
+ mReference(reference)
+{
+ if (target >= 0 && reference >= 0)
+ {
+ // As promised, force 'reference' to refer to 'target'. This first
+ // implicitly closes 'reference', which is why we first capture a
+ // duplicate so the original target file stays open.
+ fhdup2(target, reference);
+ }
+}
+
+LLTempRedirect::LLTempRedirect(LLTempRedirect&& other)
+{
+ mOrigTarget = other.mOrigTarget;
+ mReference = other.mReference;
+ // other LLTempRedirect must be in moved-from state so its destructor
+ // won't repeat the same operations as ours!
+ other.mOrigTarget = -1;
+ other.mReference = -1;
+}
+
+LLTempRedirect::~LLTempRedirect()
+{
+ reset();
+}
+
+void LLTempRedirect::reset()
+{
+ // If this instance was default-constructed (or constructed with an
+ // invalid file descriptor), skip the following.
+ if (mOrigTarget >= 0)
+ {
+ // Restore mReference to point to mOrigTarget. This implicitly closes
+ // the duplicate created by our constructor of its 'target' file
+ // descriptor.
+ fhdup2(mOrigTarget, mReference);
+ // mOrigTarget has served its purpose
+ fhclose(mOrigTarget);
+ }
+ // assign these because reset() is also responsible for a "moved from"
+ // instance
+ mOrigTarget = -1;
+ mReference = -1;
+}
+
+LLTempRedirect& LLTempRedirect::operator=(LLTempRedirect&& other)
+{
+ reset();
+ std::swap(mOrigTarget, other.mOrigTarget);
+ std::swap(mReference, other.mReference);
+ return *this;
+}
diff --git a/indra/llcommon/lltempredirect.h b/indra/llcommon/lltempredirect.h
new file mode 100644
index 0000000000..33e05dc06b
--- /dev/null
+++ b/indra/llcommon/lltempredirect.h
@@ -0,0 +1,91 @@
+/**
+ * @file lltempredirect.h
+ * @author Nat Goodspeed
+ * @date 2019-10-31
+ * @brief RAII low-level file-descriptor redirection
+ *
+ * $LicenseInfo:firstyear=2019&license=viewerlgpl$
+ * Copyright (c) 2019, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if ! defined(LL_LLTEMPREDIRECT_H)
+#define LL_LLTEMPREDIRECT_H
+
+// Functions in this namespace are intended to insulate the caller from the
+// aggravating distinction between ::close() and Microsoft _close().
+namespace llfd
+{
+
+int close(int fd);
+int dup(int target);
+int dup2(int target, int reference);
+FILE* open(int fd, const char* mode);
+int fileno(FILE* stream);
+
+} // namespace llfd
+
+/**
+ * LLTempRedirect is an RAII class that performs file redirection on low-level
+ * file descriptors, expressed as ints. (Use llfd::fileno() to obtain the file
+ * descriptor from a classic-C FILE*. There is no portable way to obtain the
+ * file descriptor from a std::fstream.)
+ *
+ * Instantiate LLTempRedirect with a target file descriptor (e.g. for some
+ * open file) and a reference file descriptor (e.g. for stderr). From that
+ * point until the LLTempRedirect instance is destroyed, all OS-level writes
+ * to the reference file descriptor will be redirected to the target file.
+ *
+ * Because dup2() is used for redirection, the original passed target file
+ * descriptor remains open. If you want LLTempRedirect's destructor to close
+ * the target file, close() the target file descriptor after passing it to
+ * LLTempRedirect's constructor.
+ *
+ * LLTempRedirect's constructor saves the original target of the reference
+ * file descriptor. Its destructor restores the reference file descriptor to
+ * point once again to its original target.
+ */
+class LLTempRedirect
+{
+public:
+ LLTempRedirect();
+ /**
+ * For the lifespan of this LLTempRedirect instance, all writes to
+ * 'reference' will be redirected to 'target'. When this LLTempRedirect is
+ * destroyed, the original target for 'reference' will be restored.
+ *
+ * Pass 'target' as NULL if you simply want to save and restore
+ * 'reference' against possible redirection in the meantime.
+ */
+ LLTempRedirect(FILE* target, FILE* reference);
+ /**
+ * For the lifespan of this LLTempRedirect instance, all writes to
+ * 'reference' will be redirected to 'target'. When this LLTempRedirect is
+ * destroyed, the original target for 'reference' will be restored.
+ *
+ * Pass 'target' as -1 if you simply want to save and restore
+ * 'reference' against possible redirection in the meantime.
+ */
+ LLTempRedirect(int target, int reference);
+ LLTempRedirect(const LLTempRedirect&) = delete;
+ LLTempRedirect(LLTempRedirect&& other);
+
+ ~LLTempRedirect();
+
+ LLTempRedirect& operator=(const LLTempRedirect&) = delete;
+ LLTempRedirect& operator=(LLTempRedirect&& other);
+
+ /// returns (duplicate file descriptor for) the original target of the
+ /// 'reference' file descriptor passed to our constructor
+ int getOriginalTarget() const { return mOrigTarget; }
+ /// returns the original 'reference' file descriptor passed to our
+ /// constructor
+ int getReference() const { return mReference; }
+
+private:
+ void reset();
+
+ int mOrigTarget, mReference;
+};
+
+#endif /* ! defined(LL_LLTEMPREDIRECT_H) */
diff --git a/indra/llcommon/llthread.cpp b/indra/llcommon/llthread.cpp
index a4171729db..0b9dec969c 100644
--- a/indra/llcommon/llthread.cpp
+++ b/indra/llcommon/llthread.cpp
@@ -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();
+}
+
//============================================================================
//----------------------------------------------------------------------------
diff --git a/indra/llcommon/llthread.h b/indra/llcommon/llthread.h
index 863c9051f3..5cd0731f6c 100644
--- a/indra/llcommon/llthread.h
+++ b/indra/llcommon/llthread.h
@@ -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
diff --git a/indra/llcommon/llthreadlocalstorage.cpp b/indra/llcommon/llthreadlocalstorage.cpp
index 8cef05caac..d8a063e8d5 100644
--- a/indra/llcommon/llthreadlocalstorage.cpp
+++ b/indra/llcommon/llthreadlocalstorage.cpp
@@ -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;
}
diff --git a/indra/llcommon/llthreadsafequeue.h b/indra/llcommon/llthreadsafequeue.h
index b0bddac8e5..30dd507f73 100644
--- a/indra/llcommon/llthreadsafequeue.h
+++ b/indra/llcommon/llthreadsafequeue.h
@@ -30,18 +30,12 @@
#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>
-#include <condition_variable>
-
-#if LL_WINDOWS
-#pragma warning (pop)
-#endif
+#include <chrono>
+#include "mutex.h"
+#include "llcoros.h"
+#include LLCOROS_MUTEX_HEADER
+#include <boost/fiber/timed_mutex.hpp>
+#include LLCOROS_CONDVAR_HEADER
//
// A general queue exception.
@@ -88,18 +82,28 @@ 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);
- // Try to add an element to the front ofqueue without blocking. Returns
+ // Try to add an element to the front of queue without blocking. Returns
// true only if the element was actually added.
bool tryPushFront(ElementT const & element);
-
+
+ // Try to add an element to the front of queue, blocking if full but with
+ // timeout. Returns true if the element was added.
+ // There are potentially two different timeouts involved: how long to try
+ // to lock the mutex, versus how long to wait for the queue to stop being
+ // full. Careful settings for each timeout might be orders of magnitude
+ // apart. However, this method conflates them.
+ template <typename Rep, typename Period>
+ bool tryPushFrontFor(const std::chrono::duration<Rep, Period>& timeout,
+ ElementT const & element);
+
// 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,13 +114,29 @@ 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;
- std::mutex mLock;
- std::condition_variable mCapacityCond;
- std::condition_variable mEmptyCond;
+ boost::fibers::timed_mutex mLock;
+ typedef std::unique_lock<decltype(mLock)> lock_t;
+ boost::fibers::condition_variable_any mCapacityCond;
+ boost::fibers::condition_variable_any mEmptyCond;
};
// LLThreadSafeQueue
@@ -124,7 +144,8 @@ private:
template<typename ElementT>
LLThreadSafeQueue<ElementT>::LLThreadSafeQueue(U32 capacity) :
-mCapacity(capacity)
+ mCapacity(capacity),
+ mClosed(false)
{
}
@@ -132,13 +153,18 @@ mCapacity(capacity)
template<typename ElementT>
void LLThreadSafeQueue<ElementT>::pushFront(ElementT const & element)
{
+ lock_t lock1(mLock);
while (true)
{
- std::unique_lock<std::mutex> lock1(mLock);
+ if (mClosed)
+ {
+ LLTHROW(LLThreadSafeQueueInterrupt());
+ }
if (mStorage.size() < mCapacity)
{
mStorage.push_front(element);
+ lock1.unlock();
mEmptyCond.notify_one();
return;
}
@@ -149,17 +175,61 @@ void LLThreadSafeQueue<ElementT>::pushFront(ElementT const & element)
}
+template <typename ElementT>
+template <typename Rep, typename Period>
+bool LLThreadSafeQueue<ElementT>::tryPushFrontFor(const std::chrono::duration<Rep, Period>& timeout,
+ ElementT const & element)
+{
+ // Convert duration to time_point: passing the same timeout duration to
+ // each of multiple calls is wrong.
+ auto endpoint = std::chrono::steady_clock::now() + timeout;
+
+ lock_t lock1(mLock, std::defer_lock);
+ if (!lock1.try_lock_until(endpoint))
+ return false;
+
+ while (true)
+ {
+ if (mClosed)
+ {
+ return false;
+ }
+
+ if (mStorage.size() < mCapacity)
+ {
+ mStorage.push_front(element);
+ lock1.unlock();
+ mEmptyCond.notify_one();
+ return true;
+ }
+
+ // Storage Full. Wait for signal.
+ if (LLCoros::cv_status::timeout == mCapacityCond.wait_until(lock1, endpoint))
+ {
+ // timed out -- formally we might recheck both conditions above
+ return false;
+ }
+ // If we didn't time out, we were notified for some reason. Loop back
+ // to check.
+ }
+}
+
+
template<typename ElementT>
bool LLThreadSafeQueue<ElementT>::tryPushFront(ElementT const & element)
{
- std::unique_lock<std::mutex> 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;
}
@@ -168,18 +238,23 @@ bool LLThreadSafeQueue<ElementT>::tryPushFront(ElementT const & element)
template<typename ElementT>
ElementT LLThreadSafeQueue<ElementT>::popBack(void)
{
+ lock_t lock1(mLock);
while (true)
{
- std::unique_lock<std::mutex> lock1(mLock);
-
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);
}
@@ -189,15 +264,18 @@ ElementT LLThreadSafeQueue<ElementT>::popBack(void)
template<typename ElementT>
bool LLThreadSafeQueue<ElementT>::tryPopBack(ElementT & element)
{
- std::unique_lock<std::mutex> 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;
}
@@ -206,8 +284,34 @@ bool LLThreadSafeQueue<ElementT>::tryPopBack(ElementT & element)
template<typename ElementT>
size_t LLThreadSafeQueue<ElementT>::size(void)
{
- std::lock_guard<std::mutex> 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
diff --git a/indra/llcommon/lltrace.h b/indra/llcommon/lltrace.h
index 79ff55b739..0d0cd6f581 100644
--- a/indra/llcommon/lltrace.h
+++ b/indra/llcommon/lltrace.h
@@ -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; }
diff --git a/indra/llcommon/lltraceaccumulators.cpp b/indra/llcommon/lltraceaccumulators.cpp
index 385d31edd7..b1c23c6fb7 100644
--- a/indra/llcommon/lltraceaccumulators.cpp
+++ b/indra/llcommon/lltraceaccumulators.cpp
@@ -291,8 +291,8 @@ void EventAccumulator::reset( const EventAccumulator* other )
{
mNumSamples = 0;
mSum = 0;
- mMin = NaN;
- mMax = NaN;
+ mMin = F32(NaN);
+ mMax = F32(NaN);
mMean = NaN;
mSumOfSquares = 0;
mLastValue = other ? other->mLastValue : NaN;
diff --git a/indra/llcommon/lltraceaccumulators.h b/indra/llcommon/lltraceaccumulators.h
index 6f27b97dff..8eb5338a2a 100644
--- a/indra/llcommon/lltraceaccumulators.h
+++ b/indra/llcommon/lltraceaccumulators.h
@@ -242,8 +242,8 @@ namespace LLTrace
EventAccumulator()
: mSum(0),
- mMin(NaN),
- mMax(NaN),
+ mMin(F32(NaN)),
+ mMax(F32(NaN)),
mMean(NaN),
mSumOfSquares(0),
mNumSamples(0),
@@ -313,8 +313,8 @@ namespace LLTrace
SampleAccumulator()
: mSum(0),
- mMin(NaN),
- mMax(NaN),
+ mMin(F32(NaN)),
+ mMax(F32(NaN)),
mMean(NaN),
mSumOfSquares(0),
mLastSampleTimeStamp(0),
diff --git a/indra/llcommon/lltracethreadrecorder.cpp b/indra/llcommon/lltracethreadrecorder.cpp
index 181fc2f058..025dc57044 100644
--- a/indra/llcommon/lltracethreadrecorder.cpp
+++ b/indra/llcommon/lltracethreadrecorder.cpp
@@ -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);
diff --git a/indra/llcommon/lluuid.cpp b/indra/llcommon/lluuid.cpp
index 8f33d789eb..b05630c6b5 100644
--- a/indra/llcommon/lluuid.cpp
+++ b/indra/llcommon/lluuid.cpp
@@ -43,6 +43,7 @@
#include "llstring.h"
#include "lltimer.h"
#include "llthread.h"
+#include "llmutex.h"
const LLUUID LLUUID::null;
const LLTransactionID LLTransactionID::tnull;
@@ -738,7 +739,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};
diff --git a/indra/llcommon/llworkerthread.h b/indra/llcommon/llworkerthread.h
index b1a6f61360..0387e75c65 100644
--- a/indra/llcommon/llworkerthread.h
+++ b/indra/llcommon/llworkerthread.h
@@ -34,6 +34,7 @@
#include "llqueuedthread.h"
#include "llatomic.h"
+#include "llmutex.h"
#define USE_FRAME_CALLBACK_MANAGER 0
diff --git a/indra/llcommon/lockstatic.h b/indra/llcommon/lockstatic.h
new file mode 100644
index 0000000000..96c53c6473
--- /dev/null
+++ b/indra/llcommon/lockstatic.h
@@ -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) */
diff --git a/indra/llcommon/mutex.h b/indra/llcommon/mutex.h
new file mode 100644
index 0000000000..90d0942270
--- /dev/null
+++ b/indra/llcommon/mutex.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
diff --git a/indra/llcommon/tests/llcond_test.cpp b/indra/llcommon/tests/llcond_test.cpp
new file mode 100644
index 0000000000..478149eacf
--- /dev/null
+++ b/indra/llcommon/tests/llcond_test.cpp
@@ -0,0 +1,67 @@
+/**
+ * @file llcond_test.cpp
+ * @author Nat Goodspeed
+ * @date 2019-07-18
+ * @brief Test for llcond.
+ *
+ * $LicenseInfo:firstyear=2019&license=viewerlgpl$
+ * Copyright (c) 2019, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+// Precompiled header
+#include "linden_common.h"
+// associated header
+#include "llcond.h"
+// STL headers
+// std headers
+// external library headers
+// other Linden headers
+#include "../test/lltut.h"
+#include "llcoros.h"
+
+/*****************************************************************************
+* TUT
+*****************************************************************************/
+namespace tut
+{
+ struct llcond_data
+ {
+ LLScalarCond<int> cond{0};
+ };
+ typedef test_group<llcond_data> llcond_group;
+ typedef llcond_group::object object;
+ llcond_group llcondgrp("llcond");
+
+ template<> template<>
+ void object::test<1>()
+ {
+ set_test_name("Immediate gratification");
+ cond.set_one(1);
+ ensure("wait_for_equal() failed",
+ cond.wait_for_equal(F32Milliseconds(1), 1));
+ ensure("wait_for_unequal() should have failed",
+ ! cond.wait_for_unequal(F32Milliseconds(1), 1));
+ }
+
+ template<> template<>
+ void object::test<2>()
+ {
+ set_test_name("Simple two-coroutine test");
+ LLCoros::instance().launch(
+ "test<2>",
+ [this]()
+ {
+ // Lambda immediately entered -- control comes here first.
+ ensure_equals(cond.get(), 0);
+ cond.set_all(1);
+ cond.wait_equal(2);
+ ensure_equals(cond.get(), 2);
+ cond.set_all(3);
+ });
+ // Main coroutine is resumed only when the lambda waits.
+ ensure_equals(cond.get(), 1);
+ cond.set_all(2);
+ cond.wait_equal(3);
+ }
+} // namespace tut
diff --git a/indra/llcommon/tests/lleventcoro_test.cpp b/indra/llcommon/tests/lleventcoro_test.cpp
index fa02d2bb1a..032923a108 100644
--- a/indra/llcommon/tests/lleventcoro_test.cpp
+++ b/indra/llcommon/tests/lleventcoro_test.cpp
@@ -26,102 +26,33 @@
* $/LicenseInfo$
*/
-/*****************************************************************************/
-// test<1>() is cloned from a Boost.Coroutine example program whose copyright
-// info is reproduced here:
-/*---------------------------------------------------------------------------*/
-// Copyright (c) 2006, Giovanni P. Deretta
-//
-// This code may be used under either of the following two licences:
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
-// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE. OF SUCH DAMAGE.
-//
-// Or:
-//
-// Distributed under the Boost Software License, Version 1.0.
-// (See accompanying file LICENSE_1_0.txt or copy at
-// http://www.boost.org/LICENSE_1_0.txt)
-/*****************************************************************************/
-
#define BOOST_RESULT_OF_USE_TR1 1
-// On some platforms, Boost.Coroutine must #define magic symbols before
-// #including platform-API headers. Naturally, that's ineffective unless the
-// Boost.Coroutine #include is the *first* #include of the platform header.
-// That means that client code must generally #include Boost.Coroutine headers
-// before anything else.
-#include <boost/dcoroutine/coroutine.hpp>
#include <boost/bind.hpp>
#include <boost/range.hpp>
#include <boost/utility.hpp>
#include <boost/shared_ptr.hpp>
+#include <boost/make_shared.hpp>
#include "linden_common.h"
#include <iostream>
#include <string>
+#include <typeinfo>
#include "../test/lltut.h"
+#include "../test/lltestapp.h"
#include "llsd.h"
#include "llsdutil.h"
#include "llevents.h"
-#include "tests/wrapllerrs.h"
-#include "stringize.h"
#include "llcoros.h"
+#include "lleventfilter.h"
#include "lleventcoro.h"
#include "../test/debug.h"
+#include "../test/sync.h"
using namespace llcoro;
/*****************************************************************************
-* from the banana.cpp example program borrowed for test<1>()
-*****************************************************************************/
-namespace coroutines = boost::dcoroutines;
-using coroutines::coroutine;
-
-template<typename Iter>
-bool match(Iter first, Iter last, std::string match) {
- std::string::iterator i = match.begin();
- for(; (first != last) && (i != match.end()); ++i) {
- if (*first != *i)
- return false;
- ++first;
- }
- return i == match.end();
-}
-
-template<typename BidirectionalIterator>
-BidirectionalIterator
-match_substring(BidirectionalIterator begin,
- BidirectionalIterator end,
- std::string xmatch,
- BOOST_DEDUCED_TYPENAME coroutine<BidirectionalIterator(void)>::self& self) {
-//BidirectionalIterator begin_ = begin;
- for(; begin != end; ++begin)
- if(match(begin, end, xmatch)) {
- self.yield(begin);
- }
- return end;
-}
-
-typedef coroutine<std::string::iterator(void)> match_coroutine_type;
-
-/*****************************************************************************
* Test helpers
*****************************************************************************/
/// Simulate an event API whose response is immediate: sent on receipt of the
@@ -131,8 +62,9 @@ typedef coroutine<std::string::iterator(void)> match_coroutine_type;
class ImmediateAPI
{
public:
- ImmediateAPI():
- mPump("immediate", true)
+ ImmediateAPI(Sync& sync):
+ mPump("immediate", true),
+ mSync(sync)
{
mPump.listen("API", boost::bind(&ImmediateAPI::operator(), this, _1));
}
@@ -141,20 +73,18 @@ public:
// Invoke this with an LLSD map containing:
// ["value"]: Integer value. We will reply with ["value"] + 1.
- // ["reply"]: Name of LLEventPump on which to send success response.
- // ["error"]: Name of LLEventPump on which to send error response.
- // ["fail"]: Presence of this key selects ["error"], else ["success"] as
- // the name of the pump on which to send the response.
+ // ["reply"]: Name of LLEventPump on which to send response.
bool operator()(const LLSD& event) const
{
+ mSync.bump();
LLSD::Integer value(event["value"]);
- LLSD::String replyPumpName(event.has("fail")? "error" : "reply");
- LLEventPumps::instance().obtain(event[replyPumpName]).post(value + 1);
+ LLEventPumps::instance().obtain(event["reply"]).post(value + 1);
return false;
}
private:
LLEventStream mPump;
+ Sync& mSync;
};
/*****************************************************************************
@@ -162,633 +92,247 @@ private:
*****************************************************************************/
namespace tut
{
- struct coroutine_data {};
- typedef test_group<coroutine_data> coroutine_group;
+ struct test_data
+ {
+ Sync mSync;
+ ImmediateAPI immediateAPI{mSync};
+ std::string replyName, errorName, threw, stringdata;
+ LLSD result, errordata;
+ int which;
+ LLTestApp testApp;
+
+ void explicit_wait(boost::shared_ptr<LLCoros::Promise<std::string>>& cbp);
+ void waitForEventOn1();
+ void coroPump();
+ void postAndWait1();
+ void coroPumpPost();
+ };
+ typedef test_group<test_data> coroutine_group;
typedef coroutine_group::object object;
coroutine_group coroutinegrp("coroutine");
- template<> template<>
- void object::test<1>()
- {
- set_test_name("From banana.cpp example program in Boost.Coroutine distro");
- std::string buffer = "banananana";
- std::string match = "nana";
- std::string::iterator begin = buffer.begin();
- std::string::iterator end = buffer.end();
-
-#if defined(BOOST_CORO_POSIX_IMPL)
-// std::cout << "Using Boost.Coroutine " << BOOST_CORO_POSIX_IMPL << '\n';
-#else
-// std::cout << "Using non-Posix Boost.Coroutine implementation" << std::endl;
-#endif
-
- typedef std::string::iterator signature(std::string::iterator,
- std::string::iterator,
- std::string,
- match_coroutine_type::self&);
-
- coroutine<std::string::iterator(void)> matcher
- (boost::bind(static_cast<signature*>(match_substring),
- begin,
- end,
- match,
- _1));
-
- std::string::iterator i = matcher();
-/*==========================================================================*|
- while(matcher && i != buffer.end()) {
- std::cout <<"Match at: "<< std::distance(buffer.begin(), i)<<'\n';
- i = matcher();
- }
-|*==========================================================================*/
- size_t matches[] = { 2, 4, 6 };
- for (size_t *mi(boost::begin(matches)), *mend(boost::end(matches));
- mi != mend; ++mi, i = matcher())
- {
- ensure("more", matcher);
- ensure("found", i != buffer.end());
- ensure_equals("value", std::distance(buffer.begin(), i), *mi);
- }
- ensure("done", ! matcher);
- }
-
- // use static data so we can intersperse coroutine functions with the
- // tests that engage them
- ImmediateAPI immediateAPI;
- std::string replyName, errorName, threw, stringdata;
- LLSD result, errordata;
- int which;
-
- // reinit vars at the start of each test
- void clear()
- {
- replyName.clear();
- errorName.clear();
- threw.clear();
- stringdata.clear();
- result = LLSD();
- errordata = LLSD();
- which = 0;
- }
-
- void explicit_wait(boost::shared_ptr<LLCoros::Future<std::string>::callback_t>& cbp)
+ void test_data::explicit_wait(boost::shared_ptr<LLCoros::Promise<std::string>>& cbp)
{
BEGIN
{
+ mSync.bump();
// The point of this test is to verify / illustrate suspending a
// coroutine for something other than an LLEventPump. In other
// words, this shows how to adapt to any async operation that
// provides a callback-style notification (and prove that it
// works).
- LLCoros::Future<std::string> future;
- // get the callback from that future
- LLCoros::Future<std::string>::callback_t callback(future.make_callback());
-
// Perhaps we would send a request to a remote server and arrange
- // for 'callback' to be called on response. Of course that might
- // involve an adapter object from the actual callback signature to
- // the signature of 'callback' -- in this case, void(std::string).
- // For test purposes, instead of handing 'callback' (or the
+ // for cbp->set_value() to be called on response.
+ // For test purposes, instead of handing 'callback' (or an
// adapter) off to some I/O subsystem, we'll just pass it back to
// our caller.
- cbp.reset(new LLCoros::Future<std::string>::callback_t(callback));
+ cbp = boost::make_shared<LLCoros::Promise<std::string>>();
+ LLCoros::Future<std::string> future = LLCoros::getFuture(*cbp);
- ensure("Not yet", ! future);
// calling get() on the future causes us to suspend
debug("about to suspend");
stringdata = future.get();
- ensure("Got it", bool(future));
+ mSync.bump();
+ ensure_equals("Got it", stringdata, "received");
}
END
}
template<> template<>
- void object::test<2>()
+ void object::test<1>()
{
- clear();
set_test_name("explicit_wait");
DEBUG;
// Construct the coroutine instance that will run explicit_wait.
- boost::shared_ptr<LLCoros::Future<std::string>::callback_t> respond;
- LLCoros::instance().launch("test<2>",
- boost::bind(explicit_wait, boost::ref(respond)));
+ boost::shared_ptr<LLCoros::Promise<std::string>> respond;
+ LLCoros::instance().launch("test<1>",
+ [this, &respond](){ explicit_wait(respond); });
+ mSync.bump();
// When the coroutine waits for the future, it returns here.
debug("about to respond");
- // Now we're the I/O subsystem delivering a result. This immediately
- // transfers control back to the coroutine.
- (*respond)("received");
+ // Now we're the I/O subsystem delivering a result. This should make
+ // the coroutine ready.
+ respond->set_value("received");
+ // but give it a chance to wake up
+ mSync.yield();
// ensure the coroutine ran and woke up again with the intended result
ensure_equals(stringdata, "received");
}
- void waitForEventOn1()
+ void test_data::waitForEventOn1()
{
BEGIN
{
+ mSync.bump();
result = suspendUntilEventOn("source");
+ mSync.bump();
}
END
}
template<> template<>
- void object::test<3>()
+ void object::test<2>()
{
- clear();
set_test_name("waitForEventOn1");
DEBUG;
- LLCoros::instance().launch("test<3>", waitForEventOn1);
+ LLCoros::instance().launch("test<2>", [this](){ waitForEventOn1(); });
+ mSync.bump();
debug("about to send");
LLEventPumps::instance().obtain("source").post("received");
+ // give waitForEventOn1() a chance to run
+ mSync.yield();
debug("back from send");
ensure_equals(result.asString(), "received");
}
- void waitForEventOn2()
- {
- BEGIN
- {
- LLEventWithID pair = suspendUntilEventOn("reply", "error");
- result = pair.first;
- which = pair.second;
- debug(STRINGIZE("result = " << result << ", which = " << which));
- }
- END
- }
-
- template<> template<>
- void object::test<4>()
- {
- clear();
- set_test_name("waitForEventOn2 reply");
- {
- DEBUG;
- LLCoros::instance().launch("test<4>", waitForEventOn2);
- debug("about to send");
- LLEventPumps::instance().obtain("reply").post("received");
- debug("back from send");
- }
- ensure_equals(result.asString(), "received");
- ensure_equals("which pump", which, 0);
- }
-
- template<> template<>
- void object::test<5>()
- {
- clear();
- set_test_name("waitForEventOn2 error");
- DEBUG;
- LLCoros::instance().launch("test<5>", waitForEventOn2);
- debug("about to send");
- LLEventPumps::instance().obtain("error").post("badness");
- debug("back from send");
- ensure_equals(result.asString(), "badness");
- ensure_equals("which pump", which, 1);
- }
-
- void coroPump()
+ void test_data::coroPump()
{
BEGIN
{
+ mSync.bump();
LLCoroEventPump waiter;
replyName = waiter.getName();
result = waiter.suspend();
+ mSync.bump();
}
END
}
template<> template<>
- void object::test<6>()
+ void object::test<3>()
{
- clear();
set_test_name("coroPump");
DEBUG;
- LLCoros::instance().launch("test<6>", coroPump);
+ LLCoros::instance().launch("test<3>", [this](){ coroPump(); });
+ mSync.bump();
debug("about to send");
LLEventPumps::instance().obtain(replyName).post("received");
+ // give coroPump() a chance to run
+ mSync.yield();
debug("back from send");
ensure_equals(result.asString(), "received");
}
- void coroPumps()
- {
- BEGIN
- {
- LLCoroEventPumps waiter;
- replyName = waiter.getName0();
- errorName = waiter.getName1();
- LLEventWithID pair(waiter.suspend());
- result = pair.first;
- which = pair.second;
- }
- END
- }
-
- template<> template<>
- void object::test<7>()
- {
- clear();
- set_test_name("coroPumps reply");
- DEBUG;
- LLCoros::instance().launch("test<7>", coroPumps);
- debug("about to send");
- LLEventPumps::instance().obtain(replyName).post("received");
- debug("back from send");
- ensure_equals(result.asString(), "received");
- ensure_equals("which pump", which, 0);
- }
-
- template<> template<>
- void object::test<8>()
- {
- clear();
- set_test_name("coroPumps error");
- DEBUG;
- LLCoros::instance().launch("test<8>", coroPumps);
- debug("about to send");
- LLEventPumps::instance().obtain(errorName).post("badness");
- debug("back from send");
- ensure_equals(result.asString(), "badness");
- ensure_equals("which pump", which, 1);
- }
-
- void coroPumpsNoEx()
- {
- BEGIN
- {
- LLCoroEventPumps waiter;
- replyName = waiter.getName0();
- errorName = waiter.getName1();
- result = waiter.suspendWithException();
- }
- END
- }
-
- template<> template<>
- void object::test<9>()
- {
- clear();
- set_test_name("coroPumpsNoEx");
- DEBUG;
- LLCoros::instance().launch("test<9>", coroPumpsNoEx);
- debug("about to send");
- LLEventPumps::instance().obtain(replyName).post("received");
- debug("back from send");
- ensure_equals(result.asString(), "received");
- }
-
- void coroPumpsEx()
- {
- BEGIN
- {
- LLCoroEventPumps waiter;
- replyName = waiter.getName0();
- errorName = waiter.getName1();
- try
- {
- result = waiter.suspendWithException();
- debug("no exception");
- }
- catch (const LLErrorEvent& e)
- {
- debug(STRINGIZE("exception " << e.what()));
- errordata = e.getData();
- }
- }
- END
- }
-
- template<> template<>
- void object::test<10>()
- {
- clear();
- set_test_name("coroPumpsEx");
- DEBUG;
- LLCoros::instance().launch("test<10>", coroPumpsEx);
- debug("about to send");
- LLEventPumps::instance().obtain(errorName).post("badness");
- debug("back from send");
- ensure("no result", result.isUndefined());
- ensure_equals("got error", errordata.asString(), "badness");
- }
-
- void coroPumpsNoLog()
- {
- BEGIN
- {
- LLCoroEventPumps waiter;
- replyName = waiter.getName0();
- errorName = waiter.getName1();
- result = waiter.suspendWithLog();
- }
- END
- }
-
- template<> template<>
- void object::test<11>()
- {
- clear();
- set_test_name("coroPumpsNoLog");
- DEBUG;
- LLCoros::instance().launch("test<11>", coroPumpsNoLog);
- debug("about to send");
- LLEventPumps::instance().obtain(replyName).post("received");
- debug("back from send");
- ensure_equals(result.asString(), "received");
- }
-
- void coroPumpsLog()
- {
- BEGIN
- {
- LLCoroEventPumps waiter;
- replyName = waiter.getName0();
- errorName = waiter.getName1();
- WrapLLErrs capture;
- threw = capture.catch_llerrs([&waiter, &debug](){
- result = waiter.suspendWithLog();
- debug("no exception");
- });
- }
- END
- }
-
- template<> template<>
- void object::test<12>()
- {
- clear();
- set_test_name("coroPumpsLog");
- DEBUG;
- LLCoros::instance().launch("test<12>", coroPumpsLog);
- debug("about to send");
- LLEventPumps::instance().obtain(errorName).post("badness");
- debug("back from send");
- ensure("no result", result.isUndefined());
- ensure_contains("got error", threw, "badness");
- }
-
- void postAndWait1()
+ void test_data::postAndWait1()
{
BEGIN
{
+ mSync.bump();
result = postAndSuspend(LLSDMap("value", 17), // request event
immediateAPI.getPump(), // requestPump
"reply1", // replyPump
"reply"); // request["reply"] = name
+ mSync.bump();
}
END
}
template<> template<>
- void object::test<13>()
+ void object::test<4>()
{
- clear();
set_test_name("postAndWait1");
DEBUG;
- LLCoros::instance().launch("test<13>", postAndWait1);
+ LLCoros::instance().launch("test<4>", [this](){ postAndWait1(); });
ensure_equals(result.asInteger(), 18);
}
- void postAndWait2()
- {
- BEGIN
- {
- LLEventWithID pair = ::postAndSuspend2(LLSDMap("value", 18),
- immediateAPI.getPump(),
- "reply2",
- "error2",
- "reply",
- "error");
- result = pair.first;
- which = pair.second;
- debug(STRINGIZE("result = " << result << ", which = " << which));
- }
- END
- }
-
- template<> template<>
- void object::test<14>()
- {
- clear();
- set_test_name("postAndWait2");
- DEBUG;
- LLCoros::instance().launch("test<14>", postAndWait2);
- ensure_equals(result.asInteger(), 19);
- ensure_equals(which, 0);
- }
-
- void postAndWait2_1()
- {
- BEGIN
- {
- LLEventWithID pair = ::postAndSuspend2(LLSDMap("value", 18)("fail", LLSD()),
- immediateAPI.getPump(),
- "reply2",
- "error2",
- "reply",
- "error");
- result = pair.first;
- which = pair.second;
- debug(STRINGIZE("result = " << result << ", which = " << which));
- }
- END
- }
-
- template<> template<>
- void object::test<15>()
- {
- clear();
- set_test_name("postAndWait2_1");
- DEBUG;
- LLCoros::instance().launch("test<15>", postAndWait2_1);
- ensure_equals(result.asInteger(), 19);
- ensure_equals(which, 1);
- }
-
- void coroPumpPost()
+ void test_data::coroPumpPost()
{
BEGIN
{
+ mSync.bump();
LLCoroEventPump waiter;
result = waiter.postAndSuspend(LLSDMap("value", 17),
immediateAPI.getPump(), "reply");
+ mSync.bump();
}
END
}
template<> template<>
- void object::test<16>()
+ void object::test<5>()
{
- clear();
set_test_name("coroPumpPost");
DEBUG;
- LLCoros::instance().launch("test<16>", coroPumpPost);
+ LLCoros::instance().launch("test<5>", [this](){ coroPumpPost(); });
ensure_equals(result.asInteger(), 18);
}
- void coroPumpsPost()
- {
- BEGIN
- {
- LLCoroEventPumps waiter;
- LLEventWithID pair(waiter.postAndSuspend(LLSDMap("value", 23),
- immediateAPI.getPump(), "reply", "error"));
- result = pair.first;
- which = pair.second;
- }
- END
- }
-
- template<> template<>
- void object::test<17>()
- {
- clear();
- set_test_name("coroPumpsPost reply");
- DEBUG;
- LLCoros::instance().launch("test<17>", coroPumpsPost);
- ensure_equals(result.asInteger(), 24);
- ensure_equals("which pump", which, 0);
- }
-
- void coroPumpsPost_1()
- {
- BEGIN
- {
- LLCoroEventPumps waiter;
- LLEventWithID pair(
- waiter.postAndSuspend(LLSDMap("value", 23)("fail", LLSD()),
- immediateAPI.getPump(), "reply", "error"));
- result = pair.first;
- which = pair.second;
- }
- END
- }
-
- template<> template<>
- void object::test<18>()
- {
- clear();
- set_test_name("coroPumpsPost error");
- DEBUG;
- LLCoros::instance().launch("test<18>", coroPumpsPost_1);
- ensure_equals(result.asInteger(), 24);
- ensure_equals("which pump", which, 1);
- }
-
- void coroPumpsPostNoEx()
- {
- BEGIN
- {
- LLCoroEventPumps waiter;
- result = waiter.postAndSuspendWithException(LLSDMap("value", 8),
- immediateAPI.getPump(), "reply", "error");
- }
- END
- }
-
- template<> template<>
- void object::test<19>()
- {
- clear();
- set_test_name("coroPumpsPostNoEx");
- DEBUG;
- LLCoros::instance().launch("test<19>", coroPumpsPostNoEx);
- ensure_equals(result.asInteger(), 9);
- }
-
- void coroPumpsPostEx()
- {
- BEGIN
+ template <class PUMP>
+ void test()
+ {
+ PUMP pump(typeid(PUMP).name());
+ bool running{false};
+ LLSD data{LLSD::emptyArray()};
+ // start things off by posting once before even starting the listener
+ // coro
+ LL_DEBUGS() << "test() posting first" << LL_ENDL;
+ LLSD first{LLSDMap("desc", "first")("value", 0)};
+ bool consumed = pump.post(first);
+ ensure("should not have consumed first", ! consumed);
+ // now launch the coro
+ LL_DEBUGS() << "test() launching listener coro" << LL_ENDL;
+ running = true;
+ LLCoros::instance().launch(
+ "listener",
+ [&pump, &running, &data](){
+ // important for this test that we consume posted values
+ LLCoros::instance().set_consuming(true);
+ // should immediately retrieve 'first' without waiting
+ LL_DEBUGS() << "listener coro waiting for first" << LL_ENDL;
+ data.append(llcoro::suspendUntilEventOnWithTimeout(pump, 0.1, LLSD()));
+ // Don't use ensure() from within the coro -- ensure() failure
+ // throws tut::fail, which won't propagate out to the main
+ // test driver, which will result in an odd failure.
+ // Wait for 'second' because it's not already pending.
+ LL_DEBUGS() << "listener coro waiting for second" << LL_ENDL;
+ data.append(llcoro::suspendUntilEventOnWithTimeout(pump, 0.1, LLSD()));
+ // and wait for 'third', which should involve no further waiting
+ LL_DEBUGS() << "listener coro waiting for third" << LL_ENDL;
+ data.append(llcoro::suspendUntilEventOnWithTimeout(pump, 0.1, LLSD()));
+ LL_DEBUGS() << "listener coro done" << LL_ENDL;
+ running = false;
+ });
+ // back from coro at the point where it's waiting for 'second'
+ LL_DEBUGS() << "test() posting second" << LL_ENDL;
+ LLSD second{llsd::map("desc", "second", "value", 1)};
+ consumed = pump.post(second);
+ ensure("should have consumed second", consumed);
+ // This is a key point: even though we've post()ed the value for which
+ // the coroutine is waiting, it's actually still suspended until we
+ // pause for some other reason. The coroutine will only pick up one
+ // value at a time from our 'pump'. It's important to exercise the
+ // case when we post() two values before it picks up either.
+ LL_DEBUGS() << "test() posting third" << LL_ENDL;
+ LLSD third{llsd::map("desc", "third", "value", 2)};
+ consumed = pump.post(third);
+ ensure("should NOT yet have consumed third", ! consumed);
+ // now just wait for coro to finish -- which it eventually will, given
+ // that all its suspend calls have short timeouts.
+ while (running)
{
- LLCoroEventPumps waiter;
- try
- {
- result = waiter.postAndSuspendWithException(
- LLSDMap("value", 9)("fail", LLSD()),
- immediateAPI.getPump(), "reply", "error");
- debug("no exception");
- }
- catch (const LLErrorEvent& e)
- {
- debug(STRINGIZE("exception " << e.what()));
- errordata = e.getData();
- }
+ LL_DEBUGS() << "test() waiting for coro done" << LL_ENDL;
+ llcoro::suspendUntilTimeout(0.1);
}
- END
+ // okay, verify expected results
+ ensure_equals("should have received three values", data,
+ llsd::array(first, second, third));
+ LL_DEBUGS() << "test() done" << LL_ENDL;
}
template<> template<>
- void object::test<20>()
- {
- clear();
- set_test_name("coroPumpsPostEx");
- DEBUG;
- LLCoros::instance().launch("test<20>", coroPumpsPostEx);
- ensure("no result", result.isUndefined());
- ensure_equals("got error", errordata.asInteger(), 10);
- }
-
- void coroPumpsPostNoLog()
- {
- BEGIN
- {
- LLCoroEventPumps waiter;
- result = waiter.postAndSuspendWithLog(LLSDMap("value", 30),
- immediateAPI.getPump(), "reply", "error");
- }
- END
- }
-
- template<> template<>
- void object::test<21>()
- {
- clear();
- set_test_name("coroPumpsPostNoLog");
- DEBUG;
- LLCoros::instance().launch("test<21>", coroPumpsPostNoLog);
- ensure_equals(result.asInteger(), 31);
- }
-
- void coroPumpsPostLog()
+ void object::test<6>()
{
- BEGIN
- {
- LLCoroEventPumps waiter;
- WrapLLErrs capture;
- threw = capture.catch_llerrs(
- [&waiter, &debug](){
- result = waiter.postAndSuspendWithLog(
- LLSDMap("value", 31)("fail", LLSD()),
- immediateAPI.getPump(), "reply", "error");
- debug("no exception");
- });
- }
- END
+ set_test_name("LLEventMailDrop");
+ tut::test<LLEventMailDrop>();
}
template<> template<>
- void object::test<22>()
+ void object::test<7>()
{
- clear();
- set_test_name("coroPumpsPostLog");
- DEBUG;
- LLCoros::instance().launch("test<22>", coroPumpsPostLog);
- ensure("no result", result.isUndefined());
- ensure_contains("got error", threw, "32");
+ set_test_name("LLEventLogProxyFor<LLEventMailDrop>");
+ tut::test< LLEventLogProxyFor<LLEventMailDrop> >();
}
}
-
-/*==========================================================================*|
-#include <boost/context/guarded_stack_allocator.hpp>
-
-namespace tut
-{
- template<> template<>
- void object::test<23>()
- {
- set_test_name("stacksize");
- std::cout << "default_stacksize: " << boost::context::guarded_stack_allocator::default_stacksize() << '\n';
- }
-} // namespace tut
-|*==========================================================================*/
diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp
index a181d5c941..9da1ecfd67 100644
--- a/indra/llcommon/tests/lleventdispatcher_test.cpp
+++ b/indra/llcommon/tests/lleventdispatcher_test.cpp
@@ -23,6 +23,7 @@
#include "stringize.h"
#include "tests/wrapllerrs.h"
#include "../test/catch_and_store_what_in.h"
+#include "../test/debug.h"
#include <map>
#include <string>
@@ -46,15 +47,6 @@ using boost::lambda::var;
using namespace llsd;
/*****************************************************************************
-* Output control
-*****************************************************************************/
-#ifdef DEBUG_ON
-using std::cout;
-#else
-static std::ostringstream cout;
-#endif
-
-/*****************************************************************************
* Example data, functions, classes
*****************************************************************************/
// We don't need a whole lot of different arbitrary-params methods, just (no |
@@ -155,13 +147,13 @@ struct Vars
/*------------- no-args (non-const, const, static) methods -------------*/
void method0()
{
- cout << "method0()\n";
+ debug()("method0()");
i = 17;
}
void cmethod0() const
{
- cout << 'c';
+ debug()('c', NONL);
const_cast<Vars*>(this)->method0();
}
@@ -170,13 +162,13 @@ struct Vars
/*------------ Callable (non-const, const, static) methods -------------*/
void method1(const LLSD& obj)
{
- cout << "method1(" << obj << ")\n";
+ debug()("method1(", obj, ")");
llsd = obj;
}
void cmethod1(const LLSD& obj) const
{
- cout << 'c';
+ debug()('c', NONL);
const_cast<Vars*>(this)->method1(obj);
}
@@ -196,12 +188,12 @@ struct Vars
else
vcp = std::string("'") + cp + "'";
- cout << "methodna(" << b
- << ", " << i
- << ", " << f
- << ", " << d
- << ", " << vcp
- << ")\n";
+ debug()("methodna(", b,
+ ", ", i,
+ ", ", f,
+ ", ", d,
+ ", ", vcp,
+ ")");
this->b = b;
this->i = i;
@@ -218,12 +210,12 @@ struct Vars
vbin << std::hex << std::setfill('0') << std::setw(2) << unsigned(byte);
}
- cout << "methodnb(" << "'" << s << "'"
- << ", " << uuid
- << ", " << date
- << ", '" << uri << "'"
- << ", " << vbin.str()
- << ")\n";
+ debug()("methodnb(", "'", s, "'",
+ ", ", uuid,
+ ", ", date,
+ ", '", uri, "'",
+ ", ", vbin.str(),
+ ")");
this->s = s;
this->uuid = uuid;
@@ -234,18 +226,30 @@ struct Vars
void cmethodna(NPARAMSa) const
{
- cout << 'c';
+ debug()('c', NONL);
const_cast<Vars*>(this)->methodna(NARGSa);
}
void cmethodnb(NPARAMSb) const
{
- cout << 'c';
+ debug()('c', NONL);
const_cast<Vars*>(this)->methodnb(NARGSb);
}
static void smethodna(NPARAMSa);
static void smethodnb(NPARAMSb);
+
+ static Debug& debug()
+ {
+ // Lazily initialize this Debug instance so it can notice if main()
+ // has forcibly set LOGTEST. If it were simply a static member, it
+ // would already have examined the environment variable by the time
+ // main() gets around to checking command-line switches. Since we have
+ // a global static Vars instance, the same would be true of a plain
+ // non-static member.
+ static Debug sDebug("Vars");
+ return sDebug;
+ }
};
/*------- Global Vars instance for free functions and static methods -------*/
static Vars g;
@@ -253,25 +257,25 @@ static Vars g;
/*------------ Static Vars method implementations reference 'g' ------------*/
void Vars::smethod0()
{
- cout << "smethod0() -> ";
+ debug()("smethod0() -> ", NONL);
g.method0();
}
void Vars::smethod1(const LLSD& obj)
{
- cout << "smethod1(" << obj << ") -> ";
+ debug()("smethod1(", obj, ") -> ", NONL);
g.method1(obj);
}
void Vars::smethodna(NPARAMSa)
{
- cout << "smethodna(...) -> ";
+ debug()("smethodna(...) -> ", NONL);
g.methodna(NARGSa);
}
void Vars::smethodnb(NPARAMSb)
{
- cout << "smethodnb(...) -> ";
+ debug()("smethodnb(...) -> ", NONL);
g.methodnb(NARGSb);
}
@@ -284,25 +288,25 @@ void clear()
/*------------------- Free functions also reference 'g' --------------------*/
void free0()
{
- cout << "free0() -> ";
+ g.debug()("free0() -> ", NONL);
g.method0();
}
void free1(const LLSD& obj)
{
- cout << "free1(" << obj << ") -> ";
+ g.debug()("free1(", obj, ") -> ", NONL);
g.method1(obj);
}
void freena(NPARAMSa)
{
- cout << "freena(...) -> ";
+ g.debug()("freena(...) -> ", NONL);
g.methodna(NARGSa);
}
void freenb(NPARAMSb)
{
- cout << "freenb(...) -> ";
+ g.debug()("freenb(...) -> ", NONL);
g.methodnb(NARGSb);
}
@@ -313,6 +317,7 @@ namespace tut
{
struct lleventdispatcher_data
{
+ Debug debug{"test"};
WrapLLErrs redirect;
Dispatcher work;
Vars v;
@@ -431,12 +436,17 @@ namespace tut
// Same for freenb() et al.
params = LLSDMap("a", LLSDArray("b")("i")("f")("d")("cp"))
("b", LLSDArray("s")("uuid")("date")("uri")("bin"));
- cout << "params:\n" << params << "\nparams[\"a\"]:\n" << params["a"] << "\nparams[\"b\"]:\n" << params["b"] << std::endl;
+ debug("params:\n",
+ params, "\n"
+ "params[\"a\"]:\n",
+ params["a"], "\n"
+ "params[\"b\"]:\n",
+ params["b"]);
// default LLSD::Binary value
std::vector<U8> binary;
for (size_t ix = 0, h = 0xaa; ix < 6; ++ix, h += 0x11)
{
- binary.push_back(h);
+ binary.push_back((U8)h);
}
// Full defaults arrays. We actually don't care what the LLUUID or
// LLDate values are, as long as they're different from the
@@ -448,7 +458,8 @@ namespace tut
(LLDate::now())
(LLURI("http://www.ietf.org/rfc/rfc3986.txt"))
(binary));
- cout << "dft_array_full:\n" << dft_array_full << std::endl;
+ debug("dft_array_full:\n",
+ dft_array_full);
// Partial defaults arrays.
foreach(LLSD::String a, ab)
{
@@ -457,7 +468,8 @@ namespace tut
llsd_copy_array(dft_array_full[a].beginArray() + partition,
dft_array_full[a].endArray());
}
- cout << "dft_array_partial:\n" << dft_array_partial << std::endl;
+ debug("dft_array_partial:\n",
+ dft_array_partial);
foreach(LLSD::String a, ab)
{
@@ -473,7 +485,10 @@ namespace tut
dft_map_partial[a][params[a][ix].asString()] = dft_array_full[a][ix];
}
}
- cout << "dft_map_full:\n" << dft_map_full << "\ndft_map_partial:\n" << dft_map_partial << '\n';
+ debug("dft_map_full:\n",
+ dft_map_full, "\n"
+ "dft_map_partial:\n",
+ dft_map_partial);
// (Free function | static method) with (no | arbitrary) params,
// map style, no (empty array) defaults
@@ -918,7 +933,12 @@ namespace tut
params[a].endArray()),
dft_array_partial[a]);
}
- cout << "allreq:\n" << allreq << "\nleftreq:\n" << leftreq << "\nrightdft:\n" << rightdft << std::endl;
+ debug("allreq:\n",
+ allreq, "\n"
+ "leftreq:\n",
+ leftreq, "\n"
+ "rightdft:\n",
+ rightdft);
// Generate maps containing parameter names not provided by the
// dft_map_partial maps.
@@ -930,7 +950,8 @@ namespace tut
skipreq[a].erase(me.first);
}
}
- cout << "skipreq:\n" << skipreq << std::endl;
+ debug("skipreq:\n",
+ skipreq);
LLSD groups(LLSDArray // array of groups
@@ -975,7 +996,11 @@ namespace tut
LLSD names(grp[0]);
LLSD required(grp[1][0]);
LLSD optional(grp[1][1]);
- cout << "For " << names << ",\n" << "required:\n" << required << "\noptional:\n" << optional << std::endl;
+ debug("For ", names, ",\n",
+ "required:\n",
+ required, "\n"
+ "optional:\n",
+ optional);
// Loop through 'names'
foreach(LLSD nm, inArray(names))
@@ -1145,7 +1170,7 @@ namespace tut
std::vector<U8> binary;
for (size_t h(0x01), i(0); i < 5; h+= 0x22, ++i)
{
- binary.push_back(h);
+ binary.push_back((U8)h);
}
LLSD args(LLSDMap("a", LLSDArray(true)(17)(3.14)(123.456)("char*"))
("b", LLSDArray("string")
@@ -1163,7 +1188,7 @@ namespace tut
}
// Adjust expect["a"]["cp"] for special Vars::cp treatment.
expect["a"]["cp"] = std::string("'") + expect["a"]["cp"].asString() + "'";
- cout << "expect: " << expect << '\n';
+ debug("expect: ", expect);
// Use substantially the same logic for args and argsplus
LLSD argsarrays(LLSDArray(args)(argsplus));
@@ -1218,7 +1243,8 @@ namespace tut
{
array_overfull[a].append("bogus");
}
- cout << "array_full: " << array_full << "\narray_overfull: " << array_overfull << std::endl;
+ debug("array_full: ", array_full, "\n"
+ "array_overfull: ", array_overfull);
// We rather hope that LLDate::now() will generate a timestamp
// distinct from the one it generated in the constructor, moments ago.
ensure_not_equals("Timestamps too close",
@@ -1233,7 +1259,8 @@ namespace tut
map_overfull[a] = map_full[a];
map_overfull[a]["extra"] = "ignore";
}
- cout << "map_full: " << map_full << "\nmap_overfull: " << map_overfull << std::endl;
+ debug("map_full: ", map_full, "\n"
+ "map_overfull: ", map_overfull);
LLSD expect(map_full);
// Twiddle the const char* param.
expect["a"]["cp"] = std::string("'") + expect["a"]["cp"].asString() + "'";
@@ -1248,7 +1275,7 @@ namespace tut
// so won't bother returning it. Predict that behavior to match the
// LLSD values.
expect["a"].erase("b");
- cout << "expect: " << expect << std::endl;
+ debug("expect: ", expect);
// For this test, calling functions registered with different sets of
// parameter defaults should make NO DIFFERENCE WHATSOEVER. Every call
// should pass all params.
diff --git a/indra/llcommon/tests/lleventfilter_test.cpp b/indra/llcommon/tests/lleventfilter_test.cpp
index 1875013794..fa2cb03e95 100644
--- a/indra/llcommon/tests/lleventfilter_test.cpp
+++ b/indra/llcommon/tests/lleventfilter_test.cpp
@@ -36,9 +36,12 @@
// other Linden headers
#include "../test/lltut.h"
#include "stringize.h"
+#include "llsdutil.h"
#include "listener.h"
#include "tests/wrapllerrs.h"
+#include <typeinfo>
+
/*****************************************************************************
* Test classes
*****************************************************************************/
@@ -401,6 +404,78 @@ namespace tut
throttle.post(";17");
ensure_equals("17", cat.result, "136;12;17"); // "17" delivered
}
+
+ template<class PUMP>
+ void test()
+ {
+ PUMP pump(typeid(PUMP).name());
+ LLSD data{LLSD::emptyArray()};
+ bool consumed{true};
+ // listener that appends to 'data'
+ // but that also returns the current value of 'consumed'
+ // Instantiate this separately because we're going to listen()
+ // multiple times with the same lambda: LLEventMailDrop only replays
+ // queued events on a new listen() call.
+ auto lambda =
+ [&data, &consumed](const LLSD& event)->bool
+ {
+ data.append(event);
+ return consumed;
+ };
+ {
+ LLTempBoundListener conn = pump.listen("lambda", lambda);
+ pump.post("first");
+ }
+ // first post() should certainly be received by listener
+ ensure_equals("first", data, llsd::array("first"));
+ // the question is, since consumed was true, did it queue the value?
+ data = LLSD::emptyArray();
+ {
+ // if it queued the value, it would be delivered on subsequent
+ // listen() call
+ LLTempBoundListener conn = pump.listen("lambda", lambda);
+ }
+ ensure_equals("empty1", data, LLSD::emptyArray());
+ data = LLSD::emptyArray();
+ // now let's NOT consume the posted data
+ consumed = false;
+ {
+ LLTempBoundListener conn = pump.listen("lambda", lambda);
+ pump.post("second");
+ pump.post("third");
+ }
+ // the two events still arrive
+ ensure_equals("second,third1", data, llsd::array("second", "third"));
+ data = LLSD::emptyArray();
+ {
+ // when we reconnect, these should be delivered again
+ // but this time they should be consumed
+ consumed = true;
+ LLTempBoundListener conn = pump.listen("lambda", lambda);
+ }
+ // unconsumed events were delivered again
+ ensure_equals("second,third2", data, llsd::array("second", "third"));
+ data = LLSD::emptyArray();
+ {
+ // when we reconnect this time, no more unconsumed events
+ LLTempBoundListener conn = pump.listen("lambda", lambda);
+ }
+ ensure_equals("empty2", data, LLSD::emptyArray());
+ }
+
+ template<> template<>
+ void filter_object::test<6>()
+ {
+ set_test_name("LLEventMailDrop");
+ tut::test<LLEventMailDrop>();
+ }
+
+ template<> template<>
+ void filter_object::test<7>()
+ {
+ set_test_name("LLEventLogProxyFor<LLEventMailDrop>");
+ tut::test< LLEventLogProxyFor<LLEventMailDrop> >();
+ }
} // namespace tut
/*****************************************************************************
diff --git a/indra/llcommon/tests/llexception_test.cpp b/indra/llcommon/tests/llexception_test.cpp
index 6bee1943c2..8ddf636cd1 100644
--- a/indra/llcommon/tests/llexception_test.cpp
+++ b/indra/llcommon/tests/llexception_test.cpp
@@ -305,4 +305,19 @@ namespace tut
std::cout << center("int", '=', margin) << std::endl;
catch_several(throw_int, "throw_int");
}
+
+ template<> template<>
+ void object::test<2>()
+ {
+ set_test_name("reporting exceptions");
+
+ try
+ {
+ LLTHROW(LLException("badness"));
+ }
+ catch (...)
+ {
+ LOG_UNHANDLED_EXCEPTION("llexception test<2>()");
+ }
+ }
} // namespace tut
diff --git a/indra/llcommon/tests/llinstancetracker_test.cpp b/indra/llcommon/tests/llinstancetracker_test.cpp
index d94fc0c56d..9b89159625 100644
--- a/indra/llcommon/tests/llinstancetracker_test.cpp
+++ b/indra/llcommon/tests/llinstancetracker_test.cpp
@@ -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,11 +176,10 @@ 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);
}
@@ -192,49 +187,49 @@ namespace tut
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<>
@@ -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
diff --git a/indra/llcommon/tests/llleap_test.cpp b/indra/llcommon/tests/llleap_test.cpp
index bf0a74d10d..9d71e327d8 100644
--- a/indra/llcommon/tests/llleap_test.cpp
+++ b/indra/llcommon/tests/llleap_test.cpp
@@ -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.
diff --git a/indra/llcommon/tests/llmainthreadtask_test.cpp b/indra/llcommon/tests/llmainthreadtask_test.cpp
new file mode 100644
index 0000000000..69b11ccafb
--- /dev/null
+++ b/indra/llcommon/tests/llmainthreadtask_test.cpp
@@ -0,0 +1,137 @@
+/**
+ * @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
+ {
+ // 5-second timeout
+ Sync mSync{F32Milliseconds(5000.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");
+ skip("This test is prone to build-time hangs");
+ 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
diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp
index 222d832084..f0eafa8201 100644
--- a/indra/llcommon/tests/llprocess_test.cpp
+++ b/indra/llcommon/tests/llprocess_test.cpp
@@ -493,14 +493,18 @@ namespace tut
}
// std::cout << "child done: rv = " << rv << " (" << manager.strerror(rv) << "), why = " << why << ", rc = " << rc << '\n';
aprchk_("apr_proc_wait(wi->child, &wi->rc, &wi->why, APR_NOWAIT)", wi.rv, APR_CHILD_DONE);
- ensure_equals_(wi.why, APR_PROC_EXIT);
- ensure_equals_(wi.rc, 0);
// Beyond merely executing all the above successfully, verify that we
// obtained expected output -- and that we duly got control while
// waiting, proving the non-blocking nature of these pipes.
try
{
+ // Perform these ensure_equals_() within this try/catch so that if
+ // we don't get expected results, we'll dump whatever we did get
+ // to help diagnose.
+ ensure_equals_(wi.why, APR_PROC_EXIT);
+ ensure_equals_(wi.rc, 0);
+
unsigned i = 0;
ensure("blocking I/O on child pipe (0)", history[i].tries);
ensure_equals_(history[i].which, "out");
diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp
index 6ac974e659..642c1c3879 100644
--- a/indra/llcommon/tests/llsdserialize_test.cpp
+++ b/indra/llcommon/tests/llsdserialize_test.cpp
@@ -271,10 +271,10 @@ namespace tut
LLSD w;
mParser->reset(); // reset() call is needed since test code re-uses mParser
mParser->parse(stream, w, stream.str().size());
-
+
try
{
- ensure_equals(msg.c_str(), w, v);
+ ensure_equals(msg, w, v);
}
catch (...)
{
@@ -432,6 +432,7 @@ namespace tut
const char source[] = "it must be a blue moon again";
std::vector<U8> data;
+ // note, includes terminating '\0'
copy(&source[0], &source[sizeof(source)], back_inserter(data));
v = data;
@@ -468,28 +469,36 @@ namespace tut
checkRoundTrip(msg + " many nested maps", v);
}
- typedef tut::test_group<TestLLSDSerializeData> TestLLSDSerialzeGroup;
- typedef TestLLSDSerialzeGroup::object TestLLSDSerializeObject;
- TestLLSDSerialzeGroup gTestLLSDSerializeGroup("llsd serialization");
+ typedef tut::test_group<TestLLSDSerializeData> TestLLSDSerializeGroup;
+ typedef TestLLSDSerializeGroup::object TestLLSDSerializeObject;
+ TestLLSDSerializeGroup gTestLLSDSerializeGroup("llsd serialization");
template<> template<>
void TestLLSDSerializeObject::test<1>()
{
- mFormatter = new LLSDNotationFormatter();
+ mFormatter = new LLSDNotationFormatter(false, "", LLSDFormatter::OPTIONS_PRETTY_BINARY);
mParser = new LLSDNotationParser();
- doRoundTripTests("notation serialization");
+ doRoundTripTests("pretty binary notation serialization");
}
-
+
template<> template<>
void TestLLSDSerializeObject::test<2>()
{
+ mFormatter = new LLSDNotationFormatter(false, "", LLSDFormatter::OPTIONS_NONE);
+ mParser = new LLSDNotationParser();
+ doRoundTripTests("raw binary notation serialization");
+ }
+
+ template<> template<>
+ void TestLLSDSerializeObject::test<3>()
+ {
mFormatter = new LLSDXMLFormatter();
mParser = new LLSDXMLParser();
doRoundTripTests("xml serialization");
}
-
+
template<> template<>
- void TestLLSDSerializeObject::test<3>()
+ void TestLLSDSerializeObject::test<4>()
{
mFormatter = new LLSDBinaryFormatter();
mParser = new LLSDBinaryParser();
diff --git a/indra/llcommon/tests/llsingleton_test.cpp b/indra/llcommon/tests/llsingleton_test.cpp
index 75ddff9d7d..15ffe68e67 100644
--- a/indra/llcommon/tests/llsingleton_test.cpp
+++ b/indra/llcommon/tests/llsingleton_test.cpp
@@ -143,8 +143,6 @@ namespace tut
\
(void)CLS::instance(); \
ensure_equals(sLog, #CLS "i" #CLS); \
- LLSingletonBase::cleanupAll(); \
- ensure_equals(sLog, #CLS "i" #CLS "x" #CLS); \
LLSingletonBase::deleteAll(); \
ensure_equals(sLog, #CLS "i" #CLS "x" #CLS "~" #CLS); \
} \
@@ -159,10 +157,8 @@ namespace tut
\
(void)CLS::instance(); \
ensure_equals(sLog, #CLS #OTHER "i" #OTHER "i" #CLS); \
- LLSingletonBase::cleanupAll(); \
- ensure_equals(sLog, #CLS #OTHER "i" #OTHER "i" #CLS "x" #CLS "x" #OTHER); \
LLSingletonBase::deleteAll(); \
- ensure_equals(sLog, #CLS #OTHER "i" #OTHER "i" #CLS "x" #CLS "x" #OTHER "~" #CLS "~" #OTHER); \
+ ensure_equals(sLog, #CLS #OTHER "i" #OTHER "i" #CLS "x" #CLS "~" #CLS "x" #OTHER "~" #OTHER); \
} \
\
template<> template<> \
@@ -175,10 +171,8 @@ namespace tut
\
(void)CLS::instance(); \
ensure_equals(sLog, #CLS "i" #CLS #OTHER "i" #OTHER); \
- LLSingletonBase::cleanupAll(); \
- ensure_equals(sLog, #CLS "i" #CLS #OTHER "i" #OTHER "x" #CLS "x" #OTHER); \
LLSingletonBase::deleteAll(); \
- ensure_equals(sLog, #CLS "i" #CLS #OTHER "i" #OTHER "x" #CLS "x" #OTHER "~" #CLS "~" #OTHER); \
+ ensure_equals(sLog, #CLS "i" #CLS #OTHER "i" #OTHER "x" #CLS "~" #CLS "x" #OTHER "~" #OTHER); \
} \
\
template<> template<> \
@@ -191,10 +185,8 @@ namespace tut
\
(void)CLS::instance(); \
ensure_equals(sLog, #CLS "i" #CLS #OTHER "i" #OTHER); \
- LLSingletonBase::cleanupAll(); \
- ensure_equals(sLog, #CLS "i" #CLS #OTHER "i" #OTHER "x" #CLS "x" #OTHER); \
LLSingletonBase::deleteAll(); \
- ensure_equals(sLog, #CLS "i" #CLS #OTHER "i" #OTHER "x" #CLS "x" #OTHER "~" #CLS "~" #OTHER); \
+ ensure_equals(sLog, #CLS "i" #CLS #OTHER "i" #OTHER "x" #CLS "~" #CLS "x" #OTHER "~" #OTHER); \
}
TESTS(A, B, 4, 5, 6, 7)
diff --git a/indra/llcorehttp/CMakeLists.txt b/indra/llcorehttp/CMakeLists.txt
index 9dbc6f447e..11b2e3e929 100644
--- a/indra/llcorehttp/CMakeLists.txt
+++ b/indra/llcorehttp/CMakeLists.txt
@@ -101,12 +101,13 @@ target_link_libraries(
)
# tests
-if (LL_TESTS)
+set(LLCOREHTTP_TESTS ON CACHE BOOL
+ "Build and run llcorehttp integration tests specifically")
+if (LL_TESTS AND LLCOREHTTP_TESTS)
SET(llcorehttp_TEST_SOURCE_FILES
- tests/test_allocator.cpp
)
- set(llcorehttp_TEST_HEADER_FILS
+ set(llcorehttp_TEST_HEADER_FILES
tests/test_httpstatus.hpp
tests/test_refcounted.hpp
tests/test_httpoperation.hpp
@@ -149,7 +150,7 @@ if (LL_TESTS)
${PYTHON_EXECUTABLE}
"${CMAKE_CURRENT_SOURCE_DIR}/tests/test_llcorehttp_peer.py"
)
-
+
if (DARWIN)
# Path inside the app bundle where we'll need to copy libraries
set(LL_TEST_DESTINATION_DIR
@@ -198,6 +199,7 @@ endif (DARWIN)
)
set(example_libs
+ ${LEGACY_STDIO_LIBS}
${LLCOREHTTP_LIBRARIES}
${WINDOWS_LIBRARIES}
${LLMESSAGE_LIBRARIES}
@@ -231,5 +233,4 @@ endif (DARWIN)
target_link_libraries(http_texture_load ${example_libs})
-endif (LL_TESTS)
-
+endif (LL_TESTS AND LLCOREHTTP_TESTS)
diff --git a/indra/llcorehttp/_httpreplyqueue.h b/indra/llcorehttp/_httpreplyqueue.h
index 0e39e22dde..928ee10a83 100644
--- a/indra/llcorehttp/_httpreplyqueue.h
+++ b/indra/llcorehttp/_httpreplyqueue.h
@@ -30,6 +30,7 @@
#include "_refcounted.h"
#include "_mutex.h"
+#include "boost/noncopyable.hpp"
namespace LLCore
diff --git a/indra/llcorehttp/examples/http_texture_load.cpp b/indra/llcorehttp/examples/http_texture_load.cpp
index b91aaf0593..c7376042b3 100644
--- a/indra/llcorehttp/examples/http_texture_load.cpp
+++ b/indra/llcorehttp/examples/http_texture_load.cpp
@@ -52,7 +52,7 @@
void init_curl();
void term_curl();
-unsigned long ssl_thread_id_callback(void);
+void ssl_thread_id_callback(CRYPTO_THREADID*);
void ssl_locking_callback(int mode, int type, const char * file, int line);
void usage(std::ostream & out);
@@ -624,7 +624,7 @@ void init_curl()
}
CRYPTO_set_locking_callback(ssl_locking_callback);
- CRYPTO_set_id_callback(ssl_thread_id_callback);
+ CRYPTO_THREADID_set_callback(ssl_thread_id_callback);
}
}
@@ -640,12 +640,12 @@ void term_curl()
}
-unsigned long ssl_thread_id_callback(void)
+void ssl_thread_id_callback(CRYPTO_THREADID* pthreadid)
{
#if defined(WIN32)
- return (unsigned long) GetCurrentThread();
+ CRYPTO_THREADID_set_pointer(pthreadid, GetCurrentThread());
#else
- return (unsigned long) pthread_self();
+ CRYPTO_THREADID_set_pointer(pthreadid, pthread_self());
#endif
}
diff --git a/indra/llcorehttp/httpcommon.cpp b/indra/llcorehttp/httpcommon.cpp
index 7c93c54cdf..e37a38b05f 100644
--- a/indra/llcorehttp/httpcommon.cpp
+++ b/indra/llcorehttp/httpcommon.cpp
@@ -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
diff --git a/indra/llcorehttp/httpcommon.h b/indra/llcorehttp/httpcommon.h
index e4bd4957f8..18505e0aad 100644
--- a/indra/llcorehttp/httpcommon.h
+++ b/indra/llcorehttp/httpcommon.h
@@ -193,6 +193,7 @@
#include "boost/shared_ptr.hpp"
#include "boost/weak_ptr.hpp"
#include "boost/function.hpp"
+#include "boost/noncopyable.hpp"
#include <string>
#include <curl/curl.h>
diff --git a/indra/llcorehttp/tests/llcorehttp_test.cpp b/indra/llcorehttp/tests/llcorehttp_test.cpp
index a310fc0508..362b2309ee 100755
--- a/indra/llcorehttp/tests/llcorehttp_test.cpp
+++ b/indra/llcorehttp/tests/llcorehttp_test.cpp
@@ -41,14 +41,19 @@
#include "test_httpstatus.hpp"
#include "test_refcounted.hpp"
#include "test_httpoperation.hpp"
+// As of 2019-06-28, test_httprequest.hpp consistently crashes on Mac Release
+// builds for reasons not yet diagnosed.
+#if ! (LL_DARWIN && LL_RELEASE)
#include "test_httprequest.hpp"
+#endif
#include "test_httpheaders.hpp"
#include "test_httprequestqueue.hpp"
+#include "_httpservice.h"
#include "llproxy.h"
#include "llcleanup.h"
-unsigned long ssl_thread_id_callback(void);
+void ssl_thread_id_callback(CRYPTO_THREADID*);
void ssl_locking_callback(int mode, int type, const char * file, int line);
#if 0 // lltut provides main and runner
@@ -93,7 +98,7 @@ void init_curl()
}
CRYPTO_set_locking_callback(ssl_locking_callback);
- CRYPTO_set_id_callback(ssl_thread_id_callback);
+ CRYPTO_THREADID_set_callback(ssl_thread_id_callback);
}
LLProxy::getInstance();
@@ -113,12 +118,12 @@ void term_curl()
}
-unsigned long ssl_thread_id_callback(void)
+void ssl_thread_id_callback(CRYPTO_THREADID* pthreadid)
{
#if defined(WIN32)
- return (unsigned long) GetCurrentThread();
+ CRYPTO_THREADID_set_pointer(pthreadid, GetCurrentThread());
#else
- return (unsigned long) pthread_self();
+ CRYPTO_THREADID_set_pointer(pthreadid, pthread_self());
#endif
}
@@ -172,5 +177,3 @@ void stop_thread(LLCore::HttpRequest * req)
}
}
}
-
-
diff --git a/indra/llcorehttp/tests/test_allocator.cpp b/indra/llcorehttp/tests/test_allocator.cpp
index ea12dc58eb..597e0d2fc9 100644
--- a/indra/llcorehttp/tests/test_allocator.cpp
+++ b/indra/llcorehttp/tests/test_allocator.cpp
@@ -43,16 +43,6 @@
#include <boost/thread.hpp>
-
-#if defined(WIN32)
-#define THROW_BAD_ALLOC() _THROW1(std::bad_alloc)
-#define THROW_NOTHING() _THROW0()
-#else
-#define THROW_BAD_ALLOC() throw(std::bad_alloc)
-#define THROW_NOTHING() throw()
-#endif
-
-
struct BlockHeader
{
struct Block * next;
@@ -152,19 +142,19 @@ std::size_t GetMemTotal()
}
-void * operator new(std::size_t size) THROW_BAD_ALLOC()
+void * operator new(std::size_t size) //throw(std::bad_alloc)
{
return GetMem( size );
}
-void * operator new[](std::size_t size) THROW_BAD_ALLOC()
+void * operator new[](std::size_t size) //throw(std::bad_alloc)
{
return GetMem( size );
}
-void operator delete(void * p) THROW_NOTHING()
+void operator delete(void * p) throw()
{
if (p)
{
@@ -173,7 +163,7 @@ void operator delete(void * p) THROW_NOTHING()
}
-void operator delete[](void * p) THROW_NOTHING()
+void operator delete[](void * p) throw()
{
if (p)
{
diff --git a/indra/llcorehttp/tests/test_allocator.h b/indra/llcorehttp/tests/test_allocator.h
index 3572bbc5c5..abd88f4c98 100644
--- a/indra/llcorehttp/tests/test_allocator.h
+++ b/indra/llcorehttp/tests/test_allocator.h
@@ -30,18 +30,13 @@
#include <cstdlib>
#include <new>
+#error 2019-06-27 Do not use test_allocator.h -- does not respect alignment.
+
size_t GetMemTotal();
-#if defined(WIN32)
-void * operator new(std::size_t size) _THROW1(std::bad_alloc);
-void * operator new[](std::size_t size) _THROW1(std::bad_alloc);
-void operator delete(void * p) _THROW0();
-void operator delete[](void * p) _THROW0();
-#else
-void * operator new(std::size_t size) throw (std::bad_alloc);
-void * operator new[](std::size_t size) throw (std::bad_alloc);
+void * operator new(std::size_t size); //throw (std::bad_alloc);
+void * operator new[](std::size_t size); //throw (std::bad_alloc);
void operator delete(void * p) throw ();
void operator delete[](void * p) throw ();
-#endif
#endif // TEST_ALLOCATOR_H
diff --git a/indra/llcorehttp/tests/test_bufferarray.hpp b/indra/llcorehttp/tests/test_bufferarray.hpp
index 8a2a64d970..cc4ad2a906 100644
--- a/indra/llcorehttp/tests/test_bufferarray.hpp
+++ b/indra/llcorehttp/tests/test_bufferarray.hpp
@@ -30,8 +30,6 @@
#include <iostream>
-#include "test_allocator.h"
-
using namespace LLCore;
@@ -44,7 +42,6 @@ struct BufferArrayTestData
{
// the test objects inherit from this so the member functions and variables
// can be referenced directly inside of the test functions.
- size_t mMemTotal;
};
typedef test_group<BufferArrayTestData> BufferArrayTestGroupType;
@@ -56,13 +53,9 @@ void BufferArrayTestObjectType::test<1>()
{
set_test_name("BufferArray construction");
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
-
// create a new ref counted object with an implicit reference
BufferArray * ba = new BufferArray();
ensure("One ref on construction of BufferArray", ba->getRefCount() == 1);
- ensure("Memory being used", mMemTotal < GetMemTotal());
ensure("Nothing in BA", 0 == ba->size());
// Try to read
@@ -72,9 +65,6 @@ void BufferArrayTestObjectType::test<1>()
// release the implicit reference, causing the object to be released
ba->release();
-
- // make sure we didn't leak any memory
- ensure(mMemTotal == GetMemTotal());
}
template <> template <>
@@ -82,9 +72,6 @@ void BufferArrayTestObjectType::test<2>()
{
set_test_name("BufferArray single write");
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
-
// create a new ref counted object with an implicit reference
BufferArray * ba = new BufferArray();
@@ -105,9 +92,6 @@ void BufferArrayTestObjectType::test<2>()
// release the implicit reference, causing the object to be released
ba->release();
-
- // make sure we didn't leak any memory
- ensure(mMemTotal == GetMemTotal());
}
@@ -116,9 +100,6 @@ void BufferArrayTestObjectType::test<3>()
{
set_test_name("BufferArray multiple writes");
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
-
// create a new ref counted object with an implicit reference
BufferArray * ba = new BufferArray();
@@ -154,9 +135,6 @@ void BufferArrayTestObjectType::test<3>()
// release the implicit reference, causing the object to be released
ba->release();
-
- // make sure we didn't leak any memory
- ensure(mMemTotal == GetMemTotal());
}
template <> template <>
@@ -164,9 +142,6 @@ void BufferArrayTestObjectType::test<4>()
{
set_test_name("BufferArray overwriting");
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
-
// create a new ref counted object with an implicit reference
BufferArray * ba = new BufferArray();
@@ -208,9 +183,6 @@ void BufferArrayTestObjectType::test<4>()
// release the implicit reference, causing the object to be released
ba->release();
-
- // make sure we didn't leak any memory
- ensure(mMemTotal == GetMemTotal());
}
template <> template <>
@@ -218,9 +190,6 @@ void BufferArrayTestObjectType::test<5>()
{
set_test_name("BufferArray multiple writes - sequential reads");
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
-
// create a new ref counted object with an implicit reference
BufferArray * ba = new BufferArray();
@@ -255,9 +224,6 @@ void BufferArrayTestObjectType::test<5>()
// release the implicit reference, causing the object to be released
ba->release();
-
- // make sure we didn't leak any memory
- ensure(mMemTotal == GetMemTotal());
}
template <> template <>
@@ -265,9 +231,6 @@ void BufferArrayTestObjectType::test<6>()
{
set_test_name("BufferArray overwrite spanning blocks and appending");
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
-
// create a new ref counted object with an implicit reference
BufferArray * ba = new BufferArray();
@@ -306,9 +269,6 @@ void BufferArrayTestObjectType::test<6>()
// release the implicit reference, causing the object to be released
ba->release();
-
- // make sure we didn't leak any memory
- ensure("All memory released", mMemTotal == GetMemTotal());
}
template <> template <>
@@ -316,9 +276,6 @@ void BufferArrayTestObjectType::test<7>()
{
set_test_name("BufferArray overwrite spanning blocks and sequential writes");
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
-
// create a new ref counted object with an implicit reference
BufferArray * ba = new BufferArray();
@@ -371,9 +328,6 @@ void BufferArrayTestObjectType::test<7>()
// release the implicit reference, causing the object to be released
ba->release();
-
- // make sure we didn't leak any memory
- ensure("All memory released", mMemTotal == GetMemTotal());
}
template <> template <>
@@ -381,9 +335,6 @@ void BufferArrayTestObjectType::test<8>()
{
set_test_name("BufferArray zero-length appendBufferAlloc");
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
-
// create a new ref counted object with an implicit reference
BufferArray * ba = new BufferArray();
@@ -421,9 +372,6 @@ void BufferArrayTestObjectType::test<8>()
// release the implicit reference, causing the object to be released
ba->release();
-
- // make sure we didn't leak any memory
- ensure("All memory released", mMemTotal == GetMemTotal());
}
} // end namespace tut
diff --git a/indra/llcorehttp/tests/test_bufferstream.hpp b/indra/llcorehttp/tests/test_bufferstream.hpp
index 831c901b9d..2739a6e38e 100644
--- a/indra/llcorehttp/tests/test_bufferstream.hpp
+++ b/indra/llcorehttp/tests/test_bufferstream.hpp
@@ -30,7 +30,6 @@
#include <iostream>
-#include "test_allocator.h"
#include "llsd.h"
#include "llsdserialize.h"
@@ -45,7 +44,6 @@ struct BufferStreamTestData
{
// the test objects inherit from this so the member functions and variables
// can be referenced directly inside of the test functions.
- size_t mMemTotal;
};
typedef test_group<BufferStreamTestData> BufferStreamTestGroupType;
@@ -59,12 +57,8 @@ void BufferStreamTestObjectType::test<1>()
{
set_test_name("BufferArrayStreamBuf construction with NULL BufferArray");
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
-
// create a new ref counted object with an implicit reference
BufferArrayStreamBuf * bsb = new BufferArrayStreamBuf(NULL);
- ensure("Memory being used", mMemTotal < GetMemTotal());
// Not much will work with a NULL
ensure("underflow() on NULL fails", tst_traits_t::eof() == bsb->underflow());
@@ -78,9 +72,6 @@ void BufferStreamTestObjectType::test<1>()
// release the implicit reference, causing the object to be released
delete bsb;
bsb = NULL;
-
- // make sure we didn't leak any memory
- ensure("Allocated memory returned", mMemTotal == GetMemTotal());
}
@@ -89,12 +80,8 @@ void BufferStreamTestObjectType::test<2>()
{
set_test_name("BufferArrayStream construction with NULL BufferArray");
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
-
// create a new ref counted object with an implicit reference
BufferArrayStream * bas = new BufferArrayStream(NULL);
- ensure("Memory being used", mMemTotal < GetMemTotal());
// Not much will work with a NULL here
ensure("eof() is false on NULL", ! bas->eof());
@@ -104,9 +91,6 @@ void BufferStreamTestObjectType::test<2>()
// release the implicit reference, causing the object to be released
delete bas;
bas = NULL;
-
- // make sure we didn't leak any memory
- ensure("Allocated memory returned", mMemTotal == GetMemTotal());
}
@@ -115,13 +99,9 @@ void BufferStreamTestObjectType::test<3>()
{
set_test_name("BufferArrayStreamBuf construction with empty BufferArray");
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
-
// create a new ref counted BufferArray with implicit reference
BufferArray * ba = new BufferArray;
BufferArrayStreamBuf * bsb = new BufferArrayStreamBuf(ba);
- ensure("Memory being used", mMemTotal < GetMemTotal());
// I can release my ref on the BA
ba->release();
@@ -130,9 +110,6 @@ void BufferStreamTestObjectType::test<3>()
// release the implicit reference, causing the object to be released
delete bsb;
bsb = NULL;
-
- // make sure we didn't leak any memory
- ensure("Allocated memory returned", mMemTotal == GetMemTotal());
}
@@ -141,24 +118,17 @@ void BufferStreamTestObjectType::test<4>()
{
set_test_name("BufferArrayStream construction with empty BufferArray");
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
-
// create a new ref counted BufferArray with implicit reference
BufferArray * ba = new BufferArray;
{
// create a new ref counted object with an implicit reference
BufferArrayStream bas(ba);
- ensure("Memory being used", mMemTotal < GetMemTotal());
}
// release the implicit reference, causing the object to be released
ba->release();
ba = NULL;
-
- // make sure we didn't leak any memory
- ensure("Allocated memory returned", mMemTotal == GetMemTotal());
}
@@ -167,9 +137,6 @@ void BufferStreamTestObjectType::test<5>()
{
set_test_name("BufferArrayStreamBuf construction with real BufferArray");
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
-
// create a new ref counted BufferArray with implicit reference
BufferArray * ba = new BufferArray;
const char * content("This is a string. A fragment.");
@@ -178,7 +145,6 @@ void BufferStreamTestObjectType::test<5>()
// Creat an adapter for the BufferArray
BufferArrayStreamBuf * bsb = new BufferArrayStreamBuf(ba);
- ensure("Memory being used", mMemTotal < GetMemTotal());
// I can release my ref on the BA
ba->release();
@@ -206,9 +172,6 @@ void BufferStreamTestObjectType::test<5>()
// release the implicit reference, causing the object to be released
delete bsb;
bsb = NULL;
-
- // make sure we didn't leak any memory
- ensure("Allocated memory returned", mMemTotal == GetMemTotal());
}
@@ -217,9 +180,6 @@ void BufferStreamTestObjectType::test<6>()
{
set_test_name("BufferArrayStream construction with real BufferArray");
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
-
// create a new ref counted BufferArray with implicit reference
BufferArray * ba = new BufferArray;
//const char * content("This is a string. A fragment.");
@@ -229,7 +189,6 @@ void BufferStreamTestObjectType::test<6>()
{
// Creat an adapter for the BufferArray
BufferArrayStream bas(ba);
- ensure("Memory being used", mMemTotal < GetMemTotal());
// Basic operations
bas << "Hello" << 27 << ".";
@@ -243,10 +202,6 @@ void BufferStreamTestObjectType::test<6>()
// release the implicit reference, causing the object to be released
ba->release();
ba = NULL;
-
- // make sure we didn't leak any memory
- // ensure("Allocated memory returned", mMemTotal == GetMemTotal());
- // static U64 mem = GetMemTotal();
}
@@ -255,16 +210,12 @@ void BufferStreamTestObjectType::test<7>()
{
set_test_name("BufferArrayStream with LLSD serialization");
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
-
// create a new ref counted BufferArray with implicit reference
BufferArray * ba = new BufferArray;
{
// Creat an adapter for the BufferArray
BufferArrayStream bas(ba);
- ensure("Memory being used", mMemTotal < GetMemTotal());
// LLSD
LLSD llsd = LLSD::emptyMap();
@@ -292,9 +243,6 @@ void BufferStreamTestObjectType::test<7>()
// release the implicit reference, causing the object to be released
ba->release();
ba = NULL;
-
- // make sure we didn't leak any memory
- // ensure("Allocated memory returned", mMemTotal == GetMemTotal());
}
diff --git a/indra/llcorehttp/tests/test_httpheaders.hpp b/indra/llcorehttp/tests/test_httpheaders.hpp
index c05f1d9429..6aefb5054b 100644
--- a/indra/llcorehttp/tests/test_httpheaders.hpp
+++ b/indra/llcorehttp/tests/test_httpheaders.hpp
@@ -30,8 +30,6 @@
#include <iostream>
-#include "test_allocator.h"
-
using namespace LLCoreInt;
@@ -43,7 +41,6 @@ struct HttpHeadersTestData
{
// the test objects inherit from this so the member functions and variables
// can be referenced directly inside of the test functions.
- size_t mMemTotal;
};
typedef test_group<HttpHeadersTestData> HttpHeadersTestGroupType;
@@ -55,19 +52,12 @@ void HttpHeadersTestObjectType::test<1>()
{
set_test_name("HttpHeaders construction");
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
-
// create a new ref counted object with an implicit reference
HttpHeaders::ptr_t headers = HttpHeaders::ptr_t(new HttpHeaders());
- ensure("Memory being used", mMemTotal < GetMemTotal());
ensure("Nothing in headers", 0 == headers->size());
// release the implicit reference, causing the object to be released
headers.reset();
-
- // make sure we didn't leak any memory
- ensure(mMemTotal == GetMemTotal());
}
template <> template <>
@@ -75,9 +65,6 @@ void HttpHeadersTestObjectType::test<2>()
{
set_test_name("HttpHeaders construction");
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
-
// create a new ref counted object with an implicit reference
HttpHeaders::ptr_t headers = HttpHeaders::ptr_t(new HttpHeaders());
@@ -101,9 +88,6 @@ void HttpHeadersTestObjectType::test<2>()
// release the implicit reference, causing the object to be released
headers.reset();
-
- // make sure we didn't leak any memory
- ensure(mMemTotal == GetMemTotal());
}
template <> template <>
@@ -111,9 +95,6 @@ void HttpHeadersTestObjectType::test<3>()
{
set_test_name("HttpHeaders basic find");
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
-
// create a new ref counted object with an implicit reference
HttpHeaders::ptr_t headers = HttpHeaders::ptr_t(new HttpHeaders());
@@ -151,9 +132,6 @@ void HttpHeadersTestObjectType::test<3>()
// release the implicit reference, causing the object to be released
headers.reset();
-
- // make sure we didn't leak any memory
- ensure(mMemTotal == GetMemTotal());
}
template <> template <>
@@ -161,9 +139,6 @@ void HttpHeadersTestObjectType::test<4>()
{
set_test_name("HttpHeaders normalized header entry");
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
-
// create a new ref counted object with an implicit reference
HttpHeaders::ptr_t headers = HttpHeaders::ptr_t(new HttpHeaders());
@@ -251,9 +226,6 @@ void HttpHeadersTestObjectType::test<4>()
// release the implicit reference, causing the object to be released
headers.reset();
-
- // make sure we didn't leak any memory
- ensure(mMemTotal == GetMemTotal());
}
// Verify forward iterator finds everything as expected
@@ -262,9 +234,6 @@ void HttpHeadersTestObjectType::test<5>()
{
set_test_name("HttpHeaders iterator tests");
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
-
// create a new ref counted object with an implicit reference
HttpHeaders::ptr_t headers = HttpHeaders::ptr_t(new HttpHeaders());
@@ -337,9 +306,6 @@ void HttpHeadersTestObjectType::test<5>()
// release the implicit reference, causing the object to be released
headers.reset();
-
- // make sure we didn't leak any memory
- ensure(mMemTotal == GetMemTotal());
}
// Reverse iterators find everything as expected
@@ -348,9 +314,6 @@ void HttpHeadersTestObjectType::test<6>()
{
set_test_name("HttpHeaders reverse iterator tests");
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
-
// create a new ref counted object with an implicit reference
HttpHeaders::ptr_t headers = HttpHeaders::ptr_t(new HttpHeaders());
@@ -421,9 +384,6 @@ void HttpHeadersTestObjectType::test<6>()
// release the implicit reference, causing the object to be released
headers.reset();
-
- // make sure we didn't leak any memory
- ensure(mMemTotal == GetMemTotal());
}
} // end namespace tut
diff --git a/indra/llcorehttp/tests/test_httpoperation.hpp b/indra/llcorehttp/tests/test_httpoperation.hpp
index e7df2337de..c6407e8d04 100644
--- a/indra/llcorehttp/tests/test_httpoperation.hpp
+++ b/indra/llcorehttp/tests/test_httpoperation.hpp
@@ -31,8 +31,6 @@
#include <iostream>
-#include "test_allocator.h"
-
using namespace LLCoreInt;
@@ -60,7 +58,6 @@ namespace tut
{
// the test objects inherit from this so the member functions and variables
// can be referenced directly inside of the test functions.
- size_t mMemTotal;
};
typedef test_group<HttpOperationTestData> HttpOperationTestGroupType;
@@ -72,19 +69,12 @@ namespace tut
{
set_test_name("HttpOpNull construction");
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
-
// create a new ref counted object with an implicit reference
HttpOperation::ptr_t op (new HttpOpNull());
ensure(op.use_count() == 1);
- ensure(mMemTotal < GetMemTotal());
-
- // release the implicit reference, causing the object to be released
- op.reset();
- // make sure we didn't leak any memory
- ensure(mMemTotal == GetMemTotal());
+ // release the implicit reference, causing the object to be released
+ op.reset();
}
template <> template <>
@@ -92,9 +82,6 @@ namespace tut
{
set_test_name("HttpOpNull construction with handlers");
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
-
// Get some handlers
LLCore::HttpHandler::ptr_t h1 (new TestHandler());
@@ -109,13 +96,10 @@ namespace tut
// release the reference, releasing the operation but
// not the handlers.
- op.reset();
- ensure(mMemTotal != GetMemTotal());
-
- // release the handlers
- h1.reset();
+ op.reset();
- ensure(mMemTotal == GetMemTotal());
+ // release the handlers
+ h1.reset();
}
}
diff --git a/indra/llcorehttp/tests/test_httprequest.hpp b/indra/llcorehttp/tests/test_httprequest.hpp
index e65588e48f..3cdd17919d 100644
--- a/indra/llcorehttp/tests/test_httprequest.hpp
+++ b/indra/llcorehttp/tests/test_httprequest.hpp
@@ -39,7 +39,6 @@
#include <boost/regex.hpp>
#include <sstream>
-#include "test_allocator.h"
#include "llcorehttp_test.h"
@@ -75,7 +74,6 @@ struct HttpRequestTestData
{
// the test objects inherit from this so the member functions and variables
// can be referenced directly inside of the test functions.
- size_t mMemTotal;
int mHandlerCalls;
HttpStatus mStatus;
};
@@ -196,27 +194,19 @@ void HttpRequestTestObjectType::test<1>()
HttpRequest * req = NULL;
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
-
try
{
// Get singletons created
HttpRequest::createService();
-
+
// create a new ref counted object with an implicit reference
req = new HttpRequest();
- ensure("Memory being used", mMemTotal < GetMemTotal());
-
+
// release the request object
delete req;
req = NULL;
HttpRequest::destroyService();
-
- // make sure we didn't leak any memory
- // nat 2017-08-15 don't: requires total stasis in every other subsystem
-// ensure("Memory returned", mMemTotal == GetMemTotal());
}
catch (...)
{
@@ -235,9 +225,6 @@ void HttpRequestTestObjectType::test<2>()
HttpRequest * req = NULL;
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
-
try
{
// Get singletons created
@@ -245,7 +232,6 @@ void HttpRequestTestObjectType::test<2>()
// create a new ref counted object with an implicit reference
req = new HttpRequest();
- ensure("Memory being used", mMemTotal < GetMemTotal());
// Issue a NoOp
HttpHandle handle = req->requestNoOp(LLCore::HttpHandler::ptr_t());
@@ -255,17 +241,11 @@ void HttpRequestTestObjectType::test<2>()
delete req;
req = NULL;
- // We're still holding onto the operation which is
- // sitting, unserviced, on the request queue so...
- ensure("Memory being used 2", mMemTotal < GetMemTotal());
-
// Request queue should have two references: global singleton & service object
ensure("Two references to request queue", 2 == HttpRequestQueue::instanceOf()->getRefCount());
// Okay, tear it down
HttpRequest::destroyService();
- // printf("Old mem: %d, New mem: %d\n", mMemTotal, GetMemTotal());
- ensure("Memory returned", mMemTotal == GetMemTotal());
}
catch (...)
{
@@ -293,9 +273,6 @@ void HttpRequestTestObjectType::test<3>()
// Create before memory record as the string copy will bump numbers.
TestHandler2 handler(this, "handler");
LLCore::HttpHandler::ptr_t handlerp(&handler, NoOpDeletor);
-
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
mHandlerCalls = 0;
HttpRequest * req = NULL;
@@ -311,7 +288,6 @@ void HttpRequestTestObjectType::test<3>()
// create a new ref counted object with an implicit reference
req = new HttpRequest();
- ensure("Memory allocated on construction", mMemTotal < GetMemTotal());
// Issue a NoOp
HttpHandle handle = req->requestNoOp(handlerp);
@@ -360,8 +336,6 @@ void HttpRequestTestObjectType::test<3>()
HttpRequest::destroyService();
ensure("Two handler calls on the way out", 2 == mHandlerCalls);
- // printf("Old mem: %d, New mem: %d\n", mMemTotal, GetMemTotal());
- ensure("Memory usage back to that at entry", mMemTotal == GetMemTotal());
}
catch (...)
{
@@ -386,9 +360,6 @@ void HttpRequestTestObjectType::test<4>()
LLCore::HttpHandler::ptr_t handler1p(&handler1, NoOpDeletor);
LLCore::HttpHandler::ptr_t handler2p(&handler2, NoOpDeletor);
-
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
mHandlerCalls = 0;
HttpRequest * req1 = NULL;
@@ -407,7 +378,6 @@ void HttpRequestTestObjectType::test<4>()
// create a new ref counted object with an implicit reference
req1 = new HttpRequest();
req2 = new HttpRequest();
- ensure("Memory allocated on construction", mMemTotal < GetMemTotal());
// Issue some NoOps
HttpHandle handle = req1->requestNoOp(handler1p);
@@ -466,8 +436,6 @@ void HttpRequestTestObjectType::test<4>()
HttpRequest::destroyService();
ensure("Two handler calls on the way out", 3 == mHandlerCalls);
- // printf("Old mem: %d, New mem: %d\n", mMemTotal, GetMemTotal());
- ensure("Memory usage back to that at entry", mMemTotal == GetMemTotal());
}
catch (...)
{
@@ -491,9 +459,6 @@ void HttpRequestTestObjectType::test<5>()
// Create before memory record as the string copy will bump numbers.
TestHandler2 handler(this, "handler");
LLCore::HttpHandler::ptr_t handlerp(&handler, NoOpDeletor);
-
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
mHandlerCalls = 0;
HttpRequest * req = NULL;
@@ -509,7 +474,6 @@ void HttpRequestTestObjectType::test<5>()
// create a new ref counted object with an implicit reference
req = new HttpRequest();
- ensure("Memory allocated on construction", mMemTotal < GetMemTotal());
// Issue a Spin
HttpHandle handle = req->requestSpin(1);
@@ -535,15 +499,6 @@ void HttpRequestTestObjectType::test<5>()
// Shut down service
HttpRequest::destroyService();
-
- // Check memory usage
- // printf("Old mem: %d, New mem: %d\n", mMemTotal, GetMemTotal());
- ensure("Memory usage back to that at entry", mMemTotal == GetMemTotal());
- // This memory test should work but could give problems as it
- // relies on the worker thread picking up a friendly request
- // to shutdown. Doing so, it drops references to things and
- // we should go back to where we started. If it gives you
- // problems, look into the code before commenting things out.
}
catch (...)
{
@@ -566,9 +521,6 @@ void HttpRequestTestObjectType::test<6>()
// references to it after completion of this method.
// Create before memory record as the string copy will bump numbers.
TestHandler2 handler(this, "handler");
-
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
mHandlerCalls = 0;
HttpRequest * req = NULL;
@@ -586,7 +538,6 @@ void HttpRequestTestObjectType::test<6>()
// create a new ref counted object with an implicit reference
req = new HttpRequest();
- ensure("Memory allocated on construction", mMemTotal < GetMemTotal());
// Issue a Spin
HttpHandle handle = req->requestSpin(0); // Hard spin
@@ -612,13 +563,6 @@ void HttpRequestTestObjectType::test<6>()
// Shut down service
HttpRequest::destroyService();
-
- // Check memory usage
- // printf("Old mem: %d, New mem: %d\n", mMemTotal, GetMemTotal());
- // ensure("Memory usage back to that at entry", mMemTotal == GetMemTotal());
- // This memory test won't work because we're killing the thread
- // hard with the hard spinner. There's no opportunity to join
- // nicely so many things leak or get destroyed unilaterally.
}
catch (...)
{
@@ -643,9 +587,6 @@ void HttpRequestTestObjectType::test<7>()
TestHandler2 handler(this, "handler");
LLCore::HttpHandler::ptr_t handlerp(&handler, NoOpDeletor);
-
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
mHandlerCalls = 0;
HttpRequest * req = NULL;
@@ -662,7 +603,6 @@ void HttpRequestTestObjectType::test<7>()
// create a new ref counted object with an implicit reference
req = new HttpRequest();
- ensure("Memory allocated on construction", mMemTotal < GetMemTotal());
opts = HttpOptions::ptr_t(new HttpOptions());
opts->setRetries(1); // Don't try for too long - default retries take about 18S
@@ -726,14 +666,6 @@ void HttpRequestTestObjectType::test<7>()
HttpRequest::destroyService();
ensure("Two handler calls on the way out", 2 == mHandlerCalls);
-
-#if 0 // defined(WIN32)
- // Can't do this on any platform anymore, the LL logging system holds
- // on to memory and produces what looks like memory leaks...
-
- // printf("Old mem: %d, New mem: %d\n", mMemTotal, GetMemTotal());
- ensure("Memory usage back to that at entry", mMemTotal == GetMemTotal());
-#endif
}
catch (...)
{
@@ -761,9 +693,6 @@ void HttpRequestTestObjectType::test<8>()
// Create before memory record as the string copy will bump numbers.
TestHandler2 handler(this, "handler");
LLCore::HttpHandler::ptr_t handlerp(&handler, NoOpDeletor);
-
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
mHandlerCalls = 0;
HttpRequest * req = NULL;
@@ -779,7 +708,6 @@ void HttpRequestTestObjectType::test<8>()
// create a new ref counted object with an implicit reference
req = new HttpRequest();
- ensure("Memory allocated on construction", mMemTotal < GetMemTotal());
// Issue a GET that *can* connect
mStatus = HttpStatus(200);
@@ -835,15 +763,6 @@ void HttpRequestTestObjectType::test<8>()
HttpRequest::destroyService();
ensure("Two handler calls on the way out", 2 == mHandlerCalls);
-
-#if 0 // defined(WIN32)
- // Can only do this memory test on Windows. On other platforms,
- // the LL logging system holds on to memory and produces what looks
- // like memory leaks...
-
- // printf("Old mem: %d, New mem: %d\n", mMemTotal, GetMemTotal());
- ensure("Memory usage back to that at entry", mMemTotal == GetMemTotal());
-#endif
}
catch (...)
{
@@ -870,9 +789,6 @@ void HttpRequestTestObjectType::test<9>()
// Create before memory record as the string copy will bump numbers.
TestHandler2 handler(this, "handler");
LLCore::HttpHandler::ptr_t handlerp(&handler, NoOpDeletor);
-
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
mHandlerCalls = 0;
HttpRequest * req = NULL;
@@ -888,7 +804,6 @@ void HttpRequestTestObjectType::test<9>()
// create a new ref counted object with an implicit reference
req = new HttpRequest();
- ensure("Memory allocated on construction", mMemTotal < GetMemTotal());
// Issue a GET that *can* connect
mStatus = HttpStatus(200);
@@ -946,15 +861,6 @@ void HttpRequestTestObjectType::test<9>()
HttpRequest::destroyService();
ensure("Two handler calls on the way out", 2 == mHandlerCalls);
-
-#if 0 // defined(WIN32)
- // Can only do this memory test on Windows. On other platforms,
- // the LL logging system holds on to memory and produces what looks
- // like memory leaks...
-
- // printf("Old mem: %d, New mem: %d\n", mMemTotal, GetMemTotal());
- ensure("Memory usage back to that at entry", mMemTotal == GetMemTotal());
-#endif
}
catch (...)
{
@@ -981,9 +887,6 @@ void HttpRequestTestObjectType::test<10>()
// Create before memory record as the string copy will bump numbers.
TestHandler2 handler(this, "handler");
LLCore::HttpHandler::ptr_t handlerp(&handler, NoOpDeletor);
-
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
mHandlerCalls = 0;
HttpRequest * req = NULL;
@@ -1000,7 +903,6 @@ void HttpRequestTestObjectType::test<10>()
// create a new ref counted object with an implicit reference
req = new HttpRequest();
- ensure("Memory allocated on construction", mMemTotal < GetMemTotal());
// Issue a GET that *can* connect
static const char * body_text("Now is the time for all good men...");
@@ -1063,14 +965,6 @@ void HttpRequestTestObjectType::test<10>()
HttpRequest::destroyService();
ensure("Two handler calls on the way out", 2 == mHandlerCalls);
-
-#if 0 // defined(WIN32)
- // Can't do this on any platform anymore, the LL logging system holds
- // on to memory and produces what looks like memory leaks...
-
- // printf("Old mem: %d, New mem: %d\n", mMemTotal, GetMemTotal());
- ensure("Memory usage back to that at entry", mMemTotal == GetMemTotal());
-#endif
}
catch (...)
{
@@ -1100,9 +994,6 @@ void HttpRequestTestObjectType::test<11>()
// Create before memory record as the string copy will bump numbers.
TestHandler2 handler(this, "handler");
LLCore::HttpHandler::ptr_t handlerp(&handler, NoOpDeletor);
-
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
mHandlerCalls = 0;
HttpRequest * req = NULL;
@@ -1119,7 +1010,6 @@ void HttpRequestTestObjectType::test<11>()
// create a new ref counted object with an implicit reference
req = new HttpRequest();
- ensure("Memory allocated on construction", mMemTotal < GetMemTotal());
// Issue a GET that *can* connect
static const char * body_text("Now is the time for all good men...");
@@ -1182,15 +1072,6 @@ void HttpRequestTestObjectType::test<11>()
HttpRequest::destroyService();
ensure("Two handler calls on the way out", 2 == mHandlerCalls);
-
-#if 0 // defined(WIN32)
- // Can only do this memory test on Windows. On other platforms,
- // the LL logging system holds on to memory and produces what looks
- // like memory leaks...
-
- // printf("Old mem: %d, New mem: %d\n", mMemTotal, GetMemTotal());
- ensure("Memory usage back to that at entry", mMemTotal == GetMemTotal());
-#endif
}
catch (...)
{
@@ -1220,9 +1101,6 @@ void HttpRequestTestObjectType::test<12>()
// Create before memory record as the string copy will bump numbers.
TestHandler2 handler(this, "handler");
LLCore::HttpHandler::ptr_t handlerp(&handler, NoOpDeletor);
-
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
mHandlerCalls = 0;
HttpRequest * req = NULL;
@@ -1241,7 +1119,6 @@ void HttpRequestTestObjectType::test<12>()
// create a new ref counted object with an implicit reference
req = new HttpRequest();
- ensure("Memory allocated on construction", mMemTotal < GetMemTotal());
// Issue a GET that *can* connect
mStatus = HttpStatus(200);
@@ -1299,14 +1176,6 @@ void HttpRequestTestObjectType::test<12>()
HttpRequest::destroyService();
ensure("Two handler calls on the way out", 2 == mHandlerCalls);
-
-#if 0 // defined(WIN32)
- // Can't do this on any platform anymore, the LL logging system holds
- // on to memory and produces what looks like memory leaks...
-
- // printf("Old mem: %d, New mem: %d\n", mMemTotal, GetMemTotal());
- ensure("Memory usage back to that at entry", mMemTotal == GetMemTotal());
-#endif
}
catch (...)
{
@@ -1338,9 +1207,6 @@ void HttpRequestTestObjectType::test<13>()
TestHandler2 handler(this, "handler");
handler.mHeadersRequired.reserve(20); // Avoid memory leak test failure
LLCore::HttpHandler::ptr_t handlerp(&handler, NoOpDeletor);
-
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
mHandlerCalls = 0;
HttpRequest * req = NULL;
@@ -1360,7 +1226,6 @@ void HttpRequestTestObjectType::test<13>()
// create a new ref counted object with an implicit reference
req = new HttpRequest();
- ensure("Memory allocated on construction", mMemTotal < GetMemTotal());
opts = HttpOptions::ptr_t(new HttpOptions());
opts->setWantHeaders(true);
@@ -1428,15 +1293,6 @@ void HttpRequestTestObjectType::test<13>()
HttpRequest::destroyService();
ensure("Two handler calls on the way out", 2 == mHandlerCalls);
-
-#if 0 // defined(WIN32)
- // Can only do this memory test on Windows. On other platforms,
- // the LL logging system holds on to memory and produces what looks
- // like memory leaks...
-
- // printf("Old mem: %d, New mem: %d\n", mMemTotal, GetMemTotal());
- ensure("Memory usage back to that at entry", mMemTotal == GetMemTotal());
-#endif
}
catch (...)
{
@@ -1462,9 +1318,6 @@ void HttpRequestTestObjectType::test<14>()
TestHandler2 handler(this, "handler");
LLCore::HttpHandler::ptr_t handlerp(&handler, NoOpDeletor);
std::string url_base(get_base_url() + "/sleep/"); // path to a 30-second sleep
-
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
mHandlerCalls = 0;
HttpRequest * req = NULL;
@@ -1481,7 +1334,6 @@ void HttpRequestTestObjectType::test<14>()
// create a new ref counted object with an implicit reference
req = new HttpRequest();
- ensure("Memory allocated on construction", mMemTotal < GetMemTotal());
opts = HttpOptions::ptr_t(new HttpOptions);
opts->setRetries(0); // Don't retry
@@ -1546,14 +1398,6 @@ void HttpRequestTestObjectType::test<14>()
HttpRequest::destroyService();
ensure("Two handler calls on the way out", 2 == mHandlerCalls);
-
-#if 0 // defined(WIN32)
- // Can't do this on any platform anymore, the LL logging system holds
- // on to memory and produces what looks like memory leaks...
-
- // printf("Old mem: %d, New mem: %d\n", mMemTotal, GetMemTotal());
- ensure("Memory usage back to that at entry", mMemTotal == GetMemTotal());
-#endif
}
catch (...)
{
@@ -1586,9 +1430,6 @@ void HttpRequestTestObjectType::test<15>()
// for memory return tests.
handler.mCheckContentType = "application/llsd+xml";
handler.mCheckContentType.clear();
-
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
mHandlerCalls = 0;
HttpRequest * req = NULL;
@@ -1604,7 +1445,6 @@ void HttpRequestTestObjectType::test<15>()
// create a new ref counted object with an implicit reference
req = new HttpRequest();
- ensure("Memory allocated on construction", mMemTotal < GetMemTotal());
// Issue a GET that *can* connect
mStatus = HttpStatus(200);
@@ -1662,15 +1502,6 @@ void HttpRequestTestObjectType::test<15>()
HttpRequest::destroyService();
ensure("Two handler calls on the way out", 2 == mHandlerCalls);
-
-#if 0 // defined(WIN32)
- // Can only do this memory test on Windows. On other platforms,
- // the LL logging system holds on to memory and produces what looks
- // like memory leaks...
-
- // printf("Old mem: %d, New mem: %d\n", mMemTotal, GetMemTotal());
- ensure("Memory usage back to that at entry", mMemTotal == GetMemTotal());
-#endif
}
catch (...)
{
@@ -1701,9 +1532,6 @@ void HttpRequestTestObjectType::test<16>()
// Create before memory record as the string copy will bump numbers.
TestHandler2 handler(this, "handler");
LLCore::HttpHandler::ptr_t handlerp(&handler, NoOpDeletor);
-
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
mHandlerCalls = 0;
HttpRequest * req = NULL;
@@ -1943,9 +1771,6 @@ void HttpRequestTestObjectType::test<17>()
// Create before memory record as the string copy will bump numbers.
TestHandler2 handler(this, "handler");
LLCore::HttpHandler::ptr_t handlerp(&handler, NoOpDeletor);
-
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
mHandlerCalls = 0;
HttpRequest * req = NULL;
@@ -2131,9 +1956,6 @@ void HttpRequestTestObjectType::test<18>()
// Create before memory record as the string copy will bump numbers.
TestHandler2 handler(this, "handler");
LLCore::HttpHandler::ptr_t handlerp(&handler, NoOpDeletor);
-
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
mHandlerCalls = 0;
HttpRequest * req = NULL;
@@ -2320,9 +2142,6 @@ void HttpRequestTestObjectType::test<19>()
// Create before memory record as the string copy will bump numbers.
TestHandler2 handler(this, "handler");
LLCore::HttpHandler::ptr_t handlerp(&handler, NoOpDeletor);
-
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
mHandlerCalls = 0;
HttpRequest * req = NULL;
@@ -2503,9 +2322,6 @@ void HttpRequestTestObjectType::test<20>()
// Create before memory record as the string copy will bump numbers.
TestHandler2 handler(this, "handler");
LLCore::HttpHandler::ptr_t handlerp(&handler, NoOpDeletor);
-
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
mHandlerCalls = 0;
HttpRequest * req = NULL;
@@ -2711,9 +2527,6 @@ void HttpRequestTestObjectType::test<21>()
// Create before memory record as the string copy will bump numbers.
TestHandler2 handler(this, "handler");
LLCore::HttpHandler::ptr_t handlerp(&handler, NoOpDeletor);
-
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
mHandlerCalls = 0;
HttpRequest * req = NULL;
@@ -2915,9 +2728,6 @@ void HttpRequestTestObjectType::test<22>()
// Create before memory record as the string copy will bump numbers.
TestHandler2 handler(this, "handler");
LLCore::HttpHandler::ptr_t handlerp(&handler, NoOpDeletor);
-
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
mHandlerCalls = 0;
HttpOptions::ptr_t options;
@@ -2939,7 +2749,6 @@ void HttpRequestTestObjectType::test<22>()
// create a new ref counted object with an implicit reference
req = new HttpRequest();
- ensure("Memory allocated on construction", mMemTotal < GetMemTotal());
// ======================================
// Issue bug2295 GETs that will get a 206
@@ -3073,14 +2882,6 @@ void HttpRequestTestObjectType::test<22>()
// Shut down service
HttpRequest::destroyService();
-
-#if 0 // defined(WIN32)
- // Can't do this on any platform anymore, the LL logging system holds
- // on to memory and produces what looks like memory leaks...
-
- // printf("Old mem: %d, New mem: %d\n", mMemTotal, GetMemTotal());
- ensure("Memory usage back to that at entry", mMemTotal == GetMemTotal());
-#endif
}
catch (...)
{
@@ -3117,9 +2918,6 @@ void HttpRequestTestObjectType::test<23>()
TestHandler2 handler(this, "handler");
LLCore::HttpHandler::ptr_t handlerp(&handler, NoOpDeletor);
std::string url_base(get_base_url() + "/503/"); // path to 503 generators
-
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
mHandlerCalls = 0;
HttpRequest * req = NULL;
@@ -3136,7 +2934,6 @@ void HttpRequestTestObjectType::test<23>()
// create a new ref counted object with an implicit reference
req = new HttpRequest();
- ensure("Memory allocated on construction", mMemTotal < GetMemTotal());
opts = HttpOptions::ptr_t(new HttpOptions());
opts->setRetries(1); // Retry once only
@@ -3210,14 +3007,6 @@ void HttpRequestTestObjectType::test<23>()
// Shut down service
HttpRequest::destroyService();
-
-#if 0 // defined(WIN32)
- // Can't do this on any platform anymore, the LL logging system holds
- // on to memory and produces what looks like memory leaks...
-
- // printf("Old mem: %d, New mem: %d\n", mMemTotal, GetMemTotal());
- ensure("Memory usage back to that at entry", mMemTotal == GetMemTotal());
-#endif
}
catch (...)
{
diff --git a/indra/llcorehttp/tests/test_httprequestqueue.hpp b/indra/llcorehttp/tests/test_httprequestqueue.hpp
index ef4ce0479b..dba9e0b250 100644
--- a/indra/llcorehttp/tests/test_httprequestqueue.hpp
+++ b/indra/llcorehttp/tests/test_httprequestqueue.hpp
@@ -30,7 +30,6 @@
#include <iostream>
-#include "test_allocator.h"
#include "_httpoperation.h"
@@ -45,7 +44,6 @@ struct HttpRequestqueueTestData
{
// the test objects inherit from this so the member functions and variables
// can be referenced directly inside of the test functions.
- size_t mMemTotal;
};
typedef test_group<HttpRequestqueueTestData> HttpRequestqueueTestGroupType;
@@ -57,20 +55,13 @@ void HttpRequestqueueTestObjectType::test<1>()
{
set_test_name("HttpRequestQueue construction");
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
-
// create a new ref counted object with an implicit reference
HttpRequestQueue::init();
ensure("One ref on construction of HttpRequestQueue", HttpRequestQueue::instanceOf()->getRefCount() == 1);
- ensure("Memory being used", mMemTotal < GetMemTotal());
// release the implicit reference, causing the object to be released
HttpRequestQueue::term();
-
- // make sure we didn't leak any memory
- ensure(mMemTotal == GetMemTotal());
}
template <> template <>
@@ -78,9 +69,6 @@ void HttpRequestqueueTestObjectType::test<2>()
{
set_test_name("HttpRequestQueue refcount works");
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
-
// create a new ref counted object with an implicit reference
HttpRequestQueue::init();
@@ -91,13 +79,9 @@ void HttpRequestqueueTestObjectType::test<2>()
HttpRequestQueue::term();
ensure("One ref after term() called", rq->getRefCount() == 1);
- ensure("Memory being used", mMemTotal < GetMemTotal());
// Drop ref
rq->release();
-
- // make sure we didn't leak any memory
- ensure(mMemTotal == GetMemTotal());
}
template <> template <>
@@ -105,9 +89,6 @@ void HttpRequestqueueTestObjectType::test<3>()
{
set_test_name("HttpRequestQueue addOp/fetchOp work");
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
-
// create a new ref counted object with an implicit reference
HttpRequestQueue::init();
@@ -126,9 +107,6 @@ void HttpRequestqueueTestObjectType::test<3>()
// release the singleton, hold on to the object
HttpRequestQueue::term();
-
- // make sure we didn't leak any memory
- ensure(mMemTotal == GetMemTotal());
}
template <> template <>
@@ -136,9 +114,6 @@ void HttpRequestqueueTestObjectType::test<4>()
{
set_test_name("HttpRequestQueue addOp/fetchAll work");
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
-
// create a new ref counted object with an implicit reference
HttpRequestQueue::init();
@@ -164,9 +139,6 @@ void HttpRequestqueueTestObjectType::test<4>()
// release the singleton, hold on to the object
HttpRequestQueue::term();
-
- // We're still holding onto the ops.
- ensure(mMemTotal < GetMemTotal());
// Release them
ops.clear();
@@ -177,9 +149,6 @@ void HttpRequestqueueTestObjectType::test<4>()
// op->release();
// }
}
-
- // Should be clean
- ensure("All memory returned", mMemTotal == GetMemTotal());
}
} // end namespace tut
diff --git a/indra/llcorehttp/tests/test_refcounted.hpp b/indra/llcorehttp/tests/test_refcounted.hpp
index 5dff143e5d..2310812d5a 100644
--- a/indra/llcorehttp/tests/test_refcounted.hpp
+++ b/indra/llcorehttp/tests/test_refcounted.hpp
@@ -28,9 +28,8 @@
#include "_refcounted.h"
-#include "test_allocator.h"
-
-#if 0 // disable all of this because it's hanging win64 builds?
+// disable all of this because it's hanging win64 builds?
+#if ! (LL_WINDOWS && ADDRESS_SIZE == 64)
using namespace LLCoreInt;
namespace tut
@@ -39,7 +38,6 @@ namespace tut
{
// the test objects inherit from this so the member functions and variables
// can be referenced directly inside of the test functions.
- size_t mMemTotal;
};
typedef test_group<RefCountedTestData> RefCountedTestGroupType;
@@ -51,18 +49,12 @@ namespace tut
{
set_test_name("RefCounted construction with implicit count");
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
-
// create a new ref counted object with an implicit reference
RefCounted * rc = new RefCounted(true);
ensure(rc->getRefCount() == 1);
// release the implicit reference, causing the object to be released
rc->release();
-
- // make sure we didn't leak any memory
- ensure(mMemTotal == GetMemTotal());
}
template <> template <>
@@ -70,9 +62,6 @@ namespace tut
{
set_test_name("RefCounted construction without implicit count");
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
-
// create a new ref counted object with an implicit reference
RefCounted * rc = new RefCounted(false);
ensure(rc->getRefCount() == 0);
@@ -83,8 +72,6 @@ namespace tut
// release the implicit reference, causing the object to be released
rc->release();
-
- ensure(mMemTotal == GetMemTotal());
}
template <> template <>
@@ -92,9 +79,6 @@ namespace tut
{
set_test_name("RefCounted addRef and release");
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
-
RefCounted * rc = new RefCounted(false);
for (int i = 0; i < 1024; ++i)
@@ -108,9 +92,6 @@ namespace tut
{
rc->release();
}
-
- // make sure we didn't leak any memory
- ensure(mMemTotal == GetMemTotal());
}
template <> template <>
@@ -118,9 +99,6 @@ namespace tut
{
set_test_name("RefCounted isLastRef check");
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
-
RefCounted * rc = new RefCounted(true);
// with only one reference, isLastRef should be true
@@ -128,9 +106,6 @@ namespace tut
// release it to clean up memory
rc->release();
-
- // make sure we didn't leak any memory
- ensure(mMemTotal == GetMemTotal());
}
template <> template <>
@@ -138,9 +113,6 @@ namespace tut
{
set_test_name("RefCounted noRef check");
- // record the total amount of dynamically allocated memory
- mMemTotal = GetMemTotal();
-
RefCounted * rc = new RefCounted(false);
// set the noRef
@@ -148,10 +120,7 @@ namespace tut
// with only one reference, isLastRef should be true
ensure(rc->getRefCount() == RefCounted::NOT_REF_COUNTED);
-
- // allow this memory leak, but check that we're leaking a known amount
- ensure(mMemTotal == (GetMemTotal() - sizeof(RefCounted)));
}
}
-#endif // if 0
+#endif // disabling on Win64
#endif // TEST_LLCOREINT_REF_COUNTED_H_
diff --git a/indra/llcrashlogger/llcrashlogger.cpp b/indra/llcrashlogger/llcrashlogger.cpp
index 5e74715500..62fcdaf545 100644
--- a/indra/llcrashlogger/llcrashlogger.cpp
+++ b/indra/llcrashlogger/llcrashlogger.cpp
@@ -642,7 +642,7 @@ void LLCrashLogger::init_curl()
}
CRYPTO_set_locking_callback(ssl_locking_callback);
- CRYPTO_set_id_callback(ssl_thread_id_callback);
+ CRYPTO_THREADID_set_callback(ssl_thread_id_callback);
}
}
@@ -658,12 +658,12 @@ void LLCrashLogger::term_curl()
}
-unsigned long LLCrashLogger::ssl_thread_id_callback(void)
+void LLCrashLogger::ssl_thread_id_callback(CRYPTO_THREADID* pthreadid)
{
#if LL_WINDOWS
- return (unsigned long)GetCurrentThread();
+ CRYPTO_THREADID_set_pointer(pthreadid, GetCurrentThread());
#else
- return (unsigned long)pthread_self();
+ CRYPTO_THREADID_set_pointer(pthreadid, reinterpret_cast<void*>(pthread_self()));
#endif
}
diff --git a/indra/llcrashlogger/llcrashlogger.h b/indra/llcrashlogger/llcrashlogger.h
index 56e26c23ba..e3e8110a47 100644
--- a/indra/llcrashlogger/llcrashlogger.h
+++ b/indra/llcrashlogger/llcrashlogger.h
@@ -36,6 +36,11 @@
#include "llcrashlock.h"
#include "_mutex.h"
+// We shouldn't have to know the exact declaration of CRYPTO_THREADID, but VS
+// 2017 complains if we forward-declare it as simply 'struct CRYPTO_THREADID'.
+struct crypto_threadid_st;
+typedef crypto_threadid_st CRYPTO_THREADID;
+
// Crash reporter behavior
const S32 CRASH_BEHAVIOR_ASK = 0;
const S32 CRASH_BEHAVIOR_ALWAYS_SEND = 1;
@@ -68,7 +73,7 @@ public:
protected:
static void init_curl();
static void term_curl();
- static unsigned long ssl_thread_id_callback(void);
+ static void ssl_thread_id_callback(CRYPTO_THREADID*);
static void ssl_locking_callback(int mode, int type, const char * file, int line);
S32 mCrashBehavior;
diff --git a/indra/llimage/llimagejpeg.cpp b/indra/llimage/llimagejpeg.cpp
index ead9a37fb8..62638fa16c 100644
--- a/indra/llimage/llimagejpeg.cpp
+++ b/indra/llimage/llimagejpeg.cpp
@@ -386,7 +386,6 @@ boolean LLImageJPEG::encodeEmptyOutputBuffer( j_compress_ptr cinfo )
{
self->setLastError("Out of memory in LLImageJPEG::encodeEmptyOutputBuffer( j_compress_ptr cinfo )");
LLTHROW(LLContinueError("Out of memory in LLImageJPEG::encodeEmptyOutputBuffer( j_compress_ptr cinfo )"));
- return false;
}
memcpy( new_buffer, self->mOutputBuffer, self->mOutputBufferSize ); /* Flawfinder: ignore */
delete[] self->mOutputBuffer;
diff --git a/indra/llinventory/llsettingsdaycycle.cpp b/indra/llinventory/llsettingsdaycycle.cpp
index 457e5b7478..a687fd840d 100644
--- a/indra/llinventory/llsettingsdaycycle.cpp
+++ b/indra/llinventory/llsettingsdaycycle.cpp
@@ -41,8 +41,8 @@
//=========================================================================
namespace
{
- LLTrace::BlockTimerStatHandle FTM_BLEND_WATERVALUES("Blending Water Environment");
- LLTrace::BlockTimerStatHandle FTM_UPDATE_WATERVALUES("Update Water Environment");
+ LLTrace::BlockTimerStatHandle FTM_BLEND_WATERVALUES("Blending Water Environment Day");
+ LLTrace::BlockTimerStatHandle FTM_UPDATE_WATERVALUES("Update Water Environment Day");
template<typename T>
inline T get_wrapping_distance(T begin, T end)
diff --git a/indra/llkdu/llimagej2ckdu.cpp b/indra/llkdu/llimagej2ckdu.cpp
index 4048b9a43d..dac5349f57 100644
--- a/indra/llkdu/llimagej2ckdu.cpp
+++ b/indra/llkdu/llimagej2ckdu.cpp
@@ -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 {
diff --git a/indra/llmath/tests/v3dmath_test.cpp b/indra/llmath/tests/v3dmath_test.cpp
index 20b26faa12..c4744e1b25 100644
--- a/indra/llmath/tests/v3dmath_test.cpp
+++ b/indra/llmath/tests/v3dmath_test.cpp
@@ -520,7 +520,12 @@ namespace tut
vec3Da.normVec();
F64 angle = vec3Db*vec3Da;
angle = acos(angle);
+#if LL_WINDOWS && _MSC_VER > 1900
+ skip("This fails on VS2017!");
+#else
ensure("2:angle_between: Fail ", (angle == angle2));
#endif
+
+#endif
}
}
diff --git a/indra/llmessage/CMakeLists.txt b/indra/llmessage/CMakeLists.txt
index e0922c0667..2f99ca069e 100644
--- a/indra/llmessage/CMakeLists.txt
+++ b/indra/llmessage/CMakeLists.txt
@@ -217,7 +217,7 @@ target_link_libraries(
${NGHTTP2_LIBRARIES}
${XMLRPCEPI_LIBRARIES}
${LLCOREHTTP_LIBRARIES}
- ${BOOST_COROUTINE_LIBRARY}
+ ${BOOST_FIBER_LIBRARY}
${BOOST_CONTEXT_LIBRARY}
${BOOST_SYSTEM_LIBRARY}
rt
@@ -235,7 +235,7 @@ target_link_libraries(
${NGHTTP2_LIBRARIES}
${XMLRPCEPI_LIBRARIES}
${LLCOREHTTP_LIBRARIES}
- ${BOOST_COROUTINE_LIBRARY}
+ ${BOOST_FIBER_LIBRARY}
${BOOST_CONTEXT_LIBRARY}
${BOOST_SYSTEM_LIBRARY}
)
@@ -244,6 +244,7 @@ endif(LINUX)
# tests
if (LL_TESTS)
SET(llmessage_TEST_SOURCE_FILES
+ llcoproceduremanager.cpp
llnamevalue.cpp
lltrustedmessageservice.cpp
lltemplatemessagedispatcher.cpp
@@ -264,7 +265,7 @@ if (LINUX)
${LLMESSAGE_LIBRARIES}
${LLCOREHTTP_LIBRARIES}
${JSONCPP_LIBRARIES}
- ${BOOST_COROUTINE_LIBRARY}
+ ${BOOST_FIBER_LIBRARY}
${BOOST_CONTEXT_LIBRARY}
rt
${GOOGLEMOCK_LIBRARIES}
@@ -280,7 +281,7 @@ else (LINUX)
${LLMESSAGE_LIBRARIES}
${LLCOREHTTP_LIBRARIES}
${JSONCPP_LIBRARIES}
- ${BOOST_COROUTINE_LIBRARY}
+ ${BOOST_FIBER_LIBRARY}
${BOOST_CONTEXT_LIBRARY}
${GOOGLEMOCK_LIBRARIES}
)
diff --git a/indra/llmessage/llavatarnamecache.cpp b/indra/llmessage/llavatarnamecache.cpp
index 6ada12962c..7380df041d 100644
--- a/indra/llmessage/llavatarnamecache.cpp
+++ b/indra/llmessage/llavatarnamecache.cpp
@@ -134,7 +134,7 @@ LLAvatarNameCache::~LLAvatarNameCache()
void LLAvatarNameCache::requestAvatarNameCache_(std::string url, std::vector<LLUUID> agentIds)
{
- LL_DEBUGS("AvNameCache") << "Entering coroutine " << LLCoros::instance().getName()
+ LL_DEBUGS("AvNameCache") << "Entering coroutine " << LLCoros::getName()
<< " with url '" << url << "', requesting " << agentIds.size() << " Agent Ids" << LL_ENDL;
// Check pointer that can be cleaned up by cleanupClass()
@@ -188,7 +188,7 @@ void LLAvatarNameCache::requestAvatarNameCache_(std::string url, std::vector<LLU
}
catch (...)
{
- LOG_UNHANDLED_EXCEPTION(STRINGIZE("coroutine " << LLCoros::instance().getName()
+ LOG_UNHANDLED_EXCEPTION(STRINGIZE("coroutine " << LLCoros::getName()
<< "('" << url << "', " << agentIds.size()
<< " http result: " << httpResults.asString()
<< " Agent Ids)"));
diff --git a/indra/llmessage/llbuffer.cpp b/indra/llmessage/llbuffer.cpp
index 1a0eceba0f..cfe38605ad 100644
--- a/indra/llmessage/llbuffer.cpp
+++ b/indra/llmessage/llbuffer.cpp
@@ -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())
diff --git a/indra/llmessage/llbufferstream.cpp b/indra/llmessage/llbufferstream.cpp
index ff1c9993cc..39508c1c52 100644
--- a/indra/llmessage/llbufferstream.cpp
+++ b/indra/llmessage/llbufferstream.cpp
@@ -31,6 +31,7 @@
#include "llbuffer.h"
#include "llthread.h"
+#include "llmutex.h"
static const S32 DEFAULT_OUTPUT_SEGMENT_SIZE = 1024 * 4;
diff --git a/indra/llmessage/llcoproceduremanager.cpp b/indra/llmessage/llcoproceduremanager.cpp
index 74cdff2b00..a7bd836c4d 100644
--- a/indra/llmessage/llcoproceduremanager.cpp
+++ b/indra/llmessage/llcoproceduremanager.cpp
@@ -25,23 +25,29 @@
* $/LicenseInfo$
*/
-#include "linden_common.h"
+#include "llwin32headers.h"
+
+#include "linden_common.h"
+
#include "llcoproceduremanager.h"
+
+#include <chrono>
+
+#include <boost/fiber/buffered_channel.hpp>
+
#include "llexception.h"
#include "stringize.h"
-#include <boost/assign.hpp>
//=========================================================================
// Map of pool sizes for known pools
-// *TODO$: When C++11 this can be initialized here as follows:
-// = {{"AIS", 25}, {"Upload", 1}}
-static std::map<std::string, U32> DefaultPoolSizes =
- boost::assign::map_list_of
- (std::string("Upload"), 1)
- (std::string("AIS"), 1);
- // *TODO: Rider for the moment keep AIS calls serialized otherwise the COF will tend to get out of sync.
+static const std::map<std::string, U32> DefaultPoolSizes{
+ {std::string("Upload"), 1},
+ {std::string("AIS"), 1},
+ // *TODO: Rider for the moment keep AIS calls serialized otherwise the COF will tend to get out of sync.
+};
-#define DEFAULT_POOL_SIZE 5
+static const U32 DEFAULT_POOL_SIZE = 5;
+static const U32 DEFAULT_QUEUE_SIZE = 4096;
//=========================================================================
class LLCoprocedurePool: private boost::noncopyable
@@ -50,7 +56,7 @@ public:
typedef LLCoprocedureManager::CoProcedure_t CoProcedure_t;
LLCoprocedurePool(const std::string &name, size_t size);
- virtual ~LLCoprocedurePool();
+ ~LLCoprocedurePool();
/// Places the coprocedure on the queue for processing.
///
@@ -60,20 +66,11 @@ public:
/// @return This method returns a UUID that can be used later to cancel execution.
LLUUID enqueueCoprocedure(const std::string &name, CoProcedure_t proc);
- /// Cancel a coprocedure. If the coprocedure is already being actively executed
- /// this method calls cancelSuspendedOperation() on the associated HttpAdapter
- /// If it has not yet been dequeued it is simply removed from the queue.
- bool cancelCoprocedure(const LLUUID &id);
-
- /// Requests a shutdown of the upload manager. Passing 'true' will perform
- /// an immediate kill on the upload coroutine.
- void shutdown(bool hardShutdown = false);
-
/// Returns the number of coprocedures in the queue awaiting processing.
///
inline size_t countPending() const
{
- return mPendingCoprocs.size();
+ return mPending;
}
/// Returns the number of coprocedures actively being processed.
@@ -90,6 +87,8 @@ public:
return countPending() + countActive();
}
+ void close();
+
private:
struct QueuedCoproc
{
@@ -106,25 +105,29 @@ private:
CoProcedure_t mProc;
};
- // we use a deque here rather than std::queue since we want to be able to
- // iterate through the queue and potentially erase an entry from the middle.
- typedef std::deque<QueuedCoproc::ptr_t> CoprocQueue_t;
+ // we use a buffered_channel here rather than unbuffered_channel since we want to be able to
+ // push values without blocking,even if there's currently no one calling a pop operation (due to
+ // fiber running right now)
+ typedef boost::fibers::buffered_channel<QueuedCoproc::ptr_t> CoprocQueue_t;
+ // Use shared_ptr to control the lifespan of our CoprocQueue_t instance
+ // because the consuming coroutine might outlive this LLCoprocedurePool
+ // instance.
+ typedef boost::shared_ptr<CoprocQueue_t> CoprocQueuePtr;
typedef std::map<LLUUID, LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t> ActiveCoproc_t;
std::string mPoolName;
- size_t mPoolSize;
- CoprocQueue_t mPendingCoprocs;
+ size_t mPoolSize, mPending{0};
+ CoprocQueuePtr mPendingCoprocs;
ActiveCoproc_t mActiveCoprocs;
- bool mShutdown;
- LLEventStream mWakeupTrigger;
+ LLTempBoundListener mStatusListener;
typedef std::map<std::string, LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t> CoroAdapterMap_t;
LLCore::HttpRequest::policy_t mHTTPPolicy;
CoroAdapterMap_t mCoroMapping;
- void coprocedureInvokerCoro(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter);
-
+ void coprocedureInvokerCoro(CoprocQueuePtr pendingCoprocs,
+ LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter);
};
//=========================================================================
@@ -134,7 +137,7 @@ LLCoprocedureManager::LLCoprocedureManager()
LLCoprocedureManager::~LLCoprocedureManager()
{
-
+ close();
}
LLCoprocedureManager::poolPtr_t LLCoprocedureManager::initializePool(const std::string &poolName)
@@ -143,33 +146,34 @@ LLCoprocedureManager::poolPtr_t LLCoprocedureManager::initializePool(const std::
std::string keyName = "PoolSize" + poolName;
int size = 0;
- if (poolName.empty())
- LL_ERRS("CoprocedureManager") << "Poolname must not be empty" << LL_ENDL;
+ LL_ERRS_IF(poolName.empty(), "CoprocedureManager") << "Poolname must not be empty" << LL_ENDL;
- if (mPropertyQueryFn && !mPropertyQueryFn.empty())
+ if (mPropertyQueryFn)
{
size = mPropertyQueryFn(keyName);
}
if (size == 0)
- { // if not found grab the know default... if there is no known
+ {
+ // if not found grab the know default... if there is no known
// default use a reasonable number like 5.
- std::map<std::string, U32>::iterator it = DefaultPoolSizes.find(poolName);
- if (it == DefaultPoolSizes.end())
- size = DEFAULT_POOL_SIZE;
- else
- size = (*it).second;
+ auto it = DefaultPoolSizes.find(poolName);
+ size = (it != DefaultPoolSizes.end()) ? it->second : DEFAULT_POOL_SIZE;
- if (mPropertyDefineFn && !mPropertyDefineFn.empty())
+ if (mPropertyDefineFn)
+ {
mPropertyDefineFn(keyName, size, "Coroutine Pool size for " + poolName);
- LL_WARNS() << "LLCoprocedureManager: No setting for \"" << keyName << "\" setting pool size to default of " << size << LL_ENDL;
+ }
+
+ LL_WARNS("CoProcMgr") << "LLCoprocedureManager: No setting for \"" << keyName << "\" setting pool size to default of " << size << LL_ENDL;
}
poolPtr_t pool(new LLCoprocedurePool(poolName, size));
- mPoolMap.insert(poolMap_t::value_type(poolName, pool));
+ LL_ERRS_IF(!pool, "CoprocedureManager") << "Unable to create pool named \"" << poolName << "\" FATAL!" << LL_ENDL;
+
+ bool inserted = mPoolMap.emplace(poolName, pool).second;
+ LL_ERRS_IF(!inserted, "CoprocedureManager") << "Unable to add pool named \"" << poolName << "\" to map. FATAL!" << LL_ENDL;
- if (!pool)
- LL_ERRS("CoprocedureManager") << "Unable to create pool named \"" << poolName << "\" FATAL!" << LL_ENDL;
return pool;
}
@@ -178,40 +182,13 @@ LLUUID LLCoprocedureManager::enqueueCoprocedure(const std::string &pool, const s
{
// Attempt to find the pool and enqueue the procedure. If the pool does
// not exist, create it.
- poolPtr_t targetPool;
poolMap_t::iterator it = mPoolMap.find(pool);
- if (it == mPoolMap.end())
- {
- targetPool = initializePool(pool);
- }
- else
- {
- targetPool = (*it).second;
- }
+ poolPtr_t targetPool = (it != mPoolMap.end()) ? it->second : initializePool(pool);
return targetPool->enqueueCoprocedure(name, proc);
}
-void LLCoprocedureManager::cancelCoprocedure(const LLUUID &id)
-{
- for (poolMap_t::const_iterator it = mPoolMap.begin(); it != mPoolMap.end(); ++it)
- {
- if ((*it).second->cancelCoprocedure(id))
- return;
- }
- LL_INFOS() << "Coprocedure not found." << LL_ENDL;
-}
-
-void LLCoprocedureManager::shutdown(bool hardShutdown)
-{
- for (poolMap_t::const_iterator it = mPoolMap.begin(); it != mPoolMap.end(); ++it)
- {
- (*it).second->shutdown(hardShutdown);
- }
- mPoolMap.clear();
-}
-
void LLCoprocedureManager::setPropertyMethods(SettingQuery_t queryfn, SettingUpdate_t updatefn)
{
mPropertyQueryFn = queryfn;
@@ -222,9 +199,9 @@ void LLCoprocedureManager::setPropertyMethods(SettingQuery_t queryfn, SettingUpd
size_t LLCoprocedureManager::countPending() const
{
size_t count = 0;
- for (poolMap_t::const_iterator it = mPoolMap.begin(); it != mPoolMap.end(); ++it)
+ for (const auto& pair : mPoolMap)
{
- count += (*it).second->countPending();
+ count += pair.second->countPending();
}
return count;
}
@@ -235,7 +212,7 @@ size_t LLCoprocedureManager::countPending(const std::string &pool) const
if (it == mPoolMap.end())
return 0;
- return (*it).second->countPending();
+ return it->second->countPending();
}
size_t LLCoprocedureManager::countActive() const
@@ -243,7 +220,7 @@ size_t LLCoprocedureManager::countActive() const
size_t count = 0;
for (poolMap_t::const_iterator it = mPoolMap.begin(); it != mPoolMap.end(); ++it)
{
- count += (*it).second->countActive();
+ count += it->second->countActive();
}
return count;
}
@@ -253,16 +230,18 @@ size_t LLCoprocedureManager::countActive(const std::string &pool) const
poolMap_t::const_iterator it = mPoolMap.find(pool);
if (it == mPoolMap.end())
+ {
return 0;
- return (*it).second->countActive();
+ }
+ return it->second->countActive();
}
size_t LLCoprocedureManager::count() const
{
size_t count = 0;
- for (poolMap_t::const_iterator it = mPoolMap.begin(); it != mPoolMap.end(); ++it)
+ for (const auto& pair : mPoolMap)
{
- count += (*it).second->count();
+ count += pair.second->count();
}
return count;
}
@@ -273,59 +252,70 @@ size_t LLCoprocedureManager::count(const std::string &pool) const
if (it == mPoolMap.end())
return 0;
- return (*it).second->count();
+ return it->second->count();
+}
+
+void LLCoprocedureManager::close()
+{
+ for(auto & poolEntry : mPoolMap)
+ {
+ poolEntry.second->close();
+ }
+}
+
+void LLCoprocedureManager::close(const std::string &pool)
+{
+ poolMap_t::iterator it = mPoolMap.find(pool);
+ if (it != mPoolMap.end())
+ {
+ it->second->close();
+ }
}
//=========================================================================
LLCoprocedurePool::LLCoprocedurePool(const std::string &poolName, size_t size):
mPoolName(poolName),
mPoolSize(size),
- mPendingCoprocs(),
- mShutdown(false),
- mWakeupTrigger("CoprocedurePool" + poolName, true),
- mCoroMapping(),
- mHTTPPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID)
+ mPendingCoprocs(boost::make_shared<CoprocQueue_t>(DEFAULT_QUEUE_SIZE)),
+ mHTTPPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID),
+ mCoroMapping()
{
+ // store in our LLTempBoundListener so that when the LLCoprocedurePool is
+ // destroyed, we implicitly disconnect from this LLEventPump
+ mStatusListener = LLEventPumps::instance().obtain("LLApp").listen(
+ poolName,
+ [pendingCoprocs=mPendingCoprocs, poolName](const LLSD& status)
+ {
+ auto& statsd = status["status"];
+ if (statsd.asString() != "running")
+ {
+ LL_INFOS("CoProcMgr") << "Pool " << poolName
+ << " closing queue because status " << statsd
+ << LL_ENDL;
+ // This should ensure that all waiting coprocedures in this
+ // pool will wake up and terminate.
+ pendingCoprocs->close();
+ }
+ return false;
+ });
+
for (size_t count = 0; count < mPoolSize; ++count)
{
LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter( mPoolName + "Adapter", mHTTPPolicy));
- std::string pooledCoro = LLCoros::instance().launch("LLCoprocedurePool("+mPoolName+")::coprocedureInvokerCoro",
- boost::bind(&LLCoprocedurePool::coprocedureInvokerCoro, this, httpAdapter));
+ std::string pooledCoro = LLCoros::instance().launch(
+ "LLCoprocedurePool("+mPoolName+")::coprocedureInvokerCoro",
+ boost::bind(&LLCoprocedurePool::coprocedureInvokerCoro, this,
+ mPendingCoprocs, httpAdapter));
mCoroMapping.insert(CoroAdapterMap_t::value_type(pooledCoro, httpAdapter));
}
- LL_INFOS() << "Created coprocedure pool named \"" << mPoolName << "\" with " << size << " items." << LL_ENDL;
-
- mWakeupTrigger.post(LLSD());
+ LL_INFOS("CoProcMgr") << "Created coprocedure pool named \"" << mPoolName << "\" with " << size << " items, queue max " << DEFAULT_QUEUE_SIZE << LL_ENDL;
}
LLCoprocedurePool::~LLCoprocedurePool()
{
- shutdown();
-}
-
-//-------------------------------------------------------------------------
-void LLCoprocedurePool::shutdown(bool hardShutdown)
-{
- CoroAdapterMap_t::iterator it;
-
- for (it = mCoroMapping.begin(); it != mCoroMapping.end(); ++it)
- {
- if (hardShutdown)
- {
- LLCoros::instance().kill((*it).first);
- }
- if ((*it).second)
- {
- (*it).second->cancelSuspendedOperation();
- }
- }
-
- mShutdown = true;
- mCoroMapping.clear();
- mPendingCoprocs.clear();
}
//-------------------------------------------------------------------------
@@ -333,76 +323,94 @@ LLUUID LLCoprocedurePool::enqueueCoprocedure(const std::string &name, LLCoproced
{
LLUUID id(LLUUID::generateNewID());
- mPendingCoprocs.push_back(QueuedCoproc::ptr_t(new QueuedCoproc(name, id, proc)));
- LL_INFOS() << "Coprocedure(" << name << ") enqueued with id=" << id.asString() << " in pool \"" << mPoolName << "\"" << LL_ENDL;
-
- mWakeupTrigger.post(LLSD());
-
- return id;
-}
-
-bool LLCoprocedurePool::cancelCoprocedure(const LLUUID &id)
-{
- // first check the active coroutines. If there, remove it and return.
- ActiveCoproc_t::iterator itActive = mActiveCoprocs.find(id);
- if (itActive != mActiveCoprocs.end())
+ LL_INFOS("CoProcMgr") << "Coprocedure(" << name << ") enqueuing with id=" << id.asString() << " in pool \"" << mPoolName << "\" at " << mPending << LL_ENDL;
+ auto pushed = mPendingCoprocs->try_push(boost::make_shared<QueuedCoproc>(name, id, proc));
+ if (pushed == boost::fibers::channel_op_status::success)
{
- LL_INFOS() << "Found and canceling active coprocedure with id=" << id.asString() << " in pool \"" << mPoolName << "\"" << LL_ENDL;
- (*itActive).second->cancelSuspendedOperation();
- mActiveCoprocs.erase(itActive);
- return true;
+ ++mPending;
+ return id;
}
- for (CoprocQueue_t::iterator it = mPendingCoprocs.begin(); it != mPendingCoprocs.end(); ++it)
+ // Here we didn't succeed in pushing. Shutdown could be the reason.
+ if (pushed == boost::fibers::channel_op_status::closed)
{
- if ((*it)->mId == id)
- {
- LL_INFOS() << "Found and removing queued coroutine(" << (*it)->mName << ") with Id=" << id.asString() << " in pool \"" << mPoolName << "\"" << LL_ENDL;
- mPendingCoprocs.erase(it);
- return true;
- }
+ LL_WARNS("CoProcMgr") << "Discarding coprocedure '" << name << "' because shutdown" << LL_ENDL;
+ return {};
}
- LL_INFOS() << "Coprocedure with Id=" << id.asString() << " was not found in pool \"" << mPoolName << "\"" << LL_ENDL;
- return false;
+ // The queue should never fill up.
+ LL_ERRS("CoProcMgr") << "Enqueue failed (" << unsigned(pushed) << ")" << LL_ENDL;
+ return {}; // never executed, pacify the compiler
}
//-------------------------------------------------------------------------
-void LLCoprocedurePool::coprocedureInvokerCoro(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter)
+void LLCoprocedurePool::coprocedureInvokerCoro(
+ CoprocQueuePtr pendingCoprocs,
+ LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter)
{
- LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest);
-
- while (!mShutdown)
+ for (;;)
{
- llcoro::suspendUntilEventOn(mWakeupTrigger);
- if (mShutdown)
+ // It is VERY IMPORTANT that we instantiate a new ptr_t just before
+ // the pop_wait_for() call below. When this ptr_t was declared at
+ // function scope (outside the for loop), NickyD correctly diagnosed a
+ // mysterious hang condition due to:
+ // - the second time through the loop, the ptr_t held the last pointer
+ // to the previous QueuedCoproc, which indirectly held the last
+ // LLPointer to an LLInventoryCallback instance
+ // - while holding the lock on pendingCoprocs, pop_wait_for() assigned
+ // the popped value to the ptr_t variable
+ // - assignment destroyed the previous value of that variable, which
+ // indirectly destroyed the LLInventoryCallback
+ // - whose destructor called ~LLRequestServerAppearanceUpdateOnDestroy()
+ // - which called LLAppearanceMgr::requestServerAppearanceUpdate()
+ // - which called enqueueCoprocedure()
+ // - which tried to acquire the lock on pendingCoprocs... alas.
+ // Using a fresh, clean ptr_t ensures that no previous value is
+ // destroyed during pop_wait_for().
+ QueuedCoproc::ptr_t coproc;
+ boost::fibers::channel_op_status status;
+ {
+ LLCoros::TempStatus st("waiting for work for 10s");
+ status = pendingCoprocs->pop_wait_for(coproc, std::chrono::seconds(10));
+ }
+ if (status == boost::fibers::channel_op_status::closed)
+ {
break;
-
- while (!mPendingCoprocs.empty())
+ }
+
+ if(status == boost::fibers::channel_op_status::timeout)
{
- QueuedCoproc::ptr_t coproc = mPendingCoprocs.front();
- mPendingCoprocs.pop_front();
- ActiveCoproc_t::iterator itActive = mActiveCoprocs.insert(ActiveCoproc_t::value_type(coproc->mId, httpAdapter)).first;
+ LL_DEBUGS_ONCE("CoProcMgr") << "pool '" << mPoolName << "' waiting." << LL_ENDL;
+ continue;
+ }
+ // we actually popped an item
+ --mPending;
- LL_INFOS() << "Dequeued and invoking coprocedure(" << coproc->mName << ") with id=" << coproc->mId.asString() << " in pool \"" << mPoolName << "\"" << LL_ENDL;
+ ActiveCoproc_t::iterator itActive = mActiveCoprocs.insert(ActiveCoproc_t::value_type(coproc->mId, httpAdapter)).first;
- try
- {
- coproc->mProc(httpAdapter, coproc->mId);
- }
- catch (...)
- {
- LOG_UNHANDLED_EXCEPTION(STRINGIZE("Coprocedure('" << coproc->mName
- << "', id=" << coproc->mId.asString()
- << ") in pool '" << mPoolName << "'"));
- // must NOT omit this or we deplete the pool
- mActiveCoprocs.erase(itActive);
- throw;
- }
-
- LL_INFOS() << "Finished coprocedure(" << coproc->mName << ")" << " in pool \"" << mPoolName << "\"" << LL_ENDL;
+ LL_DEBUGS("CoProcMgr") << "Dequeued and invoking coprocedure(" << coproc->mName << ") with id=" << coproc->mId.asString() << " in pool \"" << mPoolName << "\" (" << mPending << " left)" << LL_ENDL;
+ try
+ {
+ coproc->mProc(httpAdapter, coproc->mId);
+ }
+ catch (...)
+ {
+ LOG_UNHANDLED_EXCEPTION(STRINGIZE("Coprocedure('" << coproc->mName
+ << "', id=" << coproc->mId.asString()
+ << ") in pool '" << mPoolName << "'"));
+ // must NOT omit this or we deplete the pool
mActiveCoprocs.erase(itActive);
+ continue;
}
+
+ LL_DEBUGS("CoProcMgr") << "Finished coprocedure(" << coproc->mName << ")" << " in pool \"" << mPoolName << "\"" << LL_ENDL;
+
+ mActiveCoprocs.erase(itActive);
}
}
+
+void LLCoprocedurePool::close()
+{
+ mPendingCoprocs->close();
+}
diff --git a/indra/llmessage/llcoproceduremanager.h b/indra/llmessage/llcoproceduremanager.h
index 7d0e83180c..70204ba02b 100644
--- a/indra/llmessage/llcoproceduremanager.h
+++ b/indra/llmessage/llcoproceduremanager.h
@@ -32,6 +32,7 @@
#include "llcoros.h"
#include "llcorehttputil.h"
#include "lluuid.h"
+#include <boost/smart_ptr/shared_ptr.hpp>
class LLCoprocedurePool;
@@ -57,11 +58,7 @@ public:
/// Cancel a coprocedure. If the coprocedure is already being actively executed
/// this method calls cancelYieldingOperation() on the associated HttpAdapter
/// If it has not yet been dequeued it is simply removed from the queue.
- void cancelCoprocedure(const LLUUID &id);
-
- /// Requests a shutdown of the upload manager. Passing 'true' will perform
- /// an immediate kill on the upload coroutine.
- void shutdown(bool hardShutdown = false);
+ //void cancelCoprocedure(const LLUUID &id);
void setPropertyMethods(SettingQuery_t queryfn, SettingUpdate_t updatefn);
@@ -80,6 +77,9 @@ public:
size_t count() const;
size_t count(const std::string &pool) const;
+ void close();
+ void close(const std::string &pool);
+
private:
typedef boost::shared_ptr<LLCoprocedurePool> poolPtr_t;
diff --git a/indra/llmessage/llexperiencecache.cpp b/indra/llmessage/llexperiencecache.cpp
index aa7b3c1260..7d96ac4b02 100644
--- a/indra/llmessage/llexperiencecache.cpp
+++ b/indra/llmessage/llexperiencecache.cpp
@@ -338,10 +338,10 @@ void LLExperienceCache::requestExperiences()
F64 now = LLFrameTimer::getTotalSeconds();
const U32 EXP_URL_SEND_THRESHOLD = 3000;
- const U32 PAGE_SIZE = EXP_URL_SEND_THRESHOLD / UUID_STR_LENGTH;
+ const U32 PAGE_SIZE1 = EXP_URL_SEND_THRESHOLD / UUID_STR_LENGTH;
std::ostringstream ostr;
- ostr << urlBase << "?page_size=" << PAGE_SIZE;
+ ostr << urlBase << "?page_size=" << PAGE_SIZE1;
RequestQueue_t requests;
while (!mRequestQueue.empty())
@@ -360,7 +360,7 @@ void LLExperienceCache::requestExperiences()
boost::bind(&LLExperienceCache::requestExperiencesCoro, this, _1, ostr.str(), requests) );
ostr.str(std::string());
- ostr << urlBase << "?page_size=" << PAGE_SIZE;
+ ostr << urlBase << "?page_size=" << PAGE_SIZE1;
requests.clear();
}
}
diff --git a/indra/llmessage/lliosocket.cpp b/indra/llmessage/lliosocket.cpp
index 7caf0766b7..a9cc71c365 100644
--- a/indra/llmessage/lliosocket.cpp
+++ b/indra/llmessage/lliosocket.cpp
@@ -62,9 +62,9 @@ bool is_addr_in_use(apr_status_t status)
#endif
}
-#if LL_LINUX
+#if ! LL_WINDOWS
// Define this to see the actual file descriptors being tossed around.
-//#define LL_DEBUG_SOCKET_FILE_DESCRIPTORS 1
+#define LL_DEBUG_SOCKET_FILE_DESCRIPTORS 1
#if LL_DEBUG_SOCKET_FILE_DESCRIPTORS
#include "apr_portable.h"
#endif
@@ -77,7 +77,7 @@ void ll_debug_socket(const char* msg, apr_socket_t* apr_sock)
#if LL_DEBUG_SOCKET_FILE_DESCRIPTORS
if(!apr_sock)
{
- LL_DEBUGS() << "Socket -- " << (msg?msg:"") << ": no socket." << LL_ENDL;
+ LL_DEBUGS("Socket") << "Socket -- " << (msg?msg:"") << ": no socket." << LL_ENDL;
return;
}
// *TODO: Why doesn't this work?
@@ -85,12 +85,12 @@ void ll_debug_socket(const char* msg, apr_socket_t* apr_sock)
int os_sock;
if(APR_SUCCESS == apr_os_sock_get(&os_sock, apr_sock))
{
- LL_DEBUGS() << "Socket -- " << (msg?msg:"") << " on fd " << os_sock
+ LL_DEBUGS("Socket") << "Socket -- " << (msg?msg:"") << " on fd " << os_sock
<< " at " << apr_sock << LL_ENDL;
}
else
{
- LL_DEBUGS() << "Socket -- " << (msg?msg:"") << " no fd "
+ LL_DEBUGS("Socket") << "Socket -- " << (msg?msg:"") << " no fd "
<< " at " << apr_sock << LL_ENDL;
}
#endif
@@ -144,6 +144,9 @@ LLSocket::ptr_t LLSocket::create(apr_pool_t* pool, EType type, U16 port, const c
if(new_pool) apr_pool_destroy(new_pool);
return rv;
}
+ // At this point, the new LLSocket instance takes ownership of new_pool,
+ // which is why no early return below this call explicitly destroys it: it
+ // is instead cleaned up by ~LLSocket().
rv = ptr_t(new LLSocket(socket, new_pool));
if(port > 0)
{
@@ -186,7 +189,7 @@ LLSocket::ptr_t LLSocket::create(apr_pool_t* pool, EType type, U16 port, const c
}
}
}
- else
+ else // port <= 0
{
// we need to indicate that we have an ephemeral port if the
// previous calls were successful. It will
diff --git a/indra/llmessage/llproxy.cpp b/indra/llmessage/llproxy.cpp
index d11a1487ab..749e599c66 100644
--- a/indra/llmessage/llproxy.cpp
+++ b/indra/llmessage/llproxy.cpp
@@ -126,9 +126,9 @@ S32 LLProxy::proxyHandshake(LLHost proxy)
U32 request_size = socks_username.size() + socks_password.size() + 3;
char * password_auth = new char[request_size];
password_auth[0] = 0x01;
- password_auth[1] = socks_username.size();
+ password_auth[1] = (char)(socks_username.size());
memcpy(&password_auth[2], socks_username.c_str(), socks_username.size());
- password_auth[socks_username.size() + 2] = socks_password.size();
+ password_auth[socks_username.size() + 2] = (char)(socks_password.size());
memcpy(&password_auth[socks_username.size() + 3], socks_password.c_str(), socks_password.size());
authmethod_password_reply_t password_reply;
diff --git a/indra/llmessage/llproxy.h b/indra/llmessage/llproxy.h
index f2eefb26d0..25f6977e14 100644
--- a/indra/llmessage/llproxy.h
+++ b/indra/llmessage/llproxy.h
@@ -32,6 +32,7 @@
#include "llmemory.h"
#include "llsingleton.h"
#include "llthread.h"
+#include "llmutex.h"
#include <curl/curl.h>
#include <string>
diff --git a/indra/llmessage/message.cpp b/indra/llmessage/message.cpp
index 6ef4025ab1..da62bb12e8 100644
--- a/indra/llmessage/message.cpp
+++ b/indra/llmessage/message.cpp
@@ -117,8 +117,8 @@ void LLMessageHandlerBridge::post(LLHTTPNode::ResponsePtr response,
gMessageSystem->mLastSender = LLHost(input["sender"].asString());
gMessageSystem->mPacketsIn += 1;
gMessageSystem->mLLSDMessageReader->setMessage(namePtr, input["body"]);
- gMessageSystem->mMessageReader = gMessageSystem->mLLSDMessageReader;
-
+ LockMessageReader rdr(gMessageSystem->mMessageReader, gMessageSystem->mLLSDMessageReader);
+
if(gMessageSystem->callHandler(namePtr, false, gMessageSystem))
{
response->result(LLSD());
@@ -189,7 +189,7 @@ void LLMessageSystem::init()
mTimingCallbackData = NULL;
mMessageBuilder = NULL;
- mMessageReader = NULL;
+ LockMessageReader(mMessageReader, NULL);
}
// Read file and build message templates
@@ -230,7 +230,6 @@ LLMessageSystem::LLMessageSystem(const std::string& filename, U32 port,
mTemplateMessageReader = new LLTemplateMessageReader(mMessageNumbers);
mLLSDMessageReader = new LLSDMessageReader();
- mMessageReader = NULL;
// initialize various bits of net info
mSocket = 0;
@@ -330,7 +329,6 @@ LLMessageSystem::~LLMessageSystem()
delete mTemplateMessageReader;
mTemplateMessageReader = NULL;
- mMessageReader = NULL;
delete mTemplateMessageBuilder;
mTemplateMessageBuilder = NULL;
@@ -480,11 +478,12 @@ LLCircuitData* LLMessageSystem::findCircuit(const LLHost& host,
}
// Returns TRUE if a valid, on-circuit message has been received.
-BOOL LLMessageSystem::checkMessages( S64 frame_count )
+// Requiring a non-const LockMessageChecker reference ensures that
+// mMessageReader has been set to mTemplateMessageReader.
+BOOL LLMessageSystem::checkMessages(LockMessageChecker&, S64 frame_count )
{
// Pump
BOOL valid_packet = FALSE;
- mMessageReader = mTemplateMessageReader;
LLTransferTargetVFile::updateQueue();
@@ -748,7 +747,7 @@ S32 LLMessageSystem::getReceiveBytes() const
}
-void LLMessageSystem::processAcks(F32 collect_time)
+void LLMessageSystem::processAcks(LockMessageChecker&, F32 collect_time)
{
F64Seconds mt_sec = getMessageTimeSeconds();
{
@@ -2062,8 +2061,9 @@ void LLMessageSystem::dispatch(
return;
}
// enable this for output of message names
- //LL_INFOS("Messaging") << "< \"" << msg_name << "\"" << LL_ENDL;
- //LL_DEBUGS() << "data: " << LLSDNotationStreamer(message) << LL_ENDL;
+ LL_DEBUGS("Messaging") << "< \"" << msg_name << "\"" << LL_ENDL;
+ LL_DEBUGS("Messaging") << "context: " << context << LL_ENDL;
+ LL_DEBUGS("Messaging") << "message: " << message << LL_ENDL;
handler->post(responsep, context, message);
}
@@ -3268,6 +3268,8 @@ void null_message_callback(LLMessageSystem *msg, void **data)
// up, and then sending auth messages.
void LLMessageSystem::establishBidirectionalTrust(const LLHost &host, S64 frame_count )
{
+ LockMessageChecker lmc(this);
+
std::string shared_secret = get_shared_secret();
if(shared_secret.empty())
{
@@ -3287,7 +3289,7 @@ void LLMessageSystem::establishBidirectionalTrust(const LLHost &host, S64 frame_
addU8Fast(_PREHASH_PingID, 0);
addU32Fast(_PREHASH_OldestUnacked, 0);
sendMessage(host);
- if (checkMessages( frame_count ))
+ if (lmc.checkMessages( frame_count ))
{
if (isMessageFast(_PREHASH_CompletePingCheck) &&
(getSender() == host))
@@ -3295,7 +3297,7 @@ void LLMessageSystem::establishBidirectionalTrust(const LLHost &host, S64 frame_
break;
}
}
- processAcks();
+ lmc.processAcks();
ms_sleep(1);
}
@@ -3314,8 +3316,8 @@ void LLMessageSystem::establishBidirectionalTrust(const LLHost &host, S64 frame_
cdp = mCircuitInfo.findCircuit(host);
if(!cdp) break; // no circuit anymore, no point continuing.
if(cdp->getTrusted()) break; // circuit is trusted.
- checkMessages(frame_count);
- processAcks();
+ lmc.checkMessages(frame_count);
+ lmc.processAcks();
ms_sleep(1);
}
}
@@ -3973,11 +3975,18 @@ void LLMessageSystem::setTimeDecodesSpamThreshold( F32 seconds )
LLMessageReader::setTimeDecodesSpamThreshold(seconds);
}
+LockMessageChecker::LockMessageChecker(LLMessageSystem* msgsystem):
+ // for the lifespan of this LockMessageChecker instance, use
+ // LLTemplateMessageReader as msgsystem's mMessageReader
+ LockMessageReader(msgsystem->mMessageReader, msgsystem->mTemplateMessageReader),
+ mMessageSystem(msgsystem)
+{}
+
// HACK! babbage: return true if message rxed via either UDP or HTTP
// TODO: babbage: move gServicePump in to LLMessageSystem?
-bool LLMessageSystem::checkAllMessages(S64 frame_count, LLPumpIO* http_pump)
+bool LLMessageSystem::checkAllMessages(LockMessageChecker& lmc, S64 frame_count, LLPumpIO* http_pump)
{
- if(checkMessages(frame_count))
+ if(lmc.checkMessages(frame_count))
{
return true;
}
diff --git a/indra/llmessage/message.h b/indra/llmessage/message.h
index 0af5a1b96d..52dbf871db 100644
--- a/indra/llmessage/message.h
+++ b/indra/llmessage/message.h
@@ -61,6 +61,8 @@
#include "llstoredmessage.h"
#include "boost/function.hpp"
#include "llpounceable.h"
+#include "llcoros.h"
+#include LLCOROS_MUTEX_HEADER
const U32 MESSAGE_MAX_STRINGS_LENGTH = 64;
const U32 MESSAGE_NUMBER_OF_HASH_BUCKETS = 8192;
@@ -199,6 +201,91 @@ public:
virtual void complete(const LLHost& host, const LLUUID& agent) const = 0;
};
+/**
+ * SL-12204: We've observed crashes when consumer code sets
+ * LLMessageSystem::mMessageReader, assuming that all subsequent processing of
+ * the current message will use the same mMessageReader value -- only to have
+ * a different coroutine sneak in and replace mMessageReader before
+ * completion. This is a limitation of sharing a stateful global resource for
+ * message parsing; instead code receiving a new message should instantiate a
+ * (trivially constructed) local message parser and use that.
+ *
+ * Until then, when one coroutine sets a particular LLMessageReader subclass
+ * as the current message reader, ensure that no other coroutine can replace
+ * it until the first coroutine has finished with its message.
+ *
+ * This is achieved with two helper classes. LLMessageSystem::mMessageReader
+ * is now an LLMessageReaderPointer instance, which can efficiently compare or
+ * dereference its contained LLMessageReader* but which cannot be directly
+ * assigned. To change the value of LLMessageReaderPointer, you must
+ * instantiate LockMessageReader with the LLMessageReader* you wish to make
+ * current. mMessageReader will have that value for the lifetime of the
+ * LockMessageReader instance, then revert to nullptr. Moreover, as its name
+ * implies, LockMessageReader locks the mutex in LLMessageReaderPointer so
+ * that any other coroutine instantiating LockMessageReader will block until
+ * the first coroutine has destroyed its instance.
+ */
+class LLMessageReaderPointer
+{
+public:
+ LLMessageReaderPointer(): mPtr(nullptr) {}
+ // It is essential that comparison and dereferencing must be fast, which
+ // is why we don't check for nullptr when dereferencing.
+ LLMessageReader* operator->() const { return mPtr; }
+ bool operator==(const LLMessageReader* other) const { return mPtr == other; }
+ bool operator!=(const LLMessageReader* other) const { return ! (*this == other); }
+private:
+ // Only LockMessageReader can set mPtr.
+ friend class LockMessageReader;
+ LLMessageReader* mPtr;
+ LLCoros::Mutex mMutex;
+};
+
+/**
+ * To set mMessageReader to nullptr:
+ *
+ * @code
+ * // use an anonymous instance that is destroyed immediately
+ * LockMessageReader(gMessageSystem->mMessageReader, nullptr);
+ * @endcode
+ *
+ * Why do we still require going through LockMessageReader at all? Because it
+ * would be Bad if any coroutine set mMessageReader to nullptr while another
+ * coroutine was still parsing a message.
+ */
+class LockMessageReader
+{
+public:
+ LockMessageReader(LLMessageReaderPointer& var, LLMessageReader* instance):
+ mVar(var.mPtr),
+ mLock(var.mMutex)
+ {
+ mVar = instance;
+ }
+ // Some compilers reportedly fail to suppress generating implicit copy
+ // operations even though we have a move-only LockType data member.
+ LockMessageReader(const LockMessageReader&) = delete;
+ LockMessageReader& operator=(const LockMessageReader&) = delete;
+ ~LockMessageReader()
+ {
+ mVar = nullptr;
+ }
+private:
+ // capture a reference to LLMessageReaderPointer::mPtr
+ decltype(LLMessageReaderPointer::mPtr)& mVar;
+ // while holding a lock on LLMessageReaderPointer::mMutex
+ LLCoros::LockType mLock;
+};
+
+/**
+ * LockMessageReader is great as long as you only need mMessageReader locked
+ * during a single LLMessageSystem function call. However, empirically the
+ * sequence from checkAllMessages() through processAcks() need mMessageReader
+ * locked to LLTemplateMessageReader. Enforce that by making them require an
+ * instance of LockMessageChecker.
+ */
+class LockMessageChecker;
+
class LLMessageSystem : public LLMessageSenderInterface
{
private:
@@ -331,8 +418,8 @@ public:
bool addCircuitCode(U32 code, const LLUUID& session_id);
BOOL poll(F32 seconds); // Number of seconds that we want to block waiting for data, returns if data was received
- BOOL checkMessages( S64 frame_count = 0 );
- void processAcks(F32 collect_time = 0.f);
+ BOOL checkMessages(LockMessageChecker&, S64 frame_count = 0 );
+ void processAcks(LockMessageChecker&, F32 collect_time = 0.f);
BOOL isMessageFast(const char *msg);
BOOL isMessage(const char *msg)
@@ -730,7 +817,7 @@ public:
const LLSD& data);
// Check UDP messages and pump http_pump to receive HTTP messages.
- bool checkAllMessages(S64 frame_count, LLPumpIO* http_pump);
+ bool checkAllMessages(LockMessageChecker&, S64 frame_count, LLPumpIO* http_pump);
// Moved to allow access from LLTemplateMessageDispatcher
void clearReceiveState();
@@ -817,12 +904,13 @@ private:
LLMessageBuilder* mMessageBuilder;
LLTemplateMessageBuilder* mTemplateMessageBuilder;
LLSDMessageBuilder* mLLSDMessageBuilder;
- LLMessageReader* mMessageReader;
+ LLMessageReaderPointer mMessageReader;
LLTemplateMessageReader* mTemplateMessageReader;
LLSDMessageReader* mLLSDMessageReader;
friend class LLMessageHandlerBridge;
-
+ friend class LockMessageChecker;
+
bool callHandler(const char *name, bool trustedSource,
LLMessageSystem* msg);
@@ -835,6 +923,40 @@ private:
// external hook into messaging system
extern LLPounceable<LLMessageSystem*, LLPounceableStatic> gMessageSystem;
+// Implementation of LockMessageChecker depends on definition of
+// LLMessageSystem, hence must follow it.
+class LockMessageChecker: public LockMessageReader
+{
+public:
+ LockMessageChecker(LLMessageSystem* msgsystem);
+
+ // For convenience, provide forwarding wrappers so you can call (e.g.)
+ // checkAllMessages() on your LockMessageChecker instance instead of
+ // passing the instance to LLMessageSystem::checkAllMessages(). Use
+ // perfect forwarding to avoid having to maintain these wrappers in sync
+ // with the target methods.
+ template <typename... ARGS>
+ bool checkAllMessages(ARGS&&... args)
+ {
+ return mMessageSystem->checkAllMessages(*this, std::forward<ARGS>(args)...);
+ }
+
+ template <typename... ARGS>
+ bool checkMessages(ARGS&&... args)
+ {
+ return mMessageSystem->checkMessages(*this, std::forward<ARGS>(args)...);
+ }
+
+ template <typename... ARGS>
+ void processAcks(ARGS&&... args)
+ {
+ return mMessageSystem->processAcks(*this, std::forward<ARGS>(args)...);
+ }
+
+private:
+ LLMessageSystem* mMessageSystem;
+};
+
// Must specific overall system version, which is used to determine
// if a patch is available in the message template checksum verification.
// Return true if able to initialize system.
diff --git a/indra/llmessage/tests/llcoproceduremanager_test.cpp b/indra/llmessage/tests/llcoproceduremanager_test.cpp
new file mode 100644
index 0000000000..9db13a37b5
--- /dev/null
+++ b/indra/llmessage/tests/llcoproceduremanager_test.cpp
@@ -0,0 +1,178 @@
+/**
+ * @file llcoproceduremanager_test.cpp
+ * @author Brad
+ * @date 2019-02
+ * @brief LLCoprocedureManager unit test
+ *
+ * $LicenseInfo:firstyear=2019&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#include "llwin32headers.h"
+
+#include "linden_common.h"
+#include "llsdserialize.h"
+
+#include "../llcoproceduremanager.h"
+
+#include <functional>
+
+#include <boost/fiber/fiber.hpp>
+#include <boost/fiber/buffered_channel.hpp>
+#include <boost/fiber/unbuffered_channel.hpp>
+
+#include "../test/lltut.h"
+#include "../test/sync.h"
+
+
+#if LL_WINDOWS
+// disable unreachable code warnings
+#pragma warning(disable: 4702)
+#endif
+
+LLCoreHttpUtil::HttpCoroutineAdapter::HttpCoroutineAdapter(std::string const&, unsigned int, unsigned int)
+{
+}
+
+void LLCoreHttpUtil::HttpCoroutineAdapter::cancelSuspendedOperation()
+{
+}
+
+LLCoreHttpUtil::HttpCoroutineAdapter::~HttpCoroutineAdapter()
+{
+}
+
+LLCore::HttpRequest::HttpRequest()
+{
+}
+
+LLCore::HttpRequest::~HttpRequest()
+{
+}
+
+namespace tut
+{
+ struct coproceduremanager_test
+ {
+ coproceduremanager_test()
+ {
+ }
+
+ ~coproceduremanager_test()
+ {
+ LLCoprocedureManager::instance().close();
+ }
+ };
+ typedef test_group<coproceduremanager_test> coproceduremanager_t;
+ typedef coproceduremanager_t::object coproceduremanager_object_t;
+ tut::coproceduremanager_t tut_coproceduremanager("LLCoprocedureManager");
+
+
+ template<> template<>
+ void coproceduremanager_object_t::test<1>()
+ {
+ Sync sync;
+ int foo = 0;
+ LLUUID queueId = LLCoprocedureManager::instance().enqueueCoprocedure("PoolName", "ProcName",
+ [&foo, &sync] (LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t & ptr, const LLUUID & id) {
+ sync.bump();
+ foo = 1;
+ });
+
+ sync.yield();
+ ensure_equals("coprocedure failed to update foo", foo, 1);
+
+ LLCoprocedureManager::instance().close("PoolName");
+ }
+
+ template<> template<>
+ void coproceduremanager_object_t::test<2>()
+ {
+ const size_t capacity = 2;
+ boost::fibers::buffered_channel<std::function<void(void)>> chan(capacity);
+
+ boost::fibers::fiber worker([&chan]() {
+ chan.value_pop()();
+ });
+
+ chan.push([]() {
+ LL_INFOS("Test") << "test 1" << LL_ENDL;
+ });
+
+ worker.join();
+ }
+
+ template<> template<>
+ void coproceduremanager_object_t::test<3>()
+ {
+ boost::fibers::unbuffered_channel<std::function<void(void)>> chan;
+
+ boost::fibers::fiber worker([&chan]() {
+ chan.value_pop()();
+ });
+
+ chan.push([]() {
+ LL_INFOS("Test") << "test 1" << LL_ENDL;
+ });
+
+ worker.join();
+ }
+
+ template<> template<>
+ void coproceduremanager_object_t::test<4>()
+ {
+ boost::fibers::buffered_channel<std::function<void(void)>> chan(4);
+
+ boost::fibers::fiber worker([&chan]() {
+ std::function<void(void)> f;
+
+ // using namespace std::chrono_literals;
+ // const auto timeout = 5s;
+ // boost::fibers::channel_op_status status;
+ while (chan.pop(f) != boost::fibers::channel_op_status::closed)
+ {
+ LL_INFOS("CoWorker") << "got coproc" << LL_ENDL;
+ f();
+ }
+ LL_INFOS("CoWorker") << "got closed" << LL_ENDL;
+ });
+
+ int counter = 0;
+
+ for (int i = 0; i < 5; ++i)
+ {
+ LL_INFOS("CoMain") << "pushing coproc " << i << LL_ENDL;
+ chan.push([&counter]() {
+ LL_INFOS("CoProc") << "in coproc" << LL_ENDL;
+ ++counter;
+ });
+ }
+
+ LL_INFOS("CoMain") << "closing channel" << LL_ENDL;
+ chan.close();
+
+ LL_INFOS("CoMain") << "joining worker" << LL_ENDL;
+ worker.join();
+
+ LL_INFOS("CoMain") << "checking count" << LL_ENDL;
+ ensure_equals("coprocedure failed to update counter", counter, 5);
+ }
+} // namespace tut
diff --git a/indra/llplugin/llpluginmessagepipe.h b/indra/llplugin/llpluginmessagepipe.h
index c3498beac0..9d5835eb82 100644
--- a/indra/llplugin/llpluginmessagepipe.h
+++ b/indra/llplugin/llpluginmessagepipe.h
@@ -31,6 +31,7 @@
#include "lliosocket.h"
#include "llthread.h"
+#include "llmutex.h"
class LLPluginMessagePipe;
diff --git a/indra/llplugin/slplugin/CMakeLists.txt b/indra/llplugin/slplugin/CMakeLists.txt
index 33520ad64c..e4f64448c5 100644
--- a/indra/llplugin/slplugin/CMakeLists.txt
+++ b/indra/llplugin/slplugin/CMakeLists.txt
@@ -63,6 +63,7 @@ set_target_properties(SLPlugin
endif ()
target_link_libraries(SLPlugin
+ ${LEGACY_STDIO_LIBS}
${LLPLUGIN_LIBRARIES}
${LLMESSAGE_LIBRARIES}
${LLCOMMON_LIBRARIES}
diff --git a/indra/llprimitive/CMakeLists.txt b/indra/llprimitive/CMakeLists.txt
index dd2e806dda..7b6d04b096 100644
--- a/indra/llprimitive/CMakeLists.txt
+++ b/indra/llprimitive/CMakeLists.txt
@@ -80,7 +80,7 @@ target_link_libraries(llprimitive
${LLXML_LIBRARIES}
${LLPHYSICSEXTENSIONS_LIBRARIES}
${LLCHARACTER_LIBRARIES}
- ${BOOST_COROUTINE_LIBRARY}
+ ${BOOST_FIBER_LIBRARY}
${BOOST_CONTEXT_LIBRARY}
)
diff --git a/indra/llprimitive/llmodel.cpp b/indra/llprimitive/llmodel.cpp
index 37548e3fe3..a2d9b4cd9b 100644
--- a/indra/llprimitive/llmodel.cpp
+++ b/indra/llprimitive/llmodel.cpp
@@ -1579,7 +1579,7 @@ void LLModel::Decomposition::fromLLSD(LLSD& decomp)
range = max-min;
- U16 count = position.size()/6;
+ U16 count = (U16)(position.size()/6);
for (U32 j = 0; j < count; ++j)
{
diff --git a/indra/llrender/llgl.cpp b/indra/llrender/llgl.cpp
index 4c56b8eace..84f3796398 100644
--- a/indra/llrender/llgl.cpp
+++ b/indra/llrender/llgl.cpp
@@ -2461,9 +2461,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();
}
}
@@ -2471,9 +2470,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();
}
}
diff --git a/indra/llrender/llvertexbuffer.cpp b/indra/llrender/llvertexbuffer.cpp
index 94a04d4ddb..6a02cd9c19 100644
--- a/indra/llrender/llvertexbuffer.cpp
+++ b/indra/llrender/llvertexbuffer.cpp
@@ -1478,7 +1478,12 @@ void LLVertexBuffer::setupVertexArray()
//glVertexattribIPointer requires GLSL 1.30 or later
if (gGLManager.mGLSLVersionMajor > 1 || gGLManager.mGLSLVersionMinor >= 30)
{
- glVertexAttribIPointer(i, attrib_size[i], attrib_type[i], sTypeSize[i], (const GLvoid*) mOffsets[i]);
+ // nat 2018-10-24: VS 2017 also notices the issue
+ // described below, and warns even with reinterpret_cast.
+ // Cast via intptr_t to make it painfully obvious to the
+ // compiler that we're doing this intentionally.
+ glVertexAttribIPointer(i, attrib_size[i], attrib_type[i], sTypeSize[i],
+ reinterpret_cast<const GLvoid*>(intptr_t(mOffsets[i])));
}
#endif
}
@@ -1493,7 +1498,7 @@ void LLVertexBuffer::setupVertexArray()
// rather than as an actual pointer, so it's okay.
glVertexAttribPointerARB(i, attrib_size[i], attrib_type[i],
attrib_normalized[i], sTypeSize[i],
- reinterpret_cast<GLvoid*>(mOffsets[i]));
+ reinterpret_cast<GLvoid*>(intptr_t(mOffsets[i])));
}
}
else
diff --git a/indra/llui/CMakeLists.txt b/indra/llui/CMakeLists.txt
index 730e277dec..cce618487b 100644
--- a/indra/llui/CMakeLists.txt
+++ b/indra/llui/CMakeLists.txt
@@ -303,7 +303,7 @@ if(LL_TESTS)
set(test_libs llui llmessage llcorehttp llcommon
${HUNSPELL_LIBRARY}
${LLCOMMON_LIBRARIES}
- ${BOOST_COROUTINE_LIBRARY} ${BOOST_CONTEXT_LIBRARY} ${BOOST_SYSTEM_LIBRARY}
+ ${BOOST_FIBER_LIBRARY} ${BOOST_CONTEXT_LIBRARY} ${BOOST_SYSTEM_LIBRARY}
${WINDOWS_LIBRARIES})
if(NOT LINUX)
LL_ADD_INTEGRATION_TEST(llurlentry llurlentry.cpp "${test_libs}")
diff --git a/indra/llui/llaccordionctrl.cpp b/indra/llui/llaccordionctrl.cpp
index 623f570cef..edcbc3fbb7 100644
--- a/indra/llui/llaccordionctrl.cpp
+++ b/indra/llui/llaccordionctrl.cpp
@@ -338,7 +338,7 @@ void LLAccordionCtrl::addCollapsibleCtrl(LLView* view)
addChild(accordion_tab);
mAccordionTabs.push_back(accordion_tab);
- accordion_tab->setDropDownStateChangedCallback( boost::bind(&LLAccordionCtrl::onCollapseCtrlCloseOpen, this, mAccordionTabs.size() - 1) );
+ accordion_tab->setDropDownStateChangedCallback( boost::bind(&LLAccordionCtrl::onCollapseCtrlCloseOpen, this, (S16)(mAccordionTabs.size() - 1)) );
arrange();
}
diff --git a/indra/llui/llconsole.cpp b/indra/llui/llconsole.cpp
index 5f50e46233..7817d99aef 100644
--- a/indra/llui/llconsole.cpp
+++ b/indra/llui/llconsole.cpp
@@ -369,9 +369,9 @@ LLConsole::Paragraph::Paragraph (LLWString str, const LLColor4 &color, F32 add_t
// static
void LLConsole::updateClass()
{
- for (instance_iter it = beginInstances(); it != endInstances(); ++it)
+ for (auto& con : instance_snapshot())
{
- it->update();
+ con.update();
}
}
diff --git a/indra/llui/lllayoutstack.cpp b/indra/llui/lllayoutstack.cpp
index 4a464b3507..4aae1e374b 100644
--- a/indra/llui/lllayoutstack.cpp
+++ b/indra/llui/lllayoutstack.cpp
@@ -636,10 +636,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;
}
}
diff --git a/indra/llui/llnotifications.h b/indra/llui/llnotifications.h
index 62cf41256b..cac687f53d 100644
--- a/indra/llui/llnotifications.h
+++ b/indra/llui/llnotifications.h
@@ -746,42 +746,24 @@ public:
virtual ~LLNotificationChannelBase() {}
// you can also connect to a Channel, so you can be notified of
// changes to this channel
- template <typename LISTENER>
- LLBoundListener connectChanged(const LISTENER& slot)
+ LLBoundListener connectChanged(const LLEventListener& slot)
{
- // Examine slot to see if it binds an LLEventTrackable subclass, or a
- // boost::shared_ptr to something, or a boost::weak_ptr to something.
// Call this->connectChangedImpl() to actually connect it.
- return LLEventDetail::visit_and_connect(slot,
- boost::bind(&LLNotificationChannelBase::connectChangedImpl,
- this,
- _1));
+ return connectChangedImpl(slot);
}
- template <typename LISTENER>
- LLBoundListener connectAtFrontChanged(const LISTENER& slot)
+ LLBoundListener connectAtFrontChanged(const LLEventListener& slot)
{
- return LLEventDetail::visit_and_connect(slot,
- boost::bind(&LLNotificationChannelBase::connectAtFrontChangedImpl,
- this,
- _1));
+ return connectAtFrontChangedImpl(slot);
}
- template <typename LISTENER>
- LLBoundListener connectPassedFilter(const LISTENER& slot)
+ LLBoundListener connectPassedFilter(const LLEventListener& slot)
{
// see comments in connectChanged()
- return LLEventDetail::visit_and_connect(slot,
- boost::bind(&LLNotificationChannelBase::connectPassedFilterImpl,
- this,
- _1));
+ return connectPassedFilterImpl(slot);
}
- template <typename LISTENER>
- LLBoundListener connectFailedFilter(const LISTENER& slot)
+ LLBoundListener connectFailedFilter(const LLEventListener& slot)
{
// see comments in connectChanged()
- return LLEventDetail::visit_and_connect(slot,
- boost::bind(&LLNotificationChannelBase::connectFailedFilterImpl,
- this,
- _1));
+ return connectFailedFilterImpl(slot);
}
// use this when items change or to add a new one
diff --git a/indra/llui/llnotificationslistener.cpp b/indra/llui/llnotificationslistener.cpp
index be26416cbb..e73ba1fbe9 100644
--- a/indra/llui/llnotificationslistener.cpp
+++ b/indra/llui/llnotificationslistener.cpp
@@ -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);
}
diff --git a/indra/llvfs/lldir.cpp b/indra/llvfs/lldir.cpp
index 2076ce334e..10fbc06c61 100644
--- a/indra/llvfs/lldir.cpp
+++ b/indra/llvfs/lldir.cpp
@@ -1090,7 +1090,7 @@ LLDir::SepOff LLDir::needSep(const std::string& path, const std::string& name) c
{
// But if BOTH path and name bring a separator, we need not add one.
// Moreover, we should actually skip the leading separator of 'name'.
- return SepOff(false, seplen);
+ return SepOff(false, (unsigned short)seplen);
}
// Here we know that either path_ends_sep or name_starts_sep is true --
// but not both. So don't add a separator, and don't skip any characters:
diff --git a/indra/llvfs/llvfs.h b/indra/llvfs/llvfs.h
index dca5ff4ad5..42feafe20b 100644
--- a/indra/llvfs/llvfs.h
+++ b/indra/llvfs/llvfs.h
@@ -31,6 +31,7 @@
#include "lluuid.h"
#include "llassettype.h"
#include "llthread.h"
+#include "llmutex.h"
enum EVFSValid
{
diff --git a/indra/llwindow/CMakeLists.txt b/indra/llwindow/CMakeLists.txt
index 0743fd899f..8bfb23ed64 100644
--- a/indra/llwindow/CMakeLists.txt
+++ b/indra/llwindow/CMakeLists.txt
@@ -11,7 +11,6 @@
project(llwindow)
include(00-Common)
-include(DirectX)
include(DragDrop)
include(LLCommon)
include(LLImage)
@@ -30,7 +29,6 @@ include_directories(
${LLVFS_INCLUDE_DIRS}
${LLWINDOW_INCLUDE_DIRS}
${LLXML_INCLUDE_DIRS}
- ${DIRECTX_INCLUDE_DIR}
)
include_directories(SYSTEM
${LLCOMMON_SYSTEM_INCLUDE_DIRS}
diff --git a/indra/llwindow/llwindow.cpp b/indra/llwindow/llwindow.cpp
index 1b24250618..d77997a928 100644
--- a/indra/llwindow/llwindow.cpp
+++ b/indra/llwindow/llwindow.cpp
@@ -457,9 +457,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();
}
@@ -467,18 +466,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();
}
@@ -486,7 +483,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);
}
diff --git a/indra/llxml/llcontrol.h b/indra/llxml/llcontrol.h
index f136918896..6e6004cdb2 100644
--- a/indra/llxml/llcontrol.h
+++ b/indra/llxml/llcontrol.h
@@ -202,8 +202,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);
diff --git a/indra/mac_crash_logger/CMakeLists.txt b/indra/mac_crash_logger/CMakeLists.txt
index f6c4dfb59d..95637c9a28 100644
--- a/indra/mac_crash_logger/CMakeLists.txt
+++ b/indra/mac_crash_logger/CMakeLists.txt
@@ -77,7 +77,7 @@ target_link_libraries(mac-crash-logger
${LLCOREHTTP_LIBRARIES}
${LLCOMMON_LIBRARIES}
${BOOST_CONTEXT_LIBRARY}
- ${BOOST_COROUTINE_LIBRARY}
+ ${BOOST_FIBER_LIBRARY}
)
add_custom_command(
diff --git a/indra/media_plugins/cef/windows_volume_catcher.cpp b/indra/media_plugins/cef/windows_volume_catcher.cpp
index 6953ad3ab8..7a36123a11 100644
--- a/indra/media_plugins/cef/windows_volume_catcher.cpp
+++ b/indra/media_plugins/cef/windows_volume_catcher.cpp
@@ -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);
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index 33fa186a2e..88667bdc11 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -15,7 +15,6 @@ include(BuildPackagesInfo)
include(BuildVersion)
include(CMakeCopyIfDifferent)
include(DBusGlib)
-include(DirectX)
include(DragDrop)
include(EXPAT)
include(FMODSTUDIO)
@@ -1583,20 +1582,12 @@ if (WINDOWS)
list(APPEND viewer_SOURCE_FILES ${viewer_RESOURCE_FILES})
endif (NOT USESYSTEMLIBS)
- find_library(DINPUT_LIBRARY dinput8 ${DIRECTX_LIBRARY_DIR})
- find_library(DXGUID_LIBRARY dxguid ${DIRECTX_LIBRARY_DIR})
- mark_as_advanced(
- DINPUT_LIBRARY
- DXGUID_LIBRARY
- )
-
# see EXP-1765 - theory is opengl32.lib needs to be included before gdi32.lib (windows libs)
set(viewer_LIBRARIES
opengl32
${WINDOWS_LIBRARIES}
comdlg32
- ${DINPUT_LIBRARY}
- ${DXGUID_LIBRARY}
+ dxguid
kernel32
odbc32
odbccp32
@@ -1783,6 +1774,11 @@ if (WINDOWS)
# be met. I'm looking forward to a source-code split-up project next year that will address this kind of thing.
# In the meantime, if you have any ideas on how to easily maintain one list, either here or in viewer_manifest.py
# and have the build deps get tracked *please* tell me about it.
+ # nat: https://cmake.org/cmake/help/v3.14/command/file.html
+ # "For example, the code
+ # file(STRINGS myfile.txt myfile)
+ # stores a list in the variable myfile in which each item is a line from the input file."
+ # And of course it's straightforward to read a text file in Python.
set(COPY_INPUT_DEPENDENCIES
# The following commented dependencies are determined at variably at build time. Can't do this here.
@@ -1801,12 +1797,6 @@ if (WINDOWS)
${SHARED_LIB_STAGING_DIR}/Release/openjpeg.dll
${SHARED_LIB_STAGING_DIR}/RelWithDebInfo/openjpeg.dll
${SHARED_LIB_STAGING_DIR}/Debug/openjpegd.dll
- ${SHARED_LIB_STAGING_DIR}/Release/msvcr100.dll
- ${SHARED_LIB_STAGING_DIR}/Release/msvcp100.dll
- ${SHARED_LIB_STAGING_DIR}/RelWithDebInfo/msvcr100.dll
- ${SHARED_LIB_STAGING_DIR}/RelWithDebInfo/msvcp100.dll
- ${SHARED_LIB_STAGING_DIR}/Debug/msvcr100d.dll
- ${SHARED_LIB_STAGING_DIR}/Debug/msvcp100d.dll
${SHARED_LIB_STAGING_DIR}/Release/libhunspell.dll
${SHARED_LIB_STAGING_DIR}/RelWithDebInfo/libhunspell.dll
${SHARED_LIB_STAGING_DIR}/Debug/libhunspell.dll
@@ -1990,6 +1980,7 @@ endif (WINDOWS)
# modern version.
target_link_libraries(${VIEWER_BINARY_NAME}
+ ${LEGACY_STDIO_LIBS}
${PNG_PRELOAD_ARCHIVES}
${ZLIB_PRELOAD_ARCHIVES}
${URIPARSER_PRELOAD_ARCHIVES}
@@ -2016,7 +2007,7 @@ target_link_libraries(${VIEWER_BINARY_NAME}
${viewer_LIBRARIES}
${BOOST_PROGRAM_OPTIONS_LIBRARY}
${BOOST_REGEX_LIBRARY}
- ${BOOST_COROUTINE_LIBRARY}
+ ${BOOST_FIBER_LIBRARY}
${BOOST_CONTEXT_LIBRARY}
${DBUSGLIB_LIBRARIES}
${OPENGL_LIBRARIES}
@@ -2450,6 +2441,7 @@ if (LL_TESTS)
set_source_files_properties(
lllogininstance.cpp
PROPERTIES
+ LL_TEST_ADDITIONAL_SOURCE_FILES llversioninfo.cpp
LL_TEST_ADDITIONAL_LIBRARIES "${BOOST_SYSTEM_LIBRARY}"
)
@@ -2499,7 +2491,7 @@ if (LL_TESTS)
${OPENSSL_LIBRARIES}
${CRYPTO_LIBRARIES}
${LIBRT_LIBRARY}
- ${BOOST_COROUTINE_LIBRARY}
+ ${BOOST_FIBER_LIBRARY}
${BOOST_CONTEXT_LIBRARY}
)
diff --git a/indra/newview/VIEWER_VERSION.txt b/indra/newview/VIEWER_VERSION.txt
index 7d765dabde..3d05e8cfb4 100644
--- a/indra/newview/VIEWER_VERSION.txt
+++ b/indra/newview/VIEWER_VERSION.txt
@@ -1 +1 @@
-6.4.5
+6.4.6
diff --git a/indra/newview/llaccountingcostmanager.cpp b/indra/newview/llaccountingcostmanager.cpp
index 1dddf52961..e09527a34b 100644
--- a/indra/newview/llaccountingcostmanager.cpp
+++ b/indra/newview/llaccountingcostmanager.cpp
@@ -48,7 +48,7 @@ LLAccountingCostManager::LLAccountingCostManager()
void LLAccountingCostManager::accountingCostCoro(std::string url,
eSelectionType selectionType, const LLHandle<LLAccountingCostObserver> observerHandle)
{
- LL_DEBUGS("LLAccountingCostManager") << "Entering coroutine " << LLCoros::instance().getName()
+ LL_DEBUGS("LLAccountingCostManager") << "Entering coroutine " << LLCoros::getName()
<< " with url '" << url << LL_ENDL;
LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
@@ -158,7 +158,7 @@ void LLAccountingCostManager::accountingCostCoro(std::string url,
}
catch (...)
{
- LOG_UNHANDLED_EXCEPTION(STRINGIZE("coroutine " << LLCoros::instance().getName()
+ LOG_UNHANDLED_EXCEPTION(STRINGIZE("coroutine " << LLCoros::getName()
<< "('" << url << "')"));
throw;
}
diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index 79c0a8c8eb..4b2e8c00e0 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -1126,7 +1126,7 @@ bool LLAppViewer::init()
// Save the current version to the prefs file
gSavedSettings.setString("LastRunVersion",
- LLVersionInfo::getChannelAndVersion());
+ LLVersionInfo::instance().getChannelAndVersion());
gSimLastTime = gRenderStartTime.getElapsedTimeF32();
gSimFrames = (F32)gFrameCount;
@@ -1168,7 +1168,7 @@ bool LLAppViewer::init()
// UpdaterServiceSettings
updater.args.add(stringize(gSavedSettings.getU32("UpdaterServiceSetting")));
// channel
- updater.args.add(LLVersionInfo::getChannel());
+ updater.args.add(LLVersionInfo::instance().getChannel());
// testok
updater.args.add(stringize(gSavedSettings.getBOOL("UpdaterWillingToTest")));
// ForceAddressSize
@@ -1432,6 +1432,8 @@ bool LLAppViewer::doFrame()
// canonical per-frame event
mainloop.post(newFrame);
+ // give listeners a chance to run
+ llcoro::suspend();
if (!LLApp::isExiting())
{
@@ -1693,24 +1695,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
@@ -2133,25 +2120,19 @@ bool LLAppViewer::cleanup()
removeMarkerFiles();
- // It's not at first obvious where, in this long sequence, generic cleanup
- // calls OUGHT to go. So let's say this: as we migrate cleanup from
+ // It's not at first obvious where, in this long sequence, a generic cleanup
+ // call OUGHT to go. So let's say this: as we migrate cleanup from
// explicit hand-placed calls into the generic mechanism, eventually
- // all cleanup will get subsumed into the generic calls. So the calls you
+ // all cleanup will get subsumed into the generic call. So the calls you
// still see above are calls that MUST happen before the generic cleanup
// kicks in.
- // This calls every remaining LLSingleton's cleanupSingleton() method.
- // This method should perform any cleanup that might take significant
- // realtime, or might throw an exception.
- LLSingletonBase::cleanupAll();
-
// The logging subsystem depends on an LLSingleton. Any logging after
// LLSingletonBase::deleteAll() won't be recorded.
LL_INFOS() << "Goodbye!" << LL_ENDL;
- // This calls every remaining LLSingleton's deleteSingleton() method.
- // No class destructor should perform any cleanup that might take
- // significant realtime, or throw an exception.
+ // This calls every remaining LLSingleton's cleanupSingleton() and
+ // deleteSingleton() methods.
LLSingletonBase::deleteAll();
removeDumpDir();
@@ -2664,7 +2645,7 @@ bool LLAppViewer::initConfiguration()
std::string CmdLineChannel(gSavedSettings.getString("CmdLineChannel"));
if(! CmdLineChannel.empty())
{
- LLVersionInfo::resetChannel(CmdLineChannel);
+ LLVersionInfo::instance().resetChannel(CmdLineChannel);
}
// If we have specified crash on startup, set the global so we'll trigger the crash at the right time
@@ -2885,12 +2866,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));
}
return true; // Config was successful.
@@ -3110,16 +3090,12 @@ LLSD LLAppViewer::getViewerInfo() const
// is available to a getInfo() caller as to the user opening
// LLFloaterAbout.
LLSD info;
- LLSD version;
- version.append(LLVersionInfo::getMajor());
- version.append(LLVersionInfo::getMinor());
- version.append(LLVersionInfo::getPatch());
- version.append(LLVersionInfo::getBuild());
- info["VIEWER_VERSION"] = version;
- info["VIEWER_VERSION_STR"] = LLVersionInfo::getVersion();
- info["CHANNEL"] = LLVersionInfo::getChannel();
+ auto& versionInfo(LLVersionInfo::instance());
+ info["VIEWER_VERSION"] = LLSDArray(versionInfo.getMajor())(versionInfo.getMinor())(versionInfo.getPatch())(versionInfo.getBuild());
+ info["VIEWER_VERSION_STR"] = versionInfo.getVersion();
+ info["CHANNEL"] = versionInfo.getChannel();
info["ADDRESS_SIZE"] = ADDRESS_SIZE;
- std::string build_config = LLVersionInfo::getBuildConfig();
+ std::string build_config = versionInfo.getBuildConfig();
if (build_config != "Release")
{
info["BUILD_CONFIG"] = build_config;
@@ -3127,12 +3103,8 @@ LLSD LLAppViewer::getViewerInfo() const
// return a URL to the release notes for this viewer, such as:
// https://releasenotes.secondlife.com/viewer/2.1.0.123456.html
- std::string url = LLTrans::getString("RELEASE_NOTES_BASE_URL");
- if (! LLStringUtil::endsWith(url, "/"))
- url += "/";
- url += LLURI::escape(LLVersionInfo::getVersion()) + ".html";
-
- info["VIEWER_RELEASE_NOTES_URL"] = url;
+ std::string url = versionInfo.getReleaseNotes();
+ info["VIEWER_RELEASE_NOTES_URL"] = url.empty()? LLTrans::getString("RetrievingData") : url;
// Position
LLViewerRegion* region = gAgent.getRegion();
@@ -3425,12 +3397,12 @@ void LLAppViewer::writeSystemInfo()
gDebugInfo["SLLog"] = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,"SecondLife.old"); //LLError::logFileName();
#endif
- gDebugInfo["ClientInfo"]["Name"] = LLVersionInfo::getChannel();
- gDebugInfo["ClientInfo"]["MajorVersion"] = LLVersionInfo::getMajor();
- gDebugInfo["ClientInfo"]["MinorVersion"] = LLVersionInfo::getMinor();
- gDebugInfo["ClientInfo"]["PatchVersion"] = LLVersionInfo::getPatch();
- gDebugInfo["ClientInfo"]["BuildVersion"] = LLVersionInfo::getBuild();
- gDebugInfo["ClientInfo"]["AddressSize"] = LLVersionInfo::getAddressSize();
+ gDebugInfo["ClientInfo"]["Name"] = LLVersionInfo::instance().getChannel();
+ gDebugInfo["ClientInfo"]["MajorVersion"] = LLVersionInfo::instance().getMajor();
+ gDebugInfo["ClientInfo"]["MinorVersion"] = LLVersionInfo::instance().getMinor();
+ gDebugInfo["ClientInfo"]["PatchVersion"] = LLVersionInfo::instance().getPatch();
+ gDebugInfo["ClientInfo"]["BuildVersion"] = LLVersionInfo::instance().getBuild();
+ gDebugInfo["ClientInfo"]["AddressSize"] = LLVersionInfo::instance().getAddressSize();
gDebugInfo["CAFilename"] = gDirUtilp->getCAFile();
@@ -3473,7 +3445,7 @@ void LLAppViewer::writeSystemInfo()
// Dump some debugging info
LL_INFOS("SystemInfo") << "Application: " << LLTrans::getString("APP_NAME") << LL_ENDL;
- LL_INFOS("SystemInfo") << "Version: " << LLVersionInfo::getChannelAndVersion() << LL_ENDL;
+ LL_INFOS("SystemInfo") << "Version: " << LLVersionInfo::instance().getChannelAndVersion() << LL_ENDL;
// Dump the local time and time zone
time_t now;
@@ -3695,7 +3667,7 @@ void LLAppViewer::handleViewerCrash()
// static
void LLAppViewer::recordMarkerVersion(LLAPRFile& marker_file)
{
- std::string marker_version(LLVersionInfo::getChannelAndVersion());
+ std::string marker_version(LLVersionInfo::instance().getChannelAndVersion());
if ( marker_version.length() > MAX_MARKER_LENGTH )
{
LL_WARNS_ONCE("MarkerFile") << "Version length ("<< marker_version.length()<< ")"
@@ -3712,7 +3684,7 @@ bool LLAppViewer::markerIsSameVersion(const std::string& marker_name) const
{
bool sameVersion = false;
- std::string my_version(LLVersionInfo::getChannelAndVersion());
+ std::string my_version(LLVersionInfo::instance().getChannelAndVersion());
char marker_version[MAX_MARKER_LENGTH];
S32 marker_version_length;
@@ -4672,6 +4644,9 @@ void LLAppViewer::idle()
LLFrameTimer::updateFrameTime();
LLFrameTimer::updateFrameCount();
LLEventTimer::updateClass();
+ // LLApp::stepFrame() performs the above three calls plus mRunner.run().
+ // Not sure why we don't call stepFrame() here, except that LLRunner seems
+ // completely redundant with LLEventTimer.
LLNotificationsUI::LLToast::updateClass();
LLSmoothInterpolation::updateInterpolants();
LLMortician::updateClass();
@@ -5284,37 +5259,40 @@ void LLAppViewer::idleNetwork()
const S64 frame_count = gFrameCount; // U32->S64
F32 total_time = 0.0f;
- while (gMessageSystem->checkAllMessages(frame_count, gServicePump))
{
- if (gDoDisconnect)
+ LockMessageChecker lmc(gMessageSystem);
+ while (lmc.checkAllMessages(frame_count, gServicePump))
{
- // We're disconnecting, don't process any more messages from the server
- // We're usually disconnecting due to either network corruption or a
- // server going down, so this is OK.
- break;
- }
+ if (gDoDisconnect)
+ {
+ // We're disconnecting, don't process any more messages from the server
+ // We're usually disconnecting due to either network corruption or a
+ // server going down, so this is OK.
+ break;
+ }
- total_decoded++;
- gPacketsIn++;
+ total_decoded++;
+ gPacketsIn++;
- if (total_decoded > MESSAGE_MAX_PER_FRAME)
- {
- break;
- }
+ if (total_decoded > MESSAGE_MAX_PER_FRAME)
+ {
+ break;
+ }
#ifdef TIME_THROTTLE_MESSAGES
- // Prevent slow packets from completely destroying the frame rate.
- // This usually happens due to clumps of avatars taking huge amount
- // of network processing time (which needs to be fixed, but this is
- // a good limit anyway).
- total_time = check_message_timer.getElapsedTimeF32();
- if (total_time >= CheckMessagesMaxTime)
- break;
+ // Prevent slow packets from completely destroying the frame rate.
+ // This usually happens due to clumps of avatars taking huge amount
+ // of network processing time (which needs to be fixed, but this is
+ // a good limit anyway).
+ total_time = check_message_timer.getElapsedTimeF32();
+ if (total_time >= CheckMessagesMaxTime)
+ break;
#endif
- }
+ }
- // Handle per-frame message system processing.
- gMessageSystem->processAcks(gSavedSettings.getF32("AckCollectTime"));
+ // Handle per-frame message system processing.
+ lmc.processAcks(gSavedSettings.getF32("AckCollectTime"));
+ }
#ifdef TIME_THROTTLE_MESSAGES
if (total_time >= CheckMessagesMaxTime)
@@ -5572,12 +5550,12 @@ void LLAppViewer::handleLoginComplete()
initMainloopTimeout("Mainloop Init");
// Store some data to DebugInfo in case of a freeze.
- gDebugInfo["ClientInfo"]["Name"] = LLVersionInfo::getChannel();
+ gDebugInfo["ClientInfo"]["Name"] = LLVersionInfo::instance().getChannel();
- gDebugInfo["ClientInfo"]["MajorVersion"] = LLVersionInfo::getMajor();
- gDebugInfo["ClientInfo"]["MinorVersion"] = LLVersionInfo::getMinor();
- gDebugInfo["ClientInfo"]["PatchVersion"] = LLVersionInfo::getPatch();
- gDebugInfo["ClientInfo"]["BuildVersion"] = LLVersionInfo::getBuild();
+ gDebugInfo["ClientInfo"]["MajorVersion"] = LLVersionInfo::instance().getMajor();
+ gDebugInfo["ClientInfo"]["MinorVersion"] = LLVersionInfo::instance().getMinor();
+ gDebugInfo["ClientInfo"]["PatchVersion"] = LLVersionInfo::instance().getPatch();
+ gDebugInfo["ClientInfo"]["BuildVersion"] = LLVersionInfo::instance().getBuild();
LLParcel* parcel = LLViewerParcelMgr::getInstance()->getAgentParcel();
if ( parcel && parcel->getMusicURL()[0])
diff --git a/indra/newview/llappviewerwin32.cpp b/indra/newview/llappviewerwin32.cpp
index f0aa355342..156a1c5893 100644
--- a/indra/newview/llappviewerwin32.cpp
+++ b/indra/newview/llappviewerwin32.cpp
@@ -507,63 +507,65 @@ const S32 MAX_CONSOLE_LINES = 500;
namespace {
-FILE* set_stream(const char* which, DWORD handle_id, const char* mode);
+void set_stream(const char* desc, FILE* fp, DWORD handle_id, const char* name, const char* mode="w");
bool create_console()
{
- // allocate a console for this app
- const bool isConsoleAllocated = AllocConsole();
-
- // set the screen buffer to be big enough to let us scroll text
- CONSOLE_SCREEN_BUFFER_INFO coninfo;
- GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &coninfo);
- coninfo.dwSize.Y = MAX_CONSOLE_LINES;
- SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), coninfo.dwSize);
-
- // redirect unbuffered STDOUT to the console
- FILE* fp = set_stream("stdout", STD_OUTPUT_HANDLE, "w");
- if (fp)
- {
- *stdout = *fp;
- }
-
- // redirect unbuffered STDIN to the console
- fp = set_stream("stdin", STD_INPUT_HANDLE, "r");
- if (fp)
- {
- *stdin = *fp;
- }
+ // allocate a console for this app
+ const bool isConsoleAllocated = AllocConsole();
- // redirect unbuffered STDERR to the console
- fp = set_stream("stderr", STD_ERROR_HANDLE, "w");
- if (fp)
- {
- *stderr = *fp;
- }
+ if (isConsoleAllocated)
+ {
+ // set the screen buffer to be big enough to let us scroll text
+ CONSOLE_SCREEN_BUFFER_INFO coninfo;
+ GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &coninfo);
+ coninfo.dwSize.Y = MAX_CONSOLE_LINES;
+ SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), coninfo.dwSize);
+
+ // redirect unbuffered STDOUT to the console
+ set_stream("stdout", stdout, STD_OUTPUT_HANDLE, "CONOUT$");
+ // redirect unbuffered STDERR to the console
+ set_stream("stderr", stderr, STD_ERROR_HANDLE, "CONOUT$");
+ // redirect unbuffered STDIN to the console
+ // Don't bother: our console is solely for log output. We never read stdin.
+// set_stream("stdin", stdin, STD_INPUT_HANDLE, "CONIN$", "r");
+ }
- return isConsoleAllocated;
+ return isConsoleAllocated;
}
-FILE* set_stream(const char* desc, DWORD handle_id, const char* mode)
+void set_stream(const char* desc, FILE* fp, DWORD handle_id, const char* name, const char* mode)
{
- auto l_std_handle = GetStdHandle(handle_id);
- int h_con_handle = _open_osfhandle(reinterpret_cast<intptr_t>(l_std_handle), _O_TEXT);
- if (h_con_handle == -1)
- {
- LL_WARNS() << "create_console() failed to open " << desc << " handle" << LL_ENDL;
- return nullptr;
- }
- else
- {
- FILE* fp = _fdopen( h_con_handle, mode );
- setvbuf( fp, NULL, _IONBF, 0 );
- // Enable color processing on Windows 10 console windows.
- DWORD dwMode = 0;
- GetConsoleMode(l_std_handle, &dwMode);
- dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
- SetConsoleMode(l_std_handle, dwMode);
- return fp;
- }
+ // SL-13528: This code used to be based on
+ // http://dslweb.nwnexus.com/~ast/dload/guicon.htm
+ // (referenced in https://stackoverflow.com/a/191880).
+ // But one of the comments on that StackOverflow answer points out that
+ // assigning to *stdout or *stderr "probably doesn't even work with the
+ // Universal CRT that was introduced in 2015," suggesting freopen_s()
+ // instead. Code below is based on https://stackoverflow.com/a/55875595.
+ auto std_handle = GetStdHandle(handle_id);
+ if (std_handle == INVALID_HANDLE_VALUE)
+ {
+ LL_WARNS() << "create_console() failed to get " << desc << " handle" << LL_ENDL;
+ }
+ else
+ {
+ if (mode == std::string("w"))
+ {
+ // Enable color processing on Windows 10 console windows.
+ DWORD dwMode = 0;
+ GetConsoleMode(std_handle, &dwMode);
+ dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
+ SetConsoleMode(std_handle, dwMode);
+ }
+ // Redirect the passed fp to the console.
+ FILE* ignore;
+ if (freopen_s(&ignore, name, mode, fp) == 0)
+ {
+ // use unbuffered I/O
+ setvbuf( fp, NULL, _IONBF, 0 );
+ }
+ }
}
} // anonymous namespace
diff --git a/indra/newview/llchannelmanager.cpp b/indra/newview/llchannelmanager.cpp
index 0b7b9cbbc7..9e7a8ba95c 100644
--- a/indra/newview/llchannelmanager.cpp
+++ b/indra/newview/llchannelmanager.cpp
@@ -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();
}
//--------------------------------------------------------------------------
diff --git a/indra/newview/llchathistory.cpp b/indra/newview/llchathistory.cpp
index 1099d4bc09..4131af828e 100644
--- a/indra/newview/llchathistory.cpp
+++ b/indra/newview/llchathistory.cpp
@@ -1344,10 +1344,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"
diff --git a/indra/newview/llcurrencyuimanager.cpp b/indra/newview/llcurrencyuimanager.cpp
index b4a1457f47..df94e337da 100644
--- a/indra/newview/llcurrencyuimanager.cpp
+++ b/indra/newview/llcurrencyuimanager.cpp
@@ -166,11 +166,11 @@ void LLCurrencyUIManager::Impl::updateCurrencyInfo()
gAgent.getSecureSessionID().asString());
keywordArgs.appendString("language", LLUI::getLanguage());
keywordArgs.appendInt("currencyBuy", mUserCurrencyBuy);
- keywordArgs.appendString("viewerChannel", LLVersionInfo::getChannel());
- keywordArgs.appendInt("viewerMajorVersion", LLVersionInfo::getMajor());
- keywordArgs.appendInt("viewerMinorVersion", LLVersionInfo::getMinor());
- keywordArgs.appendInt("viewerPatchVersion", LLVersionInfo::getPatch());
- keywordArgs.appendInt("viewerBuildVersion", LLVersionInfo::getBuild());
+ keywordArgs.appendString("viewerChannel", LLVersionInfo::instance().getChannel());
+ keywordArgs.appendInt("viewerMajorVersion", LLVersionInfo::instance().getMajor());
+ keywordArgs.appendInt("viewerMinorVersion", LLVersionInfo::instance().getMinor());
+ keywordArgs.appendInt("viewerPatchVersion", LLVersionInfo::instance().getPatch());
+ keywordArgs.appendInt("viewerBuildVersion", LLVersionInfo::instance().getBuild());
LLXMLRPCValue params = LLXMLRPCValue::createArray();
params.append(keywordArgs);
@@ -241,11 +241,11 @@ void LLCurrencyUIManager::Impl::startCurrencyBuy(const std::string& password)
{
keywordArgs.appendString("password", password);
}
- keywordArgs.appendString("viewerChannel", LLVersionInfo::getChannel());
- keywordArgs.appendInt("viewerMajorVersion", LLVersionInfo::getMajor());
- keywordArgs.appendInt("viewerMinorVersion", LLVersionInfo::getMinor());
- keywordArgs.appendInt("viewerPatchVersion", LLVersionInfo::getPatch());
- keywordArgs.appendInt("viewerBuildVersion", LLVersionInfo::getBuild());
+ keywordArgs.appendString("viewerChannel", LLVersionInfo::instance().getChannel());
+ keywordArgs.appendInt("viewerMajorVersion", LLVersionInfo::instance().getMajor());
+ keywordArgs.appendInt("viewerMinorVersion", LLVersionInfo::instance().getMinor());
+ keywordArgs.appendInt("viewerPatchVersion", LLVersionInfo::instance().getPatch());
+ keywordArgs.appendInt("viewerBuildVersion", LLVersionInfo::instance().getBuild());
LLXMLRPCValue params = LLXMLRPCValue::createArray();
params.append(keywordArgs);
diff --git a/indra/newview/llfloaterregioninfo.cpp b/indra/newview/llfloaterregioninfo.cpp
index 8bcc5bbe7a..ec1909d02a 100644
--- a/indra/newview/llfloaterregioninfo.cpp
+++ b/indra/newview/llfloaterregioninfo.cpp
@@ -637,11 +637,7 @@ void LLFloaterRegionInfo::refreshFromRegion(LLViewerRegion* region)
mInfoPanels.begin(),
mInfoPanels.end(),
llbind2nd(
-#if LL_WINDOWS
- std::mem_fun1(&LLPanelRegionInfo::refreshFromRegion),
-#else
std::mem_fun(&LLPanelRegionInfo::refreshFromRegion),
-#endif
region));
mEnvironmentPanel->refreshFromRegion(region);
}
diff --git a/indra/newview/llfloaterreporter.cpp b/indra/newview/llfloaterreporter.cpp
index 4cc43254a5..64b7880938 100644
--- a/indra/newview/llfloaterreporter.cpp
+++ b/indra/newview/llfloaterreporter.cpp
@@ -765,7 +765,7 @@ LLSD LLFloaterReporter::gatherReport()
std::ostringstream details;
- details << "V" << LLVersionInfo::getVersion() << std::endl << std::endl; // client version moved to body of email for abuse reports
+ details << "V" << LLVersionInfo::instance().getVersion() << std::endl << std::endl; // client version moved to body of email for abuse reports
std::string object_name = getChild<LLUICtrl>("object_name")->getValue().asString();
if (!object_name.empty() && !mOwnerName.empty())
@@ -783,7 +783,7 @@ LLSD LLFloaterReporter::gatherReport()
std::string version_string;
version_string = llformat(
"%s %s %s %s %s",
- LLVersionInfo::getShortVersion().c_str(),
+ LLVersionInfo::instance().getShortVersion().c_str(),
platform,
gSysCPU.getFamily().c_str(),
gGLManager.mGLRenderer.c_str(),
diff --git a/indra/newview/llimprocessing.cpp b/indra/newview/llimprocessing.cpp
index 79cf49ddb1..6ccb9766a5 100644
--- a/indra/newview/llimprocessing.cpp
+++ b/indra/newview/llimprocessing.cpp
@@ -1404,10 +1404,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())
{
diff --git a/indra/newview/lllogininstance.cpp b/indra/newview/lllogininstance.cpp
index 873531ef22..9d54c8c9c5 100644
--- a/indra/newview/lllogininstance.cpp
+++ b/indra/newview/lllogininstance.cpp
@@ -215,8 +215,8 @@ void LLLoginInstance::constructAuthParams(LLPointer<LLCredential> user_credentia
request_params["last_exec_event"] = mLastExecEvent;
request_params["last_exec_duration"] = mLastExecDuration;
request_params["mac"] = (char*)hashed_unique_id_string;
- request_params["version"] = LLVersionInfo::getVersion();
- request_params["channel"] = LLVersionInfo::getChannel();
+ request_params["version"] = LLVersionInfo::instance().getVersion();
+ request_params["channel"] = LLVersionInfo::instance().getChannel();
request_params["platform"] = mPlatform;
request_params["address_size"] = ADDRESS_SIZE;
request_params["platform_version"] = mPlatformVersion;
@@ -332,7 +332,7 @@ void LLLoginInstance::handleLoginFailure(const LLSD& event)
{
data["certificate"] = response["certificate"];
}
-
+
if (gViewerWindow)
gViewerWindow->setShowProgress(FALSE);
@@ -349,13 +349,31 @@ void LLLoginInstance::handleLoginFailure(const LLSD& event)
// login.cgi is insisting on a required update. We were called with an
// event that bundles both the login.cgi 'response' and the
// synchronization event from the 'updater'.
- std::string required_version = response["message_args"]["VERSION"];
- LL_WARNS("LLLogin") << "Login failed because an update to version " << required_version << " is required." << LL_ENDL;
+ std::string login_version = response["message_args"]["VERSION"];
+ std::string vvm_version = updater["VERSION"];
+ std::string relnotes = updater["URL"];
+ LL_WARNS("LLLogin") << "Login failed because an update to version " << login_version << " is required." << LL_ENDL;
+ // vvm_version might be empty because we might not have gotten
+ // SLVersionChecker's LoginSync handshake. But if it IS populated, it
+ // should (!) be the same as the version we got from login.cgi.
+ if ((! vvm_version.empty()) && vvm_version != login_version)
+ {
+ LL_WARNS("LLLogin") << "VVM update version " << vvm_version
+ << " differs from login version " << login_version
+ << "; presenting VVM version to match release notes URL"
+ << LL_ENDL;
+ login_version = vvm_version;
+ }
+ if (relnotes.empty())
+ {
+ // I thought this would be available in strings.xml or some such
+ relnotes = "https://secondlife.com/support/downloads/";
+ }
if (gViewerWindow)
gViewerWindow->setShowProgress(FALSE);
- LLSD args(LLSDMap("VERSION", required_version));
+ LLSD args(LLSDMap("VERSION", login_version)("URL", relnotes));
if (updater.isUndefined())
{
// If the updater failed to shake hands, better advise the user to
diff --git a/indra/newview/llpaneleditwearable.cpp b/indra/newview/llpaneleditwearable.cpp
index 6573be0aaf..c601a6c210 100644
--- a/indra/newview/llpaneleditwearable.cpp
+++ b/indra/newview/llpaneleditwearable.cpp
@@ -778,7 +778,7 @@ BOOL LLPanelEditWearable::postBuild()
LL_WARNS() << "could not get wearable dictionary entry for wearable of type: " << type << LL_ENDL;
continue;
}
- U8 num_subparts = wearable_entry->mSubparts.size();
+ U8 num_subparts = (U8)(wearable_entry->mSubparts.size());
for (U8 index = 0; index < num_subparts; ++index)
{
@@ -1181,7 +1181,7 @@ void LLPanelEditWearable::showWearable(LLViewerWearable* wearable, BOOL show, BO
updatePanelPickerControls(type);
// clear and rebuild visual param list
- U8 num_subparts = wearable_entry->mSubparts.size();
+ U8 num_subparts = (U8)(wearable_entry->mSubparts.size());
for (U8 index = 0; index < num_subparts; ++index)
{
@@ -1372,7 +1372,7 @@ void LLPanelEditWearable::updateScrollingPanelUI()
const LLEditWearableDictionary::WearableEntry *wearable_entry = LLEditWearableDictionary::getInstance()->getWearable(type);
llassert(wearable_entry);
if (!wearable_entry) return;
- U8 num_subparts = wearable_entry->mSubparts.size();
+ U8 num_subparts = (U8)(wearable_entry->mSubparts.size());
LLScrollingPanelParam::sUpdateDelayFrames = 0;
for (U8 index = 0; index < num_subparts; ++index)
diff --git a/indra/newview/llpanellogin.cpp b/indra/newview/llpanellogin.cpp
index 224cec9650..70757882d8 100644
--- a/indra/newview/llpanellogin.cpp
+++ b/indra/newview/llpanellogin.cpp
@@ -338,10 +338,10 @@ LLPanelLogin::LLPanelLogin(const LLRect &rect,
LLButton* def_btn = getChild<LLButton>("connect_btn");
setDefaultBtn(def_btn);
- std::string channel = LLVersionInfo::getChannel();
+ std::string channel = LLVersionInfo::instance().getChannel();
std::string version = llformat("%s (%d)",
- LLVersionInfo::getShortVersion().c_str(),
- LLVersionInfo::getBuild());
+ LLVersionInfo::instance().getShortVersion().c_str(),
+ LLVersionInfo::instance().getBuild());
LLTextBox* forgot_password_text = getChild<LLTextBox>("forgot_password_text");
forgot_password_text->setClickedCallback(onClickForgotPassword, NULL);
@@ -943,9 +943,9 @@ void LLPanelLogin::loadLoginPage()
// Channel and Version
params["version"] = llformat("%s (%d)",
- LLVersionInfo::getShortVersion().c_str(),
- LLVersionInfo::getBuild());
- params["channel"] = LLVersionInfo::getChannel();
+ LLVersionInfo::instance().getShortVersion().c_str(),
+ LLVersionInfo::instance().getBuild());
+ params["channel"] = LLVersionInfo::instance().getChannel();
// Grid
params["grid"] = LLGridManager::getInstance()->getGridId();
diff --git a/indra/newview/llscenemonitor.cpp b/indra/newview/llscenemonitor.cpp
index 5ab0013055..2c0c38dc75 100644
--- a/indra/newview/llscenemonitor.cpp
+++ b/indra/newview/llscenemonitor.cpp
@@ -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';
diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp
index 1be1c4ba96..4b65ead236 100644
--- a/indra/newview/llstartup.cpp
+++ b/indra/newview/llstartup.cpp
@@ -512,9 +512,9 @@ bool idle_startup()
if(!start_messaging_system(
message_template_path,
port,
- LLVersionInfo::getMajor(),
- LLVersionInfo::getMinor(),
- LLVersionInfo::getPatch(),
+ LLVersionInfo::instance().getMajor(),
+ LLVersionInfo::instance().getMinor(),
+ LLVersionInfo::instance().getPatch(),
FALSE,
std::string(),
responder,
@@ -1534,12 +1534,14 @@ bool idle_startup()
{
LLStartUp::setStartupState( STATE_AGENT_SEND );
}
- LLMessageSystem* msg = gMessageSystem;
- while (msg->checkAllMessages(gFrameCount, gServicePump))
{
- display_startup();
+ LockMessageChecker lmc(gMessageSystem);
+ while (lmc.checkAllMessages(gFrameCount, gServicePump))
+ {
+ display_startup();
+ }
+ lmc.processAcks();
}
- msg->processAcks();
display_startup();
return FALSE;
}
@@ -1589,25 +1591,27 @@ bool idle_startup()
//---------------------------------------------------------------------
if (STATE_AGENT_WAIT == LLStartUp::getStartupState())
{
- LLMessageSystem* msg = gMessageSystem;
- while (msg->checkAllMessages(gFrameCount, gServicePump))
{
- if (gAgentMovementCompleted)
- {
- // Sometimes we have more than one message in the
- // queue. break out of this loop and continue
- // processing. If we don't, then this could skip one
- // or more login steps.
- break;
- }
- else
+ LockMessageChecker lmc(gMessageSystem);
+ while (lmc.checkAllMessages(gFrameCount, gServicePump))
{
- LL_DEBUGS("AppInit") << "Awaiting AvatarInitComplete, got "
- << msg->getMessageName() << LL_ENDL;
+ if (gAgentMovementCompleted)
+ {
+ // Sometimes we have more than one message in the
+ // queue. break out of this loop and continue
+ // processing. If we don't, then this could skip one
+ // or more login steps.
+ break;
+ }
+ else
+ {
+ LL_DEBUGS("AppInit") << "Awaiting AvatarInitComplete, got "
+ << gMessageSystem->getMessageName() << LL_ENDL;
+ }
+ display_startup();
}
- display_startup();
+ lmc.processAcks();
}
- msg->processAcks();
display_startup();
@@ -2297,13 +2301,29 @@ void login_callback(S32 option, void *userdata)
void show_release_notes_if_required()
{
static bool release_notes_shown = false;
- if (!release_notes_shown && (LLVersionInfo::getChannelAndVersion() != gLastRunVersion)
- && LLVersionInfo::getViewerMaturity() != LLVersionInfo::TEST_VIEWER // don't show Release Notes for the test builds
+ // We happen to know that instantiating LLVersionInfo implicitly
+ // instantiates the LLEventMailDrop named "relnotes", which we (might) use
+ // below. If viewer release notes stop working, might be because that
+ // LLEventMailDrop got moved out of LLVersionInfo and hasn't yet been
+ // instantiated.
+ if (!release_notes_shown && (LLVersionInfo::instance().getChannelAndVersion() != gLastRunVersion)
+ && LLVersionInfo::instance().getViewerMaturity() != LLVersionInfo::TEST_VIEWER // don't show Release Notes for the test builds
&& gSavedSettings.getBOOL("UpdaterShowReleaseNotes")
&& !gSavedSettings.getBOOL("FirstLoginThisInstall"))
{
- LLSD info(LLAppViewer::instance()->getViewerInfo());
- LLWeb::loadURLInternal(info["VIEWER_RELEASE_NOTES_URL"]);
+ // Instantiate a "relnotes" listener which assumes any arriving event
+ // is the release notes URL string. Since "relnotes" is an
+ // LLEventMailDrop, this listener will be invoked whether or not the
+ // URL has already been posted. If so, it will fire immediately;
+ // otherwise it will fire whenever the URL is (later) posted. Either
+ // way, it will display the release notes as soon as the URL becomes
+ // available.
+ LLEventPumps::instance().obtain("relnotes").listen(
+ "showrelnotes",
+ [](const LLSD& url){
+ LLWeb::loadURLInternal(url.asString());
+ return false;
+ });
release_notes_shown = true;
}
}
diff --git a/indra/newview/llstartup.h b/indra/newview/llstartup.h
index d7d294e9f4..3ec3ff4133 100644
--- a/indra/newview/llstartup.h
+++ b/indra/newview/llstartup.h
@@ -128,6 +128,7 @@ public:
static LLViewerStats::PhaseMap& getPhases() { return *sPhases; }
private:
+ friend class LLStartupListener;
static LLSLURL sStartSLURL;
static std::string startupStateToString(EStartupState state);
diff --git a/indra/newview/llstartuplistener.cpp b/indra/newview/llstartuplistener.cpp
index d9a21f908e..5770b595d0 100644
--- a/indra/newview/llstartuplistener.cpp
+++ b/indra/newview/llstartuplistener.cpp
@@ -35,7 +35,7 @@
// external library headers
// other Linden headers
#include "llstartup.h"
-
+#include "stringize.h"
LLStartupListener::LLStartupListener(/* LLStartUp* instance */):
LLEventAPI("LLStartUp", "Access e.g. LLStartup::postStartupState()") /* ,
@@ -43,9 +43,33 @@ LLStartupListener::LLStartupListener(/* LLStartUp* instance */):
{
add("postStartupState", "Refresh \"StartupState\" listeners with current startup state",
&LLStartupListener::postStartupState);
+ add("getStateTable", "Reply with array of EStartupState string names",
+ &LLStartupListener::getStateTable);
}
void LLStartupListener::postStartupState(const LLSD&) const
{
LLStartUp::postStartupState();
}
+
+void LLStartupListener::getStateTable(const LLSD& event) const
+{
+ Response response(LLSD(), event);
+
+ // This relies on our knowledge that STATE_STARTED is the very last
+ // EStartupState value. If that ever stops being true, we're going to lie
+ // without realizing it. I can think of no reliable way to test whether
+ // the enum has been extended *beyond* STATE_STARTED. We could, of course,
+ // test whether stuff has been inserted before it, by testing its
+ // numerical value against the constant value as of the last time we
+ // looked; but that's pointless, as values inserted before STATE_STARTED
+ // will continue to work fine. The bad case is if new symbols get added
+ // *after* it.
+ LLSD table;
+ // note <= comparison: we want to *include* STATE_STARTED.
+ for (LLSD::Integer istate{0}; istate <= LLSD::Integer(STATE_STARTED); ++istate)
+ {
+ table.append(LLStartUp::startupStateToString(EStartupState(istate)));
+ }
+ response["table"] = table;
+}
diff --git a/indra/newview/llstartuplistener.h b/indra/newview/llstartuplistener.h
index a35e11f6eb..0b4380a568 100644
--- a/indra/newview/llstartuplistener.h
+++ b/indra/newview/llstartuplistener.h
@@ -40,6 +40,7 @@ public:
private:
void postStartupState(const LLSD&) const;
+ void getStateTable(const LLSD&) const;
//LLStartup* mStartup;
};
diff --git a/indra/newview/lltexturestats.cpp b/indra/newview/lltexturestats.cpp
index b55b4d9ca4..8f4b7d000c 100644
--- a/indra/newview/lltexturestats.cpp
+++ b/indra/newview/lltexturestats.cpp
@@ -46,8 +46,8 @@ void send_texture_stats_to_sim(const LLSD &texture_stats)
LLUUID agent_id = gAgent.getID();
texture_stats_report["agent_id"] = agent_id;
texture_stats_report["region_id"] = gAgent.getRegion()->getRegionID();
- texture_stats_report["viewer_channel"] = LLVersionInfo::getChannel();
- texture_stats_report["viewer_version"] = LLVersionInfo::getVersion();
+ texture_stats_report["viewer_channel"] = LLVersionInfo::instance().getChannel();
+ texture_stats_report["viewer_version"] = LLVersionInfo::instance().getVersion();
texture_stats_report["stats_data"] = texture_stats;
std::string texture_cap_url = gAgent.getRegion()->getCapability("TextureStats");
diff --git a/indra/newview/lltoast.cpp b/indra/newview/lltoast.cpp
index 870e0d94f0..bf56a10d4d 100644
--- a/indra/newview/lltoast.cpp
+++ b/indra/newview/lltoast.cpp
@@ -612,11 +612,8 @@ S32 LLToast::notifyParent(const LLSD& info)
//static
void LLToast::updateClass()
{
- for (LLInstanceTracker<LLToast>::instance_iter iter = LLInstanceTracker<LLToast>::beginInstances();
- iter != LLInstanceTracker<LLToast>::endInstances(); )
+ for (auto& toast : LLInstanceTracker<LLToast>::instance_snapshot())
{
- LLToast& toast = *iter++;
-
toast.updateHoveredState();
}
}
@@ -624,22 +621,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();
}
diff --git a/indra/newview/lltranslate.cpp b/indra/newview/lltranslate.cpp
index e424983cf8..fa3b44f702 100644
--- a/indra/newview/lltranslate.cpp
+++ b/indra/newview/lltranslate.cpp
@@ -134,11 +134,11 @@ void LLTranslationAPIHandler::verifyKeyCoro(LLTranslate::EService service, std::
std::string user_agent = llformat("%s %d.%d.%d (%d)",
- LLVersionInfo::getChannel().c_str(),
- LLVersionInfo::getMajor(),
- LLVersionInfo::getMinor(),
- LLVersionInfo::getPatch(),
- LLVersionInfo::getBuild());
+ LLVersionInfo::instance().getChannel().c_str(),
+ LLVersionInfo::instance().getMajor(),
+ LLVersionInfo::instance().getMinor(),
+ LLVersionInfo::instance().getPatch(),
+ LLVersionInfo::instance().getBuild());
httpHeaders->append(HTTP_OUT_HEADER_ACCEPT, HTTP_CONTENT_TEXT_PLAIN);
httpHeaders->append(HTTP_OUT_HEADER_USER_AGENT, user_agent);
@@ -177,11 +177,11 @@ void LLTranslationAPIHandler::translateMessageCoro(LanguagePair_t fromTo, std::s
std::string user_agent = llformat("%s %d.%d.%d (%d)",
- LLVersionInfo::getChannel().c_str(),
- LLVersionInfo::getMajor(),
- LLVersionInfo::getMinor(),
- LLVersionInfo::getPatch(),
- LLVersionInfo::getBuild());
+ LLVersionInfo::instance().getChannel().c_str(),
+ LLVersionInfo::instance().getMajor(),
+ LLVersionInfo::instance().getMinor(),
+ LLVersionInfo::instance().getPatch(),
+ LLVersionInfo::instance().getBuild());
httpHeaders->append(HTTP_OUT_HEADER_ACCEPT, HTTP_CONTENT_TEXT_PLAIN);
httpHeaders->append(HTTP_OUT_HEADER_USER_AGENT, user_agent);
diff --git a/indra/newview/llversioninfo.cpp b/indra/newview/llversioninfo.cpp
index 4e07223784..4720a989b0 100644
--- a/indra/newview/llversioninfo.cpp
+++ b/indra/newview/llversioninfo.cpp
@@ -26,9 +26,10 @@
*/
#include "llviewerprecompiledheaders.h"
-#include <iostream>
-#include <sstream>
+#include "llevents.h"
+#include "lleventfilter.h"
#include "llversioninfo.h"
+#include "stringize.h"
#include <boost/regex.hpp>
#if ! defined(LL_VIEWER_CHANNEL) \
@@ -43,100 +44,90 @@
// Set the version numbers in indra/VIEWER_VERSION
//
-//static
+LLVersionInfo::LLVersionInfo():
+ short_version(STRINGIZE(LL_VIEWER_VERSION_MAJOR << "."
+ << LL_VIEWER_VERSION_MINOR << "."
+ << LL_VIEWER_VERSION_PATCH)),
+ // LL_VIEWER_CHANNEL is a macro defined on the compiler command line. The
+ // macro expands to the string name of the channel, but without quotes. We
+ // need to turn it into a quoted string. LL_TO_STRING() does that.
+ mWorkingChannelName(LL_TO_STRING(LL_VIEWER_CHANNEL)),
+ build_configuration(LLBUILD_CONFIG), // set in indra/cmake/BuildVersion.cmake
+ // instantiate an LLEventMailDrop with canonical name to listen for news
+ // from SLVersionChecker
+ mPump{new LLEventMailDrop("relnotes")},
+ // immediately listen on mPump, store arriving URL into mReleaseNotes
+ mStore{new LLStoreListener<std::string>(*mPump, mReleaseNotes)}
+{
+}
+
+void LLVersionInfo::initSingleton()
+{
+ // We override initSingleton() not because we have dependencies on other
+ // LLSingletons, but because certain initializations call other member
+ // functions. We should refrain from calling methods until this object is
+ // fully constructed; such calls don't really belong in the constructor.
+
+ // cache the version string
+ version = STRINGIZE(getShortVersion() << "." << getBuild());
+}
+
+LLVersionInfo::~LLVersionInfo()
+{
+}
+
S32 LLVersionInfo::getMajor()
{
return LL_VIEWER_VERSION_MAJOR;
}
-//static
S32 LLVersionInfo::getMinor()
{
return LL_VIEWER_VERSION_MINOR;
}
-//static
S32 LLVersionInfo::getPatch()
{
return LL_VIEWER_VERSION_PATCH;
}
-//static
S32 LLVersionInfo::getBuild()
{
return LL_VIEWER_VERSION_BUILD;
}
-//static
-const std::string &LLVersionInfo::getVersion()
+std::string LLVersionInfo::getVersion()
{
- static std::string version("");
- if (version.empty())
- {
- std::ostringstream stream;
- stream << LLVersionInfo::getShortVersion() << "." << LLVersionInfo::getBuild();
- // cache the version string
- version = stream.str();
- }
return version;
}
-//static
-const std::string &LLVersionInfo::getShortVersion()
+std::string LLVersionInfo::getShortVersion()
{
- static std::string short_version("");
- if(short_version.empty())
- {
- // cache the version string
- std::ostringstream stream;
- stream << LL_VIEWER_VERSION_MAJOR << "."
- << LL_VIEWER_VERSION_MINOR << "."
- << LL_VIEWER_VERSION_PATCH;
- short_version = stream.str();
- }
return short_version;
}
-namespace
-{
- // LL_VIEWER_CHANNEL is a macro defined on the compiler command line. The
- // macro expands to the string name of the channel, but without quotes. We
- // need to turn it into a quoted string. LL_TO_STRING() does that.
- /// Storage of the channel name the viewer is using.
- // The channel name is set by hardcoded constant,
- // or by calling LLVersionInfo::resetChannel()
- std::string sWorkingChannelName(LL_TO_STRING(LL_VIEWER_CHANNEL));
-
- // Storage for the "version and channel" string.
- // This will get reset too.
- std::string sVersionChannel("");
-}
-
-//static
-const std::string &LLVersionInfo::getChannelAndVersion()
+std::string LLVersionInfo::getChannelAndVersion()
{
- if (sVersionChannel.empty())
+ if (mVersionChannel.empty())
{
// cache the version string
- sVersionChannel = LLVersionInfo::getChannel() + " " + LLVersionInfo::getVersion();
+ mVersionChannel = getChannel() + " " + getVersion();
}
- return sVersionChannel;
+ return mVersionChannel;
}
-//static
-const std::string &LLVersionInfo::getChannel()
+std::string LLVersionInfo::getChannel()
{
- return sWorkingChannelName;
+ return mWorkingChannelName;
}
void LLVersionInfo::resetChannel(const std::string& channel)
{
- sWorkingChannelName = channel;
- sVersionChannel.clear(); // Reset version and channel string til next use.
+ mWorkingChannelName = channel;
+ mVersionChannel.clear(); // Reset version and channel string til next use.
}
-//static
LLVersionInfo::ViewerMaturity LLVersionInfo::getViewerMaturity()
{
ViewerMaturity maturity;
@@ -175,8 +166,12 @@ LLVersionInfo::ViewerMaturity LLVersionInfo::getViewerMaturity()
}
-const std::string &LLVersionInfo::getBuildConfig()
+std::string LLVersionInfo::getBuildConfig()
{
- static const std::string build_configuration(LLBUILD_CONFIG); // set in indra/cmake/BuildVersion.cmake
return build_configuration;
}
+
+std::string LLVersionInfo::getReleaseNotes()
+{
+ return mReleaseNotes;
+}
diff --git a/indra/newview/llversioninfo.h b/indra/newview/llversioninfo.h
index b8b4341385..02ff0c094a 100644
--- a/indra/newview/llversioninfo.h
+++ b/indra/newview/llversioninfo.h
@@ -28,8 +28,14 @@
#ifndef LL_LLVERSIONINFO_H
#define LL_LLVERSIONINFO_H
-#include <string>
#include "stdtypes.h"
+#include "llsingleton.h"
+#include <string>
+#include <memory>
+
+class LLEventMailDrop;
+template <typename T>
+class LLStoreListener;
///
/// This API provides version information for the viewer. This
@@ -38,42 +44,46 @@
/// viewer code that wants to query the current version should
/// use this API.
///
-class LLVersionInfo
+class LLVersionInfo: public LLSingleton<LLVersionInfo>
{
+ LLSINGLETON(LLVersionInfo);
+ void initSingleton();
public:
- /// return the major verion number as an integer
- static S32 getMajor();
+ ~LLVersionInfo();
- /// return the minor verion number as an integer
- static S32 getMinor();
+ /// return the major version number as an integer
+ S32 getMajor();
- /// return the patch verion number as an integer
- static S32 getPatch();
+ /// return the minor version number as an integer
+ S32 getMinor();
+
+ /// return the patch version number as an integer
+ S32 getPatch();
/// return the build number as an integer
- static S32 getBuild();
+ S32 getBuild();
/// return the full viewer version as a string like "2.0.0.200030"
- static const std::string &getVersion();
+ std::string getVersion();
/// return the viewer version as a string like "2.0.0"
- static const std::string &getShortVersion();
+ std::string getShortVersion();
/// return the viewer version and channel as a string
/// like "Second Life Release 2.0.0.200030"
- static const std::string &getChannelAndVersion();
+ std::string getChannelAndVersion();
/// return the channel name, e.g. "Second Life"
- static const std::string &getChannel();
+ std::string getChannel();
/// return the CMake build type
- static const std::string &getBuildConfig();
+ std::string getBuildConfig();
/// reset the channel name used by the viewer.
- static void resetChannel(const std::string& channel);
+ void resetChannel(const std::string& channel);
/// return the bit width of an address
- static const S32 getAddressSize() { return ADDRESS_SIZE; }
+ S32 getAddressSize() { return ADDRESS_SIZE; }
typedef enum
{
@@ -82,7 +92,31 @@ public:
BETA_VIEWER,
RELEASE_VIEWER
} ViewerMaturity;
- static ViewerMaturity getViewerMaturity();
+ ViewerMaturity getViewerMaturity();
+
+ /// get the release-notes URL, once it becomes available -- until then,
+ /// return empty string
+ std::string getReleaseNotes();
+
+private:
+ std::string version;
+ std::string short_version;
+ /// Storage of the channel name the viewer is using.
+ // The channel name is set by hardcoded constant,
+ // or by calling resetChannel()
+ std::string mWorkingChannelName;
+ // Storage for the "version and channel" string.
+ // This will get reset too.
+ std::string mVersionChannel;
+ std::string build_configuration;
+ std::string mReleaseNotes;
+ // Store unique_ptrs to the next couple things so we don't have to explain
+ // to every consumer of this header file all the details of each.
+ // mPump is the LLEventMailDrop on which we listen for SLVersionChecker to
+ // post the release-notes URL from the Viewer Version Manager.
+ std::unique_ptr<LLEventMailDrop> mPump;
+ // mStore is an adapter that stores the release-notes URL in mReleaseNotes.
+ std::unique_ptr<LLStoreListener<std::string>> mStore;
};
#endif
diff --git a/indra/newview/llviewercontrollistener.cpp b/indra/newview/llviewercontrollistener.cpp
index d2484b2b23..3443bb644a 100644
--- a/indra/newview/llviewercontrollistener.cpp
+++ b/indra/newview/llviewercontrollistener.cpp
@@ -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);
}
}
diff --git a/indra/newview/llviewerjoystick.cpp b/indra/newview/llviewerjoystick.cpp
index a75e0b5569..b2516f3c71 100644
--- a/indra/newview/llviewerjoystick.cpp
+++ b/indra/newview/llviewerjoystick.cpp
@@ -71,8 +71,46 @@ F32 LLViewerJoystick::sDelta[] = {0,0,0,0,0,0,0};
#define MAX_SPACENAVIGATOR_INPUT 3000.0f
#define MAX_JOYSTICK_INPUT_VALUE MAX_SPACENAVIGATOR_INPUT
-#if LL_WINDOWS && !LL_MESA_HEADLESS
+#if LIB_NDOF
+std::ostream& operator<<(std::ostream& out, NDOF_Device* ptr)
+{
+ if (! ptr)
+ {
+ return out << "nullptr";
+ }
+ out << "NDOF_Device{ ";
+ out << "axes [";
+ const char* delim = "";
+ for (short axis = 0; axis < ptr->axes_count; ++axis)
+ {
+ out << delim << ptr->axes[axis];
+ delim = ", ";
+ }
+ out << "]";
+ out << ", buttons [";
+ delim = "";
+ for (short button = 0; button < ptr->btn_count; ++button)
+ {
+ out << delim << ptr->buttons[button];
+ delim = ", ";
+ }
+ out << "]";
+ out << ", range " << ptr->axes_min << ':' << ptr->axes_max;
+ // If we don't coerce these to unsigned, they're streamed as characters,
+ // e.g. ctrl-A or nul.
+ out << ", absolute " << unsigned(ptr->absolute);
+ out << ", valid " << unsigned(ptr->valid);
+ out << ", manufacturer '" << ptr->manufacturer << "'";
+ out << ", product '" << ptr->product << "'";
+ out << ", private " << ptr->private_data;
+ out << " }";
+ return out;
+}
+#endif // LIB_NDOF
+
+
+#if LL_WINDOWS && !LL_MESA_HEADLESS
// this should reflect ndof and set axises, see ndofdev_win.cpp from ndof package
BOOL CALLBACK EnumObjectsCallback(const DIDEVICEOBJECTINSTANCE* inst, VOID* user_data)
{
@@ -237,11 +275,11 @@ NDOF_HotPlugResult LLViewerJoystick::HotPlugAddCallback(NDOF_Device *dev)
LLViewerJoystick* joystick(LLViewerJoystick::getInstance());
if (joystick->mDriverState == JDS_UNINITIALIZED)
{
- LL_INFOS("Joystick") << "HotPlugAddCallback: will use device:" << LL_ENDL;
- ndof_dump(dev);
+ LL_INFOS("Joystick") << "HotPlugAddCallback: will use device:" << LL_ENDL;
+ ndof_dump(stderr, dev);
joystick->mNdofDev = dev;
- joystick->mDriverState = JDS_INITIALIZED;
- res = NDOF_KEEP_HOTPLUGGED;
+ joystick->mDriverState = JDS_INITIALIZED;
+ res = NDOF_KEEP_HOTPLUGGED;
}
joystick->updateEnabled(true);
return res;
@@ -257,7 +295,7 @@ void LLViewerJoystick::HotPlugRemovalCallback(NDOF_Device *dev)
{
LL_INFOS("Joystick") << "HotPlugRemovalCallback: joystick->mNdofDev="
<< joystick->mNdofDev << "; removed device:" << LL_ENDL;
- ndof_dump(dev);
+ ndof_dump(stderr, dev);
joystick->mDriverState = JDS_UNINITIALIZED;
}
joystick->updateEnabled(true);
@@ -390,8 +428,8 @@ void LLViewerJoystick::init(bool autoenable)
{
// No device connected, don't change any settings
}
-
- LL_INFOS("Joystick") << "ndof: mDriverState=" << mDriverState << "; mNdofDev="
+
+ LL_INFOS("Joystick") << "ndof: mDriverState=" << mDriverState << "; mNdofDev="
<< mNdofDev << "; libinit=" << libinit << LL_ENDL;
#endif
}
@@ -1356,7 +1394,7 @@ std::string LLViewerJoystick::getDescription()
bool LLViewerJoystick::isLikeSpaceNavigator() const
{
-#if LIB_NDOF
+#if LIB_NDOF
return (isJoystickInitialized()
&& (strncmp(mNdofDev->product, "SpaceNavigator", 14) == 0
|| strncmp(mNdofDev->product, "SpaceExplorer", 13) == 0
@@ -1380,10 +1418,10 @@ void LLViewerJoystick::setSNDefaults()
const float platformScaleAvXZ = 2.f;
const bool is_3d_cursor = true;
#endif
-
+
//gViewerWindow->alertXml("CacheWillClear");
- LL_INFOS() << "restoring SpaceNavigator defaults..." << LL_ENDL;
-
+ LL_INFOS("Joystick") << "restoring SpaceNavigator defaults..." << LL_ENDL;
+
gSavedSettings.setS32("JoystickAxis0", 1); // z (at)
gSavedSettings.setS32("JoystickAxis1", 0); // x (slide)
gSavedSettings.setS32("JoystickAxis2", 2); // y (up)
@@ -1391,11 +1429,11 @@ void LLViewerJoystick::setSNDefaults()
gSavedSettings.setS32("JoystickAxis4", 3); // roll
gSavedSettings.setS32("JoystickAxis5", 5); // yaw
gSavedSettings.setS32("JoystickAxis6", -1);
-
+
gSavedSettings.setBOOL("Cursor3D", is_3d_cursor);
gSavedSettings.setBOOL("AutoLeveling", true);
gSavedSettings.setBOOL("ZoomDirect", false);
-
+
gSavedSettings.setF32("AvatarAxisScale0", 1.f * platformScaleAvXZ);
gSavedSettings.setF32("AvatarAxisScale1", 1.f * platformScaleAvXZ);
gSavedSettings.setF32("AvatarAxisScale2", 1.f);
diff --git a/indra/newview/llviewermedia.cpp b/indra/newview/llviewermedia.cpp
index ed5dff1600..e31dfb29c7 100644
--- a/indra/newview/llviewermedia.cpp
+++ b/indra/newview/llviewermedia.cpp
@@ -412,7 +412,7 @@ std::string LLViewerMedia::getCurrentUserAgent()
// Just in case we need to check browser differences in A/B test
// builds.
- std::string channel = LLVersionInfo::getChannel();
+ std::string channel = LLVersionInfo::instance().getChannel();
// append our magic version number string to the browser user agent id
// See the HTTP 1.0 and 1.1 specifications for allowed formats:
@@ -422,7 +422,7 @@ std::string LLViewerMedia::getCurrentUserAgent()
// http://www.mozilla.org/build/revised-user-agent-strings.html
std::ostringstream codec;
codec << "SecondLife/";
- codec << LLVersionInfo::getVersion();
+ codec << LLVersionInfo::instance().getVersion();
codec << " (" << channel << "; " << skin_name << " skin)";
LL_INFOS() << codec.str() << LL_ENDL;
diff --git a/indra/newview/llviewerprecompiledheaders.h b/indra/newview/llviewerprecompiledheaders.h
index 999d9092bd..bbbacce8fa 100644
--- a/indra/newview/llviewerprecompiledheaders.h
+++ b/indra/newview/llviewerprecompiledheaders.h
@@ -29,6 +29,8 @@
#ifndef LL_LLVIEWERPRECOMPILEDHEADERS_H
#define LL_LLVIEWERPRECOMPILEDHEADERS_H
+#include "llwin32headers.h"
+
// This file MUST be the first one included by each .cpp file
// in viewer.
// It is used to precompile headers for improved build speed.
diff --git a/indra/newview/llviewerstats.cpp b/indra/newview/llviewerstats.cpp
index 85d87a43af..0f58933005 100644
--- a/indra/newview/llviewerstats.cpp
+++ b/indra/newview/llviewerstats.cpp
@@ -473,7 +473,7 @@ void send_stats()
// send fps only for time app spends in foreground
agent["fps"] = (F32)gForegroundFrameCount / gForegroundTime.getElapsedTimeF32();
- agent["version"] = LLVersionInfo::getChannelAndVersion();
+ agent["version"] = LLVersionInfo::instance().getChannelAndVersion();
std::string language = LLUI::getLanguage();
agent["language"] = language;
diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp
index 44d02b4224..5dd3270b2e 100644
--- a/indra/newview/llviewerwindow.cpp
+++ b/indra/newview/llviewerwindow.cpp
@@ -2498,7 +2498,7 @@ void LLViewerWindow::setMenuBackgroundColor(bool god_mode, bool dev_grid)
}
else
{
- switch (LLVersionInfo::getViewerMaturity())
+ switch (LLVersionInfo::instance().getViewerMaturity())
{
case LLVersionInfo::TEST_VIEWER:
new_bg_color = LLUIColorTable::instance().getColor( "MenuBarTestBgColor" );
diff --git a/indra/newview/llvoicevivox.cpp b/indra/newview/llvoicevivox.cpp
index 530adb8975..42a1cf95a7 100644
--- a/indra/newview/llvoicevivox.cpp
+++ b/indra/newview/llvoicevivox.cpp
@@ -338,15 +338,15 @@ LLVivoxVoiceClient::LLVivoxVoiceClient() :
mPlayRequestCount(0),
mAvatarNameCacheConnection(),
- mIsInTuningMode(false),
- mIsInChannel(false),
- mIsJoiningSession(false),
- mIsWaitingForFonts(false),
- mIsLoggingIn(false),
- mIsLoggedIn(false),
- mIsProcessingChannels(false),
- mIsCoroutineActive(false),
- mVivoxPump("vivoxClientPump")
+ mIsInTuningMode(false),
+ mIsInChannel(false),
+ mIsJoiningSession(false),
+ mIsWaitingForFonts(false),
+ mIsLoggingIn(false),
+ mIsLoggedIn(false),
+ mIsProcessingChannels(false),
+ mIsCoroutineActive(false),
+ mVivoxPump("vivoxClientPump")
{
mSpeakerVolume = scale_speaker_volume(0);
@@ -390,7 +390,7 @@ void LLVivoxVoiceClient::init(LLPumpIO *pump)
// constructor will set up LLVoiceClient::getInstance()
LLVivoxVoiceClient::getInstance()->mPump = pump;
-// LLCoros::instance().launch("LLVivoxVoiceClient::voiceControlCoro();",
+// LLCoros::instance().launch("LLVivoxVoiceClient::voiceControlCoro",
// boost::bind(&LLVivoxVoiceClient::voiceControlCoro, LLVivoxVoiceClient::getInstance()));
}
@@ -527,7 +527,7 @@ void LLVivoxVoiceClient::connectorCreate()
<< "<FileNameSuffix>.log</FileNameSuffix>"
<< "<LogLevel>" << vivoxLogLevel << "</LogLevel>"
<< "</Logging>"
- << "<Application>" << LLVersionInfo::getChannel().c_str() << " " << LLVersionInfo::getVersion().c_str() << "</Application>"
+ << "<Application>" << LLVersionInfo::instance().getChannel() << " " << LLVersionInfo::instance().getVersion() << "</Application>"
//<< "<Application></Application>" //Name can cause problems per vivox.
<< "<MaxCalls>12</MaxCalls>"
<< "</Request>\n\n\n";
@@ -806,6 +806,21 @@ bool LLVivoxVoiceClient::startAndLaunchDaemon()
LLProcess::Params params;
params.executable = exe_path;
+ // VOICE-88: Cycle through [portbase..portbase+portrange) on
+ // successive tries because attempting to relaunch (after manually
+ // disabling and then re-enabling voice) with the same port can
+ // cause SLVoice's bind() call to fail with EADDRINUSE. We expect
+ // that eventually the OS will time out previous ports, which is
+ // why we cycle instead of incrementing indefinitely.
+ U32 portbase = gSavedSettings.getU32("VivoxVoicePort");
+ static U32 portoffset = 0;
+ static const U32 portrange = 100;
+ std::string host(gSavedSettings.getString("VivoxVoiceHost"));
+ U32 port = portbase + portoffset;
+ portoffset = (portoffset + 1) % portrange;
+ params.args.add("-i");
+ params.args.add(STRINGIZE(host << ':' << port));
+
std::string loglevel = gSavedSettings.getString("VivoxDebugLevel");
if (loglevel.empty())
{
@@ -862,7 +877,7 @@ bool LLVivoxVoiceClient::startAndLaunchDaemon()
sGatewayPtr = LLProcess::create(params);
- mDaemonHost = LLHost(gSavedSettings.getString("VivoxVoiceHost").c_str(), gSavedSettings.getU32("VivoxVoicePort"));
+ mDaemonHost = LLHost(host.c_str(), port);
}
else
{
@@ -1038,8 +1053,6 @@ bool LLVivoxVoiceClient::provisionVoiceAccount()
bool LLVivoxVoiceClient::establishVoiceConnection()
{
- LLEventPump &voiceConnectPump = LLEventPumps::instance().obtain("vivoxClientPump");
-
if (!mVoiceEnabled && mIsInitialized)
{
LL_WARNS("Voice") << "cannot establish connection; enabled "<<mVoiceEnabled<<" initialized "<<mIsInitialized<<LL_ENDL;
@@ -1056,7 +1069,7 @@ bool LLVivoxVoiceClient::establishVoiceConnection()
connectorCreate();
do
{
- result = llcoro::suspendUntilEventOn(voiceConnectPump);
+ result = llcoro::suspendUntilEventOn(mVivoxPump);
LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL;
if (result.has("connector"))
@@ -1108,7 +1121,6 @@ bool LLVivoxVoiceClient::establishVoiceConnection()
bool LLVivoxVoiceClient::breakVoiceConnection(bool corowait)
{
LL_DEBUGS("Voice") << "( wait=" << corowait << ")" << LL_ENDL;
- LLEventPump &voicePump = LLEventPumps::instance().obtain("vivoxClientPump");
bool retval(true);
mShutdownComplete = false;
@@ -1118,7 +1130,7 @@ bool LLVivoxVoiceClient::breakVoiceConnection(bool corowait)
{
LLSD timeoutResult(LLSDMap("connector", "timeout"));
- LLSD result = llcoro::suspendUntilEventOnWithTimeout(voicePump, LOGOUT_ATTEMPT_TIMEOUT, timeoutResult);
+ LLSD result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, LOGOUT_ATTEMPT_TIMEOUT, timeoutResult);
LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL;
retval = result.has("connector");
@@ -1140,7 +1152,7 @@ bool LLVivoxVoiceClient::breakVoiceConnection(bool corowait)
{
mConnected = false;
LLSD vivoxevent(LLSDMap("connector", LLSD::Boolean(false)));
- LLEventPumps::instance().post("vivoxClientPump", vivoxevent);
+ mVivoxPump.post(vivoxevent);
}
mShutdownComplete = true;
}
@@ -1157,8 +1169,6 @@ bool LLVivoxVoiceClient::breakVoiceConnection(bool corowait)
bool LLVivoxVoiceClient::loginToVivox()
{
- LLEventPump &voicePump = LLEventPumps::instance().obtain("vivoxClientPump");
-
LLSD timeoutResult(LLSDMap("login", "timeout"));
int loginRetryCount(0);
@@ -1176,7 +1186,7 @@ bool LLVivoxVoiceClient::loginToVivox()
send_login = false;
}
- LLSD result = llcoro::suspendUntilEventOnWithTimeout(voicePump, LOGIN_ATTEMPT_TIMEOUT, timeoutResult);
+ LLSD result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, LOGIN_ATTEMPT_TIMEOUT, timeoutResult);
LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL;
if (result.has("login"))
@@ -1259,17 +1269,23 @@ void LLVivoxVoiceClient::logoutOfVivox(bool wait)
if (wait)
{
- LLEventPump &voicePump = LLEventPumps::instance().obtain("vivoxClientPump");
LLSD timeoutResult(LLSDMap("logout", "timeout"));
+ LLSD result;
- LL_DEBUGS("Voice")
- << "waiting for logout response on "
- << voicePump.getName()
- << LL_ENDL;
-
- LLSD result = llcoro::suspendUntilEventOnWithTimeout(voicePump, LOGOUT_ATTEMPT_TIMEOUT, timeoutResult);
-
- LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL;
+ do
+ {
+ LL_DEBUGS("Voice")
+ << "waiting for logout response on "
+ << mVivoxPump.getName()
+ << LL_ENDL;
+
+ result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, LOGOUT_ATTEMPT_TIMEOUT, timeoutResult);
+
+ LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL;
+ // Don't get confused by prior queued events -- note that it's
+ // very important that mVivoxPump is an LLEventMailDrop, which
+ // does queue events.
+ } while (! result["logout"]);
}
else
{
@@ -1283,8 +1299,6 @@ void LLVivoxVoiceClient::logoutOfVivox(bool wait)
bool LLVivoxVoiceClient::retrieveVoiceFonts()
{
- LLEventPump &voicePump = LLEventPumps::instance().obtain("vivoxClientPump");
-
// Request the set of available voice fonts.
refreshVoiceEffectLists(true);
@@ -1292,7 +1306,7 @@ bool LLVivoxVoiceClient::retrieveVoiceFonts()
LLSD result;
do
{
- result = llcoro::suspendUntilEventOn(voicePump);
+ result = llcoro::suspendUntilEventOn(mVivoxPump);
LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL;
if (result.has("voice_fonts"))
@@ -1408,7 +1422,6 @@ bool LLVivoxVoiceClient::requestParcelVoiceInfo()
bool LLVivoxVoiceClient::addAndJoinSession(const sessionStatePtr_t &nextSession)
{
- LLEventPump &voicePump = LLEventPumps::instance().obtain("vivoxClientPump");
mIsJoiningSession = true;
sessionStatePtr_t oldSession = mAudioSession;
@@ -1497,7 +1510,7 @@ bool LLVivoxVoiceClient::addAndJoinSession(const sessionStatePtr_t &nextSession)
// We are about to start a whole new session. Anything that MIGHT still be in our
// maildrop is going to be stale and cause us much wailing and gnashing of teeth.
// Just flush it all out and start new.
- voicePump.flush();
+ mVivoxPump.discard();
// It appears that I need to wait for BOTH the SessionGroup.AddSession response and the SessionStateChangeEvent with state 4
// before continuing from this state. They can happen in either order, and if I don't wait for both, things can get stuck.
@@ -1505,7 +1518,7 @@ bool LLVivoxVoiceClient::addAndJoinSession(const sessionStatePtr_t &nextSession)
// This is a cheap way to make sure both have happened before proceeding.
do
{
- result = llcoro::suspendUntilEventOnWithTimeout(voicePump, SESSION_JOIN_TIMEOUT, timeoutResult);
+ result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, SESSION_JOIN_TIMEOUT, timeoutResult);
LL_INFOS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL;
if (result.has("session"))
@@ -1619,13 +1632,12 @@ bool LLVivoxVoiceClient::terminateAudioSession(bool wait)
if (wait)
{
- LLEventPump &voicePump = LLEventPumps::instance().obtain("vivoxClientPump");
LLSD result;
do
{
LLSD timeoutResult(LLSDMap("session", "timeout"));
- result = llcoro::suspendUntilEventOnWithTimeout(voicePump, LOGOUT_ATTEMPT_TIMEOUT, timeoutResult);
+ result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, LOGOUT_ATTEMPT_TIMEOUT, timeoutResult);
LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL;
if (result.has("session"))
@@ -1822,7 +1834,6 @@ bool LLVivoxVoiceClient::runSession(const sessionStatePtr_t &session)
LLSD timeoutEvent(LLSDMap("timeout", LLSD::Boolean(true)));
- LLEventPump &voicePump = LLEventPumps::instance().obtain("vivoxClientPump");
mIsInChannel = true;
mMuteMicDirty = true;
@@ -1874,7 +1885,7 @@ bool LLVivoxVoiceClient::runSession(const sessionStatePtr_t &session)
sendLocalAudioUpdates();
mIsInitialized = true;
- LLSD result = llcoro::suspendUntilEventOnWithTimeout(voicePump, UPDATE_THROTTLE_SECONDS, timeoutEvent);
+ LLSD result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, UPDATE_THROTTLE_SECONDS, timeoutEvent);
if (!result.has("timeout")) // logging the timeout event spams the log
{
LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL;
@@ -1945,14 +1956,13 @@ void LLVivoxVoiceClient::sendCaptureAndRenderDevices()
void LLVivoxVoiceClient::recordingAndPlaybackMode()
{
LL_INFOS("Voice") << "In voice capture/playback mode." << LL_ENDL;
- LLEventPump &voicePump = LLEventPumps::instance().obtain("vivoxClientPump");
while (true)
{
LLSD command;
do
{
- command = llcoro::suspendUntilEventOn(voicePump);
+ command = llcoro::suspendUntilEventOn(mVivoxPump);
LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(command) << LL_ENDL;
} while (!command.has("recplay"));
@@ -1985,7 +1995,6 @@ int LLVivoxVoiceClient::voiceRecordBuffer()
LL_INFOS("Voice") << "Recording voice buffer" << LL_ENDL;
- LLEventPump &voicePump = LLEventPumps::instance().obtain("vivoxClientPump");
LLSD result;
captureBufferRecordStartSendMessage();
@@ -1993,7 +2002,7 @@ int LLVivoxVoiceClient::voiceRecordBuffer()
do
{
- result = llcoro::suspendUntilEventOnWithTimeout(voicePump, CAPTURE_BUFFER_MAX_TIME, timeoutResult);
+ result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, CAPTURE_BUFFER_MAX_TIME, timeoutResult);
LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL;
} while (!result.has("recplay"));
@@ -2015,7 +2024,6 @@ int LLVivoxVoiceClient::voicePlaybackBuffer()
LL_INFOS("Voice") << "Playing voice buffer" << LL_ENDL;
- LLEventPump &voicePump = LLEventPumps::instance().obtain("vivoxClientPump");
LLSD result;
do
@@ -2030,7 +2038,7 @@ int LLVivoxVoiceClient::voicePlaybackBuffer()
// Update UI, should really use a separate callback.
notifyVoiceFontObservers();
- result = llcoro::suspendUntilEventOnWithTimeout(voicePump, CAPTURE_BUFFER_MAX_TIME, timeoutResult);
+ result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, CAPTURE_BUFFER_MAX_TIME, timeoutResult);
LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL;
} while (!result.has("recplay"));
@@ -2551,7 +2559,7 @@ void LLVivoxVoiceClient::tuningStart()
mTuningMode = true;
if (!mIsCoroutineActive)
{
- LLCoros::instance().launch("LLVivoxVoiceClient::voiceControlCoro();",
+ LLCoros::instance().launch("LLVivoxVoiceClient::voiceControlCoro",
boost::bind(&LLVivoxVoiceClient::voiceControlCoro, LLVivoxVoiceClient::getInstance()));
}
else if (mIsInChannel)
@@ -3214,7 +3222,7 @@ void LLVivoxVoiceClient::connectorCreateResponse(int statusCode, std::string &st
result["connector"] = LLSD::Boolean(false);
}
- LLEventPumps::instance().post("vivoxClientPump", result);
+ mVivoxPump.post(result);
}
void LLVivoxVoiceClient::loginResponse(int statusCode, std::string &statusString, std::string &accountHandle, int numberOfAliases)
@@ -3244,7 +3252,7 @@ void LLVivoxVoiceClient::loginResponse(int statusCode, std::string &statusString
result["login"] = LLSD::String("response_ok");
}
- LLEventPumps::instance().post("vivoxClientPump", result);
+ mVivoxPump.post(result);
}
@@ -3270,7 +3278,7 @@ void LLVivoxVoiceClient::sessionCreateResponse(std::string &requestId, int statu
("session", "failed")
("reason", LLSD::Integer(statusCode)));
- LLEventPumps::instance().post("vivoxClientPump", vivoxevent);
+ mVivoxPump.post(vivoxevent);
}
else
{
@@ -3288,7 +3296,7 @@ void LLVivoxVoiceClient::sessionCreateResponse(std::string &requestId, int statu
LLSD vivoxevent(LLSDMap("handle", LLSD::String(sessionHandle))
("session", "created"));
- LLEventPumps::instance().post("vivoxClientPump", vivoxevent);
+ mVivoxPump.post(vivoxevent);
}
}
@@ -3313,7 +3321,7 @@ void LLVivoxVoiceClient::sessionGroupAddSessionResponse(std::string &requestId,
LLSD vivoxevent(LLSDMap("handle", LLSD::String(sessionHandle))
("session", "failed"));
- LLEventPumps::instance().post("vivoxClientPump", vivoxevent);
+ mVivoxPump.post(vivoxevent);
}
else
{
@@ -3332,7 +3340,7 @@ void LLVivoxVoiceClient::sessionGroupAddSessionResponse(std::string &requestId,
LLSD vivoxevent(LLSDMap("handle", LLSD::String(sessionHandle))
("session", "added"));
- LLEventPumps::instance().post("vivoxClientPump", vivoxevent);
+ mVivoxPump.post(vivoxevent);
}
}
@@ -3375,7 +3383,7 @@ void LLVivoxVoiceClient::logoutResponse(int statusCode, std::string &statusStrin
}
LLSD vivoxevent(LLSDMap("logout", LLSD::Boolean(true)));
- LLEventPumps::instance().post("vivoxClientPump", vivoxevent);
+ mVivoxPump.post(vivoxevent);
}
void LLVivoxVoiceClient::connectorShutdownResponse(int statusCode, std::string &statusString)
@@ -3391,7 +3399,7 @@ void LLVivoxVoiceClient::connectorShutdownResponse(int statusCode, std::string &
LLSD vivoxevent(LLSDMap("connector", LLSD::Boolean(false)));
- LLEventPumps::instance().post("vivoxClientPump", vivoxevent);
+ mVivoxPump.post(vivoxevent);
}
void LLVivoxVoiceClient::sessionAddedEvent(
@@ -3500,7 +3508,7 @@ void LLVivoxVoiceClient::joinedAudioSession(const sessionStatePtr_t &session)
LLSD vivoxevent(LLSDMap("handle", LLSD::String(session->mHandle))
("session", "joined"));
- LLEventPumps::instance().post("vivoxClientPump", vivoxevent);
+ mVivoxPump.post(vivoxevent);
// Add the current user as a participant here.
participantStatePtr_t participant(session->addParticipant(sipURIFromName(mAccountName)));
@@ -3644,7 +3652,7 @@ void LLVivoxVoiceClient::leftAudioSession(const sessionStatePtr_t &session)
LLSD vivoxevent(LLSDMap("handle", LLSD::String(session->mHandle))
("session", "removed"));
- LLEventPumps::instance().post("vivoxClientPump", vivoxevent);
+ mVivoxPump.post(vivoxevent);
}
}
@@ -3672,7 +3680,7 @@ void LLVivoxVoiceClient::accountLoginStateChangeEvent(
case 1:
levent["login"] = LLSD::String("account_login");
- LLEventPumps::instance().post("vivoxClientPump", levent);
+ mVivoxPump.post(levent);
break;
case 2:
break;
@@ -3680,7 +3688,7 @@ void LLVivoxVoiceClient::accountLoginStateChangeEvent(
case 3:
levent["login"] = LLSD::String("account_loggingOut");
- LLEventPumps::instance().post("vivoxClientPump", levent);
+ mVivoxPump.post(levent);
break;
case 4:
@@ -3693,7 +3701,7 @@ void LLVivoxVoiceClient::accountLoginStateChangeEvent(
case 0:
levent["login"] = LLSD::String("account_logout");
- LLEventPumps::instance().post("vivoxClientPump", levent);
+ mVivoxPump.post(levent);
break;
default:
@@ -3728,7 +3736,7 @@ void LLVivoxVoiceClient::mediaCompletionEvent(std::string &sessionGroupHandle, s
}
if (!result.isUndefined())
- LLEventPumps::instance().post("vivoxClientPump", result);
+ mVivoxPump.post(result);
}
void LLVivoxVoiceClient::mediaStreamUpdatedEvent(
@@ -5146,7 +5154,7 @@ void LLVivoxVoiceClient::setVoiceEnabled(bool enabled)
if (!mIsCoroutineActive)
{
- LLCoros::instance().launch("LLVivoxVoiceClient::voiceControlCoro();",
+ LLCoros::instance().launch("LLVivoxVoiceClient::voiceControlCoro",
boost::bind(&LLVivoxVoiceClient::voiceControlCoro, LLVivoxVoiceClient::getInstance()));
}
else
@@ -6541,7 +6549,7 @@ void LLVivoxVoiceClient::accountGetSessionFontsResponse(int statusCode, const st
// receiving the last one.
LLSD result(LLSDMap("voice_fonts", LLSD::Boolean(true)));
- LLEventPumps::instance().post("vivoxClientPump", result);
+ mVivoxPump.post(result);
}
notifyVoiceFontObservers();
mVoiceFontsReceived = true;
@@ -6692,7 +6700,7 @@ void LLVivoxVoiceClient::enablePreviewBuffer(bool enable)
else
result["recplay"] = "quit";
- LLEventPumps::instance().post("vivoxClientPump", result);
+ mVivoxPump.post(result);
if(mCaptureBufferMode && mIsInChannel)
{
@@ -6713,7 +6721,7 @@ void LLVivoxVoiceClient::recordPreviewBuffer()
mCaptureBufferRecording = true;
LLSD result(LLSDMap("recplay", "record"));
- LLEventPumps::instance().post("vivoxClientPump", result);
+ mVivoxPump.post(result);
}
void LLVivoxVoiceClient::playPreviewBuffer(const LLUUID& effect_id)
@@ -6736,7 +6744,7 @@ void LLVivoxVoiceClient::playPreviewBuffer(const LLUUID& effect_id)
mCaptureBufferPlaying = true;
LLSD result(LLSDMap("recplay", "playback"));
- LLEventPumps::instance().post("vivoxClientPump", result);
+ mVivoxPump.post(result);
}
void LLVivoxVoiceClient::stopPreviewBuffer()
@@ -6745,7 +6753,7 @@ void LLVivoxVoiceClient::stopPreviewBuffer()
mCaptureBufferPlaying = false;
LLSD result(LLSDMap("recplay", "quit"));
- LLEventPumps::instance().post("vivoxClientPump", result);
+ mVivoxPump.post(result);
}
bool LLVivoxVoiceClient::isPreviewRecording()
diff --git a/indra/newview/llvovolume.cpp b/indra/newview/llvovolume.cpp
index 819933fa72..db9f909a8b 100644
--- a/indra/newview/llvovolume.cpp
+++ b/indra/newview/llvovolume.cpp
@@ -2063,7 +2063,7 @@ void LLVOVolume::setNumTEs(const U8 num_tes)
}
else if(old_num_tes > num_tes && mMediaImplList.size() > num_tes) //old faces removed
{
- U8 end = mMediaImplList.size() ;
+ U8 end = (U8)(mMediaImplList.size()) ;
for(U8 i = num_tes; i < end ; i++)
{
removeMediaImpl(i) ;
diff --git a/indra/newview/llwatchdog.cpp b/indra/newview/llwatchdog.cpp
index dd6c77ca7d..6273f10c69 100644
--- a/indra/newview/llwatchdog.cpp
+++ b/indra/newview/llwatchdog.cpp
@@ -91,7 +91,11 @@ void LLWatchdogEntry::start()
void LLWatchdogEntry::stop()
{
- LLWatchdog::getInstance()->remove(this);
+ // this can happen very late in the shutdown sequence
+ if (! LLWatchdog::wasDeleted())
+ {
+ LLWatchdog::getInstance()->remove(this);
+ }
}
// LLWatchdogTimeout
diff --git a/indra/newview/llweb.cpp b/indra/newview/llweb.cpp
index a34c5826ed..63257d6543 100644
--- a/indra/newview/llweb.cpp
+++ b/indra/newview/llweb.cpp
@@ -157,12 +157,12 @@ std::string LLWeb::expandURLSubstitutions(const std::string &url,
const LLSD &default_subs)
{
LLSD substitution = default_subs;
- substitution["VERSION"] = LLVersionInfo::getVersion();
- substitution["VERSION_MAJOR"] = LLVersionInfo::getMajor();
- substitution["VERSION_MINOR"] = LLVersionInfo::getMinor();
- substitution["VERSION_PATCH"] = LLVersionInfo::getPatch();
- substitution["VERSION_BUILD"] = LLVersionInfo::getBuild();
- substitution["CHANNEL"] = LLVersionInfo::getChannel();
+ substitution["VERSION"] = LLVersionInfo::instance().getVersion();
+ substitution["VERSION_MAJOR"] = LLVersionInfo::instance().getMajor();
+ substitution["VERSION_MINOR"] = LLVersionInfo::instance().getMinor();
+ substitution["VERSION_PATCH"] = LLVersionInfo::instance().getPatch();
+ substitution["VERSION_BUILD"] = LLVersionInfo::instance().getBuild();
+ substitution["CHANNEL"] = LLVersionInfo::instance().getChannel();
substitution["GRID"] = LLGridManager::getInstance()->getGridId();
substitution["GRID_LOWERCASE"] = utf8str_tolower(LLGridManager::getInstance()->getGridId());
substitution["OS"] = LLOSInfo::instance().getOSStringSimple();
diff --git a/indra/newview/llwindebug.h b/indra/newview/llwindebug.h
index 7e5818ba1c..524adba652 100644
--- a/indra/newview/llwindebug.h
+++ b/indra/newview/llwindebug.h
@@ -29,7 +29,11 @@
#include "stdtypes.h"
#include "llwin32headerslean.h"
+
+#pragma warning (push)
+#pragma warning (disable:4091) // a microsoft header has warnings. Very nice.
#include <dbghelp.h>
+#pragma warning (pop)
class LLWinDebug:
public LLSingleton<LLWinDebug>
diff --git a/indra/newview/llxmlrpclistener.cpp b/indra/newview/llxmlrpclistener.cpp
index 0693d08dfb..663a75156f 100644
--- a/indra/newview/llxmlrpclistener.cpp
+++ b/indra/newview/llxmlrpclistener.cpp
@@ -43,6 +43,7 @@
// other Linden headers
#include "llerror.h"
+#include "lleventcoro.h"
#include "stringize.h"
#include "llxmlrpctransaction.h"
#include "llsecapi.h"
@@ -366,6 +367,8 @@ public:
// whether successful or not, send reply on requested LLEventPump
replyPump.post(data);
+ // need to wake up the loginCoro now
+ llcoro::suspend();
// Because mTransaction is a boost::scoped_ptr, deleting this object
// frees our LLXMLRPCTransaction object.
diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml
index 8a91a1f721..05fd1947fe 100644
--- a/indra/newview/skins/default/xui/en/notifications.xml
+++ b/indra/newview/skins/default/xui/en/notifications.xml
@@ -4016,6 +4016,8 @@ Finished download of raw terrain file to:
[DOWNLOAD_PATH].
</notification>
+ <!-- RequiredUpdate does not display release notes URL because we don't get
+ that from login.cgi's login failure message. -->
<notification
icon="alertmodal.tga"
name="RequiredUpdate"
@@ -4033,6 +4035,7 @@ Please download from https://secondlife.com/support/downloads/
name="PauseForUpdate"
type="alertmodal">
Version [VERSION] is required for login.
+Release notes: [URL]
Click OK to download and install.
<tag>confirm</tag>
<usetemplate
@@ -4045,6 +4048,7 @@ Click OK to download and install.
name="OptionalUpdateReady"
type="alertmodal">
Version [VERSION] has been downloaded and is ready to install.
+Release notes: [URL]
Click OK to install.
<tag>confirm</tag>
<usetemplate
@@ -4057,6 +4061,7 @@ Click OK to install.
name="PromptOptionalUpdate"
type="alertmodal">
Version [VERSION] has been downloaded and is ready to install.
+Release notes: [URL]
Proceed?
<tag>confirm</tag>
<usetemplate
diff --git a/indra/newview/tests/lllogininstance_test.cpp b/indra/newview/tests/lllogininstance_test.cpp
index 2edad30493..57f2d31eab 100644
--- a/indra/newview/tests/lllogininstance_test.cpp
+++ b/indra/newview/tests/lllogininstance_test.cpp
@@ -202,8 +202,6 @@ void LLUIColorTable::saveUserSettings(void)const {}
//-----------------------------------------------------------------------------
#include "../llversioninfo.h"
-const std::string &LLVersionInfo::getVersion() { return VIEWERLOGIN_VERSION; }
-const std::string &LLVersionInfo::getChannel() { return VIEWERLOGIN_CHANNEL; }
bool llHashedUniqueID(unsigned char* id)
{
diff --git a/indra/newview/tests/llversioninfo_test.cpp b/indra/newview/tests/llversioninfo_test.cpp
index 58f0469552..51a6f8f113 100644
--- a/indra/newview/tests/llversioninfo_test.cpp
+++ b/indra/newview/tests/llversioninfo_test.cpp
@@ -83,39 +83,39 @@ namespace tut
void versioninfo_object_t::test<1>()
{
std::cout << "What we parsed from CMake: " << LL_VIEWER_VERSION_BUILD << std::endl;
- std::cout << "What we get from llversioninfo: " << LLVersionInfo::getBuild() << std::endl;
+ std::cout << "What we get from llversioninfo: " << LLVersionInfo::instance().getBuild() << std::endl;
ensure_equals("Major version",
- LLVersionInfo::getMajor(),
+ LLVersionInfo::instance().getMajor(),
LL_VIEWER_VERSION_MAJOR);
ensure_equals("Minor version",
- LLVersionInfo::getMinor(),
+ LLVersionInfo::instance().getMinor(),
LL_VIEWER_VERSION_MINOR);
ensure_equals("Patch version",
- LLVersionInfo::getPatch(),
+ LLVersionInfo::instance().getPatch(),
LL_VIEWER_VERSION_PATCH);
ensure_equals("Build version",
- LLVersionInfo::getBuild(),
+ LLVersionInfo::instance().getBuild(),
LL_VIEWER_VERSION_BUILD);
ensure_equals("Channel version",
- LLVersionInfo::getChannel(),
+ LLVersionInfo::instance().getChannel(),
ll_viewer_channel);
ensure_equals("Version String",
- LLVersionInfo::getVersion(),
+ LLVersionInfo::instance().getVersion(),
mVersion);
ensure_equals("Short Version String",
- LLVersionInfo::getShortVersion(),
+ LLVersionInfo::instance().getShortVersion(),
mShortVersion);
ensure_equals("Version and channel String",
- LLVersionInfo::getChannelAndVersion(),
+ LLVersionInfo::instance().getChannelAndVersion(),
mVersionAndChannel);
- LLVersionInfo::resetChannel(mResetChannel);
+ LLVersionInfo::instance().resetChannel(mResetChannel);
ensure_equals("Reset channel version",
- LLVersionInfo::getChannel(),
+ LLVersionInfo::instance().getChannel(),
mResetChannel);
ensure_equals("Reset Version and channel String",
- LLVersionInfo::getChannelAndVersion(),
+ LLVersionInfo::instance().getChannelAndVersion(),
mResetVersionAndChannel);
}
}
diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py
index b385717dbe..f5edde1923 100755
--- a/indra/newview/viewer_manifest.py
+++ b/indra/newview/viewer_manifest.py
@@ -528,12 +528,8 @@ class WindowsManifest(ViewerManifest):
# These need to be installed as a SxS assembly, currently a 'private' assembly.
# See http://msdn.microsoft.com/en-us/library/ms235291(VS.80).aspx
- if self.args['configuration'].lower() == 'debug':
- self.path("msvcr120d.dll")
- self.path("msvcp120d.dll")
- else:
- self.path("msvcr120.dll")
- self.path("msvcp120.dll")
+ self.path("msvcp140.dll")
+ self.path("vcruntime140.dll")
# SLVoice executable
with self.prefix(src=os.path.join(pkgdir, 'bin', 'release')):
@@ -605,8 +601,8 @@ class WindowsManifest(ViewerManifest):
# MSVC DLLs needed for CEF and have to be in same directory as plugin
with self.prefix(src=os.path.join(self.args['build'], os.pardir,
'sharedlibs', 'Release')):
- self.path("msvcp120.dll")
- self.path("msvcr120.dll")
+ self.path("msvcp140.dll")
+ self.path("vcruntime140.dll")
# CEF files common to all configurations
with self.prefix(src=os.path.join(pkgdir, 'resources')):
@@ -956,7 +952,7 @@ class DarwinManifest(ViewerManifest):
with self.prefix(src=relpkgdir, dst=""):
self.path("libndofdev.dylib")
- self.path("libhunspell-1.3.a")
+ self.path("libhunspell-*.dylib")
with self.prefix(src_dst="cursors_mac"):
self.path("*.tif")
@@ -1215,11 +1211,6 @@ class DarwinManifest(ViewerManifest):
devfile = re.search("/dev/disk([0-9]+)[^s]", hdi_output).group(0).strip()
volpath = re.search('HFS\s+(.+)', hdi_output).group(1).strip()
- if devfile != '/dev/disk1':
- # adding more debugging info based upon nat's hunches to the
- # logs to help track down 'SetFile -a V' failures -brad
- print "WARNING: 'SetFile -a V' command below is probably gonna fail"
-
# Copy everything in to the mounted .dmg
app_name = self.app_name()
@@ -1247,21 +1238,6 @@ class DarwinManifest(ViewerManifest):
# Hide the background image, DS_Store file, and volume icon file (set their "visible" bit)
for f in ".VolumeIcon.icns", "background.jpg", ".DS_Store":
pathname = os.path.join(volpath, f)
- # We've observed mysterious "no such file" failures of the SetFile
- # command, especially on the first file listed above -- yet
- # subsequent inspection of the target directory confirms it's
- # there. Timing problem with copy command? Try to handle.
- for x in xrange(3):
- if os.path.exists(pathname):
- print "Confirmed existence: %r" % pathname
- break
- print "Waiting for %s copy command to complete (%s)..." % (f, x+1)
- sys.stdout.flush()
- time.sleep(1)
- # If we fall out of the loop above without a successful break, oh
- # well, possibly we've mistaken the nature of the problem. In any
- # case, don't hang up the whole build looping indefinitely, let
- # the original problem manifest by executing the desired command.
self.run_command(['SetFile', '-a', 'V', pathname])
# Create the alias file (which is a resource file) from the .r
@@ -1564,6 +1540,11 @@ class Linux_x86_64_Manifest(LinuxManifest):
################################################################
if __name__ == "__main__":
+ # Report our own command line so that, in case of trouble, a developer can
+ # manually rerun the same command.
+ print('%s \\\n%s' %
+ (sys.executable,
+ ' '.join((("'%s'" % arg) if ' ' in arg else arg) for arg in sys.argv)))
extra_arguments = [
dict(name='bugsplat', description="""BugSplat database to which to post crashes,
if BugSplat crash reporting is desired""", default=''),
diff --git a/indra/test/CMakeLists.txt b/indra/test/CMakeLists.txt
index 8344cead57..87536e146b 100644
--- a/indra/test/CMakeLists.txt
+++ b/indra/test/CMakeLists.txt
@@ -67,6 +67,7 @@ set(test_HEADER_FILES
llpipeutil.h
llsdtraits.h
lltut.h
+ sync.h
)
if (NOT WINDOWS)
@@ -83,6 +84,7 @@ list(APPEND test_SOURCE_FILES ${test_HEADER_FILES})
add_executable(lltest ${test_SOURCE_FILES})
target_link_libraries(lltest
+ ${LEGACY_STDIO_LIBS}
${LLDATABASE_LIBRARIES}
${LLINVENTORY_LIBRARIES}
${LLMESSAGE_LIBRARIES}
@@ -98,7 +100,7 @@ target_link_libraries(lltest
${WINDOWS_LIBRARIES}
${BOOST_PROGRAM_OPTIONS_LIBRARY}
${BOOST_REGEX_LIBRARY}
- ${BOOST_COROUTINE_LIBRARY}
+ ${BOOST_FIBER_LIBRARY}
${BOOST_CONTEXT_LIBRARY}
${BOOST_SYSTEM_LIBRARY}
${DL_LIBRARY}
diff --git a/indra/test/chained_callback.h b/indra/test/chained_callback.h
new file mode 100644
index 0000000000..05929e33ad
--- /dev/null
+++ b/indra/test/chained_callback.h
@@ -0,0 +1,107 @@
+/**
+ * @file chained_callback.h
+ * @author Nat Goodspeed
+ * @date 2020-01-03
+ * @brief Subclass of tut::callback used for chaining callbacks.
+ *
+ * $LicenseInfo:firstyear=2020&license=viewerlgpl$
+ * Copyright (c) 2020, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if ! defined(LL_CHAINED_CALLBACK_H)
+#define LL_CHAINED_CALLBACK_H
+
+#include "lltut.h"
+
+/**
+ * Derive your TUT callback from chained_callback instead of tut::callback to
+ * ensure that multiple such callbacks can coexist in a given test executable.
+ * The relevant callback method will be called for each callback instance in
+ * reverse order of the instance's link() methods being called: the most
+ * recently link()ed callback will be called first, then the previous, and so
+ * forth.
+ *
+ * Obviously, for this to work, all relevant callbacks must be derived from
+ * chained_callback instead of tut::callback. Given that, control should reach
+ * each of them regardless of their construction order. The chain is
+ * guaranteed to stop because the first link() call will link to test_runner's
+ * default_callback, which is simply an instance of the callback() base class.
+ *
+ * The rule for deriving from chained_callback is that you may override any of
+ * its virtual methods, but your override must at some point call the
+ * corresponding chained_callback method.
+ */
+class chained_callback: public tut::callback
+{
+public:
+ /**
+ * Instead of calling tut::test_runner::set_callback(&your_callback), call
+ * your_callback.link();
+ * This uses the canonical instance of tut::test_runner.
+ */
+ void link()
+ {
+ link(tut::runner.get());
+ }
+
+ /**
+ * If for some reason you have a different instance of test_runner...
+ */
+ void link(tut::test_runner& runner)
+ {
+ // Since test_runner's constructor sets a default callback,
+ // get_callback() will always return a reference to a valid callback
+ // instance.
+ mPrev = &runner.get_callback();
+ runner.set_callback(this);
+ }
+
+ /**
+ * Called when new test run started.
+ */
+ virtual void run_started()
+ {
+ mPrev->run_started();
+ }
+
+ /**
+ * Called when a group started
+ * @param name Name of the group
+ */
+ virtual void group_started(const std::string& name)
+ {
+ mPrev->group_started(name);
+ }
+
+ /**
+ * Called when a test finished.
+ * @param tr Test results.
+ */
+ virtual void test_completed(const tut::test_result& tr)
+ {
+ mPrev->test_completed(tr);
+ }
+
+ /**
+ * Called when a group is completed
+ * @param name Name of the group
+ */
+ virtual void group_completed(const std::string& name)
+ {
+ mPrev->group_completed(name);
+ }
+
+ /**
+ * Called when all tests in run completed.
+ */
+ virtual void run_completed()
+ {
+ mPrev->run_completed();
+ }
+
+private:
+ tut::callback* mPrev;
+};
+
+#endif /* ! defined(LL_CHAINED_CALLBACK_H) */
diff --git a/indra/test/debug.h b/indra/test/debug.h
index d61eba651b..76dbb973b2 100644
--- a/indra/test/debug.h
+++ b/indra/test/debug.h
@@ -29,42 +29,64 @@
#if ! defined(LL_DEBUG_H)
#define LL_DEBUG_H
-#include <iostream>
+#include "print.h"
/*****************************************************************************
* Debugging stuff
*****************************************************************************/
-// This class is intended to illuminate entry to a given block, exit from the
-// same block and checkpoints along the way. It also provides a convenient
-// place to turn std::cout output on and off.
+/**
+ * This class is intended to illuminate entry to a given block, exit from the
+ * same block and checkpoints along the way. It also provides a convenient
+ * place to turn std::cerr output on and off.
+ *
+ * If the environment variable LOGTEST is non-empty, each Debug instance will
+ * announce its construction and destruction, presumably at entry and exit to
+ * the block in which it's declared. Moreover, any arguments passed to its
+ * operator()() will be streamed to std::cerr, prefixed by the block
+ * description.
+ *
+ * The variable LOGTEST is used because that's the environment variable
+ * checked by test.cpp, our TUT main() program, to turn on LLError logging. It
+ * is expected that Debug is solely for use in test programs.
+ */
class Debug
{
public:
Debug(const std::string& block):
- mBlock(block)
+ mBlock(block),
+ mLOGTEST(getenv("LOGTEST")),
+ // debug output enabled when LOGTEST is set AND non-empty
+ mEnabled(mLOGTEST && *mLOGTEST)
{
(*this)("entry");
}
+ // non-copyable
+ Debug(const Debug&) = delete;
+
~Debug()
{
(*this)("exit");
}
- void operator()(const std::string& status)
+ template <typename... ARGS>
+ void operator()(ARGS&&... args)
{
-#if defined(DEBUG_ON)
- std::cout << mBlock << ' ' << status << std::endl;
-#endif
+ if (mEnabled)
+ {
+ print(mBlock, ' ', std::forward<ARGS>(args)...);
+ }
}
private:
const std::string mBlock;
+ const char* mLOGTEST;
+ bool mEnabled;
};
// It's often convenient to use the name of the enclosing function as the name
// of the Debug block.
-#define DEBUG Debug debug(__FUNCTION__)
+#define DEBUG Debug debug(LL_PRETTY_FUNCTION)
// These BEGIN/END macros are specifically for debugging output -- please
// don't assume you must use such for coroutines in general! They only help to
diff --git a/indra/test/llevents_tut.cpp b/indra/test/llevents_tut.cpp
index 3abae3e43e..17f64a4953 100644
--- a/indra/test/llevents_tut.cpp
+++ b/indra/test/llevents_tut.cpp
@@ -38,7 +38,6 @@
#define testable public
#include "llevents.h"
#undef testable
-#include "lllistenerwrapper.h"
// STL headers
// std headers
#include <iostream>
@@ -92,9 +91,7 @@ template<> template<>
void events_object::test<1>()
{
set_test_name("basic operations");
- // Now there's a static constructor in llevents.cpp that registers on
- // the "mainloop" pump to call LLEventPumps::flush().
- // Actually -- having to modify this to track the statically-
+ // Having to modify this to track the statically-
// constructed pumps in other TUT modules in this giant monolithic test
// executable isn't such a hot idea.
// ensure_equals("initial pump", pumps.mPumpMap.size(), 1);
@@ -211,43 +208,6 @@ bool chainEvents(Listener& someListener, const LLSD& event)
template<> template<>
void events_object::test<3>()
{
- set_test_name("LLEventQueue delayed action");
- // This access is NOT legal usage: we can do it only because we're
- // hacking private for test purposes. Normally we'd either compile in
- // a particular name, or (later) edit a config file.
- pumps.mQueueNames.insert("login");
- LLEventPump& login(pumps.obtain("login"));
- // The "mainloop" pump is special: posting on that implicitly calls
- // LLEventPumps::flush(), which in turn should flush our "login"
- // LLEventQueue.
- LLEventPump& mainloop(pumps.obtain("mainloop"));
- ensure("LLEventQueue leaf class", dynamic_cast<LLEventQueue*> (&login));
- listener0.listenTo(login);
- listener0.reset(0);
- login.post(1);
- check_listener("waiting for queued event", listener0, 0);
- mainloop.post(LLSD());
- check_listener("got queued event", listener0, 1);
- login.stopListening(listener0.getName());
- // Verify that when an event handler posts a new event on the same
- // LLEventQueue, it doesn't get processed in the same flush() call --
- // it waits until the next flush() call.
- listener0.reset(17);
- login.listen("chainEvents", boost::bind(chainEvents, boost::ref(listener0), _1));
- login.post(1);
- check_listener("chainEvents(1) not yet called", listener0, 17);
- mainloop.post(LLSD());
- check_listener("chainEvents(1) called", listener0, 1);
- mainloop.post(LLSD());
- check_listener("chainEvents(0) called", listener0, 0);
- mainloop.post(LLSD());
- check_listener("chainEvents(-1) not called", listener0, 0);
- login.stopListening("chainEvents");
-}
-
-template<> template<>
-void events_object::test<4>()
-{
set_test_name("explicitly-instantiated LLEventStream");
// Explicitly instantiate an LLEventStream, and verify that it
// self-registers with LLEventPumps
@@ -271,7 +231,7 @@ void events_object::test<4>()
}
template<> template<>
-void events_object::test<5>()
+void events_object::test<4>()
{
set_test_name("stopListening()");
LLEventPump& login(pumps.obtain("login"));
@@ -285,7 +245,7 @@ void events_object::test<5>()
}
template<> template<>
-void events_object::test<6>()
+void events_object::test<5>()
{
set_test_name("chaining LLEventPump instances");
LLEventPump& upstream(pumps.obtain("upstream"));
@@ -310,7 +270,7 @@ void events_object::test<6>()
}
template<> template<>
-void events_object::test<7>()
+void events_object::test<6>()
{
set_test_name("listener dependency order");
typedef LLEventPump::NameList NameList;
@@ -392,7 +352,7 @@ void events_object::test<7>()
}
template<> template<>
-void events_object::test<8>()
+void events_object::test<7>()
{
set_test_name("tweaked and untweaked LLEventPump instance names");
{ // nested scope
@@ -424,7 +384,7 @@ void eventSource(const LLListenerOrPumpName& listener)
}
template<> template<>
-void events_object::test<9>()
+void events_object::test<8>()
{
set_test_name("LLListenerOrPumpName");
// Passing a boost::bind() expression to LLListenerOrPumpName
@@ -465,7 +425,7 @@ private:
};
template<> template<>
-void events_object::test<10>()
+void events_object::test<9>()
{
set_test_name("listen(boost::bind(...TempListener...))");
// listen() can't do anything about a plain TempListener instance:
@@ -493,223 +453,60 @@ void events_object::test<10>()
heaptest.stopListening("temp");
}
-template<> template<>
-void events_object::test<11>()
-{
- set_test_name("listen(boost::bind(...weak_ptr...))");
- // listen() detecting weak_ptr<TempListener> in boost::bind() object
- bool live = false;
- LLEventPump& heaptest(pumps.obtain("heaptest"));
- LLBoundListener connection;
- ensure("default state", !connection.connected());
- {
- boost::shared_ptr<TempListener> newListener(new TempListener("heap", live));
- newListener->reset();
- ensure("TempListener constructed", live);
- connection = heaptest.listen(newListener->getName(),
- boost::bind(&Listener::call,
- weaken(newListener),
- _1));
- ensure("new connection", connection.connected());
- heaptest.post(1);
- check_listener("received", *newListener, 1);
- } // presumably this will make newListener go away?
- // verify that
- ensure("TempListener destroyed", !live);
- ensure("implicit disconnect", !connection.connected());
- // now just make sure we don't blow up trying to access a freed object!
- heaptest.post(2);
-}
-
-template<> template<>
-void events_object::test<12>()
-{
- set_test_name("listen(boost::bind(...shared_ptr...))");
- /*==========================================================================*|
- // DISABLED because I've made this case produce a compile error.
- // Following the error leads the disappointed dev to a comment
- // instructing her to use the weaken() function to bind a weak_ptr<T>
- // instead of binding a shared_ptr<T>, and explaining why. I know of
- // no way to use TUT to code a repeatable test in which the expected
- // outcome is a compile error. The interested reader is invited to
- // uncomment this block and build to see for herself.
-
- // listen() detecting shared_ptr<TempListener> in boost::bind() object
- bool live = false;
- LLEventPump& heaptest(pumps.obtain("heaptest"));
- LLBoundListener connection;
- std::string listenerName("heap");
- ensure("default state", !connection.connected());
- {
- boost::shared_ptr<TempListener> newListener(new TempListener(listenerName, live));
- ensure_equals("use_count", newListener.use_count(), 1);
- newListener->reset();
- ensure("TempListener constructed", live);
- connection = heaptest.listen(newListener->getName(),
- boost::bind(&Listener::call, newListener, _1));
- ensure("new connection", connection.connected());
- ensure_equals("use_count", newListener.use_count(), 2);
- heaptest.post(1);
- check_listener("received", *newListener, 1);
- } // this should make newListener go away...
- // Unfortunately, the fact that we've bound a shared_ptr by value into
- // our LLEventPump means that copy will keep the referenced object alive.
- ensure("TempListener still alive", live);
- ensure("still connected", connection.connected());
- // disconnecting explicitly should delete the TempListener...
- heaptest.stopListening(listenerName);
-#if 0 // however, in my experience, it does not. I don't know why not.
- // Ah: on 2009-02-19, Frank Mori Hess, author of the Boost.Signals2
- // library, stated on the boost-users mailing list:
- // http://www.nabble.com/Re%3A--signals2--review--The-review-of-the-signals2-library-(formerly-thread_safe_signals)-begins-today%2C-Nov-1st-p22102367.html
- // "It will get destroyed eventually. The signal cleans up its slot
- // list little by little during connect/invoke. It doesn't immediately
- // remove disconnected slots from the slot list since other threads
- // might be using the same slot list concurrently. It might be
- // possible to make it immediately reset the shared_ptr owning the
- // slot though, leaving an empty shared_ptr in the slot list, since
- // that wouldn't invalidate any iterators."
- ensure("TempListener destroyed", ! live);
- ensure("implicit disconnect", ! connection.connected());
-#endif // 0
- // now just make sure we don't blow up trying to access a freed object!
- heaptest.post(2);
-|*==========================================================================*/
-}
-
class TempTrackableListener: public TempListener, public LLEventTrackable
{
public:
-TempTrackableListener(const std::string& name, bool& liveFlag):
- TempListener(name, liveFlag)
-{}
+ TempTrackableListener(const std::string& name, bool& liveFlag):
+ TempListener(name, liveFlag)
+ {}
};
template<> template<>
-void events_object::test<13>()
-{
-set_test_name("listen(boost::bind(...TempTrackableListener ref...))");
-bool live = false;
-LLEventPump& heaptest(pumps.obtain("heaptest"));
-LLBoundListener connection;
-{
- TempTrackableListener tempListener("temp", live);
- ensure("TempTrackableListener constructed", live);
- connection = heaptest.listen(tempListener.getName(),
- boost::bind(&TempTrackableListener::call,
- boost::ref(tempListener), _1));
- heaptest.post(1);
- check_listener("received", tempListener, 1);
-} // presumably this will make tempListener go away?
-// verify that
-ensure("TempTrackableListener destroyed", ! live);
-ensure("implicit disconnect", ! connection.connected());
-// now just make sure we don't blow up trying to access a freed object!
-heaptest.post(2);
-}
-
-template<> template<>
-void events_object::test<14>()
-{
-set_test_name("listen(boost::bind(...TempTrackableListener pointer...))");
-bool live = false;
-LLEventPump& heaptest(pumps.obtain("heaptest"));
-LLBoundListener connection;
+void events_object::test<10>()
{
- TempTrackableListener* newListener(new TempTrackableListener("temp", live));
- ensure("TempTrackableListener constructed", live);
- connection = heaptest.listen(newListener->getName(),
- boost::bind(&TempTrackableListener::call,
- newListener, _1));
- heaptest.post(1);
- check_listener("received", *newListener, 1);
- // explicitly destroy newListener
- delete newListener;
-}
-// verify that
-ensure("TempTrackableListener destroyed", ! live);
-ensure("implicit disconnect", ! connection.connected());
-// now just make sure we don't blow up trying to access a freed object!
-heaptest.post(2);
+ set_test_name("listen(boost::bind(...TempTrackableListener ref...))");
+ bool live = false;
+ LLEventPump& heaptest(pumps.obtain("heaptest"));
+ LLBoundListener connection;
+ {
+ TempTrackableListener tempListener("temp", live);
+ ensure("TempTrackableListener constructed", live);
+ connection = heaptest.listen(tempListener.getName(),
+ boost::bind(&TempTrackableListener::call,
+ boost::ref(tempListener), _1));
+ heaptest.post(1);
+ check_listener("received", tempListener, 1);
+ } // presumably this will make tempListener go away?
+ // verify that
+ ensure("TempTrackableListener destroyed", ! live);
+ ensure("implicit disconnect", ! connection.connected());
+ // now just make sure we don't blow up trying to access a freed object!
+ heaptest.post(2);
}
template<> template<>
-void events_object::test<15>()
-{
-// This test ensures that using an LLListenerWrapper subclass doesn't
-// block Boost.Signals2 from recognizing a bound LLEventTrackable
-// subclass.
-set_test_name("listen(llwrap<LLLogListener>(boost::bind(...TempTrackableListener ref...)))");
-bool live = false;
-LLEventPump& heaptest(pumps.obtain("heaptest"));
-LLBoundListener connection;
+void events_object::test<11>()
{
- TempTrackableListener tempListener("temp", live);
- ensure("TempTrackableListener constructed", live);
- connection = heaptest.listen(tempListener.getName(),
- llwrap<LLLogListener>(
- boost::bind(&TempTrackableListener::call,
- boost::ref(tempListener), _1)));
- heaptest.post(1);
- check_listener("received", tempListener, 1);
-} // presumably this will make tempListener go away?
-// verify that
-ensure("TempTrackableListener destroyed", ! live);
-ensure("implicit disconnect", ! connection.connected());
-// now just make sure we don't blow up trying to access a freed object!
-heaptest.post(2);
+ set_test_name("listen(boost::bind(...TempTrackableListener pointer...))");
+ bool live = false;
+ LLEventPump& heaptest(pumps.obtain("heaptest"));
+ LLBoundListener connection;
+ {
+ TempTrackableListener* newListener(new TempTrackableListener("temp", live));
+ ensure("TempTrackableListener constructed", live);
+ connection = heaptest.listen(newListener->getName(),
+ boost::bind(&TempTrackableListener::call,
+ newListener, _1));
+ heaptest.post(1);
+ check_listener("received", *newListener, 1);
+ // explicitly destroy newListener
+ delete newListener;
+ }
+ // verify that
+ ensure("TempTrackableListener destroyed", ! live);
+ ensure("implicit disconnect", ! connection.connected());
+ // now just make sure we don't blow up trying to access a freed object!
+ heaptest.post(2);
}
-class TempSharedListener: public TempListener,
-public boost::enable_shared_from_this<TempSharedListener>
-{
-public:
-TempSharedListener(const std::string& name, bool& liveFlag):
- TempListener(name, liveFlag)
-{}
-};
-
-template<> template<>
-void events_object::test<16>()
-{
- set_test_name("listen(boost::bind(...TempSharedListener ref...))");
-#if 0
-bool live = false;
-LLEventPump& heaptest(pumps.obtain("heaptest"));
-LLBoundListener connection;
-{
- // We MUST have at least one shared_ptr to an
- // enable_shared_from_this subclass object before
- // shared_from_this() can work.
- boost::shared_ptr<TempSharedListener>
- tempListener(new TempSharedListener("temp", live));
- ensure("TempSharedListener constructed", live);
- // However, we're not passing either the shared_ptr or its
- // corresponding weak_ptr -- instead, we're passing a reference to
- // the TempSharedListener.
-/*==========================================================================*|
- std::cout << "Capturing const ref" << std::endl;
- const boost::enable_shared_from_this<TempSharedListener>& cref(*tempListener);
- std::cout << "Capturing const ptr" << std::endl;
- const boost::enable_shared_from_this<TempSharedListener>* cp(&cref);
- std::cout << "Capturing non-const ptr" << std::endl;
- boost::enable_shared_from_this<TempSharedListener>* p(const_cast<boost::enable_shared_from_this<TempSharedListener>*>(cp));
- std::cout << "Capturing shared_from_this()" << std::endl;
- boost::shared_ptr<TempSharedListener> sp(p->shared_from_this());
- std::cout << "Capturing weak_ptr" << std::endl;
- boost::weak_ptr<TempSharedListener> wp(weaken(sp));
- std::cout << "Binding weak_ptr" << std::endl;
-|*==========================================================================*/
- connection = heaptest.listen(tempListener->getName(),
- boost::bind(&TempSharedListener::call, *tempListener, _1));
- heaptest.post(1);
- check_listener("received", *tempListener, 1);
-} // presumably this will make tempListener go away?
-// verify that
-ensure("TempSharedListener destroyed", ! live);
-ensure("implicit disconnect", ! connection.connected());
-// now just make sure we don't blow up trying to access a freed object!
-heaptest.post(2);
-#endif // 0
-}
} // namespace tut
diff --git a/indra/test/lltestapp.h b/indra/test/lltestapp.h
new file mode 100644
index 0000000000..382516cd2b
--- /dev/null
+++ b/indra/test/lltestapp.h
@@ -0,0 +1,34 @@
+/**
+ * @file lltestapp.h
+ * @author Nat Goodspeed
+ * @date 2019-10-21
+ * @brief LLApp subclass useful for testing.
+ *
+ * $LicenseInfo:firstyear=2019&license=viewerlgpl$
+ * Copyright (c) 2019, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if ! defined(LL_LLTESTAPP_H)
+#define LL_LLTESTAPP_H
+
+#include "llapp.h"
+
+/**
+ * LLTestApp is a dummy LLApp that simply sets LLApp::isRunning() for anyone
+ * who cares.
+ */
+class LLTestApp: public LLApp
+{
+public:
+ LLTestApp()
+ {
+ setStatus(APP_STATUS_RUNNING);
+ }
+
+ bool init() { return true; }
+ bool cleanup() { return true; }
+ bool frame() { return true; }
+};
+
+#endif /* ! defined(LL_LLTESTAPP_H) */
diff --git a/indra/test/print.h b/indra/test/print.h
new file mode 100644
index 0000000000..08e36caddf
--- /dev/null
+++ b/indra/test/print.h
@@ -0,0 +1,42 @@
+/**
+ * @file print.h
+ * @author Nat Goodspeed
+ * @date 2020-01-02
+ * @brief print() function for debugging
+ *
+ * $LicenseInfo:firstyear=2020&license=viewerlgpl$
+ * Copyright (c) 2020, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if ! defined(LL_PRINT_H)
+#define LL_PRINT_H
+
+#include <iostream>
+
+// print(..., NONL);
+// leaves the output dangling, suppressing the normally appended std::endl
+struct NONL_t {};
+#define NONL (NONL_t())
+
+// normal recursion end
+inline
+void print()
+{
+ std::cerr << std::endl;
+}
+
+// print(NONL) is a no-op
+inline
+void print(NONL_t)
+{
+}
+
+template <typename T, typename... ARGS>
+void print(T&& first, ARGS&&... rest)
+{
+ std::cerr << first;
+ print(std::forward<ARGS>(rest)...);
+}
+
+#endif /* ! defined(LL_PRINT_H) */
diff --git a/indra/test/setenv.h b/indra/test/setenv.h
new file mode 100644
index 0000000000..ed2de9ccca
--- /dev/null
+++ b/indra/test/setenv.h
@@ -0,0 +1,66 @@
+/**
+ * @file setenv.h
+ * @author Nat Goodspeed
+ * @date 2020-04-01
+ * @brief Provide a way for a particular test program to alter the
+ * environment before entry to main().
+ *
+ * $LicenseInfo:firstyear=2020&license=viewerlgpl$
+ * Copyright (c) 2020, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if ! defined(LL_SETENV_H)
+#define LL_SETENV_H
+
+#include <stdlib.h> // setenv()
+
+/**
+ * Our test.cpp main program responds to environment variables LOGTEST and
+ * LOGFAIL. But if you set (e.g.) LOGTEST=DEBUG before a viewer build, @em
+ * every test program in the build emits debug log output. This can be so
+ * voluminous as to slow down the build.
+ *
+ * With an integration test program, you can specifically build (e.g.) the
+ * INTEGRATION_TEST_llstring target, and set any environment variables you
+ * want for that. But with a unit test program, since executing the program is
+ * a side effect rather than an explicit target, specifically building (e.g.)
+ * PROJECT_lllogin_TEST_lllogin only builds the executable without running it.
+ *
+ * To set an environment variable for a particular test program, declare a
+ * static instance of SetEnv in its .cpp file. SetEnv's constructor takes
+ * pairs of strings, e.g.
+ *
+ * @code
+ * static SetEnv sLOGGING("LOGTEST", "INFO");
+ * @endcode
+ *
+ * Declaring a static instance of SetEnv is important because that ensures
+ * that the environment variables are set before main() is entered, since it
+ * is main() that examines LOGTEST and LOGFAIL.
+ */
+struct SetEnv
+{
+ // degenerate constructor, terminate recursion
+ SetEnv() {}
+
+ /**
+ * SetEnv() accepts an arbitrary number of pairs of strings: variable
+ * name, value, variable name, value ... Entering the constructor sets
+ * those variables in the process environment using Posix setenv(),
+ * overriding any previous value. If static SetEnv declarations in
+ * different translation units specify overlapping sets of variable names,
+ * it is indeterminate which instance will "win."
+ */
+ template <typename VAR, typename VAL, typename... ARGS>
+ SetEnv(VAR&& var, VAL&& val, ARGS&&... rest):
+ // constructor forwarding handles the tail of the list
+ SetEnv(std::forward<ARGS>(rest)...)
+ {
+ // set just the first (variable, value) pair
+ // 1 means override previous value if any
+ setenv(std::forward<VAR>(var), std::forward<VAL>(val), 1);
+ }
+};
+
+#endif /* ! defined(LL_SETENV_H) */
diff --git a/indra/test/sync.h b/indra/test/sync.h
new file mode 100644
index 0000000000..ca8b7262d6
--- /dev/null
+++ b/indra/test/sync.h
@@ -0,0 +1,116 @@
+/**
+ * @file sync.h
+ * @author Nat Goodspeed
+ * @date 2019-03-13
+ * @brief Synchronize coroutines within a test program so we can observe side
+ * effects. Certain test programs test coroutine synchronization
+ * mechanisms. Such tests usually want to interleave coroutine
+ * executions in strictly stepwise fashion. This class supports that
+ * paradigm.
+ *
+ * $LicenseInfo:firstyear=2019&license=viewerlgpl$
+ * Copyright (c) 2019, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if ! defined(LL_SYNC_H)
+#define LL_SYNC_H
+
+#include "llcond.h"
+#include "lltut.h"
+#include "stringize.h"
+#include "llerror.h"
+#include "llcoros.h"
+
+/**
+ * Instantiate Sync in any test in which we need to suspend one coroutine
+ * until we're sure that another has had a chance to run. Simply calling
+ * llcoro::suspend() isn't necessarily enough; that provides a chance for the
+ * other to run, but doesn't guarantee that it has. If each coroutine is
+ * consistent about calling Sync::bump() every time it wakes from any
+ * suspension, Sync::yield() and yield_until() should at least ensure that
+ * somebody else has had a chance to run.
+ */
+class Sync
+{
+ LLScalarCond<int> mCond{0};
+ F32Milliseconds mTimeout;
+
+public:
+ Sync(F32Milliseconds timeout=F32Milliseconds(10000.0f)):
+ 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.
+ */
+ void bump(int n=1)
+ {
+ // 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
+ void yield(int n=1)
+ {
+ return yield_until(STRINGIZE("Sync::yield_for(" << n << ") timed out after "
+ << int(mTimeout.value()) << "ms"),
+ mCond.get() + n);
+ }
+
+ /// suspend until "somebody else" has bumped mCond to a specific value
+ void yield_until(int until)
+ {
+ return yield_until(STRINGIZE("Sync::yield_until(" << until << ") timed out after "
+ << int(mTimeout.value()) << "ms"),
+ until);
+ }
+
+private:
+ void yield_until(const std::string& desc, int until)
+ {
+ std::string name(llcoro::logname());
+ LL_DEBUGS() << name << " yield_until(" << until << ") suspending" << LL_ENDL;
+ tut::ensure(name + ' ' + desc, mCond.wait_for_equal(mTimeout, until));
+ // each time we wake up, bump mCond
+ bump();
+ }
+};
+
+#endif /* ! defined(LL_SYNC_H) */
diff --git a/indra/test/test.cpp b/indra/test/test.cpp
index b14c2eb255..87c4a8d8a3 100644
--- a/indra/test/test.cpp
+++ b/indra/test/test.cpp
@@ -37,6 +37,7 @@
#include "linden_common.h"
#include "llerrorcontrol.h"
#include "lltut.h"
+#include "chained_callback.h"
#include "stringize.h"
#include "namedtempfile.h"
#include "lltrace.h"
@@ -71,7 +72,6 @@
#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>
#include <boost/foreach.hpp>
-#include <boost/lambda/lambda.hpp>
#include <fstream>
@@ -172,8 +172,10 @@ private:
LLError::RecorderPtr mRecorder;
};
-class LLTestCallback : public tut::callback
+class LLTestCallback : public chained_callback
{
+ typedef chained_callback super;
+
public:
LLTestCallback(bool verbose_mode, std::ostream *stream,
boost::shared_ptr<LLReplayLog> replayer) :
@@ -184,7 +186,7 @@ public:
mSkippedTests(0),
// By default, capture a shared_ptr to std::cout, with a no-op "deleter"
// so that destroying the shared_ptr makes no attempt to delete std::cout.
- mStream(boost::shared_ptr<std::ostream>(&std::cout, boost::lambda::_1)),
+ mStream(boost::shared_ptr<std::ostream>(&std::cout, [](std::ostream*){})),
mReplayer(replayer)
{
if (stream)
@@ -205,22 +207,25 @@ public:
~LLTestCallback()
{
- }
+ }
virtual void run_started()
{
//std::cout << "run_started" << std::endl;
LL_INFOS("TestRunner")<<"Test Started"<< LL_ENDL;
+ super::run_started();
}
virtual void group_started(const std::string& name) {
LL_INFOS("TestRunner")<<"Unit test group_started name=" << name << LL_ENDL;
*mStream << "Unit test group_started name=" << name << std::endl;
+ super::group_started(name);
}
virtual void group_completed(const std::string& name) {
LL_INFOS("TestRunner")<<"Unit test group_completed name=" << name << LL_ENDL;
*mStream << "Unit test group_completed name=" << name << std::endl;
+ super::group_completed(name);
}
virtual void test_completed(const tut::test_result& tr)
@@ -282,6 +287,7 @@ public:
*mStream << std::endl;
}
LL_INFOS("TestRunner")<<out.str()<<LL_ENDL;
+ super::test_completed(tr);
}
virtual int getFailedTests() const { return mFailedTests; }
@@ -309,6 +315,7 @@ public:
*mStream << "Please report or fix the problem." << std::endl;
*mStream << "*********************************" << std::endl;
}
+ super::run_completed();
}
protected:
@@ -474,9 +481,8 @@ void stream_usage(std::ostream& s, const char* app)
<< "LOGTEST=level : for all tests, emit log messages at level 'level'\n"
<< "LOGFAIL=level : only for failed tests, emit log messages at level 'level'\n"
<< "where 'level' is one of ALL, DEBUG, INFO, WARN, ERROR, NONE.\n"
- << "--debug is like LOGTEST=DEBUG, but --debug overrides LOGTEST.\n"
- << "Setting LOGFAIL overrides both LOGTEST and --debug: the only log\n"
- << "messages you will see will be for failed tests.\n\n";
+ << "--debug is like LOGTEST=DEBUG, but --debug overrides LOGTEST,\n"
+ << "while LOGTEST overrides LOGFAIL.\n\n";
s << "Examples:" << std::endl;
s << " " << app << " --verbose" << std::endl;
@@ -520,35 +526,8 @@ int main(int argc, char **argv)
#ifndef LL_WINDOWS
::testing::InitGoogleMock(&argc, argv);
#endif
- // LOGTEST overrides default, but can be overridden by --debug or LOGFAIL.
- const char* LOGTEST = getenv("LOGTEST");
- if (LOGTEST)
- {
- LLError::initForApplication(".", ".", true /* log to stderr */);
- LLError::setDefaultLevel(LLError::decodeLevel(LOGTEST));
- }
- else
- {
- LLError::initForApplication(".", ".", false /* do not log to stderr */);
- LLError::setDefaultLevel(LLError::LEVEL_DEBUG);
- }
- LLError::setFatalFunction(wouldHaveCrashed);
- std::string test_app_name(argv[0]);
- std::string test_log = test_app_name + ".log";
- LLFile::remove(test_log);
- LLError::logToFile(test_log);
-
-#ifdef CTYPE_WORKAROUND
- ctype_workaround();
-#endif
ll_init_apr();
-
- if (!sMasterThreadRecorder)
- {
- sMasterThreadRecorder = new LLTrace::ThreadRecorder();
- LLTrace::set_master_thread_recorder(sMasterThreadRecorder);
- }
apr_getopt_t* os = NULL;
if(APR_SUCCESS != apr_getopt_init(&os, gAPRPoolp, argc, argv))
{
@@ -562,7 +541,10 @@ int main(int argc, char **argv)
std::string test_group;
std::string suite_name;
- // values use for options parsing
+ // LOGTEST overrides default, but can be overridden by --debug.
+ const char* LOGTEST = getenv("LOGTEST");
+
+ // values used for options parsing
apr_status_t apr_err;
const char* opt_arg = NULL;
int opt_id = 0;
@@ -611,7 +593,7 @@ int main(int argc, char **argv)
wait_at_exit = true;
break;
case 'd':
- LLError::setDefaultLevel(LLError::LEVEL_DEBUG);
+ LOGTEST = "DEBUG";
break;
case 'x':
suite_name.assign(opt_arg);
@@ -623,22 +605,45 @@ int main(int argc, char **argv)
}
}
- // run the tests
-
+ // set up logging
const char* LOGFAIL = getenv("LOGFAIL");
- boost::shared_ptr<LLReplayLog> replayer;
- // As described in stream_usage(), LOGFAIL overrides both --debug and
- // LOGTEST.
- if (LOGFAIL)
+ boost::shared_ptr<LLReplayLog> replayer{boost::make_shared<LLReplayLog>()};
+
+ // Testing environment variables for both 'set' and 'not empty' allows a
+ // user to suppress a pre-existing environment variable by forcing empty.
+ if (LOGTEST && *LOGTEST)
{
- LLError::ELevel level = LLError::decodeLevel(LOGFAIL);
- replayer.reset(new LLReplayLogReal(level, gAPRPoolp));
+ LLError::initForApplication(".", ".", true /* log to stderr */);
+ LLError::setDefaultLevel(LLError::decodeLevel(LOGTEST));
}
else
{
- replayer.reset(new LLReplayLog());
+ LLError::initForApplication(".", ".", false /* do not log to stderr */);
+ LLError::setDefaultLevel(LLError::LEVEL_DEBUG);
+ if (LOGFAIL && *LOGFAIL)
+ {
+ LLError::ELevel level = LLError::decodeLevel(LOGFAIL);
+ replayer.reset(new LLReplayLogReal(level, gAPRPoolp));
+ }
+ }
+ LLError::setFatalFunction(wouldHaveCrashed);
+ std::string test_app_name(argv[0]);
+ std::string test_log = test_app_name + ".log";
+ LLFile::remove(test_log);
+ LLError::logToFile(test_log);
+
+#ifdef CTYPE_WORKAROUND
+ ctype_workaround();
+#endif
+
+ if (!sMasterThreadRecorder)
+ {
+ sMasterThreadRecorder = new LLTrace::ThreadRecorder();
+ LLTrace::set_master_thread_recorder(sMasterThreadRecorder);
}
+ // run the tests
+
LLTestCallback* mycallback;
if (getenv("TEAMCITY_PROJECT_NAME"))
{
@@ -649,7 +654,8 @@ int main(int argc, char **argv)
mycallback = new LLTestCallback(verbose_mode, output.get(), replayer);
}
- tut::runner.get().set_callback(mycallback);
+ // a chained_callback subclass must be linked with previous
+ mycallback->link();
if(test_group.empty())
{
diff --git a/indra/tools/vstool/VSTool.exe b/indra/tools/vstool/VSTool.exe
index 854290b90a..751540413a 100755
--- a/indra/tools/vstool/VSTool.exe
+++ b/indra/tools/vstool/VSTool.exe
Binary files differ
diff --git a/indra/tools/vstool/main.cs b/indra/tools/vstool/main.cs
index ef2e582b90..1d6b2f14d1 100755
--- a/indra/tools/vstool/main.cs
+++ b/indra/tools/vstool/main.cs
@@ -556,7 +556,7 @@ namespace VSTool
break;
case "12.00":
- version = "VC120";
+ version = "VC150";
break;
default:
@@ -603,6 +603,10 @@ namespace VSTool
progid = "VisualStudio.DTE.12.0";
break;
+ case "VC150":
+ progid = "VisualStudio.DTE.15.0";
+ break;
+
default:
throw new ApplicationException("Can't handle VS version: " + version);
}
diff --git a/indra/viewer_components/login/CMakeLists.txt b/indra/viewer_components/login/CMakeLists.txt
index 3bedeb7292..23518b791c 100644
--- a/indra/viewer_components/login/CMakeLists.txt
+++ b/indra/viewer_components/login/CMakeLists.txt
@@ -50,7 +50,7 @@ target_link_libraries(lllogin
${LLMATH_LIBRARIES}
${LLXML_LIBRARIES}
${BOOST_THREAD_LIBRARY}
- ${BOOST_COROUTINE_LIBRARY}
+ ${BOOST_FIBER_LIBRARY}
${BOOST_CONTEXT_LIBRARY}
${BOOST_SYSTEM_LIBRARY}
)
@@ -62,7 +62,7 @@ if(LL_TESTS)
set_source_files_properties(
lllogin.cpp
PROPERTIES
- LL_TEST_ADDITIONAL_LIBRARIES "${LLMESSAGE_LIBRARIES};${LLCOREHTTP_LIBRARIES};${LLCOMMON_LIBRARIES};${BOOST_COROUTINE_LIBRARY};${BOOST_CONTEXT_LIBRARY};${BOOST_THREAD_LIBRARY};${BOOST_SYSTEM_LIBRARY}"
+ LL_TEST_ADDITIONAL_LIBRARIES "${LLMESSAGE_LIBRARIES};${LLCOREHTTP_LIBRARIES};${LLCOMMON_LIBRARIES};${BOOST_FIBER_LIBRARY};${BOOST_CONTEXT_LIBRARY};${BOOST_THREAD_LIBRARY};${BOOST_SYSTEM_LIBRARY}"
)
LL_ADD_PROJECT_UNIT_TESTS(lllogin "${lllogin_TEST_SOURCE_FILES}")
diff --git a/indra/viewer_components/login/lllogin.cpp b/indra/viewer_components/login/lllogin.cpp
index 9193d32b49..d485203fa1 100644
--- a/indra/viewer_components/login/lllogin.cpp
+++ b/indra/viewer_components/login/lllogin.cpp
@@ -23,6 +23,7 @@
* $/LicenseInfo$
*/
+#include "llwin32headers.h"
#include "linden_common.h"
#include "llsd.h"
#include "llsdutil.h"
@@ -147,167 +148,170 @@ void LLLogin::Impl::loginCoro(std::string uri, LLSD login_params)
}
try
{
- LL_DEBUGS("LLLogin") << "Entering coroutine " << LLCoros::instance().getName()
- << " with uri '" << uri << "', parameters " << printable_params << LL_ENDL;
+ LL_DEBUGS("LLLogin") << "Entering coroutine " << LLCoros::getName()
+ << " with uri '" << uri << "', parameters " << printable_params << LL_ENDL;
- LLEventPump& xmlrpcPump(LLEventPumps::instance().obtain("LLXMLRPCTransaction"));
- // EXT-4193: use a DIFFERENT reply pump than for the SRV request. We used
- // to share them -- but the EXT-3934 fix made it possible for an abandoned
- // SRV response to arrive just as we were expecting the XMLRPC response.
- LLEventStream loginReplyPump("loginreply", true);
+ LLEventPump& xmlrpcPump(LLEventPumps::instance().obtain("LLXMLRPCTransaction"));
+ // EXT-4193: use a DIFFERENT reply pump than for the SRV request. We used
+ // to share them -- but the EXT-3934 fix made it possible for an abandoned
+ // SRV response to arrive just as we were expecting the XMLRPC response.
+ LLEventStream loginReplyPump("loginreply", true);
- LLSD::Integer attempts = 0;
+ LLSD::Integer attempts = 0;
- LLSD request(login_params);
- request["reply"] = loginReplyPump.getName();
- request["uri"] = uri;
- std::string status;
+ LLSD request(login_params);
+ request["reply"] = loginReplyPump.getName();
+ request["uri"] = uri;
+ std::string status;
- // Loop back to here if login attempt redirects to a different
- // request["uri"]
- for (;;)
- {
- ++attempts;
- LLSD progress_data;
- progress_data["attempt"] = attempts;
- progress_data["request"] = request;
- if (progress_data["request"].has("params")
- && progress_data["request"]["params"].has("passwd"))
- {
- progress_data["request"]["params"]["passwd"] = "*******";
- }
- sendProgressEvent("offline", "authenticating", progress_data);
-
- // We expect zero or more "Downloading" status events, followed by
- // exactly one event with some other status. Use postAndSuspend() the
- // first time, because -- at least in unit-test land -- it's
- // possible for the reply to arrive before the post() call
- // returns. Subsequent responses, of course, must be awaited
- // without posting again.
- for (mAuthResponse = validateResponse(loginReplyPump.getName(),
- llcoro::postAndSuspend(request, xmlrpcPump, loginReplyPump, "reply"));
- mAuthResponse["status"].asString() == "Downloading";
- mAuthResponse = validateResponse(loginReplyPump.getName(),
- llcoro::suspendUntilEventOn(loginReplyPump)))
+ // Loop back to here if login attempt redirects to a different
+ // request["uri"]
+ for (;;)
{
- // Still Downloading -- send progress update.
- sendProgressEvent("offline", "downloading");
- }
+ ++attempts;
+ LLSD progress_data;
+ progress_data["attempt"] = attempts;
+ progress_data["request"] = request;
+ if (progress_data["request"].has("params")
+ && progress_data["request"]["params"].has("passwd"))
+ {
+ progress_data["request"]["params"]["passwd"] = "*******";
+ }
+ sendProgressEvent("offline", "authenticating", progress_data);
+
+ // We expect zero or more "Downloading" status events, followed by
+ // exactly one event with some other status. Use postAndSuspend() the
+ // first time, because -- at least in unit-test land -- it's
+ // possible for the reply to arrive before the post() call
+ // returns. Subsequent responses, of course, must be awaited
+ // without posting again.
+ for (mAuthResponse = validateResponse(loginReplyPump.getName(),
+ llcoro::postAndSuspend(request, xmlrpcPump, loginReplyPump, "reply"));
+ mAuthResponse["status"].asString() == "Downloading";
+ mAuthResponse = validateResponse(loginReplyPump.getName(),
+ llcoro::suspendUntilEventOn(loginReplyPump)))
+ {
+ // Still Downloading -- send progress update.
+ sendProgressEvent("offline", "downloading");
+ }
- LL_DEBUGS("LLLogin") << "Auth Response: " << mAuthResponse << LL_ENDL;
- status = mAuthResponse["status"].asString();
+ LL_DEBUGS("LLLogin") << "Auth Response: " << mAuthResponse << LL_ENDL;
+ status = mAuthResponse["status"].asString();
- // Okay, we've received our final status event for this
- // request. Unless we got a redirect response, break the retry
- // loop for the current rewrittenURIs entry.
- if (!(status == "Complete" &&
- mAuthResponse["responses"]["login"].asString() == "indeterminate"))
- {
- break;
- }
+ // Okay, we've received our final status event for this
+ // request. Unless we got a redirect response, break the retry
+ // loop for the current rewrittenURIs entry.
+ if (!(status == "Complete" &&
+ mAuthResponse["responses"]["login"].asString() == "indeterminate"))
+ {
+ break;
+ }
- sendProgressEvent("offline", "indeterminate", mAuthResponse["responses"]);
+ sendProgressEvent("offline", "indeterminate", mAuthResponse["responses"]);
- // Here the login service at the current URI is redirecting us
- // to some other URI ("indeterminate" -- why not "redirect"?).
- // The response should contain another uri to try, with its
- // own auth method.
- request["uri"] = mAuthResponse["responses"]["next_url"].asString();
- request["method"] = mAuthResponse["responses"]["next_method"].asString();
- } // loop back to try the redirected URI
+ // Here the login service at the current URI is redirecting us
+ // to some other URI ("indeterminate" -- why not "redirect"?).
+ // The response should contain another uri to try, with its
+ // own auth method.
+ request["uri"] = mAuthResponse["responses"]["next_url"].asString();
+ request["method"] = mAuthResponse["responses"]["next_method"].asString();
+ } // loop back to try the redirected URI
- // Here we're done with redirects.
- if (status == "Complete")
- {
- // StatusComplete does not imply auth success. Check the
- // actual outcome of the request. We've already handled the
- // "indeterminate" case in the loop above.
- if (mAuthResponse["responses"]["login"].asString() == "true")
- {
- sendProgressEvent("online", "connect", mAuthResponse["responses"]);
- }
- else
+ // Here we're done with redirects.
+ if (status == "Complete")
{
- // Synchronize here with the updater. We synchronize here rather
- // than in the fail.login handler, which actually examines the
- // response from login.cgi, because here we are definitely in a
- // coroutine and can definitely use suspendUntilBlah(). Whoever's
- // listening for fail.login might not be.
-
- // If the reason for login failure is that we must install a
- // required update, we definitely want to pass control to the
- // updater to manage that for us. We'll handle any other login
- // failure ourselves, as usual. We figure that no matter where you
- // are in the world, or what kind of network you're on, we can
- // reasonably expect the Viewer Version Manager to respond more or
- // less as quickly as login.cgi. This synchronization is only
- // intended to smooth out minor races between the two services.
- // But what if the updater crashes? Use a timeout so that
- // eventually we'll tire of waiting for it and carry on as usual.
- // Given the above, it can be a fairly short timeout, at least
- // from a human point of view.
-
- // Since sSyncPoint is an LLEventMailDrop, we DEFINITELY want to
- // consume the posted event.
- LLCoros::OverrideConsuming oc(true);
- // Timeout should produce the isUndefined() object passed here.
- LL_DEBUGS("LLLogin") << "Login failure, waiting for sync from updater" << LL_ENDL;
- LLSD updater = llcoro::suspendUntilEventOnWithTimeout(sSyncPoint, 10, LLSD());
- if (updater.isUndefined())
+ // StatusComplete does not imply auth success. Check the
+ // actual outcome of the request. We've already handled the
+ // "indeterminate" case in the loop above.
+ if (mAuthResponse["responses"]["login"].asString() == "true")
{
- LL_WARNS("LLLogin") << "Failed to hear from updater, proceeding with fail.login"
- << LL_ENDL;
+ sendProgressEvent("online", "connect", mAuthResponse["responses"]);
}
else
{
- LL_DEBUGS("LLLogin") << "Got responses from updater and login.cgi" << LL_ENDL;
+ // Synchronize here with the updater. We synchronize here rather
+ // than in the fail.login handler, which actually examines the
+ // response from login.cgi, because here we are definitely in a
+ // coroutine and can definitely use suspendUntilBlah(). Whoever's
+ // listening for fail.login might not be.
+
+ // If the reason for login failure is that we must install a
+ // required update, we definitely want to pass control to the
+ // updater to manage that for us. We'll handle any other login
+ // failure ourselves, as usual. We figure that no matter where you
+ // are in the world, or what kind of network you're on, we can
+ // reasonably expect the Viewer Version Manager to respond more or
+ // less as quickly as login.cgi. This synchronization is only
+ // intended to smooth out minor races between the two services.
+ // But what if the updater crashes? Use a timeout so that
+ // eventually we'll tire of waiting for it and carry on as usual.
+ // Given the above, it can be a fairly short timeout, at least
+ // from a human point of view.
+
+ // Since sSyncPoint is an LLEventMailDrop, we DEFINITELY want to
+ // consume the posted event.
+ LLCoros::OverrideConsuming oc(true);
+ // Timeout should produce the isUndefined() object passed here.
+ LL_DEBUGS("LLLogin") << "Login failure, waiting for sync from updater" << LL_ENDL;
+ LLSD updater = llcoro::suspendUntilEventOnWithTimeout(sSyncPoint, 10, LLSD());
+ if (updater.isUndefined())
+ {
+ LL_WARNS("LLLogin") << "Failed to hear from updater, proceeding with fail.login"
+ << LL_ENDL;
+ }
+ else
+ {
+ LL_DEBUGS("LLLogin") << "Got responses from updater and login.cgi" << LL_ENDL;
+ }
+ // Let the fail.login handler deal with empty updater response.
+ LLSD responses(mAuthResponse["responses"]);
+ responses["updater"] = updater;
+ sendProgressEvent("offline", "fail.login", responses);
}
- // Let the fail.login handler deal with empty updater response.
- LLSD responses(mAuthResponse["responses"]);
- responses["updater"] = updater;
- sendProgressEvent("offline", "fail.login", responses);
+ return; // Done!
}
- return; // Done!
- }
-// /* Sometimes we end with "Started" here. Slightly slow server?
-// * Seems to be ok to just skip it. Otherwise we'd error out and crash in the if below.
-// */
-// if( status == "Started")
-// {
-// LL_DEBUGS("LLLogin") << mAuthResponse << LL_ENDL;
-// continue;
-// }
-
- // If we don't recognize status at all, trouble
- if (! (status == "CURLError"
- || status == "XMLRPCError"
- || status == "OtherError"))
- {
- LL_ERRS("LLLogin") << "Unexpected status from " << xmlrpcPump.getName() << " pump: "
- << mAuthResponse << LL_ENDL;
- return;
- }
+/*==========================================================================*|
+ // Sometimes we end with "Started" here. Slightly slow server? Seems
+ // to be ok to just skip it. Otherwise we'd error out and crash in the
+ // if below.
+ if( status == "Started")
+ {
+ LL_DEBUGS("LLLogin") << mAuthResponse << LL_ENDL;
+ continue;
+ }
+|*==========================================================================*/
- // Here status IS one of the errors tested above.
- // Tell caller this didn't work out so well.
-
- // *NOTE: The response from LLXMLRPCListener's Poller::poll method returns an
- // llsd with no "responses" node. To make the output from an incomplete login symmetrical
- // to success, add a data/message and data/reason fields.
- LLSD error_response(LLSDMap
- ("reason", mAuthResponse["status"])
- ("errorcode", mAuthResponse["errorcode"])
- ("message", mAuthResponse["error"]));
- if(mAuthResponse.has("certificate"))
- {
- error_response["certificate"] = mAuthResponse["certificate"];
- }
- sendProgressEvent("offline", "fail.login", error_response);
+ // If we don't recognize status at all, trouble
+ if (! (status == "CURLError"
+ || status == "XMLRPCError"
+ || status == "OtherError"))
+ {
+ LL_ERRS("LLLogin") << "Unexpected status from " << xmlrpcPump.getName() << " pump: "
+ << mAuthResponse << LL_ENDL;
+ return;
+ }
+
+ // Here status IS one of the errors tested above.
+ // Tell caller this didn't work out so well.
+
+ // *NOTE: The response from LLXMLRPCListener's Poller::poll method returns an
+ // llsd with no "responses" node. To make the output from an incomplete login symmetrical
+ // to success, add a data/message and data/reason fields.
+ LLSD error_response(LLSDMap
+ ("reason", mAuthResponse["status"])
+ ("errorcode", mAuthResponse["errorcode"])
+ ("message", mAuthResponse["error"]));
+ if(mAuthResponse.has("certificate"))
+ {
+ error_response["certificate"] = mAuthResponse["certificate"];
+ }
+ sendProgressEvent("offline", "fail.login", error_response);
}
catch (...) {
- CRASH_ON_UNHANDLED_EXCEPTION(STRINGIZE("coroutine " << LLCoros::instance().getName()
- << "('" << uri << "', " << printable_params << ")"));
+ LOG_UNHANDLED_EXCEPTION(STRINGIZE("coroutine " << LLCoros::getName()
+ << "('" << uri << "', " << printable_params << ")"));
+ throw;
}
}
diff --git a/indra/viewer_components/login/tests/lllogin_test.cpp b/indra/viewer_components/login/tests/lllogin_test.cpp
index e96c495446..f9267533ff 100644
--- a/indra/viewer_components/login/tests/lllogin_test.cpp
+++ b/indra/viewer_components/login/tests/lllogin_test.cpp
@@ -36,14 +36,16 @@
#include "../lllogin.h"
// STL headers
// std headers
+#include <chrono>
#include <iostream>
// external library headers
// other Linden headers
-#include "llsd.h"
-#include "../../../test/lltut.h"
-//#define DEBUG_ON
#include "../../../test/debug.h"
+#include "../../../test/lltestapp.h"
+#include "../../../test/lltut.h"
#include "llevents.h"
+#include "lleventcoro.h"
+#include "llsd.h"
#include "stringize.h"
#if LL_WINDOWS
@@ -66,29 +68,68 @@
// This is a listener to receive results from lllogin.
class LoginListener: public LLEventTrackable
{
- std::string mName;
- LLSD mLastEvent;
+ std::string mName;
+ LLSD mLastEvent;
+ size_t mCalls{ 0 };
Debug mDebug;
public:
- LoginListener(const std::string& name) :
- mName(name),
+ LoginListener(const std::string& name) :
+ mName(name),
mDebug(stringize(*this))
- {}
+ {}
- bool call(const LLSD& event)
- {
- mDebug(STRINGIZE("LoginListener called!: " << event));
-
- mLastEvent = event;
- return false;
- }
+ bool call(const LLSD& event)
+ {
+ mDebug(STRINGIZE("LoginListener called!: " << event));
+
+ mLastEvent = event;
+ ++mCalls;
+ return false;
+ }
LLBoundListener listenTo(LLEventPump& pump)
{
return pump.listen(mName, boost::bind(&LoginListener::call, this, _1));
- }
+ }
+
+ LLSD lastEvent() const { return mLastEvent; }
- LLSD lastEvent() const { return mLastEvent; }
+ size_t getCalls() const { return mCalls; }
+
+ // wait for arbitrary predicate to become true
+ template <typename PRED>
+ LLSD waitFor(const std::string& desc, PRED&& pred, double seconds=2.0) const
+ {
+ // remember when we started waiting
+ auto start = std::chrono::system_clock::now();
+ // Break loop when the passed predicate returns true
+ while (! std::forward<PRED>(pred)())
+ {
+ // but if we've been spinning here too long, test failed
+ // how long have we been here, anyway?
+ auto now = std::chrono::system_clock::now();
+ // the default ratio for duration is seconds
+ std::chrono::duration<double> elapsed = (now - start);
+ if (elapsed.count() > seconds)
+ {
+ tut::fail(STRINGIZE("LoginListener::waitFor() took more than "
+ << seconds << " seconds waiting for " << desc));
+ }
+ // haven't yet received the new call, nor have we timed out --
+ // just wait
+ llcoro::suspend();
+ }
+ // oh good, we've gotten at least one new call! Return its event.
+ return lastEvent();
+ }
+
+ // wait for any call() calls beyond prevcalls
+ LLSD waitFor(size_t prevcalls, double seconds) const
+ {
+ return waitFor(STRINGIZE("more than " << prevcalls << " calls"),
+ [this, prevcalls]()->bool{ return getCalls() > prevcalls; },
+ seconds);
+ }
friend std::ostream& operator<<(std::ostream& out, const LoginListener& listener)
{
@@ -163,11 +204,16 @@ namespace tut
{
struct llviewerlogin_data
{
- llviewerlogin_data() :
+ llviewerlogin_data() :
pumps(LLEventPumps::instance())
- {}
- LLEventPumps& pumps;
- };
+ {}
+ ~llviewerlogin_data()
+ {
+ pumps.clear();
+ }
+ LLEventPumps& pumps;
+ LLTestApp testApp;
+ };
typedef test_group<llviewerlogin_data> llviewerlogin_group;
typedef llviewerlogin_group::object llviewerlogin_object;
@@ -186,12 +232,12 @@ namespace tut
// Have dummy XMLRPC respond immediately.
LLXMLRPCListener dummyXMLRPC("dummy_xmlrpc", respond_immediately);
- dummyXMLRPC.listenTo(xmlrpcPump);
+ LLTempBoundListener conn1 = dummyXMLRPC.listenTo(xmlrpcPump);
LLLogin login;
LoginListener listener("test_ear");
- listener.listenTo(login.getEventPump());
+ LLTempBoundListener conn2 = listener.listenTo(login.getEventPump());
LLSD credentials;
credentials["first"] = "foo";
@@ -199,8 +245,9 @@ namespace tut
credentials["passwd"] = "secret";
login.connect("login.bar.com", credentials);
-
- ensure_equals("Online state", listener.lastEvent()["state"].asString(), "online");
+ listener.waitFor(
+ "online state",
+ [&listener]()->bool{ return listener.lastEvent()["state"].asString() == "online"; });
}
template<> template<>
@@ -214,11 +261,11 @@ namespace tut
LLEventStream xmlrpcPump("LLXMLRPCTransaction"); // Dummy XMLRPC pump
LLXMLRPCListener dummyXMLRPC("dummy_xmlrpc");
- dummyXMLRPC.listenTo(xmlrpcPump);
+ LLTempBoundListener conn1 = dummyXMLRPC.listenTo(xmlrpcPump);
LLLogin login;
LoginListener listener("test_ear");
- listener.listenTo(login.getEventPump());
+ LLTempBoundListener conn2 = listener.listenTo(login.getEventPump());
LLSD credentials;
credentials["first"] = "who";
@@ -226,9 +273,12 @@ namespace tut
credentials["passwd"] = "badpasswd";
login.connect("login.bar.com", credentials);
+ llcoro::suspend();
ensure_equals("Auth state", listener.lastEvent()["change"].asString(), "authenticating");
+ auto prev = listener.getCalls();
+
// Send the failed auth request reponse
LLSD data;
data["status"] = "Complete";
@@ -238,6 +288,10 @@ namespace tut
data["responses"]["login"] = "false";
dummyXMLRPC.setResponse(data);
dummyXMLRPC.sendReply();
+ // we happen to know LLLogin uses a 10-second timeout to try to sync
+ // with SLVersionChecker -- allow at least that much time before
+ // giving up
+ listener.waitFor(prev, 11.0);
ensure_equals("Failed to offline", listener.lastEvent()["state"].asString(), "offline");
}
@@ -253,11 +307,11 @@ namespace tut
LLEventStream xmlrpcPump("LLXMLRPCTransaction"); // Dummy XMLRPC pump
LLXMLRPCListener dummyXMLRPC("dummy_xmlrpc");
- dummyXMLRPC.listenTo(xmlrpcPump);
+ LLTempBoundListener conn1 = dummyXMLRPC.listenTo(xmlrpcPump);
LLLogin login;
LoginListener listener("test_ear");
- listener.listenTo(login.getEventPump());
+ LLTempBoundListener conn2 = listener.listenTo(login.getEventPump());
LLSD credentials;
credentials["first"] = "these";
@@ -265,9 +319,12 @@ namespace tut
credentials["passwd"] = "matter";
login.connect("login.bar.com", credentials);
+ llcoro::suspend();
ensure_equals("Auth state", listener.lastEvent()["change"].asString(), "authenticating");
+ auto prev = listener.getCalls();
+
// Send the failed auth request reponse
LLSD data;
data["status"] = "OtherError";
@@ -276,40 +333,11 @@ namespace tut
data["transfer_rate"] = 0;
dummyXMLRPC.setResponse(data);
dummyXMLRPC.sendReply();
+ // we happen to know LLLogin uses a 10-second timeout to try to sync
+ // with SLVersionChecker -- allow at least that much time before
+ // giving up
+ listener.waitFor(prev, 11.0);
ensure_equals("Failed to offline", listener.lastEvent()["state"].asString(), "offline");
}
-
- template<> template<>
- void llviewerlogin_object::test<4>()
- {
- DEBUG;
- // Test SRV request timeout.
- set_test_name("LLLogin SRV timeout testing");
-
- // Testing normal login procedure.
-
- LLLogin login;
- LoginListener listener("test_ear");
- listener.listenTo(login.getEventPump());
-
- LLSD credentials;
- credentials["first"] = "these";
- credentials["last"] = "don't";
- credentials["passwd"] = "matter";
- credentials["cfg_srv_timeout"] = 0.0f;
-
- login.connect("login.bar.com", credentials);
-
- // Get the mainloop eventpump, which needs a pinging in order to drive the
- // SRV timeout.
- LLEventPump& mainloop(LLEventPumps::instance().obtain("mainloop"));
- LLSD frame_event;
- mainloop.post(frame_event);
-
- ensure_equals("Auth state", listener.lastEvent()["change"].asString(), "authenticating");
- ensure_equals("Attempt", listener.lastEvent()["data"]["attempt"].asInteger(), 1);
- ensure_equals("URI", listener.lastEvent()["data"]["request"]["uri"].asString(), "login.bar.com");
-
- }
}
diff --git a/indra/win_crash_logger/CMakeLists.txt b/indra/win_crash_logger/CMakeLists.txt
index 4fba26ab2f..86aa655f03 100644
--- a/indra/win_crash_logger/CMakeLists.txt
+++ b/indra/win_crash_logger/CMakeLists.txt
@@ -68,11 +68,11 @@ list(APPEND
${win_crash_logger_RESOURCE_FILES}
)
-find_library(DXGUID_LIBRARY dxguid ${DIRECTX_LIBRARY_DIR})
-
add_executable(windows-crash-logger WIN32 ${win_crash_logger_SOURCE_FILES})
+
target_link_libraries(windows-crash-logger
+ ${LEGACY_STDIO_LIBS}
${BREAKPAD_EXCEPTION_HANDLER_LIBRARIES}
${LLCRASHLOGGER_LIBRARIES}
${LLWINDOW_LIBRARIES}
@@ -83,9 +83,9 @@ target_link_libraries(windows-crash-logger
${LLCOREHTTP_LIBRARIES}
${LLCOMMON_LIBRARIES}
${BOOST_CONTEXT_LIBRARY}
- ${BOOST_COROUTINE_LIBRARY}
+ ${BOOST_FIBER_LIBRARY}
${WINDOWS_LIBRARIES}
- ${DXGUID_LIBRARY}
+ dxguid
${GOOGLE_PERFTOOLS_LIBRARIES}
user32
gdi32