diff options
Diffstat (limited to 'indra')
199 files changed, 6607 insertions, 5726 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 b46f49ba34..411412c883 100644 --- a/indra/llcommon/llerror.cpp +++ b/indra/llcommon/llerror.cpp @@ -39,6 +39,9 @@ #if !LL_WINDOWS # include <syslog.h> # include <unistd.h> +# include <sys/stat.h> +#else +# include <io.h> #endif // !LL_WINDOWS #include <vector> #include "string.h" @@ -53,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) @@ -118,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 @@ -148,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; @@ -161,9 +174,10 @@ namespace { { mFile << message << "\n"; } - } - + } + private: + const std::string mName; llofstream mFile; }; @@ -171,7 +185,7 @@ namespace { class RecordToStderr : public LLError::Recorder { public: - RecordToStderr(bool timestamp) : mUseANSI(ANSI_PROBE) + RecordToStderr(bool timestamp) : mUseANSI(checkANSI()) { this->showMultiline(true); } @@ -193,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 : @@ -213,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) { @@ -229,16 +236,13 @@ 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) { -#if LL_LINUX || LL_DARWIN // Check whether it's okay to use ANSI; if stderr is // a tty then we assume yes. Can be turned off with // the LL_NO_ANSI_COLOR env var. return (0 != isatty(2)) && (NULL == getenv("LL_NO_ANSI_COLOR")); -#endif // LL_LINUX - return FALSE; // works in a cygwin shell... ;) } }; @@ -308,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 " }) { - LL_DEBUGS() << "Did not see '" << class_prefix << "' prefix on '" - << name << "'" << LL_ENDL; - return name; + if (0 == name.compare(0, prefix.length(), prefix)) + { + return name.substr(prefix.length()); + } } + // huh, that's odd, we should see one or the other prefix -- but don't + // try to log unless logging is already initialized + if (is_available()) + { + // in Python, " or ".join(vector) -- but in C++, a PITB + LL_DEBUGS() << "Did not see 'class' or 'struct' prefix on '" + << name << "'" << LL_ENDL; + } + return name; - return name.substr(class_prefix.length()); - -#else +#else // neither GCC nor Visual Studio return mangled; #endif } @@ -408,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" @@ -490,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(); }; @@ -531,9 +539,6 @@ namespace LLError mCrashFunction(NULL), mTimeFunction(NULL), mRecorders(), - mFileRecorder(), - mFixedBufferRecorder(), - mFileRecorderFileName(), mShouldLogCallCounter(0) { } @@ -656,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 @@ -685,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); @@ -996,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); + } } } @@ -1154,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) @@ -1203,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; @@ -1254,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. @@ -1274,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; @@ -1314,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; @@ -1486,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 1f8384f415..3f3edb661f 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 6a287f0cc5..fbd65cc67b 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 950599217f..86bcfe6881 100644 --- a/indra/llmessage/llproxy.cpp +++ b/indra/llmessage/llproxy.cpp @@ -115,9 +115,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 87891901ad..a1ffa9e5d5 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 7560fe9505..1434ce42ed 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 7c0124322c..c1fd09a17b 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; @@ -1165,7 +1165,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 @@ -1429,6 +1429,8 @@ bool LLAppViewer::doFrame() // canonical per-frame event mainloop.post(newFrame); + // give listeners a chance to run + llcoro::suspend(); if (!LLApp::isExiting()) { @@ -1690,24 +1692,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 @@ -2128,25 +2115,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(); @@ -2659,7 +2640,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 @@ -2880,12 +2861,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. @@ -3105,16 +3085,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; @@ -3122,12 +3098,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(); @@ -3420,12 +3392,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(); @@ -3468,7 +3440,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; @@ -3690,7 +3662,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()<< ")" @@ -3707,7 +3679,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; @@ -4667,6 +4639,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(); @@ -5279,37 +5254,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) @@ -5567,12 +5545,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 d208e135bb..156a1c5893 100644 --- a/indra/newview/llappviewerwin32.cpp +++ b/indra/newview/llappviewerwin32.cpp @@ -500,68 +500,76 @@ void LLAppViewerWin32::disableWinErrorReporting() } const S32 MAX_CONSOLE_LINES = 500; +// Only defined in newer SDKs than we currently use +#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING +#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 4 +#endif -static bool create_console() -{ - int h_con_handle; - long l_std_handle; - - CONSOLE_SCREEN_BUFFER_INFO coninfo; - FILE *fp; - - // allocate a console for this app - const bool isConsoleAllocated = AllocConsole(); - - // set the screen buffer to be big enough to let us scroll text - GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &coninfo); - coninfo.dwSize.Y = MAX_CONSOLE_LINES; - SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), coninfo.dwSize); +namespace { - // redirect unbuffered STDOUT to the console - l_std_handle = (long)GetStdHandle(STD_OUTPUT_HANDLE); - h_con_handle = _open_osfhandle(l_std_handle, _O_TEXT); - if (h_con_handle == -1) - { - LL_WARNS() << "create_console() failed to open stdout handle" << LL_ENDL; - } - else - { - fp = _fdopen( h_con_handle, "w" ); - *stdout = *fp; - setvbuf( stdout, NULL, _IONBF, 0 ); - } +void set_stream(const char* desc, FILE* fp, DWORD handle_id, const char* name, const char* mode="w"); - // redirect unbuffered STDIN to the console - l_std_handle = (long)GetStdHandle(STD_INPUT_HANDLE); - h_con_handle = _open_osfhandle(l_std_handle, _O_TEXT); - if (h_con_handle == -1) - { - LL_WARNS() << "create_console() failed to open stdin handle" << LL_ENDL; - } - else - { - fp = _fdopen( h_con_handle, "r" ); - *stdin = *fp; - setvbuf( stdin, NULL, _IONBF, 0 ); - } +bool create_console() +{ + // allocate a console for this app + const bool isConsoleAllocated = AllocConsole(); - // redirect unbuffered STDERR to the console - l_std_handle = (long)GetStdHandle(STD_ERROR_HANDLE); - h_con_handle = _open_osfhandle(l_std_handle, _O_TEXT); - if (h_con_handle == -1) - { - LL_WARNS() << "create_console() failed to open stderr handle" << LL_ENDL; - } - else - { - fp = _fdopen( h_con_handle, "w" ); - *stderr = *fp; - setvbuf( stderr, NULL, _IONBF, 0 ); - } + 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; } +void set_stream(const char* desc, FILE* fp, DWORD handle_id, const char* name, const char* mode) +{ + // 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 + LLAppViewerWin32::LLAppViewerWin32(const char* cmd_line) : mCmdLine(cmd_line), mIsConsoleAllocated(false) 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 6da7bbe263..7b87b43243 100644 --- a/indra/newview/llimprocessing.cpp +++ b/indra/newview/llimprocessing.cpp @@ -857,41 +857,41 @@ void LLIMProcessing::processNewMessage(LLUUID from_id, } else // IM_TASK_INVENTORY_OFFERED { - if (offline == IM_OFFLINE && session_id.isNull() && aux_id.notNull() && binary_bucket_size > sizeof(S8)* 5) + if (sizeof(S8) == binary_bucket_size) { - // cap received offline message - std::string str_bucket = ll_safe_string((char*)binary_bucket, binary_bucket_size); - typedef boost::tokenizer<boost::char_separator<char> > tokenizer; - boost::char_separator<char> sep("|", "", boost::keep_empty_tokens); - tokenizer tokens(str_bucket, sep); - tokenizer::iterator iter = tokens.begin(); - - info->mType = (LLAssetType::EType)(atoi((*(iter++)).c_str())); - // Note There is more elements in 'tokens' ... - - info->mObjectID = LLUUID::null; - info->mFromObject = TRUE; + info->mType = (LLAssetType::EType) binary_bucket[0]; } else { - if (sizeof(S8) != binary_bucket_size) - { - LL_WARNS("Messaging") << "Malformed inventory offer from object" << LL_ENDL; - delete info; - break; - } - info->mType = (LLAssetType::EType) binary_bucket[0]; - info->mObjectID = LLUUID::null; - info->mFromObject = TRUE; + /*RIDER*/ // The previous version of the protocol returned the wrong binary bucket... we + // still might be able to figure out the type... even though the offer is not retrievable. + + // Should be safe to remove once DRTSIM-451 fully deploys + std::string str_bucket(reinterpret_cast<char *>(binary_bucket)); + std::string str_type(str_bucket.substr(0, str_bucket.find('|'))); + + std::stringstream type_convert(str_type); + + S32 type; + type_convert >> type; + + // We could try AT_UNKNOWN which would be more accurate, but that causes an auto decline + info->mType = static_cast<LLAssetType::EType>(type); + // Don't break in the case of a bad binary bucket. Go ahead and show the + // accept/decline popup even though it will not do anything. + LL_WARNS("Messaging") << "Malformed inventory offer from object, type might be " << info->mType << LL_ENDL; } + info->mObjectID = LLUUID::null; + info->mFromObject = TRUE; } info->mIM = dialog; info->mFromID = from_id; info->mFromGroup = from_group; - info->mTransactionID = session_id; info->mFolderID = gInventory.findCategoryUUIDForType(LLFolderType::assetTypeToFolderType(info->mType)); + info->mTransactionID = session_id.notNull() ? session_id : aux_id; + info->mFromName = name; info->mDesc = message; info->mHost = sender; @@ -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()) { @@ -1569,7 +1567,7 @@ void LLIMProcessing::requestOfflineMessagesCoro(std::string url) return; } - if (gAgent.getRegion() == NULL) + if (!gAgent.getRegion()) { LL_WARNS("Messaging") << "Region null while attempting to load messages." << LL_ENDL; return; @@ -1577,8 +1575,6 @@ void LLIMProcessing::requestOfflineMessagesCoro(std::string url) LL_INFOS("Messaging") << "Processing offline messages." << LL_ENDL; - std::vector<U8> data; - S32 binary_bucket_size = 0; LLHost sender = gAgent.getRegionHost(); LLSD::array_iterator i = messages.beginArray(); @@ -1587,38 +1583,58 @@ void LLIMProcessing::requestOfflineMessagesCoro(std::string url) { const LLSD &message_data(*i); - LLVector3 position(message_data["local_x"].asReal(), message_data["local_y"].asReal(), message_data["local_z"].asReal()); - data = message_data["binary_bucket"].asBinary(); - binary_bucket_size = data.size(); // message_data["count"] always 0 - U32 parent_estate_id = message_data.has("parent_estate_id") ? message_data["parent_estate_id"].asInteger() : 1; // 1 - IMMainland + /* RIDER: Many fields in this message are using a '_' rather than the standard '-'. This + * should be changed but would require tight coordination with the simulator. + */ + LLVector3 position; + if (message_data.has("position")) + { + position.setValue(message_data["position"]); + } + else + { + position.set(message_data["local_x"].asReal(), message_data["local_y"].asReal(), message_data["local_z"].asReal()); + } - // Todo: once dirtsim-369 releases, remove one of the int/str options - BOOL from_group; - if (message_data["from_group"].isInteger()) + std::vector<U8> bin_bucket; + if (message_data.has("binary_bucket")) { - from_group = message_data["from_group"].asInteger(); + bin_bucket = message_data["binary_bucket"].asBinary(); } else { - from_group = message_data["from_group"].asString() == "Y"; + bin_bucket.push_back(0); } - LLIMProcessing::processNewMessage(message_data["from_agent_id"].asUUID(), + // Todo: once drtsim-451 releases, remove the string option
+ BOOL from_group;
+ if (message_data["from_group"].isInteger())
+ {
+ from_group = message_data["from_group"].asInteger();
+ }
+ else
+ {
+ from_group = message_data["from_group"].asString() == "Y";
+ } + + LLIMProcessing::processNewMessage( + message_data["from_agent_id"].asUUID(), from_group, message_data["to_agent_id"].asUUID(), - IM_OFFLINE, - (EInstantMessage)message_data["dialog"].asInteger(), - LLUUID::null, // session id, since there is none we can only use frienship/group invite caps - message_data["timestamp"].asInteger(), + message_data.has("offline") ? static_cast<U8>(message_data["offline"].asInteger()) : IM_OFFLINE, + static_cast<EInstantMessage>(message_data["dialog"].asInteger()), + message_data["transaction-id"].asUUID(), + static_cast<U32>(message_data["timestamp"].asInteger()), message_data["from_agent_name"].asString(), message_data["message"].asString(), - parent_estate_id, + static_cast<U32>((message_data.has("parent_estate_id")) ? message_data["parent_estate_id"].asInteger() : 1), // 1 - IMMainland message_data["region_id"].asUUID(), position, - &data[0], - binary_bucket_size, + bin_bucket.data(), + bin_bucket.size(), sender, - message_data["asset_id"].asUUID()); // not necessarily an asset + message_data["asset_id"].asUUID()); + } } 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 e44d80b7ce..3d06c95080 100644 --- a/indra/newview/llviewerjoystick.cpp +++ b/indra/newview/llviewerjoystick.cpp @@ -62,6 +62,43 @@ 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 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 + // ----------------------------------------------------------------------------- void LLViewerJoystick::updateEnabled(bool autoenable) { @@ -107,11 +144,11 @@ NDOF_HotPlugResult LLViewerJoystick::HotPlugAddCallback(NDOF_Device *dev) LLViewerJoystick* joystick(LLViewerJoystick::getInstance()); if (joystick->mDriverState == JDS_UNINITIALIZED) { - LL_INFOS() << "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; @@ -125,9 +162,9 @@ void LLViewerJoystick::HotPlugRemovalCallback(NDOF_Device *dev) LLViewerJoystick* joystick(LLViewerJoystick::getInstance()); if (joystick->mNdofDev == dev) { - LL_INFOS() << "HotPlugRemovalCallback: joystick->mNdofDev=" + 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); @@ -193,6 +230,7 @@ void LLViewerJoystick::init(bool autoenable) { if (mNdofDev) { + LL_DEBUGS("joystick") << "ndof_create() returned: " << mNdofDev << LL_ENDL; // Different joysticks will return different ranges of raw values. // Since we want to handle every device in the same uniform way, // we initialize the mNdofDev struct and we set the range @@ -211,16 +249,19 @@ void LLViewerJoystick::init(bool autoenable) // just have the absolute values instead. mNdofDev->absolute = 1; + LL_DEBUGS("joystick") << "ndof_init_first() received: " << mNdofDev << LL_ENDL; // init & use the first suitable NDOF device found on the USB chain if (ndof_init_first(mNdofDev, NULL)) { mDriverState = JDS_UNINITIALIZED; - LL_WARNS() << "ndof_init_first FAILED" << LL_ENDL; + LL_WARNS("joystick") << "ndof_init_first FAILED" << LL_ENDL; + ndof_dump_list(stderr); } else { mDriverState = JDS_INITIALIZED; } + LL_DEBUGS("joystick") << "ndof_init_first() left: " << mNdofDev << LL_ENDL; } else { @@ -258,8 +299,8 @@ void LLViewerJoystick::init(bool autoenable) { // No device connected, don't change any settings } - - LL_INFOS() << "ndof: mDriverState=" << mDriverState << "; mNdofDev=" + + LL_INFOS("joystick") << "ndof: mDriverState=" << mDriverState << "; mNdofDev=" << mNdofDev << "; libinit=" << libinit << LL_ENDL; #endif } @@ -270,7 +311,7 @@ void LLViewerJoystick::terminate() #if LIB_NDOF ndof_libcleanup(); - LL_INFOS() << "Terminated connection with NDOF device." << LL_ENDL; + LL_INFOS("joystick") << "Terminated connection with NDOF device." << LL_ENDL; mDriverState = JDS_UNINITIALIZED; #endif } @@ -1075,7 +1116,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 @@ -1099,10 +1140,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) @@ -1110,11 +1151,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 04172adde9..f7ded00318 100644 --- a/indra/newview/llviewerstats.cpp +++ b/indra/newview/llviewerstats.cpp @@ -472,7 +472,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 d8802d98a9..09a8998fbb 100644 --- a/indra/newview/llviewerwindow.cpp +++ b/indra/newview/llviewerwindow.cpp @@ -2614,7 +2614,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 2ffd462ac3..f6669c44e5 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 0daf3e761d..bae615232e 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 Binary files differindex 854290b90a..751540413a 100755 --- a/indra/tools/vstool/VSTool.exe +++ b/indra/tools/vstool/VSTool.exe 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 |