diff --git a/.gitignore b/.gitignore index bbd3247a9..f30ca2cc4 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,4 @@ /LICENSES/ /edited-files.txt qtcreator-build/ +/.pc diff --git a/indra/CMakeLists.txt b/indra/CMakeLists.txt index a900d4c28..ef4a6fe0f 100644 --- a/indra/CMakeLists.txt +++ b/indra/CMakeLists.txt @@ -46,7 +46,6 @@ endif(NOT STANDALONE) add_custom_target(prepare DEPENDS ${prepare_depends}) add_subdirectory(cmake) -add_subdirectory(${LIBS_OPEN_PREFIX}cwdebug) add_subdirectory(${LIBS_OPEN_PREFIX}llaudio) add_subdirectory(${LIBS_OPEN_PREFIX}llcharacter) add_subdirectory(${LIBS_OPEN_PREFIX}llcommon) diff --git a/indra/cmake/CURL.cmake b/indra/cmake/CURL.cmake index aa6ff57f9..10ef1b0b6 100644 --- a/indra/cmake/CURL.cmake +++ b/indra/cmake/CURL.cmake @@ -1,7 +1,7 @@ # -*- cmake -*- include(Prebuilt) -set(CURL_FIND_QUIETLY ON) +set(CURL_FIND_QUIETLY OFF) set(CURL_FIND_REQUIRED ON) if (STANDALONE) diff --git a/indra/cmake/Cwdebug.cmake b/indra/cmake/Cwdebug.cmake index 9568a625f..d469386e1 100644 --- a/indra/cmake/Cwdebug.cmake +++ b/indra/cmake/Cwdebug.cmake @@ -1 +1,18 @@ -set(CWDEBUG_LIBRARIES cwdebug) +include_directories (${CMAKE_SOURCE_DIR}/cwdebug) + +set(cwdebug_SOURCE_FILES + ${CMAKE_SOURCE_DIR}/cwdebug/debug.cc + ) + +set(cwdebug_HEADER_FILES + ${CMAKE_SOURCE_DIR}/cwdebug/cwdebug.h + ${CMAKE_SOURCE_DIR}/cwdebug/sys.h + ${CMAKE_SOURCE_DIR}/cwdebug/debug.h + ${CMAKE_SOURCE_DIR}/cwdebug/debug_ostream_operators.h + ) + +set_source_files_properties(${cwdebug_HEADER_FILES} + PROPERTIES HEADER_FILE_ONLY TRUE) + +list(APPEND cwdebug_SOURCE_FILES ${cwdebug_HEADER_FILES}) + diff --git a/indra/cwdebug/CMakeLists.txt b/indra/cwdebug/CMakeLists.txt deleted file mode 100644 index 9f423c736..000000000 --- a/indra/cwdebug/CMakeLists.txt +++ /dev/null @@ -1,39 +0,0 @@ -# -*- cmake -*- - -project(cwdebug) - -include(00-Common) -include(LLCommon) -include(LLMath) -include(LLMessage) -include(LLVFS) - -include_directories (${CMAKE_CURRENT_SOURCE_DIR}) - -set(cwdebug_SOURCE_FILES - debug.cc - ) - -set(cwdebug_HEADER_FILES - CMakeLists.txt - - cwdebug.h - sys.h - debug.h - debug_ostream_operators.h - ) - -set_source_files_properties(${cwdebug_HEADER_FILES} - PROPERTIES HEADER_FILE_ONLY TRUE) - -if(NOT WORD_SIZE EQUAL 32) - if(WINDOWS) - add_definitions(/FIXED:NO) - else(WINDOWS) # not windows therefore gcc LINUX and DARWIN - add_definitions(-fPIC) - endif(WINDOWS) -endif (NOT WORD_SIZE EQUAL 32) - -list(APPEND cwdebug_SOURCE_FILES ${cwdebug_HEADER_FILES}) - -add_library (cwdebug ${cwdebug_SOURCE_FILES}) diff --git a/indra/cwdebug/debug.cc b/indra/cwdebug/debug.cc index 2a4f1c4fa..06723f8ae 100644 --- a/indra/cwdebug/debug.cc +++ b/indra/cwdebug/debug.cc @@ -173,6 +173,8 @@ void stop_recording_backtraces(void) channel_ct backtrace DDCN("BACKTRACE"); //!< This debug channel is used for backtraces. channel_ct statemachine DDCN("STATEMACHINE"); //!< This debug channel is used for output related to class AIStateMachine. channel_ct caps DDCN("CAPS"); //!< This debug channel is used for output related to Capabilities. + channel_ct curl DDCN("CURL"); //!< This debug channel is used for output related to Curl. + channel_ct curlio DDCN("CURLIO"); //!< This debug channel is used to print debug output of libcurl. } // namespace dc } // namespace DEBUGCHANNELS @@ -411,4 +413,94 @@ void cwdebug_backtrace(int n) } #endif -#endif // CWDEBUG +#elif defined(DEBUG_CURLIO) + +#include "debug.h" +#include "aithreadid.h" + +namespace debug +{ + +namespace libcwd { libcwd_do_type const libcw_do; } + +ll_thread_local int Indent::S_indentation; + +Indent::Indent(int indent) : M_indent(indent) +{ + S_indentation += M_indent; +} + +Indent::~Indent() +{ + S_indentation -= M_indent; +} + +std::ostream& operator<<(std::ostream& os, Indent::print_nt) +{ + if (Indent::S_indentation) + os << std::string(Indent::S_indentation, ' '); + return os; +} + +#ifdef DEBUG_CURLIO +std::ostream& operator<<(std::ostream& os, print_thread_id_t) +{ + if (!AIThreadID::in_main_thread_inline()) + { + os << std::hex << (size_t)AIThreadID::getCurrentThread_inline() << std::dec << ' '; + } + return os; +} +#endif + +std::ostream& operator<<(std::ostream& os, libcwd::buf2str const& b2s) +{ + static char const c2s_tab[7] = { 'a', 'b', 't', 'n', 'v', 'f', 'r' }; + size_t size = b2s.mSize; + for (char const* p1 = b2s.mBuf; size > 0; --size, ++p1) + { + char c =*p1; + if ((c > 31 && c != 92 && c != 127) || (unsigned char)c > 159) + os.put(c); + else + { + os.put('\\'); + if (c > 6 && c < 14) + { + os.put(c2s_tab[c - 7]); + return os; + } + else if (c == 27) + { + os.put('e'); + return os; + } + else if (c == '\\') + { + os.put('\\'); + return os; + } + std::ostream::char_type old_fill = os.fill('0'); + std::ios_base::fmtflags old_flgs = os.flags(); + os.width(3); + os << std::oct << (int)((unsigned char)c); + os.setf(old_flgs); + os.fill(old_fill); + } + } + return os; +} + +namespace dc +{ + +fake_channel const warning(1, "WARNING "); +fake_channel const curl(1, "CURL "); +fake_channel const curlio(1, "CURLIO "); +fake_channel const statemachine(1, "STATEMACHINE"); +fake_channel const notice(1, "NOTICE "); + +} // namespace dc +} // namespace debug + +#endif diff --git a/indra/cwdebug/debug.h b/indra/cwdebug/debug.h index 786b0c52a..a52837411 100644 --- a/indra/cwdebug/debug.h +++ b/indra/cwdebug/debug.h @@ -27,6 +27,94 @@ #ifndef CWDEBUG +#ifdef DEBUG_CURLIO + +// If CWDEBUG is not defined, but DEBUG_CURLIO is, then replace +// some of the cwd macro's with something that generates viewer +// specific debug output. Note that this generates a LOT of +// output and should not normally be defined. + +#include +#include "llpreprocessor.h" + +namespace debug { +namespace libcwd { + +struct buf2str { + buf2str(char const* buf, int size) : mBuf(buf), mSize(size) { } + char const* mBuf; + int mSize; +}; + +struct libcwd_do_type { + void on() const { } +}; +extern LL_COMMON_API libcwd_do_type const libcw_do; + +} // namespace libcwd + +enum print_thread_id_t { print_thread_id }; +inline void init() { } +struct Indent { + int M_indent; + static ll_thread_local int S_indentation; + enum LL_COMMON_API print_nt { print }; + LL_COMMON_API Indent(int indent); + LL_COMMON_API ~Indent(); +}; + +extern LL_COMMON_API std::ostream& operator<<(std::ostream& os, libcwd::buf2str const& b2s); +extern LL_COMMON_API std::ostream& operator<<(std::ostream& os, Indent::print_nt); +extern LL_COMMON_API std::ostream& operator<<(std::ostream& os, print_thread_id_t); + +namespace dc { + +struct fake_channel { + int mOn; + char const* mLabel; + fake_channel(int on, char const* label) : mOn(on), mLabel(label) { } + fake_channel(void) : mOn(0) { } + bool is_on() const { return !!mOn; } + bool is_off() const { return !mOn; } + void on() const { } + void off() const { } +}; +extern LL_COMMON_API fake_channel const warning; +extern LL_COMMON_API fake_channel const curl; +extern LL_COMMON_API fake_channel const curlio; +extern LL_COMMON_API fake_channel const statemachine; +extern LL_COMMON_API fake_channel const notice; + +} // namespace dc +} // namespace debug + +#define LIBCWD_DEBUG_CHANNELS debug +#define LibcwDoutScopeBegin(a, b, c) do { using namespace debug; using namespace debug::libcwd; llinfos_nf << print_thread_id << (c).mLabel << ": " << Indent::print; +#define LibcwDoutStream llcont +#define LibcwDoutScopeEnd llcont << llendl; } while(0) + +#define Debug(x) do { using namespace debug; using namespace debug::libcwd; x; } while(0) +#define Dout(a, b) do { using namespace debug; using namespace debug::libcwd; if ((a).mOn) { llinfos_nf << print_thread_id << (a).mLabel << ": " << Indent::print << b << llendl; } } while(0) +#define DoutEntering(a, b) \ + int __slviewer_debug_indentation = 2; \ + { \ + using namespace debug; \ + using namespace debug::libcwd; \ + if ((a).mOn) \ + llinfos_nf << print_thread_id << (a).mLabel << ": " << Indent::print << "Entering " << b << llendl; \ + else \ + __slviewer_debug_indentation = 0; \ + } \ + debug::Indent __slviewer_debug_indent(__slviewer_debug_indentation); + +#else // !DEBUG_CURLIO + +#define Debug(x) +#define Dout(a, b) +#define DoutEntering(a, b) + +#endif // !DEBUG_CURLIO + #ifndef DOXYGEN // No need to document this. See http://libcwd.sourceforge.net/ for more info. #include @@ -36,9 +124,6 @@ #define AllocTag2(p, desc) #define AllocTag_dynamic_description(p, x) #define AllocTag(p, x) -#define Debug(x) -#define Dout(a, b) -#define DoutEntering(a, b) #define DoutFatal(a, b) LibcwDoutFatal(::std, , a, b) #define ForAllDebugChannels(STATEMENT) #define ForAllDebugObjects(STATEMENT) @@ -118,6 +203,8 @@ extern CWD_API channel_ct sdl; extern CWD_API channel_ct backtrace; extern CWD_API channel_ct statemachine; extern CWD_API channel_ct caps; +extern CWD_API channel_ct curl; +extern CWD_API channel_ct curlio; #endif diff --git a/indra/llaudio/llaudiodecodemgr.cpp b/indra/llaudio/llaudiodecodemgr.cpp index 5063cbe7a..596a6dcff 100644 --- a/indra/llaudio/llaudiodecodemgr.cpp +++ b/indra/llaudio/llaudiodecodemgr.cpp @@ -41,6 +41,7 @@ #include "lldir.h" #include "llendianswizzle.h" #include "llassetstorage.h" +#include "llrefcount.h" #include "vorbis/codec.h" #include "vorbis/vorbisfile.h" diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index fe7ff31c6..5d86a6415 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -16,6 +16,7 @@ include_directories( set(llcommon_SOURCE_FILES aiframetimer.cpp + aithreadid.cpp imageids.cpp indra_constants.cpp llallocator.cpp @@ -104,6 +105,7 @@ set(llcommon_HEADER_FILES CMakeLists.txt aiframetimer.h + aithreadid.h aithreadsafe.h bitpack.h ctype_workaround.h @@ -254,6 +256,7 @@ set(llcommon_HEADER_FILES set_source_files_properties(${llcommon_HEADER_FILES} PROPERTIES HEADER_FILE_ONLY TRUE) +list(APPEND llcommon_SOURCE_FILES ${cwdebug_SOURCE_FILES}) list(APPEND llcommon_SOURCE_FILES ${llcommon_HEADER_FILES}) add_library (llcommon SHARED ${llcommon_SOURCE_FILES}) @@ -266,15 +269,9 @@ target_link_libraries( ${ZLIB_LIBRARIES} ${WINDOWS_LIBRARIES} ${Boost_REGEX_LIBRARY} - ${CWDEBUG_LIBRARIES} ${CORESERVICES_LIBRARY} ) -if (LINUX) - # When linking with llcommon later, we do not want to link with cwdebug.a again. - set_property(TARGET llcommon PROPERTY LINK_INTERFACE_LIBRARIES "-lapr-1 -laprutil-1 -lz") -endif (LINUX) - if (DARWIN) # Don't embed a full path in the library's install name set_target_properties( diff --git a/indra/llcommon/aithreadid.cpp b/indra/llcommon/aithreadid.cpp new file mode 100644 index 000000000..e8dd17097 --- /dev/null +++ b/indra/llcommon/aithreadid.cpp @@ -0,0 +1,78 @@ +/** + * @file aithreadid.cpp + * + * Copyright (c) 2012, Aleric Inglewood. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution. + * + * CHANGELOG + * and additional copyright holders. + * + * 08/08/2012 + * - Initial version, written by Aleric Inglewood @ SL + */ + +#include +#include +#include "aithreadid.h" + +AIThreadID const AIThreadID::sNone(AIThreadID::none); +apr_os_thread_t AIThreadID::sMainThreadID; +apr_os_thread_t const AIThreadID::undefinedID = (apr_os_thread_t)-1; +#ifndef LL_DARWIN +apr_os_thread_t ll_thread_local AIThreadID::lCurrentThread; +#endif + +void AIThreadID::set_main_thread_id(void) +{ + sMainThreadID = apr_os_thread_current(); +} + +void AIThreadID::set_current_thread_id(void) +{ +#ifndef LL_DARWIN + lCurrentThread = apr_os_thread_current(); +#endif +} + +#ifndef LL_DARWIN +void AIThreadID::reset(void) +{ + mID = lCurrentThread; +} + +bool AIThreadID::equals_current_thread(void) const +{ + return apr_os_thread_equal(mID, lCurrentThread); +} + +bool AIThreadID::in_main_thread(void) +{ + return apr_os_thread_equal(lCurrentThread, sMainThreadID); +} + +apr_os_thread_t AIThreadID::getCurrentThread(void) +{ + return lCurrentThread; +} +#endif + +std::ostream& operator<<(std::ostream& os, AIThreadID const& id) +{ + return os << id.mID; +} diff --git a/indra/llcommon/aithreadid.h b/indra/llcommon/aithreadid.h new file mode 100644 index 000000000..3b3b1500c --- /dev/null +++ b/indra/llcommon/aithreadid.h @@ -0,0 +1,94 @@ +/** + * @file aithreadid.h + * @brief Declaration of AIThreadID. + * + * Copyright (c) 2012, Aleric Inglewood. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution. + * + * CHANGELOG + * and additional copyright holders. + * + * 08/08/2012 + * Initial version, written by Aleric Inglewood @ SL + */ + +#ifndef AI_THREAD_ID +#define AI_THREAD_ID + +#include // apr_os_thread_t, apr_os_thread_current(), apr_os_thread_equal(). +#include // std::ostream. +#include "llpreprocessor.h" // LL_COMMON_API, LL_COMMON_API_TLS + +// Lightweight wrapper around apr_os_thread_t. +// This class introduces no extra assembly code after optimization; it's only intend is to provide type-safety. +class AIThreadID +{ +private: + apr_os_thread_t mID; + static LL_COMMON_API apr_os_thread_t sMainThreadID; + static LL_COMMON_API apr_os_thread_t const undefinedID; +#ifndef LL_DARWIN + static ll_thread_local apr_os_thread_t lCurrentThread; +#endif +public: + static LL_COMMON_API AIThreadID const sNone; + enum undefined_thread_t { none }; + +public: + AIThreadID(void) : mID(apr_os_thread_current()) { } + explicit AIThreadID(undefined_thread_t) : mID(undefinedID) { } // Used for sNone. + AIThreadID(AIThreadID const& id) : mID(id.mID) { } + AIThreadID& operator=(AIThreadID const& id) { mID = id.mID; return *this; } + bool is_main_thread(void) const { return apr_os_thread_equal(mID, sMainThreadID); } + bool is_no_thread(void) const { return apr_os_thread_equal(mID, sNone.mID); } + friend LL_COMMON_API bool operator==(AIThreadID const& id1, AIThreadID const& id2) { return apr_os_thread_equal(id1.mID, id2.mID); } + friend LL_COMMON_API bool operator!=(AIThreadID const& id1, AIThreadID const& id2) { return !apr_os_thread_equal(id1.mID, id2.mID); } + friend LL_COMMON_API std::ostream& operator<<(std::ostream& os, AIThreadID const& id); + static void set_main_thread_id(void); // Called once to set sMainThreadID. + static void set_current_thread_id(void); // Called once for every thread to set lCurrentThread. +#ifndef LL_DARWIN + LL_COMMON_API void reset(void); + LL_COMMON_API bool equals_current_thread(void) const; + LL_COMMON_API static bool in_main_thread(void); + LL_COMMON_API static apr_os_thread_t getCurrentThread(void); + // The *_inline variants cannot be exported because they access a thread-local member. + void reset_inline(void) { mID = lCurrentThread; } + bool equals_current_thread_inline(void) const { return apr_os_thread_equal(mID, lCurrentThread); } + static bool in_main_thread_inline(void) { return apr_os_thread_equal(lCurrentThread, sMainThreadID); } + static apr_os_thread_t getCurrentThread_inline(void) { return lCurrentThread; } +#else + // Both variants are inline on OS X. + void reset(void) { mID = apr_os_thread_current(); } + void reset_inline(void) { mID = apr_os_thread_current(); } + bool equals_current_thread(void) const { return apr_os_thread_equal(mID, apr_os_thread_current()); } + bool equals_current_thread_inline(void) const { return apr_os_thread_equal(mID, apr_os_thread_current()); } + static bool in_main_thread(void) { return apr_os_thread_equal(apr_os_thread_current(), sMainThreadID); } + static bool in_main_thread_inline(void) { return apr_os_thread_equal(apr_os_thread_current(), sMainThreadID); } + static apr_os_thread_t getCurrentThread(void) { return apr_os_thread_current(); } + static apr_os_thread_t getCurrentThread_inline(void) { return apr_os_thread_current(); } +#endif +}; + +// Legacy function. +inline bool is_main_thread(void) +{ + return AIThreadID::in_main_thread(); +} + +#endif // AI_THREAD_ID diff --git a/indra/llcommon/aithreadsafe.h b/indra/llcommon/aithreadsafe.h index b7d0045cb..02bfa8cd7 100644 --- a/indra/llcommon/aithreadsafe.h +++ b/indra/llcommon/aithreadsafe.h @@ -673,18 +673,18 @@ protected: #ifdef LL_DEBUG mutable bool mAccessed; - mutable apr_os_thread_t mTheadID; + mutable AIThreadID mTheadID; void accessed(void) const { if (!mAccessed) { mAccessed = true; - mTheadID = apr_os_thread_current(); + mTheadID.reset(); } else { - llassert_always(apr_os_thread_equal(mTheadID, apr_os_thread_current())); + llassert_always(mTheadID.equals_current_thread()); } } #endif diff --git a/indra/llcommon/llapp.cpp b/indra/llcommon/llapp.cpp index cb2b3c0ca..316b07e83 100644 --- a/indra/llcommon/llapp.cpp +++ b/indra/llcommon/llapp.cpp @@ -297,6 +297,20 @@ void LLApp::startErrorThread() } } +void LLApp::stopErrorThread() +{ + LLApp::setStopped(); // Signal error thread that we stopped. + int count = 0; + while (mThreadErrorp && !mThreadErrorp->isStopped() && ++count < 100) + { + ms_sleep(10); + } + if (mThreadErrorp && !mThreadErrorp->isStopped()) + { + llwarns << "Failed to stop Error Thread." << llendl; + } +} + void LLApp::setErrorHandler(LLAppErrorHandler handler) { LLApp::sErrorHandler = handler; diff --git a/indra/llcommon/llapp.h b/indra/llcommon/llapp.h index 32f1e84c3..788177dd9 100644 --- a/indra/llcommon/llapp.h +++ b/indra/llcommon/llapp.h @@ -264,6 +264,10 @@ protected: * @ brief This method is called once as soon as logging is initialized. */ void startErrorThread(); + /** + * @brief This method is called at the end, just prior to deinitializing curl. + */ + void stopErrorThread(); private: void setupErrorHandling(); // Do platform-specific error-handling setup (signals, structured exceptions) diff --git a/indra/llcommon/llaprpool.cpp b/indra/llcommon/llaprpool.cpp index 3559ff430..3dffa8300 100644 --- a/indra/llcommon/llaprpool.cpp +++ b/indra/llcommon/llaprpool.cpp @@ -60,10 +60,10 @@ void LLAPRPool::create(LLAPRPool& parent) // // In other words, it's safe for any thread to create a (sub)pool, independent of who // owns the parent pool. - mOwner = apr_os_thread_current(); + mOwner.reset_inline(); #else mOwner = mParent->mOwner; - llassert(apr_os_thread_equal(mOwner, apr_os_thread_current())); + llassert(mOwner.equals_current_thread_inline()); #endif apr_status_t const apr_pool_create_status = apr_pool_create(&mPool, mParent->mPool); llassert_always(apr_pool_create_status == APR_SUCCESS); @@ -83,7 +83,7 @@ void LLAPRPool::destroy(void) // of course. Otherwise, if we are a subpool, only the thread that owns // the parent may destruct us, since that is the pool that is still alive, // possibly being used by others and being altered here. - llassert(!mParent || apr_os_thread_equal(mParent->mOwner, apr_os_thread_current())); + llassert(!mParent || mParent->mOwner.equals_current_thread_inline()); #endif apr_pool_t* pool = mPool; mPool = NULL; // Mark that we are BEING destructed. diff --git a/indra/llcommon/llaprpool.h b/indra/llcommon/llaprpool.h index dc123e942..74af351e4 100644 --- a/indra/llcommon/llaprpool.h +++ b/indra/llcommon/llaprpool.h @@ -48,6 +48,7 @@ #include "apr_portable.h" #include "apr_pools.h" #include "llerror.h" +#include "aithreadid.h" extern void ll_init_apr(); @@ -62,22 +63,22 @@ class LL_COMMON_API LLAPRPool protected: apr_pool_t* mPool; //!< Pointer to the underlaying pool. NULL if not initialized. LLAPRPool* mParent; //!< Pointer to the parent pool, if any. Only valid when mPool is non-zero. - apr_os_thread_t mOwner; //!< The thread that owns this memory pool. Only valid when mPool is non-zero. + AIThreadID mOwner; //!< The thread that owns this memory pool. Only valid when mPool is non-zero. public: //! Construct an uninitialized (destructed) pool. - LLAPRPool(void) : mPool(NULL) { } + LLAPRPool(void) : mPool(NULL), mOwner(AIThreadID::none) { } //! Construct a subpool from an existing pool. // This is not a copy-constructor, this class doesn't have one! - LLAPRPool(LLAPRPool& parent) : mPool(NULL) { create(parent); } + LLAPRPool(LLAPRPool& parent) : mPool(NULL), mOwner(AIThreadID::none) { create(parent); } //! Destruct the memory pool (free all of it's subpools and allocated memory). ~LLAPRPool() { destroy(); } protected: // Create a pool that is allocated from the Operating System. Only used by LLAPRRootPool. - LLAPRPool(int) : mPool(NULL), mParent(NULL), mOwner(apr_os_thread_current()) + LLAPRPool(int) : mPool(NULL), mParent(NULL) { apr_status_t const apr_pool_create_status = apr_pool_create(&mPool, NULL); llassert_always(apr_pool_create_status == APR_SUCCESS); @@ -104,7 +105,7 @@ public: apr_pool_t* operator()(void) const { llassert(mPool); - llassert(apr_os_thread_equal(mOwner, apr_os_thread_current())); + llassert(mOwner.equals_current_thread()); return mPool; } @@ -112,7 +113,7 @@ public: void clear(void) { llassert(mPool); - llassert(apr_os_thread_equal(mOwner, apr_os_thread_current())); + llassert(mOwner.equals_current_thread()); apr_pool_clear(mPool); } @@ -124,13 +125,13 @@ public: void* palloc(size_t size) { llassert(mPool); - llassert(apr_os_thread_equal(mOwner, apr_os_thread_current())); + llassert(mOwner.equals_current_thread()); return apr_palloc(mPool, size); } void* pcalloc(size_t size) { llassert(mPool); - llassert(apr_os_thread_equal(mOwner, apr_os_thread_current())); + llassert(mOwner.equals_current_thread()); return apr_pcalloc(mPool, size); } #endif diff --git a/indra/llcommon/llerror.cpp b/indra/llcommon/llerror.cpp index c537a76a8..7b362b230 100644 --- a/indra/llcommon/llerror.cpp +++ b/indra/llcommon/llerror.cpp @@ -963,10 +963,14 @@ namespace LLError settings_w->shouldLogCallCounter += 1; std::string class_name = className(site.mClassInfo); - std::string function_name = functionName(site.mFunction); - if (site.mClassInfo != typeid(NoClassInfo)) + std::string function_name; + if (site.mFunction) { - function_name = class_name + "::" + function_name; + function_name = functionName(site.mFunction); + if (site.mClassInfo != typeid(NoClassInfo)) + { + function_name = class_name + "::" + function_name; + } } ELevel compareLevel = settings_w->defaultLevel; @@ -976,7 +980,7 @@ namespace LLError // So, in increasing order of importance: // Default < Broad Tag < File < Class < Function < Narrow Tag ((site.mNarrowTag != NULL) ? checkLevelMap(settings_w->tagLevelMap, site.mNarrowTag, compareLevel) : false) - || checkLevelMap(settings_w->functionLevelMap, function_name, compareLevel) + || (site.mFunction && checkLevelMap(settings_w->functionLevelMap, function_name, compareLevel)) || checkLevelMap(settings_w->classLevelMap, class_name, compareLevel) || checkLevelMap(settings_w->fileLevelMap, abbreviateFile(site.mFile), compareLevel) || ((site.mBroadTag != NULL) ? checkLevelMap(settings_w->tagLevelMap, site.mBroadTag, compareLevel) : false); @@ -1083,8 +1087,8 @@ namespace LLError default: prefix << "XXX"; break; }; - bool need_function = true; - if (site.mBroadTag && *site.mBroadTag != '\0') + bool need_function = site.mFunction; + if (need_function && site.mBroadTag && *site.mBroadTag != '\0') { prefix << "(\"" << site.mBroadTag << "\")"; #if LL_DEBUG @@ -1112,7 +1116,7 @@ namespace LLError #if LL_WINDOWS // DevStudio: __FUNCTION__ already includes the full class name #else - if (need_function && site.mClassInfo != typeid(NoClassInfo)) + if (site.mClassInfo != typeid(NoClassInfo)) { prefix << className(site.mClassInfo) << "::"; } diff --git a/indra/llcommon/llerror.h b/indra/llcommon/llerror.h index 34934c5c2..c4865e947 100644 --- a/indra/llcommon/llerror.h +++ b/indra/llcommon/llerror.h @@ -238,10 +238,10 @@ typedef LLError::NoClassInfo _LL_CLASS_TO_LOG; See top of file for common usage. */ -#define lllog(level, broadTag, narrowTag, once) \ +#define lllog(level, broadTag, narrowTag, once, nofunction) \ do { \ static LLError::CallSite _site( \ - level, __FILE__, __LINE__, typeid(_LL_CLASS_TO_LOG), __FUNCTION__, broadTag, narrowTag, once);\ + level, __FILE__, __LINE__, typeid(_LL_CLASS_TO_LOG), nofunction ? NULL : __FUNCTION__, broadTag, narrowTag, once);\ if (LL_UNLIKELY(_site.shouldLog())) \ { \ std::ostringstream* _out = LLError::Log::out(); \ @@ -255,33 +255,39 @@ typedef LLError::NoClassInfo _LL_CLASS_TO_LOG; } while(0) // DEPRECATED: Use the new macros that allow tags and *look* like macros. -#define lldebugs lllog(LLError::LEVEL_DEBUG, NULL, NULL, false) -#define llinfos lllog(LLError::LEVEL_INFO, NULL, NULL, false) -#define llwarns lllog(LLError::LEVEL_WARN, NULL, NULL, false) -#define llerrs lllog(LLError::LEVEL_ERROR, NULL, NULL, false) +#define lldebugs lllog(LLError::LEVEL_DEBUG, NULL, NULL, false, false) +#define llinfos lllog(LLError::LEVEL_INFO, NULL, NULL, false, false) +#define llwarns lllog(LLError::LEVEL_WARN, NULL, NULL, false, false) +#define llerrs lllog(LLError::LEVEL_ERROR, NULL, NULL, false, false) #define llcont (*_out) +// No function name +#define lldebugs_nf lllog(LLError::LEVEL_DEBUG, NULL, NULL, false, true) +#define llinfos_nf lllog(LLError::LEVEL_INFO, NULL, NULL, false, true) +#define llwarns_nf lllog(LLError::LEVEL_WARN, NULL, NULL, false, true) +#define llerrs_nf lllog(LLError::LEVEL_ERROR, NULL, NULL, false, true) + // NEW Macros for debugging, allow the passing of a string tag // One Tag -#define LL_DEBUGS(broadTag) lllog(LLError::LEVEL_DEBUG, broadTag, NULL, false) -#define LL_INFOS(broadTag) lllog(LLError::LEVEL_INFO, broadTag, NULL, false) -#define LL_WARNS(broadTag) lllog(LLError::LEVEL_WARN, broadTag, NULL, false) -#define LL_ERRS(broadTag) lllog(LLError::LEVEL_ERROR, broadTag, NULL, false) +#define LL_DEBUGS(broadTag) lllog(LLError::LEVEL_DEBUG, broadTag, NULL, false, false) +#define LL_INFOS(broadTag) lllog(LLError::LEVEL_INFO, broadTag, NULL, false, false) +#define LL_WARNS(broadTag) lllog(LLError::LEVEL_WARN, broadTag, NULL, false, false) +#define LL_ERRS(broadTag) lllog(LLError::LEVEL_ERROR, broadTag, NULL, false, false) // Two Tags -#define LL_DEBUGS2(broadTag, narrowTag) lllog(LLError::LEVEL_DEBUG, broadTag, narrowTag, false) -#define LL_INFOS2(broadTag, narrowTag) lllog(LLError::LEVEL_INFO, broadTag, narrowTag, false) -#define LL_WARNS2(broadTag, narrowTag) lllog(LLError::LEVEL_WARN, broadTag, narrowTag, false) -#define LL_ERRS2(broadTag, narrowTag) lllog(LLError::LEVEL_ERROR, broadTag, narrowTag, false) +#define LL_DEBUGS2(broadTag, narrowTag) lllog(LLError::LEVEL_DEBUG, broadTag, narrowTag, false, false) +#define LL_INFOS2(broadTag, narrowTag) lllog(LLError::LEVEL_INFO, broadTag, narrowTag, false, false) +#define LL_WARNS2(broadTag, narrowTag) lllog(LLError::LEVEL_WARN, broadTag, narrowTag, false, false) +#define LL_ERRS2(broadTag, narrowTag) lllog(LLError::LEVEL_ERROR, broadTag, narrowTag, false, false) // 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). -#define LL_DEBUGS_ONCE(broadTag) lllog(LLError::LEVEL_DEBUG, broadTag, NULL, true) -#define LL_INFOS_ONCE(broadTag) lllog(LLError::LEVEL_INFO, broadTag, NULL, true) -#define LL_WARNS_ONCE(broadTag) lllog(LLError::LEVEL_WARN, broadTag, NULL, true) -#define LL_DEBUGS2_ONCE(broadTag, narrowTag) lllog(LLError::LEVEL_DEBUG, broadTag, narrowTag, true) -#define LL_INFOS2_ONCE(broadTag, narrowTag) lllog(LLError::LEVEL_INFO, broadTag, narrowTag, true) -#define LL_WARNS2_ONCE(broadTag, narrowTag) lllog(LLError::LEVEL_WARN, broadTag, narrowTag, true) +#define LL_DEBUGS_ONCE(broadTag) lllog(LLError::LEVEL_DEBUG, broadTag, NULL, true, false) +#define LL_INFOS_ONCE(broadTag) lllog(LLError::LEVEL_INFO, broadTag, NULL, true, false) +#define LL_WARNS_ONCE(broadTag) lllog(LLError::LEVEL_WARN, broadTag, NULL, true, false) +#define LL_DEBUGS2_ONCE(broadTag, narrowTag) lllog(LLError::LEVEL_DEBUG, broadTag, narrowTag, true, false) +#define LL_INFOS2_ONCE(broadTag, narrowTag) lllog(LLError::LEVEL_INFO, broadTag, narrowTag, true, false) +#define LL_WARNS2_ONCE(broadTag, narrowTag) lllog(LLError::LEVEL_WARN, broadTag, narrowTag, true, false) #define LL_ENDL llendl #define LL_CONT (*_out) @@ -302,8 +308,4 @@ typedef LLError::NoClassInfo _LL_CLASS_TO_LOG; Such computation is done iff the message will be logged. */ -#ifdef SHOW_ASSERT -extern LL_COMMON_API bool is_main_thread(); -#endif - #endif // LL_LLERROR_H diff --git a/indra/llcommon/llerrorlegacy.h b/indra/llcommon/llerrorlegacy.h index fa41c5b3d..278f781ae 100644 --- a/indra/llcommon/llerrorlegacy.h +++ b/indra/llcommon/llerrorlegacy.h @@ -114,7 +114,7 @@ const int LL_ERR_PRICE_MISMATCH = -23018; : liru_slashpos2 == std::string::npos ? std::string(__FILE__)/*Apparently, we're in / or perhaps the top of the drive, print as is*/\ : std::string(__FILE__).substr(1+liru_slashpos2))/*print foo/bar.cpp or perhaps foo\bar.cpp*/ -#define llassert_always(func) if (LL_UNLIKELY(!(func))) llerrs <<"\nASSERT(" #func ")\nfile:"< +#ifdef SHOW_ASSERT +#include "aithreadid.h" // is_main_thread() +#endif class LL_COMMON_API LLFrameTimer { diff --git a/indra/llcommon/llmemory.h b/indra/llcommon/llmemory.h index d01b43917..19cc720b0 100644 --- a/indra/llcommon/llmemory.h +++ b/indra/llcommon/llmemory.h @@ -523,7 +523,6 @@ void LLPrivateMemoryPoolTester::operator delete[](void* addr) //EVENTUALLY REMOVE THESE: #include "llpointer.h" -#include "llrefcount.h" #include "llsingleton.h" #include "llsafehandle.h" diff --git a/indra/llcommon/llpreprocessor.h b/indra/llcommon/llpreprocessor.h index 009871908..a1f598d4a 100644 --- a/indra/llcommon/llpreprocessor.h +++ b/indra/llcommon/llpreprocessor.h @@ -195,4 +195,13 @@ # define LL_COMMON_API #endif // LL_COMMON_LINK_SHARED +// Darwin does not support thread-local data. +#ifndef LL_DARWIN +#if LL_WINDOWS +#define ll_thread_local __declspec(thread) +#else // Linux +#define ll_thread_local __thread +#endif +#endif + #endif // not LL_LINDEN_PREPROCESSOR_H diff --git a/indra/llcommon/llqueuedthread.cpp b/indra/llcommon/llqueuedthread.cpp index 31bb145fe..27e3dca7c 100644 --- a/indra/llcommon/llqueuedthread.cpp +++ b/indra/llcommon/llqueuedthread.cpp @@ -251,7 +251,7 @@ bool LLQueuedThread::addRequest(QueuedRequest* req) // MAIN thread bool LLQueuedThread::waitForResult(LLQueuedThread::handle_t handle, bool auto_complete) { - llassert (handle != nullHandle()) + llassert (handle != nullHandle()); bool res = false; bool waspaused = isPaused(); bool done = false; diff --git a/indra/llcommon/llqueuedthread.h b/indra/llcommon/llqueuedthread.h index 6a3dc4078..9e2e7d969 100644 --- a/indra/llcommon/llqueuedthread.h +++ b/indra/llcommon/llqueuedthread.h @@ -111,6 +111,8 @@ public: return mPriority > second.mPriority; } + virtual void deleteRequest(); // Only method to delete a request + protected: status_t setStatus(status_t newstatus) { @@ -130,7 +132,6 @@ public: virtual bool processRequest() = 0; // Return true when request has completed virtual void finishRequest(bool completed); // Always called from thread after request has completed or aborted - virtual void deleteRequest(); // Only method to delete a request void setPriority(U32 pri) { diff --git a/indra/llcommon/llrefcount.cpp b/indra/llcommon/llrefcount.cpp index e1876599f..0956e1628 100644 --- a/indra/llcommon/llrefcount.cpp +++ b/indra/llcommon/llrefcount.cpp @@ -38,14 +38,6 @@ LLRefCount::LLRefCount(const LLRefCount& other) : mRef(0) { #if LL_REF_COUNT_DEBUG - if(gAPRPoolp) - { - mMutexp = new LLMutex(gAPRPoolp) ; - } - else - { - mMutexp = NULL ; - } mCrashAtUnlock = FALSE ; #endif } @@ -60,14 +52,6 @@ LLRefCount::LLRefCount() : mRef(0) { #if LL_REF_COUNT_DEBUG - if(gAPRPoolp) - { - mMutexp = new LLMutex(gAPRPoolp) ; - } - else - { - mMutexp = NULL ; - } mCrashAtUnlock = FALSE ; #endif } @@ -78,29 +62,20 @@ LLRefCount::~LLRefCount() { llerrs << "deleting non-zero reference" << llendl; } - -#if LL_REF_COUNT_DEBUG - if(gAPRPoolp) - { - delete mMutexp ; - } -#endif } #if LL_REF_COUNT_DEBUG void LLRefCount::ref() const { - if(mMutexp) - { - if(mMutexp->isLocked()) + if(mMutex.isLocked()) { mCrashAtUnlock = TRUE ; llerrs << "the mutex is locked by the thread: " << mLockedThreadID - << " Current thread: " << LLThread::currentID() << llendl ; + << " Current thread: " << AIThreadID() << llendl ; } - mMutexp->lock() ; - mLockedThreadID = LLThread::currentID() ; + mMutex.lock() ; + mLockedThreadID.reset_inline(); mRef++; @@ -108,27 +83,20 @@ void LLRefCount::ref() const { while(1); //crash here. } - mMutexp->unlock() ; - } - else - { - mRef++; - } + mMutex.unlock() ; } S32 LLRefCount::unref() const { - if(mMutexp) - { - if(mMutexp->isLocked()) + if(mMutex.isLocked()) { mCrashAtUnlock = TRUE ; llerrs << "the mutex is locked by the thread: " << mLockedThreadID - << " Current thread: " << LLThread::currentID() << llendl ; + << " Current thread: " << AIThreadID() << llendl ; } - mMutexp->lock() ; - mLockedThreadID = LLThread::currentID() ; + mMutex.lock() ; + mLockedThreadID.reset_inline(); llassert(mRef >= 1); if (0 == --mRef) @@ -137,7 +105,7 @@ S32 LLRefCount::unref() const { while(1); //crash here. } - mMutexp->unlock() ; + mMutex.unlock() ; delete this; return 0; @@ -147,18 +115,7 @@ S32 LLRefCount::unref() const { while(1); //crash here. } - mMutexp->unlock() ; + mMutex.unlock() ; return mRef; - } - else - { - llassert(mRef >= 1); - if (0 == --mRef) - { - delete this; - return 0; - } - return mRef; - } } #endif diff --git a/indra/llcommon/llrefcount.h b/indra/llcommon/llrefcount.h index 8eb5d53f3..b7831e7fa 100644 --- a/indra/llcommon/llrefcount.h +++ b/indra/llcommon/llrefcount.h @@ -30,7 +30,7 @@ #define LL_REF_COUNT_DEBUG 0 #if LL_REF_COUNT_DEBUG -class LLMutex ; +#include "llthread.h" // LLMutexRootPool #endif //---------------------------------------------------------------------------- @@ -80,8 +80,8 @@ private: mutable S32 mRef; #if LL_REF_COUNT_DEBUG - LLMutex* mMutexp ; - mutable U32 mLockedThreadID ; + mutable LLMutexRootPool mMutex ; + mutable AIThreadID mLockedThreadID ; mutable BOOL mCrashAtUnlock ; #endif }; diff --git a/indra/llcommon/llthread.cpp b/indra/llcommon/llthread.cpp index 3716e5dab..5c1a29f24 100644 --- a/indra/llcommon/llthread.cpp +++ b/indra/llcommon/llthread.cpp @@ -29,6 +29,11 @@ * $/LicenseInfo$ */ +#ifdef __GNUC__ +// Generate code for inlines from llthread.h (needed for is_main_thread()). +#pragma implementation "llthread.h" +#endif + #include "linden_common.h" #include "llapr.h" @@ -62,18 +67,12 @@ // //---------------------------------------------------------------------------- -#if !LL_DARWIN -U32 ll_thread_local local_thread_ID = 0; -#endif - -U32 LLThread::sIDIter = 0; LLAtomicS32 LLThread::sCount = 0; LLAtomicS32 LLThread::sRunning = 0; LL_COMMON_API void assert_main_thread() { - static U32 s_thread_id = LLThread::currentID(); - if (LLThread::currentID() != s_thread_id) + if (!AIThreadID::in_main_thread_inline()) { llerrs << "Illegal execution outside main thread." << llendl; } @@ -90,9 +89,8 @@ void *APR_THREAD_FUNC LLThread::staticRun(apr_thread_t *apr_threadp, void *datap LLThread *threadp = (LLThread *)datap; -#if !LL_DARWIN - local_thread_ID = threadp->mID; -#endif + // Initialize thread-local cache of current thread ID (if supported). + AIThreadID::set_current_thread_id(); // Create a thread local data. LLThreadLocalData::create(threadp); @@ -132,7 +130,6 @@ LLThread::LLThread(std::string const& name) : mStatus(STOPPED), mThreadLocalData(NULL) { - mID = ++sIDIter; sCount++; llassert(sCount <= 50); mRunCondition = new LLCondition; @@ -276,12 +273,6 @@ void LLThread::setQuitting() wake(); } -// static -U32 LLThread::currentID() -{ - return (U32)apr_os_thread_current(); -} - // static void LLThread::yield() { @@ -310,15 +301,19 @@ void LLThread::wakeLocked() } } -#ifdef SHOW_ASSERT -// This allows the use of llassert(is_main_thread()) to assure the current thread is the main thread. -static apr_os_thread_t main_thread_id; -LL_COMMON_API bool is_main_thread(void) { return apr_os_thread_equal(main_thread_id, apr_os_thread_current()); } -#endif - // The thread private handle to access the LLThreadLocalData instance. apr_threadkey_t* LLThreadLocalData::sThreadLocalDataKey; +LLThreadLocalData::LLThreadLocalData(char const* name) : mCurlMultiHandle(NULL), mCurlErrorBuffer(NULL), mName(name) +{ +} + +LLThreadLocalData::~LLThreadLocalData() +{ + delete mCurlMultiHandle; + delete [] mCurlErrorBuffer; +} + //static void LLThreadLocalData::init(void) { @@ -328,6 +323,10 @@ void LLThreadLocalData::init(void) return; } + // This function is called by the main thread (these values are also needed in the next line). + AIThreadID::set_main_thread_id(); + AIThreadID::set_current_thread_id(); + apr_status_t status = apr_threadkey_private_create(&sThreadLocalDataKey, &LLThreadLocalData::destroy, LLAPRRootPool::get()()); ll_apr_assert_status(status); // Or out of memory, or system-imposed limit on the // total number of keys per process {PTHREAD_KEYS_MAX} @@ -335,11 +334,6 @@ void LLThreadLocalData::init(void) // Create the thread-local data for the main thread (this function is called by the main thread). LLThreadLocalData::create(NULL); - -#ifdef SHOW_ASSERT - // This function is called by the main thread. - main_thread_id = apr_os_thread_current(); -#endif } // This is called once for every thread when the thread is destructed. @@ -352,7 +346,7 @@ void LLThreadLocalData::destroy(void* thread_local_data) //static void LLThreadLocalData::create(LLThread* threadp) { - LLThreadLocalData* new_tld = new LLThreadLocalData; + LLThreadLocalData* new_tld = new LLThreadLocalData(threadp ? threadp->mName.c_str() : "main thread"); if (threadp) { threadp->mThreadLocalData = new_tld; @@ -406,27 +400,19 @@ void LLCondition::broadcast() //============================================================================ LLMutexBase::LLMutexBase() : - mLockingThread(NO_THREAD), + mLockingThread(AIThreadID::sNone), mCount(0) { } bool LLMutexBase::isSelfLocked() const { -#if LL_DARWIN - return mLockingThread == LLThread::currentID(); -#else - return mLockingThread == local_thread_ID; -#endif + return mLockingThread.equals_current_thread_inline(); } void LLMutexBase::lock() { -#if LL_DARWIN - if (mLockingThread == LLThread::currentID()) -#else - if (mLockingThread == local_thread_ID) -#endif + if (mLockingThread.equals_current_thread_inline()) { //redundant lock mCount++; return; @@ -434,11 +420,33 @@ void LLMutexBase::lock() apr_thread_mutex_lock(mAPRMutexp); -#if LL_DARWIN - mLockingThread = LLThread::currentID(); -#else - mLockingThread = local_thread_ID; -#endif + mLockingThread.reset_inline(); +} + +bool LLMutexBase::tryLock() +{ + if (mLockingThread.equals_current_thread_inline()) + { //redundant lock + mCount++; + return true; + } + bool success = !APR_STATUS_IS_EBUSY(apr_thread_mutex_trylock(mAPRMutexp)); + if (success) + { + mLockingThread.reset_inline(); + } + return success; +} + +// non-blocking, but does do a lock/unlock so not free +bool LLMutexBase::isLocked() const +{ + if (mLockingThread.equals_current_thread_inline()) + return false; // A call to lock() won't block. + if (APR_STATUS_IS_EBUSY(apr_thread_mutex_trylock(mAPRMutexp))) + return true; + apr_thread_mutex_unlock(mAPRMutexp); + return false; } void LLMutexBase::unlock() @@ -448,7 +456,7 @@ void LLMutexBase::unlock() mCount--; return; } - mLockingThread = NO_THREAD; + mLockingThread = AIThreadID::sNone; apr_thread_mutex_unlock(mAPRMutexp); } diff --git a/indra/llcommon/llthread.h b/indra/llcommon/llthread.h index 8def2c7ea..549a090ec 100644 --- a/indra/llcommon/llthread.h +++ b/indra/llcommon/llthread.h @@ -33,29 +33,29 @@ #ifndef LL_LLTHREAD_H #define LL_LLTHREAD_H +#ifdef __GNUC__ +// Needed for is_main_thread() when compiling with optimization (relwithdebinfo). +// It doesn't hurt to just always specify it though. +#pragma interface +#endif + #include "llapp.h" #include "llapr.h" #include "llmemory.h" #include "apr_thread_cond.h" #include "llaprpool.h" #include "llatomic.h" - -#ifdef SHOW_ASSERT -extern LL_COMMON_API bool is_main_thread(void); -#define ASSERT_SINGLE_THREAD do { static apr_os_thread_t first_thread_id = apr_os_thread_current(); llassert(apr_os_thread_equal(first_thread_id, apr_os_thread_current())); } while(0) -#else -#define ASSERT_SINGLE_THREAD do { } while(0) -#endif +#include "aithreadid.h" class LLThread; class LLMutex; class LLCondition; -#if LL_WINDOWS -#define ll_thread_local __declspec(thread) -#else -#define ll_thread_local __thread -#endif +class LL_COMMON_API LLThreadLocalDataMember +{ +public: + virtual ~LLThreadLocalDataMember() { }; +}; class LL_COMMON_API LLThreadLocalData { @@ -66,13 +66,23 @@ public: // Thread-local memory pool. LLAPRRootPool mRootPool; LLVolatileAPRPool mVolatileAPRPool; + LLThreadLocalDataMember* mCurlMultiHandle; // Initialized by AICurlMultiHandle::getInstance + char* mCurlErrorBuffer; // NULL, or pointing to a buffer used by libcurl. + std::string mName; // "main thread", or a copy of LLThread::mName. static void init(void); static void destroy(void* thread_local_data); static void create(LLThread* pthread); static LLThreadLocalData& tldata(void); + +private: + LLThreadLocalData(char const* name); + ~LLThreadLocalData(); }; +// Print to llerrs if the current thread is not the main thread. +LL_COMMON_API void assert_main_thread(); + class LL_COMMON_API LLThread { private: @@ -95,7 +105,6 @@ public: bool isQuitting() const { return (QUITTING == mStatus); } bool isStopped() const { return (STOPPED == mStatus); } - static U32 currentID(); // Return ID of current thread static S32 getCount() { return sCount; } static S32 getRunning() { return sRunning; } static void yield(); // Static because it can be called by the main thread, which doesn't have an LLThread data structure. @@ -122,8 +131,6 @@ public: // Return thread-local data for the current thread. static LLThreadLocalData& tldata(void) { return LLThreadLocalData::tldata(); } - U32 getID() const { return mID; } - private: bool mPaused; @@ -136,7 +143,6 @@ protected: apr_thread_t *mAPRThreadp; volatile EThreadStatus mStatus; - U32 mID; friend void LLThreadLocalData::create(LLThread* threadp); LLThreadLocalData* mThreadLocalData; @@ -165,6 +171,12 @@ protected: // mRunCondition->unlock(); }; +#ifdef SHOW_ASSERT +#define ASSERT_SINGLE_THREAD do { static AIThreadID first_thread_id; llassert(first_thread_id.equals_current_thread()); } while(0) +#else +#define ASSERT_SINGLE_THREAD do { } while(0) +#endif + //============================================================================ #define MUTEX_DEBUG (LL_DEBUG || LL_RELEASE_WITH_DEBUG_INFO) @@ -180,32 +192,29 @@ protected: class LL_COMMON_API LLMutexBase { public: - typedef enum - { - NO_THREAD = 0xFFFFFFFF - } e_locking_thread; - LLMutexBase() ; void lock(); // blocks void unlock(); // Returns true if lock was obtained successfully. - bool tryLock() { return !APR_STATUS_IS_EBUSY(apr_thread_mutex_trylock(mAPRMutexp)); } + bool tryLock(); - // non-blocking, but does do a lock/unlock so not free - bool isLocked() { bool is_not_locked = tryLock(); if (is_not_locked) unlock(); return !is_not_locked; } + // Returns true if a call to lock() would block (returns false if self-locked()). + bool isLocked() const; // Returns true if locked by this thread. bool isSelfLocked() const; - // get ID of locking thread - U32 lockingThread() const { return mLockingThread; } - protected: // mAPRMutexp is initialized and uninitialized in the derived class. apr_thread_mutex_t* mAPRMutexp; mutable U32 mCount; - mutable U32 mLockingThread; + mutable AIThreadID mLockingThread; + +private: + // Disallow copy construction and assignment. + LLMutexBase(LLMutexBase const&); + LLMutexBase& operator=(LLMutexBase const&); }; class LL_COMMON_API LLMutex : public LLMutexBase @@ -225,10 +234,6 @@ public: protected: LLAPRPool mPool; -private: - // Disable copy construction, as si teh bomb!!! -SG - LLMutex(const LLMutex&); - LLMutex& operator=(const LLMutex&); }; #if APR_HAS_THREADS @@ -289,7 +294,7 @@ private: LLMutexBase* mMutex; }; -class AIRWLock +class LL_COMMON_API AIRWLock { public: AIRWLock(LLAPRPool& parent = LLThread::tldata().mRootPool) : diff --git a/indra/llcrashlogger/llcrashlogger.cpp b/indra/llcrashlogger/llcrashlogger.cpp index a8be44313..c42697ad9 100644 --- a/indra/llcrashlogger/llcrashlogger.cpp +++ b/indra/llcrashlogger/llcrashlogger.cpp @@ -371,6 +371,9 @@ void LLCrashLogger::updateApplication(const std::string& message) bool LLCrashLogger::init() { + // Initialize curl + AICurlInterface::initCurl(); + // We assume that all the logs we're looking for reside on the current drive gDirUtilp->initAppDirs("SecondLife"); diff --git a/indra/llimage/llpngwrapper.h b/indra/llimage/llpngwrapper.h index 7def0c713..276709d1a 100644 --- a/indra/llimage/llpngwrapper.h +++ b/indra/llimage/llpngwrapper.h @@ -32,7 +32,12 @@ #ifndef LL_LLPNGWRAPPER_H #define LL_LLPNGWRAPPER_H +#ifdef LL_STANDALONE +#include +#else +// Workaround for wrongly packaged prebuilt. #include "libpng15/png.h" +#endif #include "llimage.h" class LLPngWrapper diff --git a/indra/llmessage/CMakeLists.txt b/indra/llmessage/CMakeLists.txt index b09245ba3..aac669137 100644 --- a/indra/llmessage/CMakeLists.txt +++ b/indra/llmessage/CMakeLists.txt @@ -29,7 +29,9 @@ set(llmessage_SOURCE_FILES llchainio.cpp llcircuit.cpp llclassifiedflags.cpp - llcurl.cpp + aicurl.cpp + debug_libcurl.cpp + aicurlthread.cpp lldatapacker.cpp lldispatcher.cpp llfiltersd2xmlrpc.cpp @@ -66,8 +68,6 @@ set(llmessage_SOURCE_FILES llsdmessage.cpp llsdmessagebuilder.cpp llsdmessagereader.cpp - llsdrpcclient.cpp - llsdrpcserver.cpp llservicebuilder.cpp llservice.cpp llstoredmessage.cpp @@ -117,6 +117,10 @@ set(llmessage_HEADER_FILES llcircuit.h llclassifiedflags.h llcurl.h + aicurl.h + debug_libcurl.h + aicurlprivate.h + aicurlthread.h lldatapacker.h lldbstrings.h lldispatcher.h @@ -164,8 +168,6 @@ set(llmessage_HEADER_FILES llsdmessage.h llsdmessagebuilder.h llsdmessagereader.h - llsdrpcclient.h - llsdrpcserver.h llservice.h llservicebuilder.h llstoredmessage.h diff --git a/indra/llmessage/aicurl.cpp b/indra/llmessage/aicurl.cpp new file mode 100644 index 000000000..8237fcaa4 --- /dev/null +++ b/indra/llmessage/aicurl.cpp @@ -0,0 +1,1502 @@ +/** + * @file aicurl.cpp + * @brief Implementation of AICurl. + * + * Copyright (c) 2012, Aleric Inglewood. + * Copyright (C) 2010, Linden Research, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution. + * + * CHANGELOG + * and additional copyright holders. + * + * 17/03/2012 + * Initial version, written by Aleric Inglewood @ SL + * + * 20/03/2012 + * Added copyright notice for Linden Lab for those parts that were + * copied or derived from llcurl.cpp. The code of those parts are + * already in their own llcurl.cpp, so they do not ever need to + * even look at this file; the reason I added the copyright notice + * is to make clear that I am not the author of 100% of this code + * and hence I cannot change the license of it. + */ + +#include "linden_common.h" + +#if LL_WINDOWS +#include //remove classic winsock from windows.h +#endif +#define OPENSSL_THREAD_DEFINES +#include // OPENSSL_THREADS +#include +#include + +#include "aicurl.h" +#include "llbufferstream.h" +#include "llsdserialize.h" +#include "aithreadsafe.h" +#include "llqueuedthread.h" +#include "lltimer.h" // ms_sleep +#include "llproxy.h" +#include "llhttpstatuscodes.h" +#ifdef CWDEBUG +#include +#endif + +//================================================================================== +// Local variables. +// + +namespace { + +struct CertificateAuthority { + std::string file; + std::string path; +}; + +AIThreadSafeSimpleDC gCertificateAuthority; +typedef AIAccess CertificateAuthority_wat; +typedef AIAccessConst CertificateAuthority_rat; + +enum gSSLlib_type { + ssl_unknown, + ssl_openssl, + ssl_gnutls, + ssl_nss +}; + +// No locking needed: initialized before threads are created, and subsequently only read. +gSSLlib_type gSSLlib; +bool gSetoptParamsNeedDup; +void (*statemachines_flush_hook)(void); + +} // namespace + +// See http://www.openssl.org/docs/crypto/threads.html: +// CRYPTO_THREADID and associated functions were introduced in OpenSSL 1.0.0 to replace +// (actually, deprecate) the previous CRYPTO_set_id_callback(), CRYPTO_get_id_callback(), +// and CRYPTO_thread_id() functions which assumed thread IDs to always be represented by +// 'unsigned long'. +#define HAVE_CRYPTO_THREADID (OPENSSL_VERSION_NUMBER >= (1 << 28)) + +//----------------------------------------------------------------------------------- +// Needed for thread-safe openSSL operation. + +// Must be defined in global namespace. +struct CRYPTO_dynlock_value +{ + AIRWLock rwlock; +}; + +namespace { + +AIRWLock* ssl_rwlock_array; + +// OpenSSL locking function. +void ssl_locking_function(int mode, int n, char const* file, int line) +{ + if ((mode & CRYPTO_LOCK)) + { + if ((mode & CRYPTO_READ)) + ssl_rwlock_array[n].rdlock(); + else + ssl_rwlock_array[n].wrlock(); + } + else + { + if ((mode & CRYPTO_READ)) + ssl_rwlock_array[n].rdunlock(); + else + ssl_rwlock_array[n].wrunlock(); + } +} + +#if HAVE_CRYPTO_THREADID +// OpenSSL uniq id function. +void ssl_id_function(CRYPTO_THREADID* thread_id) +{ +#if LL_WINDOWS || LL_DARWIN // apr_os_thread_current() returns a pointer, + CRYPTO_THREADID_set_pointer(thread_id, apr_os_thread_current()); +#else // else it returns an unsigned long. + CRYPTO_THREADID_set_numeric(thread_id, apr_os_thread_current()); +#endif +} +#endif // HAVE_CRYPTO_THREADID + +// OpenSSL allocate and initialize dynamic crypto lock. +CRYPTO_dynlock_value* ssl_dyn_create_function(char const* file, int line) +{ + return new CRYPTO_dynlock_value; +} + +// OpenSSL destroy dynamic crypto lock. +void ssl_dyn_destroy_function(CRYPTO_dynlock_value* l, char const* file, int line) +{ + delete l; +} + +// OpenSSL dynamic locking function. +void ssl_dyn_lock_function(int mode, CRYPTO_dynlock_value* l, char const* file, int line) +{ + if ((mode & CRYPTO_LOCK)) + { + if ((mode & CRYPTO_READ)) + l->rwlock.rdlock(); + else + l->rwlock.wrlock(); + } + else + { + if ((mode & CRYPTO_READ)) + l->rwlock.rdunlock(); + else + l->rwlock.wrunlock(); + } +} + +typedef void (*ssl_locking_function_type)(int, int, char const*, int); +#if HAVE_CRYPTO_THREADID +typedef void (*ssl_id_function_type)(CRYPTO_THREADID*); +#else +typedef unsigned long (*ulong_thread_id_function_type)(void); +#endif +typedef CRYPTO_dynlock_value* (*ssl_dyn_create_function_type)(char const*, int); +typedef void (*ssl_dyn_destroy_function_type)(CRYPTO_dynlock_value*, char const*, int); +typedef void (*ssl_dyn_lock_function_type)(int, CRYPTO_dynlock_value*, char const*, int); + +ssl_locking_function_type old_ssl_locking_function; +#if HAVE_CRYPTO_THREADID +ssl_id_function_type old_ssl_id_function; +#else +ulong_thread_id_function_type old_ulong_thread_id_function; +#endif +ssl_dyn_create_function_type old_ssl_dyn_create_function; +ssl_dyn_destroy_function_type old_ssl_dyn_destroy_function; +ssl_dyn_lock_function_type old_ssl_dyn_lock_function; + +#if LL_WINDOWS +static unsigned long __cdecl apr_os_thread_current_wrapper() +{ + return (unsigned long)apr_os_thread_current(); +} +#endif + +// Set for openssl-1.0.1...1.0.1c. +static bool need_renegotiation_hack = false; + +// Initialize OpenSSL library for thread-safety. +void ssl_init(void) +{ + // The version identifier format is: MMNNFFPPS: major minor fix patch status. + int const compiled_openSSL_major = (OPENSSL_VERSION_NUMBER >> 28) & 0xff; + int const compiled_openSSL_minor = (OPENSSL_VERSION_NUMBER >> 20) & 0xff; + unsigned long const ssleay = SSLeay(); + int const linked_openSSL_major = (ssleay >> 28) & 0xff; + int const linked_openSSL_minor = (ssleay >> 20) & 0xff; + // Check if dynamically loaded version is compatible with the one we compiled against. + // As off version 1.0.0 also minor versions are compatible. + if (linked_openSSL_major != compiled_openSSL_major || + (linked_openSSL_major == 0 && linked_openSSL_minor != compiled_openSSL_minor)) + { + llerrs << "The viewer was compiled against " << OPENSSL_VERSION_TEXT << + " but linked against " << SSLeay_version(SSLEAY_VERSION) << + ". Those versions are not compatible." << llendl; + } + // Static locks vector. + ssl_rwlock_array = new AIRWLock[CRYPTO_num_locks()]; + // Static locks callbacks. + old_ssl_locking_function = CRYPTO_get_locking_callback(); +#if HAVE_CRYPTO_THREADID + old_ssl_id_function = CRYPTO_THREADID_get_callback(); +#else + old_ulong_thread_id_function = CRYPTO_get_id_callback(); +#endif + CRYPTO_set_locking_callback(&ssl_locking_function); + // Setting this avoids the need for a thread-local error number facility, which is hard to check. +#if HAVE_CRYPTO_THREADID + CRYPTO_THREADID_set_callback(&ssl_id_function); +#else +#if LL_WINDOWS + CRYPTO_set_id_callback(&apr_os_thread_current_wrapper); +#else + CRYPTO_set_id_callback(&apr_os_thread_current); +#endif +#endif + // Dynamic locks callbacks. + old_ssl_dyn_create_function = CRYPTO_get_dynlock_create_callback(); + old_ssl_dyn_lock_function = CRYPTO_get_dynlock_lock_callback(); + old_ssl_dyn_destroy_function = CRYPTO_get_dynlock_destroy_callback(); + CRYPTO_set_dynlock_create_callback(&ssl_dyn_create_function); + CRYPTO_set_dynlock_lock_callback(&ssl_dyn_lock_function); + CRYPTO_set_dynlock_destroy_callback(&ssl_dyn_destroy_function); + need_renegotiation_hack = (0x10001000UL <= ssleay); + llinfos << "Successful initialization of " << + SSLeay_version(SSLEAY_VERSION) << " (0x" << std::hex << SSLeay() << ")." << llendl; +} + +// Cleanup OpenSSL library thread-safety. +void ssl_cleanup(void) +{ + // Dynamic locks callbacks. + CRYPTO_set_dynlock_destroy_callback(old_ssl_dyn_destroy_function); + CRYPTO_set_dynlock_lock_callback(old_ssl_dyn_lock_function); + CRYPTO_set_dynlock_create_callback(old_ssl_dyn_create_function); + // Static locks callbacks. +#if HAVE_CRYPTO_THREADID + CRYPTO_THREADID_set_callback(old_ssl_id_function); +#else + CRYPTO_set_id_callback(old_ulong_thread_id_function); +#endif + CRYPTO_set_locking_callback(old_ssl_locking_function); + // Static locks vector. + delete [] ssl_rwlock_array; +} + +} // namespace openSSL +//----------------------------------------------------------------------------------- + +static unsigned int encoded_version(int major, int minor, int patch) +{ + return (major << 16) | (minor << 8) | patch; +} + +//================================================================================== +// External API +// + +#undef AICurlPrivate + +namespace AICurlInterface { + +// MAIN-THREAD +void initCurl(void (*flush_hook)()) +{ + DoutEntering(dc::curl, "AICurlInterface::initCurl(" << (void*)flush_hook << ")"); + + llassert(LLThread::getRunning() == 0); // We must not call curl_global_init unless we are the only thread. + CURLcode res = curl_global_init(CURL_GLOBAL_ALL); + if (res != CURLE_OK) + { + llerrs << "curl_global_init(CURL_GLOBAL_ALL) failed." << llendl; + } + + // Print version and do some feature sanity checks. + { + curl_version_info_data* version_info = curl_version_info(CURLVERSION_NOW); + + llassert_always(version_info->age >= 0); + if (version_info->age < 1) + { + llwarns << "libcurl's age is 0; no ares support." << llendl; + } + llassert_always((version_info->features & CURL_VERSION_SSL)); // SSL support, added in libcurl 7.10. + if (!(version_info->features & CURL_VERSION_ASYNCHDNS)) // Asynchronous name lookups (added in libcurl 7.10.7). + { + llwarns << "libcurl was not compiled with support for asynchronous name lookups!" << llendl; + } + if (!version_info->ssl_version) + { + llerrs << "This libcurl has no SSL support!" << llendl; + } + + llinfos << "Successful initialization of libcurl " << + version_info->version << " (0x" << std::hex << version_info->version_num << "), (" << + version_info->ssl_version; + if (version_info->libz_version) + { + llcont << ", libz/" << version_info->libz_version; + } + llcont << ")." << llendl; + + // Detect SSL library used. + gSSLlib = ssl_unknown; + std::string ssl_version(version_info->ssl_version); + if (ssl_version.find("OpenSSL") != std::string::npos) + gSSLlib = ssl_openssl; // See http://www.openssl.org/docs/crypto/threads.html#DESCRIPTION + else if (ssl_version.find("GnuTLS") != std::string::npos) + gSSLlib = ssl_gnutls; // See http://www.gnu.org/software/gnutls/manual/html_node/Thread-safety.html + else if (ssl_version.find("NSS") != std::string::npos) + gSSLlib = ssl_nss; // Supposedly thread-safe without any requirements. + + // Set up thread-safety requirements of underlaying SSL library. + // See http://curl.haxx.se/libcurl/c/libcurl-tutorial.html + switch (gSSLlib) + { + case ssl_unknown: + { + llerrs << "Unknown SSL library \"" << version_info->ssl_version << "\", required actions for thread-safe handling are unknown! Bailing out." << llendl; + } + case ssl_openssl: + { +#ifndef OPENSSL_THREADS + llerrs << "OpenSSL was not configured with thread support! Bailing out." << llendl; +#endif + ssl_init(); + } + case ssl_gnutls: + { + // I don't think we ever get here, do we? --Aleric + llassert_always(gSSLlib != ssl_gnutls); + // If we do, then didn't curl_global_init already call gnutls_global_init? + // It seems there is nothing to do for us here. + } + case ssl_nss: + { + break; // No requirements. + } + } + + // Before version 7.17.0, strings were not copied. Instead the user was forced keep them available until libcurl no longer needed them. + gSetoptParamsNeedDup = (version_info->version_num < encoded_version(7, 17, 0)); + if (gSetoptParamsNeedDup) + { + llwarns << "Your libcurl version is too old." << llendl; + } + llassert_always(!gSetoptParamsNeedDup); // Might add support later. + } + + // Called in cleanupCurl. + statemachines_flush_hook = flush_hook; +} + +// MAIN-THREAD +void cleanupCurl(void) +{ + using namespace AICurlPrivate; + + DoutEntering(dc::curl, "AICurlInterface::cleanupCurl()"); + + stopCurlThread(); + if (CurlMultiHandle::getTotalMultiHandles() != 0) + llwarns << "Not all CurlMultiHandle objects were destroyed!" << llendl; + if (statemachines_flush_hook) + (*statemachines_flush_hook)(); + Stats::print(); + ssl_cleanup(); + + llassert(LLThread::getRunning() <= (curlThreadIsRunning() ? 1 : 0)); // We must not call curl_global_cleanup unless we are the only thread left. + curl_global_cleanup(); +} + +// THREAD-SAFE +std::string getVersionString(void) +{ + // libcurl is thread safe, no locking needed. + return curl_version(); +} + +// THREAD-SAFE +void setCAFile(std::string const& file) +{ + CertificateAuthority_wat CertificateAuthority_w(gCertificateAuthority); + CertificateAuthority_w->file = file; +} + +// This function is not called from anywhere, but provided as part of AICurlInterface because setCAFile is. +// THREAD-SAFE +void setCAPath(std::string const& path) +{ + CertificateAuthority_wat CertificateAuthority_w(gCertificateAuthority); + CertificateAuthority_w->path = path; +} + +// THREAD-SAFE +std::string strerror(CURLcode errorcode) +{ + // libcurl is thread safe, no locking needed. + return curl_easy_strerror(errorcode); +} + +//----------------------------------------------------------------------------- +// class Responder +// + +Responder::Responder(void) : mReferenceCount(0) +{ + DoutEntering(dc::curl, "AICurlInterface::Responder() with this = " << (void*)this); +} + +Responder::~Responder() +{ + DoutEntering(dc::curl, "AICurlInterface::Responder::~Responder() with this = " << (void*)this << "; mReferenceCount = " << mReferenceCount); + llassert(mReferenceCount == 0); +} + +void Responder::setURL(std::string const& url) +{ + // setURL is called from llhttpclient.cpp (request()), before calling any of the below (of course). + // We don't need locking here therefore; it's a case of initializing before use. + mURL = url; +} + +// Called with HTML header. +// virtual +void Responder::completedHeader(U32, std::string const&, LLSD const&) +{ + // Nothing. +} + +// Called with HTML body. +// virtual +void Responder::completedRaw(U32 status, std::string const& reason, LLChannelDescriptors const& channels, LLIOPipe::buffer_ptr_t const& buffer) +{ + LLSD content; + LLBufferStream istr(channels, buffer.get()); + if (!LLSDSerialize::fromXML(content, istr)) + { + llinfos << "Failed to deserialize LLSD. " << mURL << " [" << status << "]: " << reason << llendl; + } + + // Allow derived class to override at this point. + completed(status, reason, content); +} + +void Responder::fatalError(std::string const& reason) +{ + llwarns << "Responder::fatalError(\"" << reason << "\") is called (" << mURL << "). Passing it to Responder::completed with fake HTML error status and empty HTML body!" << llendl; + completed(U32_MAX, reason, LLSD()); +} + +// virtual +void Responder::completed(U32 status, std::string const& reason, LLSD const& content) +{ + // HTML status good? + if (200 <= status && status < 300) + { + // Allow derived class to override at this point. + result(content); + } + else + { + // Allow derived class to override at this point. + errorWithContent(status, reason, content); + } +} + +// virtual +void Responder::errorWithContent(U32 status, std::string const& reason, LLSD const&) +{ + // Allow derived class to override at this point. + error(status, reason); +} + +// virtual +void Responder::error(U32 status, std::string const& reason) +{ + llinfos << mURL << " [" << status << "]: " << reason << llendl; +} + +// virtual +void Responder::result(LLSD const&) +{ + // Nothing. +} + +// Friend functions. + +void intrusive_ptr_add_ref(Responder* responder) +{ + responder->mReferenceCount++; +} + +void intrusive_ptr_release(Responder* responder) +{ + if (--responder->mReferenceCount == 0) + { + delete responder; + } +} + +} // namespace AICurlInterface +//================================================================================== + + +//================================================================================== +// Local implementation. +// + +namespace AICurlPrivate { + +//static +LLAtomicU32 Stats::easy_calls; +LLAtomicU32 Stats::easy_errors; +LLAtomicU32 Stats::easy_init_calls; +LLAtomicU32 Stats::easy_init_errors; +LLAtomicU32 Stats::easy_cleanup_calls; +LLAtomicU32 Stats::multi_calls; +LLAtomicU32 Stats::multi_errors; + +//static +void Stats::print(void) +{ + llinfos_nf << "============ CURL STATS ============" << llendl; + llinfos_nf << " Curl multi errors/calls : " << std::dec << multi_errors << "/" << multi_calls << llendl; + llinfos_nf << " Curl easy errors/calls : " << std::dec << easy_errors << "/" << easy_calls << llendl; + llinfos_nf << " curl_easy_init() errors/calls : " << std::dec << easy_init_errors << "/" << easy_init_calls << llendl; + llinfos_nf << " Current number of curl easy handles: " << std::dec << (easy_init_calls - easy_init_errors - easy_cleanup_calls) << llendl; + llinfos_nf << "========= END OF CURL STATS =========" << llendl; +} + +// THREAD-SAFE +void handle_multi_error(CURLMcode code) +{ + Stats::multi_errors++; + llinfos << "curl multi error detected: " << curl_multi_strerror(code) << + "; (errors/calls = " << Stats::multi_errors << "/" << Stats::multi_calls << ")" << llendl; +} + +//============================================================================= +// AICurlEasyRequest (base classes) +// + +//----------------------------------------------------------------------------- +// CurlEasyHandle + +// THREAD-SAFE +//static +void CurlEasyHandle::handle_easy_error(CURLcode code) +{ + char* error_buffer = LLThreadLocalData::tldata().mCurlErrorBuffer; + llinfos << "curl easy error detected: " << curl_easy_strerror(code); + if (error_buffer && *error_buffer != '\0') + { + llcont << ": " << error_buffer; + } + Stats::easy_errors++; + llcont << "; (errors/calls = " << Stats::easy_errors << "/" << Stats::easy_calls << ")" << llendl; +} + +// Throws AICurlNoEasyHandle. +CurlEasyHandle::CurlEasyHandle(void) : mActiveMultiHandle(NULL), mErrorBuffer(NULL), mQueuedForRemoval(false) +#ifdef SHOW_ASSERT + , mRemovedPerCommand(true) +#endif +{ + mEasyHandle = curl_easy_init(); +#if 0 + // Fake curl_easy_init() failures: throw once every 10 times (for debugging purposes). + static int count = 0; + if (mEasyHandle && (++count % 10) == 5) + { + curl_easy_cleanup(mEasyHandle); + mEasyHandle = NULL; + } +#endif + Stats::easy_init_calls++; + if (!mEasyHandle) + { + Stats::easy_init_errors++; + throw AICurlNoEasyHandle("curl_easy_init() returned NULL"); + } +} + +#if 0 // Not used +CurlEasyHandle::CurlEasyHandle(CurlEasyHandle const& orig) : mActiveMultiHandle(NULL), mErrorBuffer(NULL) +#ifdef SHOW_ASSERT + , mRemovedPerCommand(true) +#endif +{ + mEasyHandle = curl_easy_duphandle(orig.mEasyHandle); + Stats::easy_init_calls++; + if (!mEasyHandle) + { + Stats::easy_init_errors++; + throw AICurlNoEasyHandle("curl_easy_duphandle() returned NULL"); + } +} +#endif + +CurlEasyHandle::~CurlEasyHandle() +{ + llassert(!mActiveMultiHandle); + curl_easy_cleanup(mEasyHandle); + Stats::easy_cleanup_calls++; +} + +//static +char* CurlEasyHandle::getTLErrorBuffer(void) +{ + LLThreadLocalData& tldata = LLThreadLocalData::tldata(); + if (!tldata.mCurlErrorBuffer) + { + tldata.mCurlErrorBuffer = new char[CURL_ERROR_SIZE]; + } + return tldata.mCurlErrorBuffer; +} + +void CurlEasyHandle::setErrorBuffer(void) +{ + char* error_buffer = getTLErrorBuffer(); + if (mErrorBuffer != error_buffer) + { + mErrorBuffer = error_buffer; + CURLcode res = curl_easy_setopt(mEasyHandle, CURLOPT_ERRORBUFFER, error_buffer); + if (res != CURLE_OK) + { + llwarns << "curl_easy_setopt(" << (void*)mEasyHandle << "CURLOPT_ERRORBUFFER, " << (void*)error_buffer << ") failed with error " << res << llendl; + mErrorBuffer = NULL; + } + } +} + +CURLcode CurlEasyHandle::getinfo_priv(CURLINFO info, void* data) +{ + setErrorBuffer(); + return check_easy_code(curl_easy_getinfo(mEasyHandle, info, data)); +} + +char* CurlEasyHandle::escape(char* url, int length) +{ + return curl_easy_escape(mEasyHandle, url, length); +} + +char* CurlEasyHandle::unescape(char* url, int inlength , int* outlength) +{ + return curl_easy_unescape(mEasyHandle, url, inlength, outlength); +} + +CURLcode CurlEasyHandle::perform(void) +{ + llassert(!mActiveMultiHandle); + setErrorBuffer(); + return check_easy_code(curl_easy_perform(mEasyHandle)); +} + +CURLcode CurlEasyHandle::pause(int bitmask) +{ + setErrorBuffer(); + return check_easy_code(curl_easy_pause(mEasyHandle, bitmask)); +} + +CURLMcode CurlEasyHandle::add_handle_to_multi(AICurlEasyRequest_wat& curl_easy_request_w, CURLM* multi) +{ + llassert_always(!mActiveMultiHandle && multi); + mActiveMultiHandle = multi; + CURLMcode res = check_multi_code(curl_multi_add_handle(multi, mEasyHandle)); + added_to_multi_handle(curl_easy_request_w); + return res; +} + +CURLMcode CurlEasyHandle::remove_handle_from_multi(AICurlEasyRequest_wat& curl_easy_request_w, CURLM* multi) +{ + llassert_always(mActiveMultiHandle && mActiveMultiHandle == multi); + mActiveMultiHandle = NULL; + CURLMcode res = check_multi_code(curl_multi_remove_handle(multi, mEasyHandle)); + removed_from_multi_handle(curl_easy_request_w); + mPostField = NULL; + return res; +} + +void intrusive_ptr_add_ref(ThreadSafeCurlEasyRequest* threadsafe_curl_easy_request) +{ + threadsafe_curl_easy_request->mReferenceCount++; +} + +void intrusive_ptr_release(ThreadSafeCurlEasyRequest* threadsafe_curl_easy_request) +{ + if (--threadsafe_curl_easy_request->mReferenceCount == 0) + { + delete threadsafe_curl_easy_request; + } +} + +CURLcode CurlEasyHandle::setopt(CURLoption option, long parameter) +{ + llassert((CURLOPTTYPE_LONG <= option && option < CURLOPTTYPE_LONG + 1000) || + (sizeof(curl_off_t) == sizeof(long) && + CURLOPTTYPE_OFF_T <= option && option < CURLOPTTYPE_OFF_T + 1000)); + llassert(!mActiveMultiHandle); + setErrorBuffer(); + return check_easy_code(curl_easy_setopt(mEasyHandle, option, parameter)); +} + +// The standard requires that sizeof(long) < sizeof(long long), so it's safe to overload like this. +// We assume that one of them is 64 bit, the size of curl_off_t. +CURLcode CurlEasyHandle::setopt(CURLoption option, long long parameter) +{ + llassert(sizeof(curl_off_t) == sizeof(long long) && + CURLOPTTYPE_OFF_T <= option && option < CURLOPTTYPE_OFF_T + 1000); + llassert(!mActiveMultiHandle); + setErrorBuffer(); + return check_easy_code(curl_easy_setopt(mEasyHandle, option, parameter)); +} + +CURLcode CurlEasyHandle::setopt(CURLoption option, void const* parameter) +{ + llassert(CURLOPTTYPE_OBJECTPOINT <= option && option < CURLOPTTYPE_OBJECTPOINT + 1000); + setErrorBuffer(); + return check_easy_code(curl_easy_setopt(mEasyHandle, option, parameter)); +} + +#define DEFINE_FUNCTION_SETOPT1(function_type, opt1) \ + CURLcode CurlEasyHandle::setopt(CURLoption option, function_type parameter) \ + { \ + llassert(option == opt1); \ + setErrorBuffer(); \ + return check_easy_code(curl_easy_setopt(mEasyHandle, option, parameter)); \ + } + +#define DEFINE_FUNCTION_SETOPT3(function_type, opt1, opt2, opt3) \ + CURLcode CurlEasyHandle::setopt(CURLoption option, function_type parameter) \ + { \ + llassert(option == opt1 || option == opt2 || option == opt3); \ + setErrorBuffer(); \ + return check_easy_code(curl_easy_setopt(mEasyHandle, option, parameter)); \ + } + +#define DEFINE_FUNCTION_SETOPT4(function_type, opt1, opt2, opt3, opt4) \ + CURLcode CurlEasyHandle::setopt(CURLoption option, function_type parameter) \ + { \ + llassert(option == opt1 || option == opt2 || option == opt3 || option == opt4); \ + setErrorBuffer(); \ + return check_easy_code(curl_easy_setopt(mEasyHandle, option, parameter)); \ + } + +DEFINE_FUNCTION_SETOPT1(curl_debug_callback, CURLOPT_DEBUGFUNCTION) +DEFINE_FUNCTION_SETOPT4(curl_write_callback, CURLOPT_HEADERFUNCTION, CURLOPT_WRITEFUNCTION, CURLOPT_INTERLEAVEFUNCTION, CURLOPT_READFUNCTION) +//DEFINE_FUNCTION_SETOPT1(curl_read_callback, CURLOPT_READFUNCTION) +DEFINE_FUNCTION_SETOPT1(curl_ssl_ctx_callback, CURLOPT_SSL_CTX_FUNCTION) +DEFINE_FUNCTION_SETOPT3(curl_conv_callback, CURLOPT_CONV_FROM_NETWORK_FUNCTION, CURLOPT_CONV_TO_NETWORK_FUNCTION, CURLOPT_CONV_FROM_UTF8_FUNCTION) +#if 0 // Not used by the viewer. +DEFINE_FUNCTION_SETOPT1(curl_progress_callback, CURLOPT_PROGRESSFUNCTION) +DEFINE_FUNCTION_SETOPT1(curl_seek_callback, CURLOPT_SEEKFUNCTION) +DEFINE_FUNCTION_SETOPT1(curl_ioctl_callback, CURLOPT_IOCTLFUNCTION) +DEFINE_FUNCTION_SETOPT1(curl_sockopt_callback, CURLOPT_SOCKOPTFUNCTION) +DEFINE_FUNCTION_SETOPT1(curl_opensocket_callback, CURLOPT_OPENSOCKETFUNCTION) +DEFINE_FUNCTION_SETOPT1(curl_closesocket_callback, CURLOPT_CLOSESOCKETFUNCTION) +DEFINE_FUNCTION_SETOPT1(curl_sshkeycallback, CURLOPT_SSH_KEYFUNCTION) +DEFINE_FUNCTION_SETOPT1(curl_chunk_bgn_callback, CURLOPT_CHUNK_BGN_FUNCTION) +DEFINE_FUNCTION_SETOPT1(curl_chunk_end_callback, CURLOPT_CHUNK_END_FUNCTION) +DEFINE_FUNCTION_SETOPT1(curl_fnmatch_callback, CURLOPT_FNMATCH_FUNCTION) +#endif + +//----------------------------------------------------------------------------- +// CurlEasyRequest + +void CurlEasyRequest::setoptString(CURLoption option, std::string const& value) +{ + llassert(!gSetoptParamsNeedDup); + setopt(option, value.c_str()); +} + +void CurlEasyRequest::setPost(AIPostFieldPtr const& postdata, S32 size) +{ + llassert_always(postdata->data()); + + Dout(dc::curl, "POST size is " << size << " bytes: \"" << libcwd::buf2str(postdata->data(), size) << "\"."); + setPostField(postdata); // Make sure the data stays around until we don't need it anymore. + + setPost_raw(size, postdata->data()); +} + +void CurlEasyRequest::setPost_raw(S32 size, char const* data) +{ + if (!data) + { + // data == NULL when we're going to read the data using CURLOPT_READFUNCTION. + Dout(dc::curl, "POST size is " << size << " bytes."); + } + + // The server never replies with 100-continue, so suppress the "Expect: 100-continue" header that libcurl adds by default. + addHeader("Expect:"); + if (size > 0) + { + addHeader("Connection: keep-alive"); + addHeader("Keep-alive: 300"); + } + setopt(CURLOPT_POSTFIELDSIZE, size); + setopt(CURLOPT_POSTFIELDS, data); +} + +ThreadSafeCurlEasyRequest* CurlEasyRequest::get_lockobj(void) +{ + return static_cast(AIThreadSafeSimpleDC::wrapper_cast(this)); +} + +//static +size_t CurlEasyRequest::headerCallback(char* ptr, size_t size, size_t nmemb, void* userdata) +{ + CurlEasyRequest* self = static_cast(userdata); + ThreadSafeCurlEasyRequest* lockobj = self->get_lockobj(); + AICurlEasyRequest_wat lock_self(*lockobj); + return self->mHeaderCallback(ptr, size, nmemb, self->mHeaderCallbackUserData); +} + +void CurlEasyRequest::setHeaderCallback(curl_write_callback callback, void* userdata) +{ + mHeaderCallback = callback; + mHeaderCallbackUserData = userdata; + setopt(CURLOPT_HEADERFUNCTION, callback ? &CurlEasyRequest::headerCallback : NULL); + setopt(CURLOPT_WRITEHEADER, userdata ? this : NULL); +} + +//static +size_t CurlEasyRequest::writeCallback(char* ptr, size_t size, size_t nmemb, void* userdata) +{ + CurlEasyRequest* self = static_cast(userdata); + ThreadSafeCurlEasyRequest* lockobj = self->get_lockobj(); + AICurlEasyRequest_wat lock_self(*lockobj); + return self->mWriteCallback(ptr, size, nmemb, self->mWriteCallbackUserData); +} + +void CurlEasyRequest::setWriteCallback(curl_write_callback callback, void* userdata) +{ + mWriteCallback = callback; + mWriteCallbackUserData = userdata; + setopt(CURLOPT_WRITEFUNCTION, callback ? &CurlEasyRequest::writeCallback : NULL); + setopt(CURLOPT_WRITEDATA, userdata ? this : NULL); +} + +//static +size_t CurlEasyRequest::readCallback(char* ptr, size_t size, size_t nmemb, void* userdata) +{ + CurlEasyRequest* self = static_cast(userdata); + ThreadSafeCurlEasyRequest* lockobj = self->get_lockobj(); + AICurlEasyRequest_wat lock_self(*lockobj); + return self->mReadCallback(ptr, size, nmemb, self->mReadCallbackUserData); +} + +void CurlEasyRequest::setReadCallback(curl_read_callback callback, void* userdata) +{ + mReadCallback = callback; + mReadCallbackUserData = userdata; + setopt(CURLOPT_READFUNCTION, callback ? &CurlEasyRequest::readCallback : NULL); + setopt(CURLOPT_READDATA, userdata ? this : NULL); +} + +//static +CURLcode CurlEasyRequest::SSLCtxCallback(CURL* curl, void* sslctx, void* userdata) +{ + CurlEasyRequest* self = static_cast(userdata); + ThreadSafeCurlEasyRequest* lockobj = self->get_lockobj(); + AICurlEasyRequest_wat lock_self(*lockobj); + return self->mSSLCtxCallback(curl, sslctx, self->mSSLCtxCallbackUserData); +} + +void CurlEasyRequest::setSSLCtxCallback(curl_ssl_ctx_callback callback, void* userdata) +{ + mSSLCtxCallback = callback; + mSSLCtxCallbackUserData = userdata; + setopt(CURLOPT_SSL_CTX_FUNCTION, callback ? &CurlEasyRequest::SSLCtxCallback : NULL); + setopt(CURLOPT_SSL_CTX_DATA, this); +} + +#define llmaybewarns lllog(LLApp::isExiting() ? LLError::LEVEL_INFO : LLError::LEVEL_WARN, NULL, NULL, false, true) + +static size_t noHeaderCallback(char* ptr, size_t size, size_t nmemb, void* userdata) +{ + llmaybewarns << "Calling noHeaderCallback(); curl session aborted." << llendl; + return 0; // Cause a CURL_WRITE_ERROR +} + +static size_t noWriteCallback(char* ptr, size_t size, size_t nmemb, void* userdata) +{ + llmaybewarns << "Calling noWriteCallback(); curl session aborted." << llendl; + return 0; // Cause a CURL_WRITE_ERROR +} + +static size_t noReadCallback(char* ptr, size_t size, size_t nmemb, void* userdata) +{ + llmaybewarns << "Calling noReadCallback(); curl session aborted." << llendl; + return CURL_READFUNC_ABORT; // Cause a CURLE_ABORTED_BY_CALLBACK +} + +static CURLcode noSSLCtxCallback(CURL* curl, void* sslctx, void* parm) +{ + llmaybewarns << "Calling noSSLCtxCallback(); curl session aborted." << llendl; + return CURLE_ABORTED_BY_CALLBACK; +} + +void CurlEasyRequest::revokeCallbacks(void) +{ + if (mHeaderCallback == &noHeaderCallback && + mWriteCallback == &noWriteCallback && + mReadCallback == &noReadCallback && + mSSLCtxCallback == &noSSLCtxCallback) + { + // Already revoked. + return; + } + mHeaderCallback = &noHeaderCallback; + mWriteCallback = &noWriteCallback; + mReadCallback = &noReadCallback; + mSSLCtxCallback = &noSSLCtxCallback; + if (active() && !no_warning()) + { + llwarns << "Revoking callbacks on a still active CurlEasyRequest object!" << llendl; + } + curl_easy_setopt(getEasyHandle(), CURLOPT_HEADERFUNCTION, &noHeaderCallback); + curl_easy_setopt(getEasyHandle(), CURLOPT_WRITEHEADER, &noWriteCallback); + curl_easy_setopt(getEasyHandle(), CURLOPT_READFUNCTION, &noReadCallback); + curl_easy_setopt(getEasyHandle(), CURLOPT_SSL_CTX_FUNCTION, &noSSLCtxCallback); +} + +CurlEasyRequest::~CurlEasyRequest() +{ + // If the CurlEasyRequest object is destructed then we need to revoke all callbacks, because + // we can't set the lock anymore, and neither will mHeaderCallback, mWriteCallback etc, + // be available anymore. + send_events_to(NULL); + revokeCallbacks(); + // This wasn't freed yet if the request never finished. + curl_slist_free_all(mHeaders); +} + +void CurlEasyRequest::resetState(void) +{ + // This function should not revoke the event call backs! + revokeCallbacks(); + reset(); + curl_slist_free_all(mHeaders); + mHeaders = NULL; + mRequestFinalized = false; + mEventsTarget = NULL; + mResult = CURLE_FAILED_INIT; + applyDefaultOptions(); +} + +void CurlEasyRequest::addHeader(char const* header) +{ + llassert(!mRequestFinalized); + mHeaders = curl_slist_append(mHeaders, header); +} + +#if defined(CWDEBUG) || defined(DEBUG_CURLIO) + +static int curl_debug_cb(CURL*, curl_infotype infotype, char* buf, size_t size, void* user_ptr) +{ +#ifdef CWDEBUG + using namespace ::libcwd; + + CurlEasyRequest* request = (CurlEasyRequest*)user_ptr; + std::ostringstream marker; + marker << (void*)request->get_lockobj(); + libcw_do.push_marker(); + libcw_do.marker().assign(marker.str().data(), marker.str().size()); + if (!debug::channels::dc::curlio.is_on()) + debug::channels::dc::curlio.on(); + LibcwDoutScopeBegin(LIBCWD_DEBUGCHANNELS, libcw_do, dc::curlio|cond_nonewline_cf(infotype == CURLINFO_TEXT)) +#else + if (infotype == CURLINFO_TEXT) + { + while (size > 0 && (buf[size - 1] == '\r' || buf[size - 1] == '\n')) + --size; + } + LibcwDoutScopeBegin(LIBCWD_DEBUGCHANNELS, libcw_do, dc::curlio) +#endif + switch (infotype) + { + case CURLINFO_TEXT: + LibcwDoutStream << "* "; + break; + case CURLINFO_HEADER_IN: + LibcwDoutStream << "H> "; + break; + case CURLINFO_HEADER_OUT: + LibcwDoutStream << "H< "; + break; + case CURLINFO_DATA_IN: + LibcwDoutStream << "D> "; + break; + case CURLINFO_DATA_OUT: + LibcwDoutStream << "D< "; + break; + case CURLINFO_SSL_DATA_IN: + LibcwDoutStream << "S> "; + break; + case CURLINFO_SSL_DATA_OUT: + LibcwDoutStream << "S< "; + break; + default: + LibcwDoutStream << "?? "; + } + if (infotype == CURLINFO_TEXT) + LibcwDoutStream.write(buf, size); + else if (infotype == CURLINFO_HEADER_IN || infotype == CURLINFO_HEADER_OUT) + LibcwDoutStream << libcwd::buf2str(buf, size); + else if (infotype == CURLINFO_DATA_IN) + { + LibcwDoutStream << size << " bytes"; + bool finished = false; + size_t i = 0; + while (i < size) + { + char c = buf[i]; + if (!('0' <= c && c <= '9') && !('a' <= c && c <= 'f')) + { + if (0 < i && i + 1 < size && buf[i] == '\r' && buf[i + 1] == '\n') + { + // Binary output: "[0-9a-f]*\r\n ...binary data..." + LibcwDoutStream << ": \"" << libcwd::buf2str(buf, i + 2) << "\"..."; + finished = true; + } + break; + } + ++i; + } + if (!finished && size > 9 && buf[0] == '<') + { + // Human readable output: html, xml or llsd. + if (!strncmp(buf, "", 6)) + { + LibcwDoutStream << ": \"" << libcwd::buf2str(buf, size) << '"'; + finished = true; + } + } + if (!finished) + { + // Unknown format. Only print the first and last 20 characters. + if (size > 40UL) + { + LibcwDoutStream << ": \"" << libcwd::buf2str(buf, 20) << "\"...\"" << libcwd::buf2str(&buf[size - 20], 20) << '"'; + } + else + { + LibcwDoutStream << ": \"" << libcwd::buf2str(buf, size) << '"'; + } + } + } + else if (infotype == CURLINFO_DATA_OUT) + LibcwDoutStream << size << " bytes: \"" << libcwd::buf2str(buf, size) << '"'; + else + LibcwDoutStream << size << " bytes"; + LibcwDoutScopeEnd; +#ifdef CWDEBUG + libcw_do.pop_marker(); +#endif + return 0; +} +#endif + +void CurlEasyRequest::applyProxySettings(void) +{ + LLProxy& proxy = *LLProxy::getInstance(); + + // Do a faster unlocked check to see if we are supposed to proxy. + if (proxy.HTTPProxyEnabled()) + { + // We think we should proxy, read lock the shared proxy members. + LLProxy::Shared_crat proxy_r(proxy.shared_lockobj()); + + // Now test again to verify that the proxy wasn't disabled between the first check and the lock. + if (proxy.HTTPProxyEnabled()) + { + setopt(CURLOPT_PROXY, proxy.getHTTPProxy(proxy_r).getIPString().c_str()); + setopt(CURLOPT_PROXYPORT, proxy.getHTTPProxy(proxy_r).getPort()); + + if (proxy.getHTTPProxyType(proxy_r) == LLPROXY_SOCKS) + { + setopt(CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5); + if (proxy.getSelectedAuthMethod(proxy_r) == METHOD_PASSWORD) + { + std::string auth_string = proxy.getSocksUser(proxy_r) + ":" + proxy.getSocksPwd(proxy_r); + setopt(CURLOPT_PROXYUSERPWD, auth_string.c_str()); + } + } + else + { + setopt(CURLOPT_PROXYTYPE, CURLPROXY_HTTP); + } + } + } +} + +//static +CURLcode CurlEasyRequest::curlCtxCallback(CURL* curl, void* sslctx, void* parm) +{ + SSL_CTX* ctx = (SSL_CTX*)sslctx; + // Turn off TLS v1.1 (which is not supported anyway by Linden Lab) because otherwise we fail to connect. + // Also turn off SSL v2, which is highly broken and strongly discouraged[1]. + // [1] http://www.openssl.org/docs/ssl/SSL_CTX_set_options.html#SECURE_RENEGOTIATION + long options = SSL_OP_NO_SSLv2; +#ifdef SSL_OP_NO_TLSv1_1 // Only defined for openssl version 1.0.1 and up. + if (need_renegotiation_hack) + { + // This option disables openssl to use TLS version 1.1. + // The Linden Lab servers don't support TLS versions later than 1.0, and libopenssl-1.0.1-beta1 up till and including + // libopenssl-1.0.1c have a bug (or feature?) where (re)negotiation fails (see http://rt.openssl.org/Ticket/Display.html?id=2828), + // causing the connection to fail completely without this hack. + // For a commandline test of the same, observe the difference between: + // openssl s_client -connect login.agni.lindenlab.com:443 -CAfile packaged/app_settings/CA.pem -debug + // which gets no response from the server after sending the initial data, and + // openssl s_client -no_tls1_1 -connect login.agni.lindenlab.com:443 -CAfile packaged/app_settings/CA.pem -debug + // which finishes the negotiation and ends with 'Verify return code: 0 (ok)' + options |= SSL_OP_NO_TLSv1_1; + } +#else + llassert_always(!need_renegotiation_hack); +#endif + SSL_CTX_set_options(ctx, options); + return CURLE_OK; +} + +void CurlEasyRequest::applyDefaultOptions(void) +{ + CertificateAuthority_rat CertificateAuthority_r(gCertificateAuthority); + setoptString(CURLOPT_CAINFO, CertificateAuthority_r->file); + setSSLCtxCallback(&curlCtxCallback, NULL); + setopt(CURLOPT_NOSIGNAL, 1); + // The old code did this for the 'buffered' version, but I think it's nonsense. + //setopt(CURLOPT_DNS_CACHE_TIMEOUT, 0); + // Set the CURL options for either SOCKS or HTTP proxy. + applyProxySettings(); + // Cause libcurl to print all it's I/O traffic on the debug channel. + Debug( + if (dc::curlio.is_on()) + { + setopt(CURLOPT_VERBOSE, 1); + setopt(CURLOPT_DEBUGFUNCTION, &curl_debug_cb); + setopt(CURLOPT_DEBUGDATA, this); + } + ); +} + +void CurlEasyRequest::finalizeRequest(std::string const& url) +{ + llassert(!mRequestFinalized); + mResult = CURLE_FAILED_INIT; // General error code, the final code is written here in MultiHandle::check_run_count when msg is CURLMSG_DONE. + lldebugs << url << llendl; +#ifdef SHOW_ASSERT + // Do a sanity check on the headers. + int content_type_count = 0; + for (curl_slist* list = mHeaders; list; list = list->next) + { + if (strncmp(list->data, "Content-Type:", 13) == 0) + { + ++content_type_count; + } + } + if (content_type_count > 1) + { + llwarns << content_type_count << " Content-Type: headers!" << llendl; + } +#endif + mRequestFinalized = true; + setopt(CURLOPT_HTTPHEADER, mHeaders); + setoptString(CURLOPT_URL, url); + // The following line is a bit tricky: we store a pointer to the object without increasing its reference count. + // Of course we could increment the reference count, but that doesn't help much: if then this pointer would + // get "lost" we'd have a memory leak. Either way we must make sure that it is impossible that this pointer + // will be used if the object is deleted [In fact, since this is finalizeRequest() and not addRequest(), + // incrementing the reference counter would be wrong: if addRequest() is never called then the object is + // destroyed shortly after and this pointer is never even used.] + // This pointer is used in MultiHandle::check_run_count, which means that addRequest() was called and + // the reference counter was increased and the object is being kept alive, see the comments above + // command_queue in aicurlthread.cpp. In fact, this object survived until MultiHandle::add_easy_request + // was called and is kept alive by MultiHandle::mAddedEasyRequests. The only way to get deleted after + // that is when MultiHandle::remove_easy_request is called, which first removes the easy handle from + // the multi handle. So that it's (hopefully) no longer possible that info_read() in + // MultiHandle::check_run_count returns this easy handle, after the object is destroyed by deleting + // it from MultiHandle::mAddedEasyRequests. + setopt(CURLOPT_PRIVATE, get_lockobj()); +} + +void CurlEasyRequest::getTransferInfo(AICurlInterface::TransferInfo* info) +{ + // Curl explicitly demands a double for these info's. + double size, total_time, speed; + getinfo(CURLINFO_SIZE_DOWNLOAD, &size); + getinfo(CURLINFO_TOTAL_TIME, &total_time); + getinfo(CURLINFO_SPEED_DOWNLOAD, &speed); + // Convert to F64. + info->mSizeDownload = size; + info->mTotalTime = total_time; + info->mSpeedDownload = speed; +} + +void CurlEasyRequest::getResult(CURLcode* result, AICurlInterface::TransferInfo* info) +{ + *result = mResult; + if (info && mResult != CURLE_FAILED_INIT) + { + getTransferInfo(info); + } +} + +void CurlEasyRequest::added_to_multi_handle(AICurlEasyRequest_wat& curl_easy_request_w) +{ + if (mEventsTarget) + mEventsTarget->added_to_multi_handle(curl_easy_request_w); +} + +void CurlEasyRequest::finished(AICurlEasyRequest_wat& curl_easy_request_w) +{ + if (mEventsTarget) + mEventsTarget->finished(curl_easy_request_w); +} + +void CurlEasyRequest::removed_from_multi_handle(AICurlEasyRequest_wat& curl_easy_request_w) +{ + if (mEventsTarget) + mEventsTarget->removed_from_multi_handle(curl_easy_request_w); +} + +//----------------------------------------------------------------------------- +// CurlResponderBuffer + +static unsigned int const MAX_REDIRECTS = 5; +static S32 const CURL_REQUEST_TIMEOUT = 30; // Seconds per operation. + +LLChannelDescriptors const CurlResponderBuffer::sChannels; + +CurlResponderBuffer::CurlResponderBuffer() +{ + ThreadSafeBufferedCurlEasyRequest* lockobj = get_lockobj(); + AICurlEasyRequest_wat curl_easy_request_w(*lockobj); + curl_easy_request_w->send_events_to(this); +} + +#define llmaybeerrs lllog(LLApp::isRunning() ? LLError::LEVEL_ERROR : LLError::LEVEL_WARN, NULL, NULL, false, true) + +// The callbacks need to be revoked when the CurlResponderBuffer is destructed (because that is what the callbacks use). +// The AIThreadSafeSimple is destructed first (right to left), so when we get here then the +// ThreadSafeCurlEasyRequest base class of ThreadSafeBufferedCurlEasyRequest is still intact and we can create +// and use curl_easy_request_w. +CurlResponderBuffer::~CurlResponderBuffer() +{ + ThreadSafeBufferedCurlEasyRequest* lockobj = get_lockobj(); + AICurlEasyRequest_wat curl_easy_request_w(*lockobj); // Wait 'til possible callbacks have returned. + curl_easy_request_w->send_events_to(NULL); + curl_easy_request_w->revokeCallbacks(); + if (mResponder) + { + // If the responder is still alive, then that means that CurlResponderBuffer::processOutput was + // never called, which means that the removed_from_multi_handle event never happened. + // This is definitely an internal error as it can only happen when libcurl is too slow, + // in which case AICurlEasyRequestStateMachine::mTimer times out, but that already + // calls CurlResponderBuffer::timed_out(). + llmaybeerrs << "Calling ~CurlResponderBuffer() with active responder!" << llendl; + if (!LLApp::isRunning()) + { + // It might happen if some CurlResponderBuffer escaped clean up somehow :/ + mResponder = NULL; + } + else + { + // User chose to continue. + timed_out(); + } + } +} + +void CurlResponderBuffer::timed_out(void) +{ + mResponder->completedRaw(HTTP_INTERNAL_ERROR, "Request timeout, aborted.", sChannels, mOutput); + mResponder = NULL; +} + +void CurlResponderBuffer::resetState(AICurlEasyRequest_wat& curl_easy_request_w) +{ + llassert(!mResponder); + + curl_easy_request_w->resetState(); + + mOutput.reset(); + mInput.reset(); + + mHeaderOutput.str(""); + mHeaderOutput.clear(); +} + +ThreadSafeBufferedCurlEasyRequest* CurlResponderBuffer::get_lockobj(void) +{ + return static_cast(AIThreadSafeSimple::wrapper_cast(this)); +} + +void CurlResponderBuffer::prepRequest(AICurlEasyRequest_wat& curl_easy_request_w, std::vector const& headers, AICurlInterface::ResponderPtr responder, S32 time_out, bool post) +{ + if (post) + { + // Accept everything (send an Accept-Encoding header containing all encodings we support (zlib and gzip)). + curl_easy_request_w->setoptString(CURLOPT_ENCODING, ""); // CURLOPT_ACCEPT_ENCODING + } + + mInput.reset(new LLBufferArray); + mInput->setThreaded(true); + mLastRead = NULL; + + mOutput.reset(new LLBufferArray); + mOutput->setThreaded(true); + + ThreadSafeBufferedCurlEasyRequest* lockobj = get_lockobj(); + curl_easy_request_w->setWriteCallback(&curlWriteCallback, lockobj); + curl_easy_request_w->setReadCallback(&curlReadCallback, lockobj); + curl_easy_request_w->setHeaderCallback(&curlHeaderCallback, lockobj); + + // Allow up to five redirects. + if (responder && responder->followRedir()) + { + curl_easy_request_w->setopt(CURLOPT_FOLLOWLOCATION, 1); + curl_easy_request_w->setopt(CURLOPT_MAXREDIRS, MAX_REDIRECTS); + } + + curl_easy_request_w->setopt(CURLOPT_SSL_VERIFYPEER, true); + // Don't verify host name so urls with scrubbed host names will work (improves DNS performance). + curl_easy_request_w->setopt(CURLOPT_SSL_VERIFYHOST, 0); + + curl_easy_request_w->setopt(CURLOPT_TIMEOUT, llmax(time_out, CURL_REQUEST_TIMEOUT)); + + // Keep responder alive. + mResponder = responder; + + if (!post) + { + // Add extra headers. + for (std::vector::const_iterator iter = headers.begin(); iter != headers.end(); ++iter) + { + curl_easy_request_w->addHeader((*iter).c_str()); + } + } +} + +//static +size_t CurlResponderBuffer::curlWriteCallback(char* data, size_t size, size_t nmemb, void* user_data) +{ + ThreadSafeBufferedCurlEasyRequest* lockobj = static_cast(user_data); + + // We need to lock the curl easy request object too, because that lock is used + // to make sure that callbacks and destruction aren't done simultaneously. + AICurlEasyRequest_wat buffered_easy_request_w(*lockobj); + + AICurlResponderBuffer_wat buffer_w(*lockobj); + S32 n = size * nmemb; + buffer_w->getOutput()->append(sChannels.in(), (U8 const*)data, n); + return n; +} + +//static +size_t CurlResponderBuffer::curlReadCallback(char* data, size_t size, size_t nmemb, void* user_data) +{ + ThreadSafeBufferedCurlEasyRequest* lockobj = static_cast(user_data); + + // We need to lock the curl easy request object too, because that lock is used + // to make sure that callbacks and destruction aren't done simultaneously. + AICurlEasyRequest_wat buffered_easy_request_w(*lockobj); + + S32 bytes = size * nmemb; // The maximum amount to read. + AICurlResponderBuffer_wat buffer_w(*lockobj); + buffer_w->mLastRead = buffer_w->getInput()->readAfter(sChannels.out(), buffer_w->mLastRead, (U8*)data, bytes); + return bytes; // Return the amount actually read. +} + +//static +size_t CurlResponderBuffer::curlHeaderCallback(char* data, size_t size, size_t nmemb, void* user_data) +{ + ThreadSafeBufferedCurlEasyRequest* lockobj = static_cast(user_data); + + // We need to lock the curl easy request object too, because that lock is used + // to make sure that callbacks and destruction aren't done simultaneously. + AICurlEasyRequest_wat buffered_easy_request_w(*lockobj); + + AICurlResponderBuffer_wat buffer_w(*lockobj); + size_t n = size * nmemb; + buffer_w->getHeaderOutput().write(data, n); + return n; +} + +void CurlResponderBuffer::added_to_multi_handle(AICurlEasyRequest_wat& curl_easy_request_w) +{ + Dout(dc::curl, "Calling CurlResponderBuffer::added_to_multi_handle(@" << (void*)&*curl_easy_request_w << ") for this = " << (void*)this); +} + +void CurlResponderBuffer::finished(AICurlEasyRequest_wat& curl_easy_request_w) +{ + Dout(dc::curl, "Calling CurlResponderBuffer::finished(@" << (void*)&*curl_easy_request_w << ") for this = " << (void*)this); +} + +void CurlResponderBuffer::removed_from_multi_handle(AICurlEasyRequest_wat& curl_easy_request_w) +{ + Dout(dc::curl, "Calling CurlResponderBuffer::removed_from_multi_handle(@" << (void*)&*curl_easy_request_w << ") for this = " << (void*)this); + + // Lock self. + ThreadSafeBufferedCurlEasyRequest* lockobj = get_lockobj(); + llassert(dynamic_cast(static_cast(ThreadSafeCurlEasyRequest::wrapper_cast(&*curl_easy_request_w))) == lockobj); + AICurlResponderBuffer_wat buffer_w(*lockobj); + llassert(&*buffer_w == this); + + processOutput(curl_easy_request_w); +} + +void CurlResponderBuffer::processOutput(AICurlEasyRequest_wat& curl_easy_request_w) +{ + U32 responseCode = 0; + std::string responseReason; + + CURLcode code; + curl_easy_request_w->getResult(&code); + if (code == CURLE_OK) + { + curl_easy_request_w->getinfo(CURLINFO_RESPONSE_CODE, &responseCode); + //*TODO: get reason from first line of mHeaderOutput + } + else + { + responseCode = 499; + responseReason = AICurlInterface::strerror(code) + " : "; + if (code == CURLE_FAILED_INIT) + { + responseReason += "Curl Easy Handle initialization failed."; + } + else + { + responseReason += curl_easy_request_w->getErrorString(); + } + curl_easy_request_w->setopt(CURLOPT_FRESH_CONNECT, TRUE); + } + + if (mResponder) + { + mResponder->completedRaw(responseCode, responseReason, sChannels, mOutput); + mResponder = NULL; + } + + resetState(curl_easy_request_w); +} + +//----------------------------------------------------------------------------- +// CurlMultiHandle + +LLAtomicU32 CurlMultiHandle::sTotalMultiHandles; + +CurlMultiHandle::CurlMultiHandle(void) +{ + DoutEntering(dc::curl, "CurlMultiHandle::CurlMultiHandle() [" << (void*)this << "]."); + mMultiHandle = curl_multi_init(); + Stats::multi_calls++; + if (!mMultiHandle) + { + Stats::multi_errors++; + throw AICurlNoMultiHandle("curl_multi_init() returned NULL"); + } + sTotalMultiHandles++; +} + +CurlMultiHandle::~CurlMultiHandle() +{ + curl_multi_cleanup(mMultiHandle); + Stats::multi_calls++; +#ifdef CWDEBUG + int total = --sTotalMultiHandles; + Dout(dc::curl, "Called CurlMultiHandle::~CurlMultiHandle() [" << (void*)this << "], " << total << " remaining."); +#else + --sTotalMultiHandles; +#endif +} + +} // namespace AICurlPrivate diff --git a/indra/llmessage/aicurl.h b/indra/llmessage/aicurl.h new file mode 100644 index 000000000..26491da67 --- /dev/null +++ b/indra/llmessage/aicurl.h @@ -0,0 +1,343 @@ +/** + * @file aicurl.h + * @brief Thread safe wrapper for libcurl. + * + * Copyright (c) 2012, Aleric Inglewood. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution. + * + * CHANGELOG + * and additional copyright holders. + * + * 17/03/2012 + * Initial version, written by Aleric Inglewood @ SL + */ + +#ifndef AICURL_H +#define AICURL_H + +#include +#include +#include +#include +#include +#include + +#include "llpreprocessor.h" +#include // Needed for files that include this header (also for aicurlprivate.h). +#ifdef DEBUG_CURLIO +#include "debug_libcurl.h" +#endif + +// Make sure we don't use this option: it is not thread-safe. +#undef CURLOPT_DNS_USE_GLOBAL_CACHE +#define CURLOPT_DNS_USE_GLOBAL_CACHE do_not_use_CURLOPT_DNS_USE_GLOBAL_CACHE + +#include "stdtypes.h" // U32 +#include "lliopipe.h" // LLIOPipe::buffer_ptr_t +#include "llatomic.h" // LLAtomicU32 +#include "aithreadsafe.h" + +class LLSD; + +//----------------------------------------------------------------------------- +// Exceptions. +// + +// A general curl exception. +// +class AICurlError : public std::runtime_error { + public: + AICurlError(std::string const& message) : std::runtime_error(message) { } +}; + +class AICurlNoEasyHandle : public AICurlError { + public: + AICurlNoEasyHandle(std::string const& message) : AICurlError(message) { } +}; + +class AICurlNoMultiHandle : public AICurlError { + public: + AICurlNoMultiHandle(std::string const& message) : AICurlError(message) { } +}; + +// End Exceptions. +//----------------------------------------------------------------------------- + +// Things defined in this namespace are called from elsewhere in the viewer code. +namespace AICurlInterface { + +// Output parameter of AICurlPrivate::CurlEasyRequest::getResult. +// Only used by LLXMLRPCTransaction::Impl. +struct TransferInfo { + TransferInfo() : mSizeDownload(0.0), mTotalTime(0.0), mSpeedDownload(0.0) { } + F64 mSizeDownload; + F64 mTotalTime; + F64 mSpeedDownload; +}; + +//----------------------------------------------------------------------------- +// Global functions. + +// Called once at start of application (from newview/llappviewer.cpp by main thread (before threads are created)), +// with main purpose to initialize curl. +void initCurl(void (*)(void) = NULL); + +// Called once at start of application (from LLAppViewer::initThreads), starts AICurlThread. +void startCurlThread(void); + +// Called once at end of application (from newview/llappviewer.cpp by main thread), +// with purpose to stop curl threads, free curl resources and deinitialize curl. +void cleanupCurl(void); + +// Called from indra/llmessage/llurlrequest.cpp to print debug output regarding +// an error code returned by EasyRequest::getResult. +// Just returns curl_easy_strerror(errorcode). +std::string strerror(CURLcode errorcode); + +// Called from indra/newview/llfloaterabout.cpp for the About floater, and +// from newview/llappviewer.cpp in behalf of debug output. +// Just returns curl_version(). +std::string getVersionString(void); + +// Called from newview/llappviewer.cpp (and llcrashlogger/llcrashlogger.cpp) to set +// the Certificate Authority file used to verify HTTPS certs. +void setCAFile(std::string const& file); + +// Not called from anywhere. +// Can be used to set the path to the Certificate Authority file. +void setCAPath(std::string const& file); + +//----------------------------------------------------------------------------- +// Global classes. + +// Responder - base class for Request::get* and Request::post API. +// +// The life cycle of classes derived from this class is as follows: +// They are allocated with new on the line where get(), getByteRange() or post() is called, +// and the pointer to the allocated object is then put in a reference counting ResponderPtr. +// This ResponderPtr is passed to CurlResponderBuffer::prepRequest which stores it in its +// member mResponder. Hence, the life time of a Responder is never longer than its +// associated CurlResponderBuffer, however, if everything works correctly, then normally a +// responder is deleted in CurlResponderBuffer::removed_from_multi_handle by setting +// mReponder to NULL. +// +// Note that the lifetime of CurlResponderBuffer is (a bit) shorter than the associated +// CurlEasyRequest (because of the order of base classes of ThreadSafeBufferedCurlEasyRequest) +// and the callbacks, as set by prepRequest, only use those two. +// A callback locks the CurlEasyRequest before actually making the callback, and the +// destruction of CurlResponderBuffer also first locks the CurlEasyRequest, and then revokes +// the callbacks. This assures that a Responder is never used when the objects it uses are +// destructed. Also, if any of those are destructed then the Responder is automatically +// destructed too. +// +class Responder { + protected: + Responder(void); + virtual ~Responder(); + + private: + // Associated URL, used for debug output. + std::string mURL; + + public: + // Called to set the URL of the current request for this Responder, + // used only when printing debug output regarding activity of the Responder. + void setURL(std::string const& url); + + public: + // Called from LLHTTPClientURLAdaptor::complete(): + + // Derived classes can override this to get the HTML header that was received, when the message is completed. + // The default does nothing. + virtual void completedHeader(U32 status, std::string const& reason, LLSD const& content); + + // Derived classes can override this to get the raw data of the body of the HTML message that was received. + // The default is to interpret the content as LLSD and call completed(). + virtual void completedRaw(U32 status, std::string const& reason, LLChannelDescriptors const& channels, LLIOPipe::buffer_ptr_t const& buffer); + + // Called from LLHTTPClient request calls, if an error occurs even before we can call one of the above. + // It calls completed() with a fake status U32_MAX, as that is what some derived clients expect (bad design). + // This means that if a derived class overrides completedRaw() it now STILL has to override completed() to catch this error. + void fatalError(std::string const& reason); + + // A derived class should return true if curl should follow redirections. + // The default is not to follow redirections. + virtual bool followRedir(void) { return false; } + + protected: + // ... or, derived classes can override this to get the LLSD content when the message is completed. + // The default is to call result() (or errorWithContent() in case of a HTML status indicating an error). + virtual void completed(U32 status, std::string const& reason, LLSD const& content); + + // ... or, derived classes can override this to received the content of a body upon success. + // The default does nothing. + virtual void result(LLSD const& content); + + // Derived classes can override this to get informed when a bad HTML status code is received. + // The default calls error(). + virtual void errorWithContent(U32 status, std::string const& reason, LLSD const& content); + + // ... or, derived classes can override this to get informed when a bad HTML statis code is received. + // The default prints the error to llinfos. + virtual void error(U32 status, std::string const& reason); + + public: + // Called from LLSDMessage::ResponderAdapter::listener. + // LLSDMessage::ResponderAdapter is a hack, showing among others by fact that these functions need to be public. + + void pubErrorWithContent(U32 status, std::string const& reason, LLSD const& content) { errorWithContent(status, reason, content); } + void pubResult(LLSD const& content) { result(content); } + + private: + // Used by ResponderPtr. Object is deleted when reference count reaches zero. + LLAtomicU32 mReferenceCount; + + friend void intrusive_ptr_add_ref(Responder* p); // Called by boost::intrusive_ptr when a new copy of a boost::intrusive_ptr is made. + friend void intrusive_ptr_release(Responder* p); // Called by boost::intrusive_ptr when a boost::intrusive_ptr is destroyed. + // This function must delete the Responder object when the reference count reaches zero. +}; + +// A Responder is passed around as ResponderPtr, which causes it to automatically +// destruct when there are no pointers left pointing to it. +typedef boost::intrusive_ptr ResponderPtr; + +} // namespace AICurlInterface + +// Forward declaration (see aicurlprivate.h). +namespace AICurlPrivate { + class CurlEasyRequest; +} // namespace AICurlPrivate + +// Define access types (_crat = Const Read Access Type, _rat = Read Access Type, _wat = Write Access Type). +// Typical usage is: +// AICurlEasyRequest h1; // Create easy handle. +// AICurlEasyRequest h2(h1); // Make lightweight copies. +// AICurlEasyRequest_wat h2_w(*h2); // Lock and obtain write access to the easy handle. +// Use *h2_w, which is a reference to the locked CurlEasyRequest instance. +// Note: As it is not allowed to use curl easy handles in any way concurrently, +// read access would at most give access to a CURL const*, which will turn out +// to be completely useless; therefore it is sufficient and efficient to use +// an AIThreadSafeSimple and it's unlikely that AICurlEasyRequest_rat will be used. +typedef AIAccessConst AICurlEasyRequest_rat; +typedef AIAccess AICurlEasyRequest_wat; + +// Events generated by AICurlPrivate::CurlEasyHandle. +struct AICurlEasyHandleEvents { + // Events. + virtual void added_to_multi_handle(AICurlEasyRequest_wat& curl_easy_request_w) = 0; + virtual void finished(AICurlEasyRequest_wat& curl_easy_request_w) = 0; + virtual void removed_from_multi_handle(AICurlEasyRequest_wat& curl_easy_request_w) = 0; + // Avoid compiler warning. + virtual ~AICurlEasyHandleEvents() { } +}; + +// Pointer to data we're going to POST. +class AIPostField : public LLThreadSafeRefCount { + protected: + char const* mData; + + public: + AIPostField(char const* data) : mData(data) { } + char const* data(void) const { return mData; } +}; + +// The pointer to the data that we have to POST is passed around as AIPostFieldPtr, +// which causes it to automatically clean up when there are no pointers left +// pointing to it. +typedef LLPointer AIPostFieldPtr; + +#include "aicurlprivate.h" + +// AICurlPrivate::CurlEasyRequestPtr, a boost::intrusive_ptr, is no more threadsafe than a +// builtin type, but wrapping it in AIThreadSafe is obviously not going to help here. +// Therefore we use the following trick: we wrap CurlEasyRequestPtr too, and only allow +// read accesses on it. + +// AICurlEasyRequest: a thread safe, reference counting, auto-cleaning curl easy handle. +class AICurlEasyRequest { + public: + // Initial construction is allowed (thread-safe). + // Note: If ThreadSafeCurlEasyRequest() throws then the memory allocated is still freed. + // 'new' never returned however and neither the constructor nor destructor of mCurlEasyRequest is called in this case. + // This might throw AICurlNoEasyHandle. + AICurlEasyRequest(bool buffered) : + mCurlEasyRequest(buffered ? new AICurlPrivate::ThreadSafeBufferedCurlEasyRequest : new AICurlPrivate::ThreadSafeCurlEasyRequest) { } + AICurlEasyRequest(AICurlEasyRequest const& orig) : mCurlEasyRequest(orig.mCurlEasyRequest) { } + + // For the rest, only allow read operations. + AIThreadSafeSimple& operator*(void) const { llassert(mCurlEasyRequest.get()); return *mCurlEasyRequest; } + AIThreadSafeSimple* operator->(void) const { llassert(mCurlEasyRequest.get()); return mCurlEasyRequest.get(); } + AIThreadSafeSimple* get(void) const { return mCurlEasyRequest.get(); } + + // Returns true if this object points to the same CurlEasyRequest object. + bool operator==(AICurlEasyRequest const& cer) const { return mCurlEasyRequest == cer.mCurlEasyRequest; } + + // Returns true if this object points to a different CurlEasyRequest object. + bool operator!=(AICurlEasyRequest const& cer) const { return mCurlEasyRequest != cer.mCurlEasyRequest; } + + // Queue this request for insertion in the multi session. + void addRequest(void); + + // Queue a command to remove this request from the multi session (or cancel a queued command to add it). + void removeRequest(void); + + // Returns true when this AICurlEasyRequest wraps a AICurlPrivate::ThreadSafeBufferedCurlEasyRequest. + bool isBuffered(void) const { return mCurlEasyRequest->isBuffered(); } + + private: + // The actual pointer to the ThreadSafeCurlEasyRequest instance. + AICurlPrivate::CurlEasyRequestPtr mCurlEasyRequest; + + private: + // Assignment would not be thread-safe; we may create this object and read from it. + // Note: Destruction is implicitly assumed thread-safe, as it would be a logic error to + // destruct it while another thread still needs it, concurrent or not. + AICurlEasyRequest& operator=(AICurlEasyRequest const&) { return *this; } + + public: + // The more exotic member functions of this class, to deal with passing this class + // as CURLOPT_PRIVATE pointer to a curl handle and afterwards restore it. + // For "internal use" only; don't use things from AICurlPrivate yourself. + + // It's thread-safe to give read access the underlaying boost::intrusive_ptr. + // It's not OK to then call get() on that and store the AICurlPrivate::ThreadSafeCurlEasyRequest* separately. + AICurlPrivate::CurlEasyRequestPtr const& get_ptr(void) const { return mCurlEasyRequest; } + + // If we have a correct (with regard to reference counting) AICurlPrivate::CurlEasyRequestPtr, + // then it's OK to construct a AICurlEasyRequest from it. + // Note that the external AICurlPrivate::CurlEasyRequestPtr needs its own locking, because + // it's not thread-safe in itself. + AICurlEasyRequest(AICurlPrivate::CurlEasyRequestPtr const& ptr) : mCurlEasyRequest(ptr) { } + + // This one is obviously dangerous. It's for use only in MultiHandle::check_run_count. + // See also the long comment in CurlEasyRequest::finalizeRequest with regard to CURLOPT_PRIVATE. + explicit AICurlEasyRequest(AICurlPrivate::ThreadSafeCurlEasyRequest* ptr) : mCurlEasyRequest(ptr) { } +}; + +// Write Access Type for the buffer. +struct AICurlResponderBuffer_wat : public AIAccess { + explicit AICurlResponderBuffer_wat(AICurlPrivate::ThreadSafeBufferedCurlEasyRequest& lockobj) : + AIAccess(lockobj) { } + AICurlResponderBuffer_wat(AIThreadSafeSimple& lockobj) : + AIAccess(static_cast(lockobj)) { } +}; + +#define AICurlPrivate DONTUSE_AICurlPrivate + +#endif diff --git a/indra/llmessage/aicurlprivate.h b/indra/llmessage/aicurlprivate.h new file mode 100644 index 000000000..1acf08e14 --- /dev/null +++ b/indra/llmessage/aicurlprivate.h @@ -0,0 +1,478 @@ +/** + * @file aicurlprivate.h + * @brief Thread safe wrapper for libcurl. + * + * Copyright (c) 2012, Aleric Inglewood. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution. + * + * CHANGELOG + * and additional copyright holders. + * + * 28/04/2012 + * Initial version, written by Aleric Inglewood @ SL + */ + +#ifndef AICURLPRIVATE_H +#define AICURLPRIVATE_H + +#include +#include "llatomic.h" + +namespace AICurlPrivate { +namespace curlthread { class MultiHandle; } + +struct Stats { + static LLAtomicU32 easy_calls; + static LLAtomicU32 easy_errors; + static LLAtomicU32 easy_init_calls; + static LLAtomicU32 easy_init_errors; + static LLAtomicU32 easy_cleanup_calls; + static LLAtomicU32 multi_calls; + static LLAtomicU32 multi_errors; + + static void print(void); +}; + +void handle_multi_error(CURLMcode code); +inline CURLMcode check_multi_code(CURLMcode code) { Stats::multi_calls++; if (code != CURLM_OK) handle_multi_error(code); return code; } + +bool curlThreadIsRunning(void); +void wakeUpCurlThread(void); +void stopCurlThread(void); + +class ThreadSafeCurlEasyRequest; +class ThreadSafeBufferedCurlEasyRequest; + +#define DECLARE_SETOPT(param_type) \ + CURLcode setopt(CURLoption option, param_type parameter) + +// This class wraps CURL*'s. +// It guarantees that a pointer is cleaned up when no longer needed, as required by libcurl. +class CurlEasyHandle : public boost::noncopyable, protected AICurlEasyHandleEvents { + public: + CurlEasyHandle(void); + ~CurlEasyHandle(); + + private: + // Disallow assignment. + CurlEasyHandle& operator=(CurlEasyHandle const*); + + public: + // Reset all options of a libcurl session handle. + void reset(void) { llassert(!mActiveMultiHandle); curl_easy_reset(mEasyHandle); } + + // Set options for a curl easy handle. + DECLARE_SETOPT(long); + DECLARE_SETOPT(long long); + DECLARE_SETOPT(void const*); + DECLARE_SETOPT(curl_debug_callback); + DECLARE_SETOPT(curl_write_callback); + //DECLARE_SETOPT(curl_read_callback); Same type as curl_write_callback + DECLARE_SETOPT(curl_ssl_ctx_callback); + DECLARE_SETOPT(curl_conv_callback); +#if 0 // Not used by the viewer. + DECLARE_SETOPT(curl_progress_callback); + DECLARE_SETOPT(curl_seek_callback); + DECLARE_SETOPT(curl_ioctl_callback); + DECLARE_SETOPT(curl_sockopt_callback); + DECLARE_SETOPT(curl_opensocket_callback); + DECLARE_SETOPT(curl_closesocket_callback); + DECLARE_SETOPT(curl_sshkeycallback); + DECLARE_SETOPT(curl_chunk_bgn_callback); + DECLARE_SETOPT(curl_chunk_end_callback); + DECLARE_SETOPT(curl_fnmatch_callback); +#endif + // Automatically cast int types to a long. Note that U32/S32 are int and + // that you can overload int and long even if they have the same size. + CURLcode setopt(CURLoption option, U32 parameter) { return setopt(option, (long)parameter); } + CURLcode setopt(CURLoption option, S32 parameter) { return setopt(option, (long)parameter); } + + // Clone a libcurl session handle using all the options previously set. + //CurlEasyHandle(CurlEasyHandle const& orig); + + // URL encode/decode the given string. + char* escape(char* url, int length); + char* unescape(char* url, int inlength , int* outlength); + + // Extract information from a curl handle. + private: + CURLcode getinfo_priv(CURLINFO info, void* data); + public: + // The rest are inlines to provide some type-safety. + CURLcode getinfo(CURLINFO info, char** data) { return getinfo_priv(info, data); } + CURLcode getinfo(CURLINFO info, curl_slist** data) { return getinfo_priv(info, data); } + CURLcode getinfo(CURLINFO info, double* data) { return getinfo_priv(info, data); } + CURLcode getinfo(CURLINFO info, long* data) { return getinfo_priv(info, data); } +#ifdef __LP64__ // sizeof(long) > sizeof(int) ? + // Overload for integer types that are too small (libcurl demands a long). + CURLcode getinfo(CURLINFO info, S32* data) { long ldata; CURLcode res = getinfo_priv(info, &ldata); *data = static_cast(ldata); return res; } + CURLcode getinfo(CURLINFO info, U32* data) { long ldata; CURLcode res = getinfo_priv(info, &ldata); *data = static_cast(ldata); return res; } +#else // sizeof(long) == sizeof(int) + CURLcode getinfo(CURLINFO info, S32* data) { return getinfo_priv(info, reinterpret_cast(data)); } + CURLcode getinfo(CURLINFO info, U32* data) { return getinfo_priv(info, reinterpret_cast(data)); } +#endif + + // Perform a file transfer (blocking). + CURLcode perform(void); + // Pause and unpause a connection. + CURLcode pause(int bitmask); + + // Called when a request is queued for removal. In that case a race between the actual removal + // and revoking of the callbacks is harmless (and happens for the raw non-statemachine version). + void remove_queued(void) { mQueuedForRemoval = true; } + // In case it's added after being removed. + void add_queued(void) { mQueuedForRemoval = false; } + + private: + CURL* mEasyHandle; + CURLM* mActiveMultiHandle; + char* mErrorBuffer; + AIPostFieldPtr mPostField; // This keeps the POSTFIELD data alive for as long as the easy handle exists. + bool mQueuedForRemoval; // Set if the easy handle is (probably) added to the multi handle, but is queued for removal. +#ifdef SHOW_ASSERT + public: + bool mRemovedPerCommand; // Set if mActiveMultiHandle was reset as per command from the main thread. +#endif + + private: + // This should only be called from MultiHandle; add/remove an easy handle to/from a multi handle. + friend class curlthread::MultiHandle; + CURLMcode add_handle_to_multi(AICurlEasyRequest_wat& curl_easy_request_w, CURLM* multi_handle); + CURLMcode remove_handle_from_multi(AICurlEasyRequest_wat& curl_easy_request_w, CURLM* multi_handle); + + public: + // Returns true if this easy handle was added to a curl multi handle. + bool active(void) const { return mActiveMultiHandle; } + + // If there was an error code as result, then this returns a human readable error string. + // Only valid when setErrorBuffer was called and the curl_easy function returned an error. + std::string getErrorString(void) const { return mErrorBuffer ? mErrorBuffer : "(null)"; } + + // Returns true when it is expected that the parent will revoke callbacks before the curl + // easy handle is removed from the multi handle; that usually happens when an external + // error demands termination of the request (ie, an expiration). + bool no_warning(void) const { return mQueuedForRemoval || LLApp::isExiting(); } + + // Used for debugging purposes. + bool operator==(CURL* easy_handle) const { return mEasyHandle == easy_handle; } + + private: + // Call this prior to every curl_easy function whose return value is passed to check_easy_code. + void setErrorBuffer(void); + + static void handle_easy_error(CURLcode code); + + // Always first call setErrorBuffer()! + static inline CURLcode check_easy_code(CURLcode code) + { + Stats::easy_calls++; + if (code != CURLE_OK) + handle_easy_error(code); + return code; + } + + protected: + // Return the underlying curl easy handle. + CURL* getEasyHandle(void) const { return mEasyHandle; } + + // Keep POSTFIELD data alive. + void setPostField(AIPostFieldPtr const& post_field_ptr) { mPostField = post_field_ptr; } + + private: + // Return, and possibly create, the curl (easy) error buffer used by the current thread. + static char* getTLErrorBuffer(void); +}; + +// CurlEasyRequest adds a slightly more powerful interface that can be used +// to set the options on a curl easy handle. +// +// Calling sendRequest() will then connect to the given URL and perform +// the data exchange. If an error occurs related to this handle, it can +// be read by calling getErrorString(). +// +// Note that the life cycle of a CurlEasyRequest is controlled by AICurlEasyRequest: +// a CurlEasyRequest is only ever created as base class of a ThreadSafeCurlEasyRequest, +// which is only created by creating a AICurlEasyRequest. When the last copy of such +// AICurlEasyRequest is deleted, then also the ThreadSafeCurlEasyRequest is deleted +// and the CurlEasyRequest destructed. +class CurlEasyRequest : public CurlEasyHandle { + private: + void setPost_raw(S32 size, char const* data); + public: + void setPost(S32 size) { setPost_raw(size, NULL); } + void setPost(AIPostFieldPtr const& postdata, S32 size); + void setPost(char const* data, S32 size) { setPost(new AIPostField(data), size); } + void setoptString(CURLoption option, std::string const& value); + void addHeader(char const* str); + + private: + // Callback stubs. + static size_t headerCallback(char* ptr, size_t size, size_t nmemb, void* userdata); + static size_t writeCallback(char* ptr, size_t size, size_t nmemb, void* userdata); + static size_t readCallback(char* ptr, size_t size, size_t nmemb, void* userdata); + static CURLcode SSLCtxCallback(CURL* curl, void* sslctx, void* userdata); + + curl_write_callback mHeaderCallback; + void* mHeaderCallbackUserData; + curl_write_callback mWriteCallback; + void* mWriteCallbackUserData; + curl_read_callback mReadCallback; + void* mReadCallbackUserData; + curl_ssl_ctx_callback mSSLCtxCallback; + void* mSSLCtxCallbackUserData; + + public: + void setHeaderCallback(curl_write_callback callback, void* userdata); + void setWriteCallback(curl_write_callback callback, void* userdata); + void setReadCallback(curl_read_callback callback, void* userdata); + void setSSLCtxCallback(curl_ssl_ctx_callback callback, void* userdata); + + // Call this if the set callbacks are about to be invalidated. + void revokeCallbacks(void); + + // Reset everything to the state it was in when this object was just created. + void resetState(void); + + private: + // Called from applyDefaultOptions. + void applyProxySettings(void); + + // Used in applyProxySettings. + static CURLcode curlCtxCallback(CURL* curl, void* sslctx, void* parm); + + public: + // Set default options that we want applied to all curl easy handles. + void applyDefaultOptions(void); + + // Prepare the request for adding it to a multi session, or calling perform. + // This actually adds the headers that were collected with addHeader. + void finalizeRequest(std::string const& url); + + // Store result code that is returned by getResult. + void store_result(CURLcode result) { mResult = result; } + + // Called when the curl easy handle is done. + void done(AICurlEasyRequest_wat& curl_easy_request_w) { finished(curl_easy_request_w); } + + // Fill info with the transfer info. + void getTransferInfo(AICurlInterface::TransferInfo* info); + + // If result != CURLE_FAILED_INIT then also info was filled. + void getResult(CURLcode* result, AICurlInterface::TransferInfo* info = NULL); + + private: + curl_slist* mHeaders; + bool mRequestFinalized; + AICurlEasyHandleEvents* mEventsTarget; + CURLcode mResult; + + private: + // This class may only be created by constructing a ThreadSafeCurlEasyRequest. + friend class ThreadSafeCurlEasyRequest; + // Throws AICurlNoEasyHandle. + CurlEasyRequest(void) : + mHeaders(NULL), mRequestFinalized(false), mEventsTarget(NULL), mResult(CURLE_FAILED_INIT) + { applyDefaultOptions(); } + public: + ~CurlEasyRequest(); + + public: + // Post-initialization, set the parent to pass the events to. + void send_events_to(AICurlEasyHandleEvents* target) { mEventsTarget = target; } + + // For debugging purposes + bool is_finalized(void) const { return mRequestFinalized; } + + // Return pointer to the ThreadSafe (wrapped) version of this object. + ThreadSafeCurlEasyRequest* get_lockobj(void); + + protected: + // Pass events to parent. + /*virtual*/ void added_to_multi_handle(AICurlEasyRequest_wat& curl_easy_request_w); + /*virtual*/ void finished(AICurlEasyRequest_wat& curl_easy_request_w); + /*virtual*/ void removed_from_multi_handle(AICurlEasyRequest_wat& curl_easy_request_w); +}; + +// Buffers used by the AICurlInterface::Request API. +// Curl callbacks write into and read from these buffers. +// The interface with the rest of the code is through AICurlInterface::Responder. +// +// The lifetime of a CurlResponderBuffer is slightly shorter than its +// associated CurlEasyRequest; this class can only be created as base class +// of ThreadSafeBufferedCurlEasyRequest, and is therefore constructed after +// the construction of the associated CurlEasyRequest and destructed before it. +// Hence, it's safe to use get_lockobj() and through that access the CurlEasyRequest +// object at all times. +// +// A CurlResponderBuffer is thus created when a ThreadSafeBufferedCurlEasyRequest +// is created which only happens by creating a AICurlEasyRequest(true) instance, +// and when the last AICurlEasyRequest is deleted, then the ThreadSafeBufferedCurlEasyRequest +// is deleted and the CurlResponderBuffer destructed. +class CurlResponderBuffer : protected AICurlEasyHandleEvents { + public: + void resetState(AICurlEasyRequest_wat& curl_easy_request_w); + void prepRequest(AICurlEasyRequest_wat& buffered_curl_easy_request_w, std::vector const& headers, AICurlInterface::ResponderPtr responder, S32 time_out = 0, bool post = false); + + LLIOPipe::buffer_ptr_t& getInput(void) { return mInput; } + std::stringstream& getHeaderOutput(void) { return mHeaderOutput; } + LLIOPipe::buffer_ptr_t& getOutput(void) { return mOutput; } + + // Called if libcurl doesn't deliver within CurlRequestTimeOut seconds. + void timed_out(void); + + // Called after removed_from_multi_handle was called. + void processOutput(AICurlEasyRequest_wat& curl_easy_request_w); + + protected: + /*virtual*/ void added_to_multi_handle(AICurlEasyRequest_wat& curl_easy_request_w); + /*virtual*/ void finished(AICurlEasyRequest_wat& curl_easy_request_w); + /*virtual*/ void removed_from_multi_handle(AICurlEasyRequest_wat& curl_easy_request_w); + + private: + LLIOPipe::buffer_ptr_t mInput; + U8* mLastRead; // Pointer into mInput where we last stopped reading (or NULL to start at the beginning). + std::stringstream mHeaderOutput; + LLIOPipe::buffer_ptr_t mOutput; + AICurlInterface::ResponderPtr mResponder; + + public: + static LLChannelDescriptors const sChannels; // Channel object for mInput (channel out()) and mOutput (channel in()). + + private: + // This class may only be created by constructing a ThreadSafeBufferedCurlEasyRequest. + friend class ThreadSafeBufferedCurlEasyRequest; + CurlResponderBuffer(void); + public: + ~CurlResponderBuffer(); + + private: + static size_t curlWriteCallback(char* data, size_t size, size_t nmemb, void* user_data); + static size_t curlReadCallback(char* data, size_t size, size_t nmemb, void* user_data); + static size_t curlHeaderCallback(char* data, size_t size, size_t nmemb, void* user_data); + + public: + // Return pointer to the ThreadSafe (wrapped) version of this object. + ThreadSafeBufferedCurlEasyRequest* get_lockobj(void); + + // Return true when prepRequest was already called and the object has not been + // invalidated as a result of calling timed_out(). + bool isValid(void) const { return mResponder; } +}; + +// This class wraps CurlEasyRequest for thread-safety and adds a reference counter so we can +// copy it around cheaply and it gets destructed automatically when the last instance is deleted. +// It guarantees that the CURL* handle is never used concurrently, which is not allowed by libcurl. +// As AIThreadSafeSimpleDC contains a mutex, it cannot be copied. Therefore we need a reference counter for this object. +class ThreadSafeCurlEasyRequest : public AIThreadSafeSimple { + public: + // Throws AICurlNoEasyHandle. + ThreadSafeCurlEasyRequest(void) : mReferenceCount(0) + { new (ptr()) CurlEasyRequest; + Dout(dc::curl, "Creating ThreadSafeCurlEasyRequest with this = " << (void*)this); } + virtual ~ThreadSafeCurlEasyRequest() + { Dout(dc::curl, "Destructing ThreadSafeCurlEasyRequest with this = " << (void*)this); } + + // Returns true if this is a base class of ThreadSafeBufferedCurlEasyRequest. + virtual bool isBuffered(void) const { return false; } + + private: + LLAtomicU32 mReferenceCount; + + friend void intrusive_ptr_add_ref(ThreadSafeCurlEasyRequest* p); // Called by boost::intrusive_ptr when a new copy of a boost::intrusive_ptr is made. + friend void intrusive_ptr_release(ThreadSafeCurlEasyRequest* p); // Called by boost::intrusive_ptr when a boost::intrusive_ptr is destroyed. +}; + +// Same as the above but adds a CurlResponderBuffer. The latter has its own locking in order to +// allow casting the underlying CurlEasyRequest to ThreadSafeCurlEasyRequest, independent of +// what class it is part of: ThreadSafeCurlEasyRequest or ThreadSafeBufferedCurlEasyRequest. +// The virtual destructor of ThreadSafeCurlEasyRequest allows to treat each easy handle transparently +// as a ThreadSafeCurlEasyRequest object, or optionally dynamic_cast it to a ThreadSafeBufferedCurlEasyRequest. +// Note: the order of these base classes is important: AIThreadSafeSimple is now +// destructed before ThreadSafeCurlEasyRequest is. +class ThreadSafeBufferedCurlEasyRequest : public ThreadSafeCurlEasyRequest, public AIThreadSafeSimple { + public: + // Throws AICurlNoEasyHandle. + ThreadSafeBufferedCurlEasyRequest(void) { new (AIThreadSafeSimple::ptr()) CurlResponderBuffer; } + + /*virtual*/ bool isBuffered(void) const { return true; } +}; + +// The curl easy request type wrapped in a reference counting pointer. +typedef boost::intrusive_ptr CurlEasyRequestPtr; + +// This class wraps CURLM*'s. +// It guarantees that a pointer is cleaned up when no longer needed, as required by libcurl. +class CurlMultiHandle : public boost::noncopyable { + public: + CurlMultiHandle(void); + ~CurlMultiHandle(); + + private: + // Disallow assignment. + CurlMultiHandle& operator=(CurlMultiHandle const*); + + private: + static LLAtomicU32 sTotalMultiHandles; + + protected: + CURLM* mMultiHandle; + + public: + // Set options for a curl multi handle. + CURLMcode setopt(CURLMoption option, long parameter); + CURLMcode setopt(CURLMoption option, curl_socket_callback parameter); + CURLMcode setopt(CURLMoption option, curl_multi_timer_callback parameter); + CURLMcode setopt(CURLMoption option, void* parameter); + + // Returns total number of existing CURLM* handles (excluding ones created outside this class). + static U32 getTotalMultiHandles(void) { return sTotalMultiHandles; } +}; + +// Overload the setopt methods in order to enforce the correct types (ie, convert an int to a long). + +// curl_multi_setopt may only be passed a long, +inline CURLMcode CurlMultiHandle::setopt(CURLMoption option, long parameter) +{ + llassert(option == CURLMOPT_MAXCONNECTS || option == CURLMOPT_PIPELINING); + return check_multi_code(curl_multi_setopt(mMultiHandle, option, parameter)); +} + +// ... or a function pointer, +inline CURLMcode CurlMultiHandle::setopt(CURLMoption option, curl_socket_callback parameter) +{ + llassert(option == CURLMOPT_SOCKETFUNCTION); + return check_multi_code(curl_multi_setopt(mMultiHandle, option, parameter)); +} + +inline CURLMcode CurlMultiHandle::setopt(CURLMoption option, curl_multi_timer_callback parameter) +{ + llassert(option == CURLMOPT_TIMERFUNCTION); + return check_multi_code(curl_multi_setopt(mMultiHandle, option, parameter)); +} + +// ... or an object pointer. +inline CURLMcode CurlMultiHandle::setopt(CURLMoption option, void* parameter) +{ + llassert(option == CURLMOPT_SOCKETDATA || option == CURLMOPT_TIMERDATA); + return check_multi_code(curl_multi_setopt(mMultiHandle, option, parameter)); +} + +} // namespace AICurlPrivate + +#endif diff --git a/indra/llmessage/aicurlthread.cpp b/indra/llmessage/aicurlthread.cpp new file mode 100644 index 000000000..0b1b85c0a --- /dev/null +++ b/indra/llmessage/aicurlthread.cpp @@ -0,0 +1,1710 @@ +/** + * @file aicurlthread.cpp + * @brief Implementation of AICurl, curl thread functions. + * + * Copyright (c) 2012, Aleric Inglewood. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution. + * + * CHANGELOG + * and additional copyright holders. + * + * 28/04/2012 + * Initial version, written by Aleric Inglewood @ SL + */ + +#include "linden_common.h" +#include "aicurlthread.h" +#include "lltimer.h" // ms_sleep +#include +#if !LL_WINDOWS +#include +#include +#include +#endif +#include + +// On linux, add -DDEBUG_WINDOWS_CODE_ON_LINUX to test the windows code used in this file. +#if !defined(DEBUG_WINDOWS_CODE_ON_LINUX) || !defined(LL_LINUX) || defined(LL_RELEASE) +#undef DEBUG_WINDOWS_CODE_ON_LINUX +#define DEBUG_WINDOWS_CODE_ON_LINUX 0 +#endif + +#if DEBUG_WINDOWS_CODE_ON_LINUX + +struct windows_fd_set { + unsigned int fd_count; + curl_socket_t fd_array[64]; +}; + +unsigned int const not_found = (unsigned int)-1; + +static inline unsigned int find_fd(curl_socket_t s, windows_fd_set const* fsp) +{ + for (unsigned int i = 0; i < fsp->fd_count; ++i) + { + if (fsp->fd_array[i] == s) + return i; + } + return not_found; +} + +static int windows_select(int, windows_fd_set* readfds, windows_fd_set* writefds, windows_fd_set*, timeval* val) +{ + fd_set r; + fd_set w; + FD_ZERO(&r); + FD_ZERO(&w); + int mfd = -1; + if (readfds) + { + for (int i = 0; i < readfds->fd_count; ++i) + { + int fd = readfds->fd_array[i]; + FD_SET(fd, &r); + mfd = llmax(mfd, fd); + } + } + if (writefds) + { + for (int i = 0; i < writefds->fd_count; ++i) + { + int fd = writefds->fd_array[i]; + FD_SET(fd, &w); + mfd = llmax(mfd, fd); + } + } + int nfds = select(mfd + 1, readfds ? &r : NULL, writefds ? &w : NULL, NULL, val); + if (readfds) + { + unsigned int fd_count = 0; + for (int i = 0; i < readfds->fd_count; ++i) + { + if (FD_ISSET(readfds->fd_array[i], &r)) + readfds->fd_array[fd_count++] = readfds->fd_array[i]; + } + readfds->fd_count = fd_count; + } + if (writefds) + { + unsigned int fd_count = 0; + for (int i = 0; i < writefds->fd_count; ++i) + { + if (FD_ISSET(writefds->fd_array[i], &w)) + writefds->fd_array[fd_count++] = writefds->fd_array[i]; + } + writefds->fd_count = fd_count; + } + return nfds; +} + +#undef FD_SETSIZE +#undef FD_ZERO +#undef FD_ISSET +#undef FD_SET +#undef FD_CLR + +int const FD_SETSIZE = sizeof(windows_fd_set::fd_array) / sizeof(curl_socket_t); + +static void FD_ZERO(windows_fd_set* fsp) +{ + fsp->fd_count = 0; +} + +static bool FD_ISSET(curl_socket_t s, windows_fd_set const* fsp) +{ + return find_fd(s, fsp) != not_found; +} + +static void FD_SET(curl_socket_t s, windows_fd_set* fsp) +{ + llassert(!FD_ISSET(s, fsp)); + fsp->fd_array[fsp->fd_count++] = s; +} + +static void FD_CLR(curl_socket_t s, windows_fd_set* fsp) +{ + unsigned int i = find_fd(s, fsp); + llassert(i != not_found); + fsp->fd_array[i] = fsp->fd_array[--(fsp->fd_count)]; +} + +#define fd_set windows_fd_set +#define select windows_select + +int WSAGetLastError(void) +{ + return errno; +} + +typedef char* LPTSTR; + +bool FormatMessage(int, void*, int e, int, LPTSTR error_str_ptr, int, void*) +{ + char* error_str = *(LPTSTR*)error_str_ptr; + error_str = strerror(e); + return true; +} + +void LocalFree(LPTSTR) +{ +} + +int const FORMAT_MESSAGE_ALLOCATE_BUFFER = 0; +int const FORMAT_MESSAGE_FROM_SYSTEM = 0; +int const FORMAT_MESSAGE_IGNORE_INSERTS = 0; +int const INVALID_SOCKET = -1; +int const SOCKET_ERROR = -1; +int const WSAEWOULDBLOCK = EWOULDBLOCK; + +int closesocket(curl_socket_t fd) +{ + return close(fd); +} + +int const FIONBIO = 0; + +int ioctlsocket(int fd, int, unsigned long* nonblocking_enable) +{ + int res = fcntl(fd, F_GETFL, 0); + llassert_always(res != -1); + if (*nonblocking_enable) + res |= O_NONBLOCK; + else + res &= ~O_NONBLOCK; + return fcntl(fd, F_SETFD, res); +} + +#endif // DEBUG_WINDOWS_CODE_ON_LINUX + +#define WINDOWS_CODE (LL_WINDOWS || DEBUG_WINDOWS_CODE_ON_LINUX) + +#undef AICurlPrivate + +namespace AICurlPrivate { + +enum command_st { + cmd_none, + cmd_add, + cmd_boost, + cmd_remove +}; + +class Command { + public: + Command(void) : mCommand(cmd_none) { } + Command(AICurlEasyRequest const& easy_request, command_st command) : mCurlEasyRequest(easy_request.get_ptr()), mCommand(command) { } + + command_st command(void) const { return mCommand; } + CurlEasyRequestPtr const& easy_request(void) const { return mCurlEasyRequest; } + + bool operator==(AICurlEasyRequest const& easy_request) const { return mCurlEasyRequest == easy_request.get_ptr(); } + + void reset(void); + + private: + CurlEasyRequestPtr mCurlEasyRequest; + command_st mCommand; +}; + +void Command::reset(void) +{ + mCurlEasyRequest.reset(); + mCommand = cmd_none; +} + +// The following two globals have separate locks for speed considerations (in order not +// to block the main thread unnecessarily) but have the following correlation: +// +// MAIN-THREAD (AICurlEasyRequest::addRequest) +// * command_queue locked +// - A non-active (mActiveMultiHandle is NULL) ThreadSafeCurlEasyRequest (by means of an AICurlEasyRequest pointing to it) is added to command_queue with as command cmd_add. +// * command_queue unlocked +// +// If at this point addRequest is called again, then it is detected that the last command added to the queue +// for this ThreadSafeCurlEasyRequest is cmd_add. +// +// CURL-THREAD (AICurlThread::wakeup): +// * command_queue locked +// * command_being_processed is write-locked +// - command_being_processed is assigned the value of the command in the queue. +// * command_being_processed is unlocked +// - The command is removed from command_queue +// * command_queue unlocked +// +// If at this point addRequest is called again, then it is detected that command_being_processed adds the same ThreadSafeCurlEasyRequest. +// +// * command_being_processed is read-locked +// - mActiveMultiHandle is set to point to the curl multi handle +// - The easy handle is added to the multi handle +// * command_being_processed is write-locked +// - command_being_processed is reset +// * command_being_processed is unlocked +// +// If at this point addRequest is called again, then it is detected that the ThreadSafeCurlEasyRequest is active. + +// Multi-threaded queue for passing Command objects from the main-thread to the curl-thread. +AIThreadSafeSimpleDC > command_queue; +typedef AIAccess > command_queue_wat; + +AIThreadSafeDC command_being_processed; +typedef AIWriteAccess command_being_processed_wat; +typedef AIReadAccess command_being_processed_rat; + +namespace curlthread { +// All functions in this namespace are only run by the curl thread, unless they are marked with MAIN-THREAD. + +//----------------------------------------------------------------------------- +// PollSet + +int const empty = 0x1; +int const complete = 0x2; + +enum refresh_t { + not_complete_not_empty = 0, + complete_not_empty = complete, + empty_and_complete = complete|empty +}; + +class PollSet +{ + public: + PollSet(void); + + // Add/remove a filedescriptor to/from mFileDescriptors. + void add(curl_socket_t s); + void remove(curl_socket_t s); + + // Copy mFileDescriptors to an internal fd_set that is returned by access(). + // Returns if all fds could be copied (complete) and/or if the resulting fd_set is empty. + refresh_t refresh(void); + + // Return a pointer to the underlaying fd_set. + fd_set* access(void) { return &mFdSet; } + +#if !WINDOWS_CODE + // Return the largest fd set in mFdSet by refresh. + curl_socket_t get_max_fd(void) const { return mMaxFdSet; } +#endif + + // Return true if a filedescriptor is set in mFileDescriptors (used for debugging). + bool contains(curl_socket_t s) const; + + // Return true if a filedescriptor is set in mFdSet. + bool is_set(curl_socket_t s) const; + + // Clear filedescriptor in mFdSet. + void clr(curl_socket_t fd); + + // Iterate over all file descriptors that were set by refresh and are still set in mFdSet. + void reset(void); // Reset the iterator. + curl_socket_t get(void) const; // Return next filedescriptor, or CURL_SOCKET_BAD when there are no more. + // Only valid if reset() was called after the last call to refresh(). + void next(void); // Advance to next filedescriptor. + + private: + curl_socket_t* mFileDescriptors; + int mNrFds; // The number of filedescriptors in the array. + int mNext; // The index of the first file descriptor to start copying, the next call to refresh(). + + fd_set mFdSet; // Output variable for select(). (Re)initialized by calling refresh(). + +#if !WINDOWS_CODE + curl_socket_t mMaxFd; // The largest filedescriptor in the array, or CURL_SOCKET_BAD when it is empty. + curl_socket_t mMaxFdSet; // The largest filedescriptor set in mFdSet by refresh(), or CURL_SOCKET_BAD when it was empty. + std::vector mCopiedFileDescriptors; // Filedescriptors copied by refresh to mFdSet. + std::vector::iterator mIter; // Index into mCopiedFileDescriptors for next(); loop variable. +#else + unsigned int mIter; // Index into fd_set::fd_array. +#endif +}; + +// A PollSet can store at least 1024 filedescriptors, or FD_SETSIZE if that is larger than 1024 [MAXSIZE]. +// The number of stored filedescriptors is mNrFds [0 <= mNrFds <= MAXSIZE]. +// The largest filedescriptor is stored is mMaxFd, which is -1 iff mNrFds == 0. +// The file descriptors are stored contiguous in mFileDescriptors[i], with 0 <= i < mNrFds. +// File descriptors with the highest priority should be stored first (low index). +// +// mNext is an index into mFileDescriptors that is copied first, the next call to refresh(). +// It is set to 0 when mNrFds < FD_SETSIZE, even if mNrFds == 0. +// +// After a call to refresh(): +// +// mFdSet has bits set for at most FD_SETSIZE - 1 filedescriptors, copied from mFileDescriptors starting +// at index mNext (wrapping around to 0). If mNrFds < FD_SETSIZE then mNext is reset to 0 before copying starts. +// If mNrFds >= FD_SETSIZE then mNext is set to the next filedescriptor that was not copied (otherwise it is left at 0). +// +// mMaxFdSet is the largest filedescriptor in mFdSet or -1 if it is empty. + +static size_t const MAXSIZE = llmax(1024, FD_SETSIZE); + +// Create an empty PollSet. +PollSet::PollSet(void) : mFileDescriptors(new curl_socket_t [MAXSIZE]), + mNrFds(0), mNext(0) +#if !WINDOWS_CODE + , mMaxFd(-1), mMaxFdSet(-1) +#endif +{ + FD_ZERO(&mFdSet); +} + +// Add filedescriptor s to the PollSet. +void PollSet::add(curl_socket_t s) +{ + llassert_always(mNrFds < (int)MAXSIZE); + mFileDescriptors[mNrFds++] = s; +#if !WINDOWS_CODE + mMaxFd = llmax(mMaxFd, s); +#endif +} + +// Remove filedescriptor s from the PollSet. +void PollSet::remove(curl_socket_t s) +{ + // The number of open filedescriptors is relatively small, + // and on top of that we rather do something CPU intensive + // than bandwidth intensive (lookup table). Hence that this + // is a linear search in an array containing just the open + // filedescriptors. Then, since we are reading this memory + // page anyway, we might as well write to it without losing + // much clock cycles. Therefore, shift the whole vector + // back, keeping it compact and keeping the filedescriptors + // in the same order (which is supposedly their priority). + // + // The general case is where mFileDescriptors contains s at an index + // between 0 and mNrFds: + // mNrFds = 6 + // v + // index: 0 1 2 3 4 5 + // a b c s d e + + // This function should never be called unless s is actually in mFileDescriptors, + // as a result of a previous call to PollSet::add(). + llassert(mNrFds > 0); + + // Correct mNrFds for when the descriptor is removed. + // Make i 'point' to the last entry. + int i = --mNrFds; + // i = NrFds = 5 + // v + // index: 0 1 2 3 4 5 + // a b c s d e + curl_socket_t cur = mFileDescriptors[i]; // cur = 'e' +#if !WINDOWS_CODE + curl_socket_t max = -1; +#endif + while (cur != s) + { + llassert(i > 0); + curl_socket_t next = mFileDescriptors[--i]; // next = 'd' + mFileDescriptors[i] = cur; // Overwrite 'd' with 'e'. +#if !WINDOWS_CODE + max = llmax(max, cur); // max is the maximum value in 'i' or higher. +#endif + cur = next; // cur = 'd' + // i NrFds = 5 + // v v + // index: 0 1 2 3 4 + // a b c s e // cur = 'd' + // + // Next loop iteration: next = 's', overwrite 's' with 'd', cur = 's'; loop terminates. + // i NrFds = 5 + // v v + // index: 0 1 2 3 4 + // a b c d e // cur = 's' + } + llassert(cur == s); + // At this point i was decremented once more and points to the element before the old s. + // i NrFds = 5 + // v v + // index: 0 1 2 3 4 + // a b c d e // max = llmax('d', 'e') + + // If mNext pointed to an element before s, it should be left alone. Otherwise, if mNext pointed + // to s it must now point to 'd', or if it pointed beyond 's' it must be decremented by 1. + if (mNext > i) // i is where s was. + --mNext; + +#if !WINDOWS_CODE + // If s was the largest file descriptor, we have to update mMaxFd. + if (s == mMaxFd) + { + while (i > 0) + { + curl_socket_t next = mFileDescriptors[--i]; + max = llmax(max, next); + } + mMaxFd = max; + llassert(mMaxFd < s); + llassert((mMaxFd == -1) == (mNrFds == 0)); + } +#endif + + // ALSO make sure that s is no longer set in mFdSet, or we might confuse libcurl by + // calling curl_multi_socket_action for a socket that it told us to remove. +#if !WINDOWS_CODE + clr(s); +#else + // We have to use a custom implementation here, because we don't want to invalidate mIter. + // This is the same algorithm as above, but with mFdSet.fd_count instead of mNrFds, + // mFdSet.fd_array instead of mFileDescriptors and mIter instead of mNext. + if (FD_ISSET(s, &mFdSet)) + { + llassert(mFdSet.fd_count > 0); + unsigned int i = --mFdSet.fd_count; + curl_socket_t cur = mFdSet.fd_array[i]; + while (cur != s) + { + llassert(i > 0); + curl_socket_t next = mFdSet.fd_array[--i]; + mFdSet.fd_array[i] = cur; + cur = next; + } + if (mIter > i) + --mIter; + llassert(mIter <= mFdSet.fd_count); + } +#endif +} + +bool PollSet::contains(curl_socket_t fd) const +{ + for (int i = 0; i < mNrFds; ++i) + if (mFileDescriptors[i] == fd) + return true; + return false; +} + +inline bool PollSet::is_set(curl_socket_t fd) const +{ + return FD_ISSET(fd, &mFdSet); +} + +inline void PollSet::clr(curl_socket_t fd) +{ + FD_CLR(fd, &mFdSet); +} + +// This function fills mFdSet with at most FD_SETSIZE - 1 filedescriptors, +// starting at index mNext (updating mNext when not all could be added), +// and updates mMaxFdSet to be the largest fd added to mFdSet, or -1 if it's empty. +refresh_t PollSet::refresh(void) +{ + FD_ZERO(&mFdSet); +#if !WINDOWS_CODE + mCopiedFileDescriptors.clear(); +#endif + + if (mNrFds == 0) + { +#if !WINDOWS_CODE + mMaxFdSet = -1; +#endif + return empty_and_complete; + } + + llassert_always(mNext < mNrFds); + + // Test if mNrFds is larger than or equal to FD_SETSIZE; equal, because we reserve one + // filedescriptor for the wakeup fd: we copy maximal FD_SETSIZE - 1 filedescriptors. + // If not then we're going to copy everything so that we can save on CPU cycles + // by not calculating mMaxFdSet here. + if (mNrFds >= FD_SETSIZE) + { + llwarns << "PollSet::reset: More than FD_SETSIZE (" << FD_SETSIZE << ") file descriptors active!" << llendl; +#if !WINDOWS_CODE + // Calculate mMaxFdSet. + // Run over FD_SETSIZE - 1 elements, starting at mNext, wrapping to 0 when we reach the end. + int max = -1, i = mNext, count = 0; + while (++count < FD_SETSIZE) { max = llmax(max, mFileDescriptors[i]); if (++i == mNrFds) i = 0; } + mMaxFdSet = max; +#endif + } + else + { + mNext = 0; // Start at the beginning if we copy everything anyway. +#if !WINDOWS_CODE + mMaxFdSet = mMaxFd; +#endif + } + int count = 0; + int i = mNext; + for(;;) + { + if (++count == FD_SETSIZE) + { + mNext = i; + return not_complete_not_empty; + } + FD_SET(mFileDescriptors[i], &mFdSet); +#if !WINDOWS_CODE + mCopiedFileDescriptors.push_back(mFileDescriptors[i]); +#endif + if (++i == mNrFds) + { + // If we reached the end and start at the beginning, then we copied everything. + if (mNext == 0) + break; + // When can only come here if mNrFds >= FD_SETSIZE, hence we can just + // wrap around and terminate on count reaching FD_SETSIZE. + i = 0; + } + } + return complete_not_empty; +} + +// The API reset(), get() and next() allows one to run over all filedescriptors +// in mFdSet that are set. This works by running only over the filedescriptors +// that were set initially (by the call to refresh()) and then checking if that +// filedescriptor is (still) set in mFdSet. +// +// A call to reset() resets mIter to the beginning, so that get() returns +// the first filedescriptor that is still set. A call to next() progresses +// the iterator to the next set filedescriptor. If get() return -1, then there +// were no more filedescriptors set. +// +// Note that one should never call next() unless get() didn't return -1, so +// the call sequence is: +// refresh(); +// /* reset some or all bits in mFdSet */ +// reset(); +// while (get() != CURL_SOCKET_BAD) // next(); +// +// Note also that this API is only used by MergeIterator, which wraps it +// and provides a different API to use. + +void PollSet::reset(void) +{ +#if WINDOWS_CODE + mIter = 0; +#else + if (mCopiedFileDescriptors.empty()) + mIter = mCopiedFileDescriptors.end(); + else + { + mIter = mCopiedFileDescriptors.begin(); + if (!FD_ISSET(*mIter, &mFdSet)) + next(); + } +#endif +} + +inline curl_socket_t PollSet::get(void) const +{ +#if WINDOWS_CODE + return (mIter >= mFdSet.fd_count) ? CURL_SOCKET_BAD : mFdSet.fd_array[mIter]; +#else + return (mIter == mCopiedFileDescriptors.end()) ? CURL_SOCKET_BAD : *mIter; +#endif +} + +void PollSet::next(void) +{ +#if WINDOWS_CODE + llassert(mIter < mFdSet.fd_count); + ++mIter; +#else + llassert(mIter != mCopiedFileDescriptors.end()); // Only call next() if the last call to get() didn't return -1. + while (++mIter != mCopiedFileDescriptors.end() && !FD_ISSET(*mIter, &mFdSet)); +#endif +} + +//----------------------------------------------------------------------------- +// MergeIterator +// +// This class takes two PollSet's and allows one to run over all filedescriptors +// that are set in one or both poll sets, returning each filedescriptor only +// once, by calling next() until it returns false. + +class MergeIterator +{ + public: + MergeIterator(PollSet* readPollSet, PollSet* writePollSet); + + bool next(curl_socket_t& fd_out, int& ev_bitmask_out); + + private: + PollSet* mReadPollSet; + PollSet* mWritePollSet; + int readIndx; + int writeIndx; +}; + +MergeIterator::MergeIterator(PollSet* readPollSet, PollSet* writePollSet) : + mReadPollSet(readPollSet), mWritePollSet(writePollSet), readIndx(0), writeIndx(0) +{ + mReadPollSet->reset(); + mWritePollSet->reset(); +} + +bool MergeIterator::next(curl_socket_t& fd_out, int& ev_bitmask_out) +{ + curl_socket_t rfd = mReadPollSet->get(); + curl_socket_t wfd = mWritePollSet->get(); + + if (rfd == CURL_SOCKET_BAD && wfd == CURL_SOCKET_BAD) + return false; + + if (rfd == wfd) + { + fd_out = rfd; + ev_bitmask_out = CURL_CSELECT_IN | CURL_CSELECT_OUT; + mReadPollSet->next(); + } + else if (wfd == CURL_SOCKET_BAD || (rfd != CURL_SOCKET_BAD && rfd < wfd)) // Use and increment smaller one, unless it's CURL_SOCKET_BAD. + { + fd_out = rfd; + ev_bitmask_out = CURL_CSELECT_IN; + mReadPollSet->next(); + if (wfd != CURL_SOCKET_BAD && mWritePollSet->is_set(rfd)) + { + ev_bitmask_out |= CURL_CSELECT_OUT; + mWritePollSet->clr(rfd); + } + } + else + { + fd_out = wfd; + ev_bitmask_out = CURL_CSELECT_OUT; + mWritePollSet->next(); + if (rfd != CURL_SOCKET_BAD && mReadPollSet->is_set(wfd)) + { + ev_bitmask_out |= CURL_CSELECT_IN; + mReadPollSet->clr(wfd); + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// CurlSocketInfo + +// A class with info for each socket that is in use by curl. +class CurlSocketInfo +{ + public: + CurlSocketInfo(MultiHandle& multi_handle, CURL* easy, curl_socket_t s, int action); + ~CurlSocketInfo(); + + void set_action(int action); + + private: + MultiHandle& mMultiHandle; + CURL const* mEasy; + curl_socket_t mSocketFd; + int mAction; +}; + +CurlSocketInfo::CurlSocketInfo(MultiHandle& multi_handle, CURL* easy, curl_socket_t s, int action) : + mMultiHandle(multi_handle), mEasy(easy), mSocketFd(s), mAction(CURL_POLL_NONE) +{ + mMultiHandle.assign(s, this); + llassert(!mMultiHandle.mReadPollSet->contains(s)); + llassert(!mMultiHandle.mWritePollSet->contains(s)); + set_action(action); +} + +CurlSocketInfo::~CurlSocketInfo() +{ + set_action(CURL_POLL_NONE); +} + +void CurlSocketInfo::set_action(int action) +{ + int toggle_action = mAction ^ action; + mAction = action; + if ((toggle_action & CURL_POLL_IN)) + { + if ((action & CURL_POLL_IN)) + mMultiHandle.mReadPollSet->add(mSocketFd); + else + mMultiHandle.mReadPollSet->remove(mSocketFd); + } + if ((toggle_action & CURL_POLL_OUT)) + { + if ((action & CURL_POLL_OUT)) + mMultiHandle.mWritePollSet->add(mSocketFd); + else + mMultiHandle.mWritePollSet->remove(mSocketFd); + } +} + +//----------------------------------------------------------------------------- +// AICurlThread + +class AICurlThread : public LLThread +{ + public: + static AICurlThread* sInstance; + LLMutex mWakeUpMutex; + bool mWakeUpFlag; // Protected by mWakeUpMutex. + + public: + // MAIN-THREAD + AICurlThread(void); + virtual ~AICurlThread(); + + // MAIN-THREAD + void wakeup_thread(void); + + // MAIN-THREAD + void stop_thread(void) { mRunning = false; wakeup_thread(); } + + protected: + virtual void run(void); + void wakeup(AICurlMultiHandle_wat const& multi_handle_w); + void process_commands(AICurlMultiHandle_wat const& multi_handle_w); + + private: + // MAIN-THREAD + void create_wakeup_fds(void); + void cleanup_wakeup_fds(void); + + curl_socket_t mWakeUpFd_in; + curl_socket_t mWakeUpFd; + + int mZeroTimeOut; + + volatile bool mRunning; +}; + +// Only the main thread is accessing this. +AICurlThread* AICurlThread::sInstance = NULL; + +// MAIN-THREAD +AICurlThread::AICurlThread(void) : LLThread("AICurlThread"), + mWakeUpFd_in(CURL_SOCKET_BAD), + mWakeUpFd(CURL_SOCKET_BAD), + mZeroTimeOut(0), mRunning(true), mWakeUpFlag(false) +{ + create_wakeup_fds(); + sInstance = this; +} + +// MAIN-THREAD +AICurlThread::~AICurlThread() +{ + sInstance = NULL; + cleanup_wakeup_fds(); +} + +#if LL_WINDOWS +static std::string formatWSAError() +{ + std::ostringstream r; + int e = WSAGetLastError(); + LPTSTR error_str = 0; + r << e; + if(FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, e, 0, (LPTSTR)&error_str, 0, NULL)) + { + r << " " << utf16str_to_utf8str(error_str); + LocalFree(error_str); + } + else + { + r << " Unknown WinSock error"; + } + return r.str(); +} +#elif WINDOWS_CODE +static std::string formatWSAError() +{ + return strerror(errno); +} +#endif + +#if LL_WINDOWS + /* Copyright 2007, 2010 by Nathan C. Myers + * This code is Free Software. It may be copied freely, in original or + * modified form, subject only to the restrictions that (1) the author is + * relieved from all responsibilities for any use for any purpose, and (2) + * this copyright notice must be retained, unchanged, in its entirety. If + * for any reason the author might be held responsible for any consequences + * of copying or use, license is withheld. + */ +static int dumb_socketpair(SOCKET socks[2], bool make_overlapped) +{ + union { + struct sockaddr_in inaddr; + struct sockaddr addr; + } a; + SOCKET listener; + int e; + socklen_t addrlen = sizeof(a.inaddr); + DWORD flags = (make_overlapped ? WSA_FLAG_OVERLAPPED : 0); + int reuse = 1; + + if (socks == 0) { + WSASetLastError(WSAEINVAL); + return SOCKET_ERROR; + } + + listener = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (listener == INVALID_SOCKET) + return SOCKET_ERROR; + + memset(&a, 0, sizeof(a)); + a.inaddr.sin_family = AF_INET; + a.inaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + a.inaddr.sin_port = 0; + + socks[0] = socks[1] = INVALID_SOCKET; + do { + if (setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, + (char*) &reuse, (socklen_t) sizeof(reuse)) == -1) + break; + if (bind(listener, &a.addr, sizeof(a.inaddr)) == SOCKET_ERROR) + break; + + memset(&a, 0, sizeof(a)); + if (getsockname(listener, &a.addr, &addrlen) == SOCKET_ERROR) + break; + // win32 getsockname may only set the port number, p=0.0005. + // ( http://msdn.microsoft.com/library/ms738543.aspx ): + a.inaddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + a.inaddr.sin_family = AF_INET; + + if (listen(listener, 1) == SOCKET_ERROR) + break; + + socks[0] = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, flags); + if (socks[0] == INVALID_SOCKET) + break; + if (connect(socks[0], &a.addr, sizeof(a.inaddr)) == SOCKET_ERROR) + break; + + socks[1] = accept(listener, NULL, NULL); + if (socks[1] == INVALID_SOCKET) + break; + + closesocket(listener); + return 0; + + } while (0); + + e = WSAGetLastError(); + closesocket(listener); + closesocket(socks[0]); + closesocket(socks[1]); + WSASetLastError(e); + return SOCKET_ERROR; +} +#elif WINDOWS_CODE +int dumb_socketpair(int socks[2], int dummy) +{ + (void) dummy; + return socketpair(AF_LOCAL, SOCK_STREAM, 0, socks); +} +#endif + +// MAIN-THREAD +void AICurlThread::create_wakeup_fds(void) +{ +#if WINDOWS_CODE + //SGTODO + curl_socket_t socks[2]; + if (dumb_socketpair(socks, false) == SOCKET_ERROR) + { + llerrs << "Failed to generate wake-up socket pair" << formatWSAError() << llendl; + return; + } + u_long nonblocking_enable = TRUE; + int error = ioctlsocket(socks[0], FIONBIO, &nonblocking_enable); + if(error) + { + llerrs << "Failed to set wake-up socket nonblocking: " << formatWSAError() << llendl; + } + llassert(nonblocking_enable); + error = ioctlsocket(socks[1], FIONBIO, &nonblocking_enable); + if(error) + { + llerrs << "Failed to set wake-up input socket nonblocking: " << formatWSAError() << llendl; + } + mWakeUpFd = socks[0]; + mWakeUpFd_in = socks[1]; +#else + int pipefd[2]; + if (pipe(pipefd)) + { + llerrs << "Failed to create wakeup pipe: " << strerror(errno) << llendl; + } + int const flags = O_NONBLOCK; + for (int i = 0; i < 2; ++i) + { + if (fcntl(pipefd[i], F_SETFL, flags)) + { + llerrs << "Failed to set pipe to non-blocking: " << strerror(errno) << llendl; + } + } + mWakeUpFd = pipefd[0]; // Read-end of the pipe. + mWakeUpFd_in = pipefd[1]; // Write-end of the pipe. +#endif +} + +// MAIN-THREAD +void AICurlThread::cleanup_wakeup_fds(void) +{ +#if WINDOWS_CODE + //SGTODO + if (mWakeUpFd != CURL_SOCKET_BAD) + { + int error = closesocket(mWakeUpFd); + if (error) + { + llwarns << "Error closing wake-up socket" << formatWSAError() << llendl; + } + } + if (mWakeUpFd_in != CURL_SOCKET_BAD) + { + int error = closesocket(mWakeUpFd_in); + if (error) + { + llwarns << "Error closing wake-up input socket" << formatWSAError() << llendl; + } + } +#else + if (mWakeUpFd_in != CURL_SOCKET_BAD) + close(mWakeUpFd_in); + if (mWakeUpFd != CURL_SOCKET_BAD) + close(mWakeUpFd); +#endif +} + +// MAIN-THREAD +void AICurlThread::wakeup_thread(void) +{ + DoutEntering(dc::curl, "AICurlThread::wakeup_thread"); + llassert(is_main_thread()); + + // Try if curl thread is still awake and if so, pass the new commands directly. + if (mWakeUpMutex.tryLock()) + { + mWakeUpFlag = true; + mWakeUpMutex.unlock(); + return; + } + +#if WINDOWS_CODE + //SGTODO + int len = send(mWakeUpFd_in, "!", 1, 0); + if (len == SOCKET_ERROR) + { + llerrs << "Send to wake-up socket failed: " << formatWSAError() << llendl; + } + llassert_always(len == 1); + //SGTODO: handle EAGAIN if needed +#else + // If write() is interrupted by a signal before it writes any data, it shall return -1 with errno set to [EINTR]. + // If write() is interrupted by a signal after it successfully writes some data, it shall return the number of bytes written. + // Write requests to a pipe or FIFO shall be handled in the same way as a regular file with the following exceptions: + // If the O_NONBLOCK flag is set, write() requests shall be handled differently, in the following ways: + // A write request for {PIPE_BUF} or fewer bytes shall have the following effect: + // if there is sufficient space available in the pipe, write() shall transfer all the data and return the number + // of bytes requested. Otherwise, write() shall transfer no data and return -1 with errno set to [EAGAIN]. + ssize_t len; + do + { + len = write(mWakeUpFd_in, "!", 1); + if (len == -1 && errno == EAGAIN) + return; // Unread characters are still in the pipe, so no need to add more. + } + while(len == -1 && errno == EINTR); + if (len == -1) + { + llerrs << "write(3) to mWakeUpFd_in: " << strerror(errno) << llendl; + } + llassert_always(len == 1); +#endif +} + +void AICurlThread::wakeup(AICurlMultiHandle_wat const& multi_handle_w) +{ + DoutEntering(dc::curl, "AICurlThread::wakeup"); + +#if WINDOWS_CODE + //SGTODO + char buf[256]; + bool got_data = false; + for(;;) + { + int len = recv(mWakeUpFd, buf, sizeof(buf), 0); + if (len > 0) + { + // Data was read from the pipe. + got_data = true; + if (len < sizeof(buf)) + break; + } + else if (len == SOCKET_ERROR) + { + // An error occurred. + if (errno == EWOULDBLOCK) + { + if (got_data) + break; + // There was no data, even though select() said so. If this ever happens at all(?), lets just return and enter select() again. + return; + } + else if (errno == EINTR) + { + continue; + } + else + { + llerrs << "read(3) from mWakeUpFd: " << formatWSAError() << llendl; + return; + } + } + else + { + // pipe(2) returned 0. + llwarns << "read(3) from mWakeUpFd returned 0, indicating that the pipe on the other end was closed! Shutting down curl thread." << llendl; + closesocket(mWakeUpFd); + mWakeUpFd = CURL_SOCKET_BAD; + mRunning = false; + return; + } + } +#else + // If a read() is interrupted by a signal before it reads any data, it shall return -1 with errno set to [EINTR]. + // If a read() is interrupted by a signal after it has successfully read some data, it shall return the number of bytes read. + // When attempting to read from an empty pipe or FIFO: + // If no process has the pipe open for writing, read() shall return 0 to indicate end-of-file. + // If some process has the pipe open for writing and O_NONBLOCK is set, read() shall return -1 and set errno to [EAGAIN]. + char buf[256]; + bool got_data = false; + for(;;) + { + ssize_t len = read(mWakeUpFd, buf, sizeof(buf)); + if (len > 0) + { + // Data was read from the pipe. + got_data = true; + if (len < sizeof(buf)) + break; + } + else if (len == -1) + { + // An error occurred. + if (errno == EAGAIN) + { + if (got_data) + break; + // There was no data, even though select() said so. If this ever happens at all(?), lets just return and enter select() again. + return; + } + else if (errno == EINTR) + { + continue; + } + else + { + llerrs << "read(3) from mWakeUpFd: " << strerror(errno) << llendl; + return; + } + } + else + { + // pipe(2) returned 0. + llwarns << "read(3) from mWakeUpFd returned 0, indicating that the pipe on the other end was closed! Shutting down curl thread." << llendl; + close(mWakeUpFd); + mWakeUpFd = CURL_SOCKET_BAD; + mRunning = false; + return; + } + } +#endif + // Data was received on mWakeUpFd. This means that the main-thread added one + // or more commands to the command queue and called wakeUpCurlThread(). + process_commands(multi_handle_w); +} + +void AICurlThread::process_commands(AICurlMultiHandle_wat const& multi_handle_w) +{ + DoutEntering(dc::curl, "AICurlThread::process_commands(void)"); + + // If we get here then the main thread called wakeup_thread() recently. + for(;;) + { + // Access command_queue, and move command to command_being_processed. + { + command_queue_wat command_queue_w(command_queue); + if (command_queue_w->empty()) + { + mWakeUpMutex.lock(); + mWakeUpFlag = false; + mWakeUpMutex.unlock(); + break; + } + // Move the next command from the queue into command_being_processed. + *command_being_processed_wat(command_being_processed) = command_queue_w->front(); + command_queue_w->pop_front(); + } + // Access command_being_processed only. + { + command_being_processed_rat command_being_processed_r(command_being_processed); + switch(command_being_processed_r->command()) + { + case cmd_none: + case cmd_boost: // FIXME: future stuff + break; + case cmd_add: + multi_handle_w->add_easy_request(AICurlEasyRequest(command_being_processed_r->easy_request())); + break; + case cmd_remove: + multi_handle_w->remove_easy_request(AICurlEasyRequest(command_being_processed_r->easy_request()), true); + break; + } + // Done processing. + command_being_processed_wat command_being_processed_w(command_being_processed_r); + command_being_processed_w->reset(); // This destroys the CurlEasyRequest in case of a cmd_remove. + } + } +} + +// The main loop of the curl thread. +void AICurlThread::run(void) +{ + DoutEntering(dc::curl, "AICurlThread::run()"); + + { + AICurlMultiHandle_wat multi_handle_w(AICurlMultiHandle::getInstance()); + while(mRunning) + { + // If mRunning is true then we can only get here if mWakeUpFd != CURL_SOCKET_BAD. + llassert(mWakeUpFd != CURL_SOCKET_BAD); + // Copy the next batch of file descriptors from the PollSets mFiledescriptors into their mFdSet. + multi_handle_w->mReadPollSet->refresh(); + refresh_t wres = multi_handle_w->mWritePollSet->refresh(); + // Add wake up fd if any, and pass NULL to select() if a set is empty. + fd_set* read_fd_set = multi_handle_w->mReadPollSet->access(); + FD_SET(mWakeUpFd, read_fd_set); + fd_set* write_fd_set = ((wres & empty)) ? NULL : multi_handle_w->mWritePollSet->access(); + // Calculate nfds (ignored on windows). +#if !WINDOWS_CODE + curl_socket_t const max_rfd = llmax(multi_handle_w->mReadPollSet->get_max_fd(), mWakeUpFd); + curl_socket_t const max_wfd = multi_handle_w->mWritePollSet->get_max_fd(); + int nfds = llmax(max_rfd, max_wfd) + 1; + llassert(0 <= nfds && nfds <= FD_SETSIZE); + llassert((max_rfd == -1) == (read_fd_set == NULL) && + (max_wfd == -1) == (write_fd_set == NULL)); // Needed on Windows. + llassert((max_rfd == -1 || multi_handle_w->mReadPollSet->is_set(max_rfd)) && + (max_wfd == -1 || multi_handle_w->mWritePollSet->is_set(max_wfd))); +#else + int nfds = 64; +#endif + int ready = 0; + // Process every command in command_queue before entering select(). + for(;;) + { + mWakeUpMutex.lock(); + if (mWakeUpFlag) + { + mWakeUpMutex.unlock(); + process_commands(multi_handle_w); + continue; + } + break; + } + // wakeup_thread() is also called after setting mRunning to false. + if (!mRunning) + { + mWakeUpMutex.unlock(); + break; + } + // If we get here then mWakeUpFlag has been false since we grabbed the lock. + // We're now entering select(), during which the main thread will write to the pipe/socket + // to wake us up, because it can't get the lock. + struct timeval timeout; + long timeout_ms = multi_handle_w->getTimeOut(); + // If no timeout is set, sleep 1 second. + if (LL_UNLIKELY(timeout_ms < 0)) + timeout_ms = 1000; + if (LL_UNLIKELY(timeout_ms == 0)) + { + if (mZeroTimeOut >= 10000) + { + if (mZeroTimeOut == 10000) + llwarns << "Detected more than 10000 zero-timeout calls of select() by curl thread (more than 101 seconds)!" << llendl; + } + else if (mZeroTimeOut >= 1000) + timeout_ms = 10; + else if (mZeroTimeOut >= 100) + timeout_ms = 1; + } + else + { + if (LL_UNLIKELY(mZeroTimeOut >= 10000)) + llinfos << "Timeout of select() call by curl thread reset (to " << timeout_ms << " ms)." << llendl; + mZeroTimeOut = 0; + } + timeout.tv_sec = timeout_ms / 1000; + timeout.tv_usec = (timeout_ms % 1000) * 1000; +#ifdef CWDEBUG + static int last_nfds = -1; + static long last_timeout_ms = -1; + static int same_count = 0; + bool same = (nfds == last_nfds && timeout_ms == last_timeout_ms); + if (!same) + { + if (same_count > 1) + Dout(dc::curl, "Last select() call repeated " << same_count << " times."); + Dout(dc::curl|flush_cf|continued_cf, "select(" << nfds << ", ..., timeout = " << timeout_ms << " ms) = "); + same_count = 1; + } + else + { + ++same_count; + } +#endif + ready = select(nfds, read_fd_set, write_fd_set, NULL, &timeout); + mWakeUpMutex.unlock(); +#ifdef CWDEBUG + static int last_ready = -2; + static int last_errno = 0; + if (!same) + Dout(dc::finish|cond_error_cf(ready == -1), ready); + else if (ready != last_ready || (ready == -1 && errno != last_errno)) + { + if (same_count > 1) + Dout(dc::curl, "Last select() call repeated " << same_count << " times."); + Dout(dc::curl|cond_error_cf(ready == -1), "select(" << last_nfds << ", ..., timeout = " << last_timeout_ms << " ms) = " << ready); + same_count = 1; + } + last_nfds = nfds; + last_timeout_ms = timeout_ms; + last_ready = ready; + if (ready == -1) + last_errno = errno; +#endif + // Select returns the total number of bits set in each of the fd_set's (upon return), + // or -1 when an error occurred. A value of 0 means that a timeout occurred. + if (ready == -1) + { + llwarns << "select() failed: " << errno << ", " << strerror(errno) << llendl; + continue; + } + else if (ready == 0) + { + multi_handle_w->socket_action(CURL_SOCKET_TIMEOUT, 0); + } + else + { + if (multi_handle_w->mReadPollSet->is_set(mWakeUpFd)) + { + // Process commands from main-thread. This can add or remove filedescriptors from the poll sets. + wakeup(multi_handle_w); + --ready; + } + // Handle all active filedescriptors. + MergeIterator iter(multi_handle_w->mReadPollSet, multi_handle_w->mWritePollSet); + curl_socket_t fd; + int ev_bitmask; + while (ready > 0 && iter.next(fd, ev_bitmask)) + { + ready -= (ev_bitmask == (CURL_CSELECT_IN|CURL_CSELECT_OUT)) ? 2 : 1; + // This can cause libcurl to do callbacks and remove filedescriptors, causing us to reset their bits in the poll sets. + multi_handle_w->socket_action(fd, ev_bitmask); + llassert(ready >= 0); + } + // Note that ready is not necessarily 0 here, because it's possible + // that libcurl removed file descriptors which we subsequently + // didn't handle. + } + multi_handle_w->check_run_count(); + } + } + AICurlMultiHandle::destroyInstance(); +} + +//----------------------------------------------------------------------------- +// MultiHandle + +MultiHandle::MultiHandle(void) : mHandleAddedOrRemoved(false), mPrevRunningHandles(0), mRunningHandles(0), mTimeOut(-1), mReadPollSet(NULL), mWritePollSet(NULL) +{ + mReadPollSet = new PollSet; + mWritePollSet = new PollSet; + check_multi_code(curl_multi_setopt(mMultiHandle, CURLMOPT_SOCKETFUNCTION, &MultiHandle::socket_callback)); + check_multi_code(curl_multi_setopt(mMultiHandle, CURLMOPT_SOCKETDATA, this)); + check_multi_code(curl_multi_setopt(mMultiHandle, CURLMOPT_TIMERFUNCTION, &MultiHandle::timer_callback)); + check_multi_code(curl_multi_setopt(mMultiHandle, CURLMOPT_TIMERDATA, this)); +} + +MultiHandle::~MultiHandle() +{ + llinfos << "Destructing MultiHandle with " << mAddedEasyRequests.size() << " active curl easy handles." << llendl; + + // This thread was terminated. + // Curl demands that all handles are removed from the multi session handle before calling curl_multi_cleanup. + for(addedEasyRequests_type::iterator iter = mAddedEasyRequests.begin(); iter != mAddedEasyRequests.end(); iter = mAddedEasyRequests.begin()) + { + remove_easy_request(*iter); + } + delete mWritePollSet; + delete mReadPollSet; +} + +#if defined(CWDEBUG) || defined(DEBUG_CURLIO) +#undef AI_CASE_RETURN +#define AI_CASE_RETURN(x) do { case x: return #x; } while(0) +char const* action_str(int action) +{ + switch(action) + { + AI_CASE_RETURN(CURL_POLL_NONE); + AI_CASE_RETURN(CURL_POLL_IN); + AI_CASE_RETURN(CURL_POLL_OUT); + AI_CASE_RETURN(CURL_POLL_INOUT); + AI_CASE_RETURN(CURL_POLL_REMOVE); + } + return ""; +} +#endif + +//static +int MultiHandle::socket_callback(CURL* easy, curl_socket_t s, int action, void* userp, void* socketp) +{ + DoutEntering(dc::curl, "MultiHandle::socket_callback(" << (void*)easy << ", " << s << ", " << action_str(action) << ", " << (void*)userp << ", " << (void*)socketp << ")"); + MultiHandle& self = *static_cast(userp); + CurlSocketInfo* sock_info = static_cast(socketp); + if (action == CURL_POLL_REMOVE) + { + delete sock_info; + } + else + { + if (!sock_info) + { + sock_info = new CurlSocketInfo(self, easy, s, action); + } + else + { + sock_info->set_action(action); + } + } + return 0; +} + +//static +int MultiHandle::timer_callback(CURLM* multi, long timeout_ms, void* userp) +{ + MultiHandle& self = *static_cast(userp); + llassert(multi == self.mMultiHandle); + self.mTimeOut = timeout_ms; + Dout(dc::curl, "MultiHandle::timer_callback(): timeout set to " << timeout_ms << " ms."); + return 0; +} + +CURLMcode MultiHandle::socket_action(curl_socket_t sockfd, int ev_bitmask) +{ + CURLMcode res; + do + { + res = check_multi_code(curl_multi_socket_action(mMultiHandle, sockfd, ev_bitmask, &mRunningHandles)); + } + while(res == CURLM_CALL_MULTI_PERFORM); + return res; +} + +CURLMcode MultiHandle::assign(curl_socket_t sockfd, void* sockptr) +{ + return check_multi_code(curl_multi_assign(mMultiHandle, sockfd, sockptr)); +} + +CURLMsg const* MultiHandle::info_read(int* msgs_in_queue) const +{ + CURLMsg const* ret = curl_multi_info_read(mMultiHandle, msgs_in_queue); + // NULL could be an error, but normally it isn't, so don't print anything and + // never increment Stats::multi_errors. However, lets just increment multi_calls + // when it certainly wasn't an error... + if (ret) + Stats::multi_calls++; + return ret; +} + +CURLMcode MultiHandle::add_easy_request(AICurlEasyRequest const& easy_request) +{ + std::pair res = mAddedEasyRequests.insert(easy_request); + llassert(res.second); // May not have been added before. + CURLMcode ret; + { + AICurlEasyRequest_wat curl_easy_request_w(*easy_request); + ret = curl_easy_request_w->add_handle_to_multi(curl_easy_request_w, mMultiHandle); + } + mHandleAddedOrRemoved = true; + Dout(dc::curl, "MultiHandle::add_easy_request: Added AICurlEasyRequest " << (void*)easy_request.get() << "; now processing " << mAddedEasyRequests.size() << " easy handles."); + return ret; +} + +CURLMcode MultiHandle::remove_easy_request(AICurlEasyRequest const& easy_request, bool as_per_command) +{ + addedEasyRequests_type::iterator iter = mAddedEasyRequests.find(easy_request); + if (iter == mAddedEasyRequests.end()) + return (CURLMcode)-2; // Was already removed before. + CURLMcode res; + { + AICurlEasyRequest_wat curl_easy_request_w(**iter); + res = curl_easy_request_w->remove_handle_from_multi(curl_easy_request_w, mMultiHandle); +#ifdef SHOW_ASSERT + curl_easy_request_w->mRemovedPerCommand = as_per_command; +#endif + } + mAddedEasyRequests.erase(iter); + mHandleAddedOrRemoved = true; + Dout(dc::curl, "MultiHandle::remove_easy_request: Removed AICurlEasyRequest " << (void*)easy_request.get() << "; now processing " << mAddedEasyRequests.size() << " easy handles."); + return res; +} + +void MultiHandle::check_run_count(void) +{ + if (mHandleAddedOrRemoved || mRunningHandles < mPrevRunningHandles) + { + CURLMsg const* msg; + int msgs_left; + while ((msg = info_read(&msgs_left))) + { + if (msg->msg == CURLMSG_DONE) + { + CURL* easy = msg->easy_handle; + ThreadSafeCurlEasyRequest* ptr; + CURLcode rese = curl_easy_getinfo(easy, CURLINFO_PRIVATE, &ptr); + llassert_always(rese == CURLE_OK); + AICurlEasyRequest easy_request(ptr); + llassert(*AICurlEasyRequest_wat(*easy_request) == easy); + // Store the result and transfer info in the easy handle. + { + AICurlEasyRequest_wat curl_easy_request_w(*easy_request); + curl_easy_request_w->store_result(msg->data.result); +#ifdef CWDEBUG + char* eff_url; + curl_easy_request_w->getinfo(CURLINFO_EFFECTIVE_URL, &eff_url); + Dout(dc::curl, "Finished: " << eff_url << " (" << msg->data.result << ")"); +#endif + // Signal that this easy handle finished. + curl_easy_request_w->done(curl_easy_request_w); + } + // This invalidates msg, but not easy_request. + CURLMcode res = remove_easy_request(easy_request); + // This should hold, I think, because the handles are obviously ok and + // the only error we could get is when remove_easy_request() was already + // called before (by this thread); but if that was the case then the easy + // handle should not have been be returned by info_read()... + llassert(res == CURLM_OK); + // Nevertheless, if it was already removed then just ignore it. + if (res == CURLM_OK) + { + } + else if (res == -2) + { + llwarns << "Curl easy handle returned by curl_multi_info_read() that is not (anymore) in MultiHandle::mAddedEasyRequests!?!" << llendl; + } + // Destruction of easy_request at this point, causes the CurlEasyRequest to be deleted. + } + } + mHandleAddedOrRemoved = false; + } + mPrevRunningHandles = mRunningHandles; +} + +} // namespace curlthread +} // namespace AICurlPrivate + +//static +void AICurlMultiHandle::destroyInstance(void) +{ + LLThreadLocalData& tldata = LLThreadLocalData::tldata(); + Dout(dc::curl, "Destroying AICurlMultiHandle [" << (void*)tldata.mCurlMultiHandle << "] for thread \"" << tldata.mName << "\"."); + delete tldata.mCurlMultiHandle; + tldata.mCurlMultiHandle = NULL; +} + +//============================================================================= +// MAIN-THREAD (needing to access the above declarations). + +//static +AICurlMultiHandle& AICurlMultiHandle::getInstance(void) +{ + LLThreadLocalData& tldata = LLThreadLocalData::tldata(); + if (!tldata.mCurlMultiHandle) + { + tldata.mCurlMultiHandle = new AICurlMultiHandle; + Dout(dc::curl, "Created AICurlMultiHandle [" << (void*)tldata.mCurlMultiHandle << "] for thread \"" << tldata.mName << "\"."); + } + return *static_cast(tldata.mCurlMultiHandle); +} + +namespace AICurlPrivate { + +bool curlThreadIsRunning(void) +{ + using curlthread::AICurlThread; + return AICurlThread::sInstance && !AICurlThread::sInstance->isStopped(); +} + +void wakeUpCurlThread(void) +{ + using curlthread::AICurlThread; + if (AICurlThread::sInstance) + AICurlThread::sInstance->wakeup_thread(); +} + +void stopCurlThread(void) +{ + using curlthread::AICurlThread; + if (AICurlThread::sInstance) + { + AICurlThread::sInstance->stop_thread(); + int count = 101; + while(--count && !AICurlThread::sInstance->isStopped()) + { + ms_sleep(10); + } + Dout(dc::curl, "Curl thread" << (curlThreadIsRunning() ? " not" : "") << " stopped after " << ((100 - count) * 10) << "ms."); + // Clear the command queue, for a cleaner cleanup. + command_queue_wat command_queue_w(command_queue); + command_queue_w->clear(); + } +} + +} // namespace AICurlPrivate + +//----------------------------------------------------------------------------- +// AICurlEasyRequest + +void AICurlEasyRequest::addRequest(void) +{ + using namespace AICurlPrivate; + + { + // Write-lock the command queue. + command_queue_wat command_queue_w(command_queue); +#ifdef SHOW_ASSERT + // This debug code checks if we aren't calling addRequest() twice for the same object. + // That means that the main thread already called (and finished, this is also the + // main thread) this function, which also follows from that we just locked command_queue. + // That leaves three options: It's still in the queue, or it was removed and is currently + // processed by the curl thread with again two options: either it was already added + // to the multi session handle or not yet. + + // Find the last command added. + command_st cmd = cmd_none; + for (std::deque::iterator iter = command_queue_w->begin(); iter != command_queue_w->end(); ++iter) + { + if (*iter == *this) + { + cmd = iter->command(); + break; + } + } + llassert(cmd == cmd_none || cmd == cmd_remove); // Not in queue, or last command was to remove it. + if (cmd == cmd_none) + { + // Read-lock command_being_processed. + command_being_processed_rat command_being_processed_r(command_being_processed); + if (*command_being_processed_r == *this) + { + // May not be in-between being removed from the command queue but not added to the multi session handle yet. + llassert(command_being_processed_r->command() == cmd_remove); + } + else + { + // May not already be added to the multi session handle. + llassert(!AICurlEasyRequest_wat(*get())->active()); + } + } +#endif + // Add a command to add the new request to the multi session to the command queue. + command_queue_w->push_back(Command(*this, cmd_add)); + AICurlEasyRequest_wat(*get())->add_queued(); + } + // Something was added to the queue, wake up the thread to get it. + wakeUpCurlThread(); +} + +void AICurlEasyRequest::removeRequest(void) +{ + using namespace AICurlPrivate; + + { + // Write-lock the command queue. + command_queue_wat command_queue_w(command_queue); +#ifdef SHOW_ASSERT + // This debug code checks if we aren't calling removeRequest() twice for the same object. + // That means that the thread calling this function already finished it, following from that + // we just locked command_queue. + // That leaves three options: It's still in the queue, or it was removed and is currently + // processed by the curl thread with again two options: either it was already removed + // from the multi session handle or not yet. + + // Find the last command added. + command_st cmd = cmd_none; + for (std::deque::iterator iter = command_queue_w->begin(); iter != command_queue_w->end(); ++iter) + { + if (*iter == *this) + { + cmd = iter->command(); + break; + } + } + llassert(cmd == cmd_none || cmd != cmd_remove); // Not in queue, or last command was not a remove command. + if (cmd == cmd_none) + { + // Read-lock command_being_processed. + command_being_processed_rat command_being_processed_r(command_being_processed); + if (*command_being_processed_r == *this) + { + // May not be in-between being removed from the command queue but not removed from the multi session handle yet. + llassert(command_being_processed_r->command() != cmd_remove); + } + else + { + // May not already have been removed from multi session handle as per command from the main thread (through this function thus). + { + AICurlEasyRequest_wat curl_easy_request_w(*get()); + llassert(curl_easy_request_w->active() || !curl_easy_request_w->mRemovedPerCommand); + } + } + } +#endif + // Add a command to remove this request from the multi session to the command queue. + command_queue_w->push_back(Command(*this, cmd_remove)); + // Suppress warning that would otherwise happen if the callbacks are revoked before the curl thread removed the request. + AICurlEasyRequest_wat(*get())->remove_queued(); + } + // Something was added to the queue, wake up the thread to get it. + wakeUpCurlThread(); +} + +//----------------------------------------------------------------------------- + +namespace AICurlInterface { + +void startCurlThread(void) +{ + using namespace AICurlPrivate::curlthread; + + llassert(is_main_thread()); + AICurlThread::sInstance = new AICurlThread; + AICurlThread::sInstance->start(); +} + +} // namespace AICurlInterface + diff --git a/indra/llmessage/aicurlthread.h b/indra/llmessage/aicurlthread.h new file mode 100644 index 000000000..a2bd36352 --- /dev/null +++ b/indra/llmessage/aicurlthread.h @@ -0,0 +1,128 @@ +/** + * @file aicurlthread.h + * @brief Thread safe wrapper for libcurl. + * + * Copyright (c) 2012, Aleric Inglewood. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution. + * + * CHANGELOG + * and additional copyright holders. + * + * 28/04/2012 + * Initial version, written by Aleric Inglewood @ SL + */ + +#ifndef AICURLTHREAD_H +#define AICURLTHREAD_H + +#include "aicurl.h" +#include + +#undef AICurlPrivate + +namespace AICurlPrivate { +namespace curlthread { + +class PollSet; + +// For ordering a std::set with AICurlEasyRequest objects. +struct AICurlEasyRequestCompare { + bool operator()(AICurlEasyRequest const& h1, AICurlEasyRequest const& h2) { return h1.get() < h2.get(); } +}; + +//----------------------------------------------------------------------------- +// MultiHandle + +// This class adds member functions that will only be called from the AICurlThread thread. +// This class guarantees that all added easy handles will be removed from the multi handle +// before the multi handle is cleaned up, as is required by libcurl. +class MultiHandle : public CurlMultiHandle +{ + public: + MultiHandle(void); + ~MultiHandle(); + + // Add/remove an easy handle to/from a multi session. + CURLMcode add_easy_request(AICurlEasyRequest const& easy_request); + CURLMcode remove_easy_request(AICurlEasyRequest const& easy_request, bool as_per_command = false); + + // Reads/writes available data from a particular socket (non-blocking). + CURLMcode socket_action(curl_socket_t sockfd, int ev_bitmask); + + // Set data to association with an internal socket. + CURLMcode assign(curl_socket_t sockfd, void* sockptr); + + // Read multi stack informationals. + CURLMsg const* info_read(int* msgs_in_queue) const; + + private: + typedef std::set addedEasyRequests_type; + addedEasyRequests_type mAddedEasyRequests; + + bool mHandleAddedOrRemoved; // Set when an easy handle was added or removed, reset in check_run_count(). + int mPrevRunningHandles; // The last value of mRunningHandles that check_run_count() was called with. + int mRunningHandles; // The last value returned by curl_multi_socket_action. + long mTimeOut; // The last time out in ms as set by the callback CURLMOPT_TIMERFUNCTION. + + private: + static int socket_callback(CURL* easy, curl_socket_t s, int action, void* userp, void* socketp); + static int timer_callback(CURLM* multi, long timeout_ms, void* userp); + + public: + // Returns the number of active easy handles as reported by the last call to curl_multi_socket_action. + int getRunningHandles(void) const { return mRunningHandles; } + + // Returns how long to wait for socket action before calling socket_action(CURL_SOCKET_TIMEOUT, 0), in ms. + int getTimeOut(void) const { return mTimeOut; } + + // This is called before sleeping, after calling (one or more times) socket_action. + void check_run_count(void); + + public: + //----------------------------------------------------------------------------- + // Curl socket administration: + + PollSet* mReadPollSet; + PollSet* mWritePollSet; +}; + +} // namespace curlthread +} // namespace AICurlPrivate + +// Thread safe, noncopyable curl multi handle. +// This class wraps MultiHandle for thread-safety. +// AIThreadSafeSingleThreadDC cannot be copied, but that is OK as we don't need that (or want that); +// this class provides a thread-local singleton (exactly one instance per thread), and because it +// can't be copied, that guarantees that the CURLM* handle is never used concurrently, which is +// not allowed by libcurl. +class AICurlMultiHandle : public AIThreadSafeSingleThreadDC, public LLThreadLocalDataMember { + public: + static AICurlMultiHandle& getInstance(void); + static void destroyInstance(void); + private: + // Use getInstance(). + AICurlMultiHandle(void) { } +}; + +typedef AISTAccessConst AICurlMultiHandle_rat; +typedef AISTAccess AICurlMultiHandle_wat; + +#define AICurlPrivate DONTUSE_AICurlPrivate + +#endif diff --git a/indra/llmessage/debug_libcurl.cpp b/indra/llmessage/debug_libcurl.cpp new file mode 100644 index 000000000..40b75d200 --- /dev/null +++ b/indra/llmessage/debug_libcurl.cpp @@ -0,0 +1,942 @@ +#ifdef DEBUG_CURLIO + +#include "sys.h" +#include +#include +#include +#include +#include +#include "llpreprocessor.h" +#include +#define COMPILING_DEBUG_LIBCURL_CC +#include "debug_libcurl.h" +#include "debug.h" +#include "llerror.h" +#ifdef CWDEBUG +#include +#endif + +#define CURL_VERSION(major, minor, patch) \ + (LIBCURL_VERSION_MAJOR > major || \ + (LIBCURL_VERSION_MAJOR == major && \ + LIBCURL_VERSION_MINOR > minor || \ + (LIBCURL_VERSION_MINOR == minor && \ + LIBCURL_VERSION_PATCH >= patch))) + +static struct curl_slist unchanged_slist; + +std::ostream& operator<<(std::ostream& os, struct curl_slist const& slist) +{ + struct curl_slist const* ptr = &slist; + if (ptr == &unchanged_slist) + os << " "; + else + { + os << "(curl_slist)@0x" << std::hex << (size_t)ptr << std::dec << "{"; + do + { + os << '"' << ptr->data << '"'; + ptr = ptr->next; + if (ptr) + os << ", "; + } + while(ptr); + os << "}"; + } + return os; +} + +#define CASEPRINT(x) case x: os << #x; break + +std::ostream& operator<<(std::ostream& os, CURLINFO info) +{ + switch (info) + { + CASEPRINT(CURLINFO_EFFECTIVE_URL); + CASEPRINT(CURLINFO_RESPONSE_CODE); + CASEPRINT(CURLINFO_TOTAL_TIME); + CASEPRINT(CURLINFO_NAMELOOKUP_TIME); + CASEPRINT(CURLINFO_CONNECT_TIME); + CASEPRINT(CURLINFO_PRETRANSFER_TIME); + CASEPRINT(CURLINFO_SIZE_UPLOAD); + CASEPRINT(CURLINFO_SIZE_DOWNLOAD); + CASEPRINT(CURLINFO_SPEED_DOWNLOAD); + CASEPRINT(CURLINFO_SPEED_UPLOAD); + CASEPRINT(CURLINFO_HEADER_SIZE); + CASEPRINT(CURLINFO_REQUEST_SIZE); + CASEPRINT(CURLINFO_SSL_VERIFYRESULT); + CASEPRINT(CURLINFO_FILETIME); + CASEPRINT(CURLINFO_CONTENT_LENGTH_DOWNLOAD); + CASEPRINT(CURLINFO_CONTENT_LENGTH_UPLOAD); + CASEPRINT(CURLINFO_STARTTRANSFER_TIME); + CASEPRINT(CURLINFO_CONTENT_TYPE); + CASEPRINT(CURLINFO_REDIRECT_TIME); + CASEPRINT(CURLINFO_REDIRECT_COUNT); + CASEPRINT(CURLINFO_PRIVATE); + CASEPRINT(CURLINFO_HTTP_CONNECTCODE); + CASEPRINT(CURLINFO_HTTPAUTH_AVAIL); + CASEPRINT(CURLINFO_PROXYAUTH_AVAIL); + CASEPRINT(CURLINFO_OS_ERRNO); + CASEPRINT(CURLINFO_NUM_CONNECTS); + CASEPRINT(CURLINFO_SSL_ENGINES); + CASEPRINT(CURLINFO_COOKIELIST); + CASEPRINT(CURLINFO_LASTSOCKET); + CASEPRINT(CURLINFO_FTP_ENTRY_PATH); + CASEPRINT(CURLINFO_REDIRECT_URL); + CASEPRINT(CURLINFO_PRIMARY_IP); + CASEPRINT(CURLINFO_APPCONNECT_TIME); + CASEPRINT(CURLINFO_CERTINFO); + CASEPRINT(CURLINFO_CONDITION_UNMET); + CASEPRINT(CURLINFO_RTSP_SESSION_ID); + CASEPRINT(CURLINFO_RTSP_CLIENT_CSEQ); + CASEPRINT(CURLINFO_RTSP_SERVER_CSEQ); + CASEPRINT(CURLINFO_RTSP_CSEQ_RECV); + CASEPRINT(CURLINFO_PRIMARY_PORT); + CASEPRINT(CURLINFO_LOCAL_IP); + CASEPRINT(CURLINFO_LOCAL_PORT); + default: + os << ""; + } + return os; +} + +std::ostream& operator<<(std::ostream& os, CURLcode code) +{ + switch(code) + { + CASEPRINT(CURLE_OK); + CASEPRINT(CURLE_UNSUPPORTED_PROTOCOL); + CASEPRINT(CURLE_FAILED_INIT); + CASEPRINT(CURLE_URL_MALFORMAT); +#if CURL_VERSION(7, 21, 5) + CASEPRINT(CURLE_NOT_BUILT_IN); +#endif + CASEPRINT(CURLE_COULDNT_RESOLVE_PROXY); + CASEPRINT(CURLE_COULDNT_RESOLVE_HOST); + CASEPRINT(CURLE_COULDNT_CONNECT); + CASEPRINT(CURLE_FTP_WEIRD_SERVER_REPLY); + CASEPRINT(CURLE_REMOTE_ACCESS_DENIED); +#if 0 + CASEPRINT(CURLE_FTP_ACCEPT_FAILED); +#endif + CASEPRINT(CURLE_FTP_WEIRD_PASS_REPLY); +#if 0 + CASEPRINT(CURLE_FTP_ACCEPT_TIMEOUT); +#endif + CASEPRINT(CURLE_FTP_WEIRD_PASV_REPLY); + CASEPRINT(CURLE_FTP_WEIRD_227_FORMAT); + CASEPRINT(CURLE_FTP_CANT_GET_HOST); + CASEPRINT(CURLE_OBSOLETE16); + CASEPRINT(CURLE_FTP_COULDNT_SET_TYPE); + CASEPRINT(CURLE_PARTIAL_FILE); + CASEPRINT(CURLE_FTP_COULDNT_RETR_FILE); + CASEPRINT(CURLE_OBSOLETE20); + CASEPRINT(CURLE_QUOTE_ERROR); + CASEPRINT(CURLE_HTTP_RETURNED_ERROR); + CASEPRINT(CURLE_WRITE_ERROR); + CASEPRINT(CURLE_OBSOLETE24); + CASEPRINT(CURLE_UPLOAD_FAILED); + CASEPRINT(CURLE_READ_ERROR); + CASEPRINT(CURLE_OUT_OF_MEMORY); + CASEPRINT(CURLE_OPERATION_TIMEDOUT); + CASEPRINT(CURLE_OBSOLETE29); + CASEPRINT(CURLE_FTP_PORT_FAILED); + CASEPRINT(CURLE_FTP_COULDNT_USE_REST); + CASEPRINT(CURLE_OBSOLETE32); + CASEPRINT(CURLE_RANGE_ERROR); + CASEPRINT(CURLE_HTTP_POST_ERROR); + CASEPRINT(CURLE_SSL_CONNECT_ERROR); + CASEPRINT(CURLE_BAD_DOWNLOAD_RESUME); + CASEPRINT(CURLE_FILE_COULDNT_READ_FILE); + CASEPRINT(CURLE_LDAP_CANNOT_BIND); + CASEPRINT(CURLE_LDAP_SEARCH_FAILED); + CASEPRINT(CURLE_OBSOLETE40); + CASEPRINT(CURLE_FUNCTION_NOT_FOUND); + CASEPRINT(CURLE_ABORTED_BY_CALLBACK); + CASEPRINT(CURLE_BAD_FUNCTION_ARGUMENT); + CASEPRINT(CURLE_OBSOLETE44); + CASEPRINT(CURLE_INTERFACE_FAILED); + CASEPRINT(CURLE_OBSOLETE46); + CASEPRINT(CURLE_TOO_MANY_REDIRECTS ); +#if CURL_VERSION(7, 21, 5) + CASEPRINT(CURLE_UNKNOWN_OPTION); +#else + CASEPRINT(CURLE_UNKNOWN_TELNET_OPTION); +#endif + CASEPRINT(CURLE_TELNET_OPTION_SYNTAX ); + CASEPRINT(CURLE_OBSOLETE50); + CASEPRINT(CURLE_PEER_FAILED_VERIFICATION); + CASEPRINT(CURLE_GOT_NOTHING); + CASEPRINT(CURLE_SSL_ENGINE_NOTFOUND); + CASEPRINT(CURLE_SSL_ENGINE_SETFAILED); + CASEPRINT(CURLE_SEND_ERROR); + CASEPRINT(CURLE_RECV_ERROR); + CASEPRINT(CURLE_OBSOLETE57); + CASEPRINT(CURLE_SSL_CERTPROBLEM); + CASEPRINT(CURLE_SSL_CIPHER); + CASEPRINT(CURLE_SSL_CACERT); + CASEPRINT(CURLE_BAD_CONTENT_ENCODING); + CASEPRINT(CURLE_LDAP_INVALID_URL); + CASEPRINT(CURLE_FILESIZE_EXCEEDED); + CASEPRINT(CURLE_USE_SSL_FAILED); + CASEPRINT(CURLE_SEND_FAIL_REWIND); + CASEPRINT(CURLE_SSL_ENGINE_INITFAILED); + CASEPRINT(CURLE_LOGIN_DENIED); + CASEPRINT(CURLE_TFTP_NOTFOUND); + CASEPRINT(CURLE_TFTP_PERM); + CASEPRINT(CURLE_REMOTE_DISK_FULL); + CASEPRINT(CURLE_TFTP_ILLEGAL); + CASEPRINT(CURLE_TFTP_UNKNOWNID); + CASEPRINT(CURLE_REMOTE_FILE_EXISTS); + CASEPRINT(CURLE_TFTP_NOSUCHUSER); + CASEPRINT(CURLE_CONV_FAILED); + CASEPRINT(CURLE_CONV_REQD); + CASEPRINT(CURLE_SSL_CACERT_BADFILE); + CASEPRINT(CURLE_REMOTE_FILE_NOT_FOUND); + CASEPRINT(CURLE_SSH); + CASEPRINT(CURLE_SSL_SHUTDOWN_FAILED); + CASEPRINT(CURLE_AGAIN); + CASEPRINT(CURLE_SSL_CRL_BADFILE); + CASEPRINT(CURLE_SSL_ISSUER_ERROR); + CASEPRINT(CURLE_FTP_PRET_FAILED); + CASEPRINT(CURLE_RTSP_CSEQ_ERROR); + CASEPRINT(CURLE_RTSP_SESSION_ERROR); + CASEPRINT(CURLE_FTP_BAD_FILE_LIST); + CASEPRINT(CURLE_CHUNK_FAILED); + default: + os << (code == CURL_LAST ? ""; + } + return os; +} + +struct AICURL; +struct AICURLM; + +std::ostream& operator<<(std::ostream& os, AICURL* curl) +{ + os << "(CURL*)0x" << std::hex << (size_t)curl << std::dec; + return os; +} + +std::ostream& operator<<(std::ostream& os, AICURLM* curl) +{ + os << "(CURLM*)0x" << std::hex << (size_t)curl << std::dec; + return os; +} + +std::ostream& operator<<(std::ostream& os, CURLoption option) +{ + switch(option) + { + CASEPRINT(CURLOPT_WRITEDATA); + CASEPRINT(CURLOPT_URL); + CASEPRINT(CURLOPT_PORT); + CASEPRINT(CURLOPT_PROXY); + CASEPRINT(CURLOPT_USERPWD); + CASEPRINT(CURLOPT_PROXYUSERPWD); + CASEPRINT(CURLOPT_RANGE); + CASEPRINT(CURLOPT_READDATA); + CASEPRINT(CURLOPT_ERRORBUFFER); + CASEPRINT(CURLOPT_WRITEFUNCTION); + CASEPRINT(CURLOPT_READFUNCTION); + CASEPRINT(CURLOPT_TIMEOUT); + CASEPRINT(CURLOPT_INFILESIZE); + CASEPRINT(CURLOPT_POSTFIELDS); + CASEPRINT(CURLOPT_REFERER); + CASEPRINT(CURLOPT_FTPPORT); + CASEPRINT(CURLOPT_USERAGENT); + CASEPRINT(CURLOPT_LOW_SPEED_LIMIT); + CASEPRINT(CURLOPT_LOW_SPEED_TIME); + CASEPRINT(CURLOPT_RESUME_FROM); + CASEPRINT(CURLOPT_COOKIE); + CASEPRINT(CURLOPT_RTSPHEADER); + CASEPRINT(CURLOPT_HTTPPOST); + CASEPRINT(CURLOPT_SSLCERT); + CASEPRINT(CURLOPT_KEYPASSWD); + CASEPRINT(CURLOPT_CRLF); + CASEPRINT(CURLOPT_QUOTE); + CASEPRINT(CURLOPT_HEADERDATA); + CASEPRINT(CURLOPT_COOKIEFILE); + CASEPRINT(CURLOPT_SSLVERSION); + CASEPRINT(CURLOPT_TIMECONDITION); + CASEPRINT(CURLOPT_TIMEVALUE); + CASEPRINT(CURLOPT_CUSTOMREQUEST); + CASEPRINT(CURLOPT_STDERR); + CASEPRINT(CURLOPT_POSTQUOTE); + CASEPRINT(CURLOPT_WRITEINFO); + CASEPRINT(CURLOPT_VERBOSE); + CASEPRINT(CURLOPT_HEADER); + CASEPRINT(CURLOPT_NOPROGRESS); + CASEPRINT(CURLOPT_NOBODY); + CASEPRINT(CURLOPT_FAILONERROR); + CASEPRINT(CURLOPT_UPLOAD); + CASEPRINT(CURLOPT_POST); + CASEPRINT(CURLOPT_DIRLISTONLY); + CASEPRINT(CURLOPT_APPEND); + CASEPRINT(CURLOPT_NETRC); + CASEPRINT(CURLOPT_FOLLOWLOCATION); + CASEPRINT(CURLOPT_TRANSFERTEXT); + CASEPRINT(CURLOPT_PUT); + CASEPRINT(CURLOPT_PROGRESSFUNCTION); + CASEPRINT(CURLOPT_PROGRESSDATA); + CASEPRINT(CURLOPT_AUTOREFERER); + CASEPRINT(CURLOPT_PROXYPORT); + CASEPRINT(CURLOPT_POSTFIELDSIZE); + CASEPRINT(CURLOPT_HTTPPROXYTUNNEL); + CASEPRINT(CURLOPT_INTERFACE); + CASEPRINT(CURLOPT_KRBLEVEL); + CASEPRINT(CURLOPT_SSL_VERIFYPEER); + CASEPRINT(CURLOPT_CAINFO); + CASEPRINT(CURLOPT_MAXREDIRS); + CASEPRINT(CURLOPT_FILETIME); + CASEPRINT(CURLOPT_TELNETOPTIONS); + CASEPRINT(CURLOPT_MAXCONNECTS); + CASEPRINT(CURLOPT_CLOSEPOLICY); + CASEPRINT(CURLOPT_FRESH_CONNECT); + CASEPRINT(CURLOPT_FORBID_REUSE); + CASEPRINT(CURLOPT_RANDOM_FILE); + CASEPRINT(CURLOPT_EGDSOCKET); + CASEPRINT(CURLOPT_CONNECTTIMEOUT); + CASEPRINT(CURLOPT_HEADERFUNCTION); + CASEPRINT(CURLOPT_HTTPGET); + CASEPRINT(CURLOPT_SSL_VERIFYHOST); + CASEPRINT(CURLOPT_COOKIEJAR); + CASEPRINT(CURLOPT_SSL_CIPHER_LIST); + CASEPRINT(CURLOPT_HTTP_VERSION); + CASEPRINT(CURLOPT_FTP_USE_EPSV); + CASEPRINT(CURLOPT_SSLCERTTYPE); + CASEPRINT(CURLOPT_SSLKEY); + CASEPRINT(CURLOPT_SSLKEYTYPE); + CASEPRINT(CURLOPT_SSLENGINE); + CASEPRINT(CURLOPT_SSLENGINE_DEFAULT); + CASEPRINT(CURLOPT_DNS_USE_GLOBAL_CACHE); + CASEPRINT(CURLOPT_DNS_CACHE_TIMEOUT); + CASEPRINT(CURLOPT_PREQUOTE); + CASEPRINT(CURLOPT_DEBUGFUNCTION); + CASEPRINT(CURLOPT_DEBUGDATA); + CASEPRINT(CURLOPT_COOKIESESSION); + CASEPRINT(CURLOPT_CAPATH); + CASEPRINT(CURLOPT_BUFFERSIZE); + CASEPRINT(CURLOPT_NOSIGNAL); + CASEPRINT(CURLOPT_SHARE); + CASEPRINT(CURLOPT_PROXYTYPE); +#if CURL_VERSION(7, 21, 6) + CASEPRINT(CURLOPT_ACCEPT_ENCODING); +#else + CASEPRINT(CURLOPT_ENCODING); +#endif + CASEPRINT(CURLOPT_PRIVATE); + CASEPRINT(CURLOPT_HTTP200ALIASES); + CASEPRINT(CURLOPT_UNRESTRICTED_AUTH); + CASEPRINT(CURLOPT_FTP_USE_EPRT); + CASEPRINT(CURLOPT_HTTPAUTH); + CASEPRINT(CURLOPT_SSL_CTX_FUNCTION); + CASEPRINT(CURLOPT_SSL_CTX_DATA); + CASEPRINT(CURLOPT_FTP_CREATE_MISSING_DIRS); + CASEPRINT(CURLOPT_PROXYAUTH); + CASEPRINT(CURLOPT_FTP_RESPONSE_TIMEOUT); + CASEPRINT(CURLOPT_IPRESOLVE); + CASEPRINT(CURLOPT_MAXFILESIZE); + CASEPRINT(CURLOPT_INFILESIZE_LARGE); + CASEPRINT(CURLOPT_RESUME_FROM_LARGE); + CASEPRINT(CURLOPT_MAXFILESIZE_LARGE); + CASEPRINT(CURLOPT_NETRC_FILE); + CASEPRINT(CURLOPT_USE_SSL); + CASEPRINT(CURLOPT_POSTFIELDSIZE_LARGE); + CASEPRINT(CURLOPT_TCP_NODELAY); + CASEPRINT(CURLOPT_FTPSSLAUTH); + CASEPRINT(CURLOPT_IOCTLFUNCTION); + CASEPRINT(CURLOPT_IOCTLDATA); + CASEPRINT(CURLOPT_FTP_ACCOUNT); + CASEPRINT(CURLOPT_COOKIELIST); + CASEPRINT(CURLOPT_IGNORE_CONTENT_LENGTH); + CASEPRINT(CURLOPT_FTP_SKIP_PASV_IP); + CASEPRINT(CURLOPT_FTP_FILEMETHOD); + CASEPRINT(CURLOPT_LOCALPORT); + CASEPRINT(CURLOPT_LOCALPORTRANGE); + CASEPRINT(CURLOPT_CONNECT_ONLY); + CASEPRINT(CURLOPT_CONV_FROM_NETWORK_FUNCTION); + CASEPRINT(CURLOPT_CONV_TO_NETWORK_FUNCTION); + CASEPRINT(CURLOPT_CONV_FROM_UTF8_FUNCTION); + CASEPRINT(CURLOPT_MAX_SEND_SPEED_LARGE); + CASEPRINT(CURLOPT_MAX_RECV_SPEED_LARGE); + CASEPRINT(CURLOPT_FTP_ALTERNATIVE_TO_USER); + CASEPRINT(CURLOPT_SOCKOPTFUNCTION); + CASEPRINT(CURLOPT_SOCKOPTDATA); + CASEPRINT(CURLOPT_SSL_SESSIONID_CACHE); + CASEPRINT(CURLOPT_SSH_AUTH_TYPES); + CASEPRINT(CURLOPT_SSH_PUBLIC_KEYFILE); + CASEPRINT(CURLOPT_SSH_PRIVATE_KEYFILE); + CASEPRINT(CURLOPT_FTP_SSL_CCC); + CASEPRINT(CURLOPT_TIMEOUT_MS); + CASEPRINT(CURLOPT_CONNECTTIMEOUT_MS); + CASEPRINT(CURLOPT_HTTP_TRANSFER_DECODING); + CASEPRINT(CURLOPT_HTTP_CONTENT_DECODING); + CASEPRINT(CURLOPT_NEW_FILE_PERMS); + CASEPRINT(CURLOPT_NEW_DIRECTORY_PERMS); + CASEPRINT(CURLOPT_POSTREDIR); + CASEPRINT(CURLOPT_SSH_HOST_PUBLIC_KEY_MD5); + CASEPRINT(CURLOPT_OPENSOCKETFUNCTION); + CASEPRINT(CURLOPT_OPENSOCKETDATA); + CASEPRINT(CURLOPT_COPYPOSTFIELDS); + CASEPRINT(CURLOPT_PROXY_TRANSFER_MODE); + CASEPRINT(CURLOPT_SEEKFUNCTION); + CASEPRINT(CURLOPT_SEEKDATA); + CASEPRINT(CURLOPT_CRLFILE); + CASEPRINT(CURLOPT_ISSUERCERT); + CASEPRINT(CURLOPT_ADDRESS_SCOPE); + CASEPRINT(CURLOPT_CERTINFO); + CASEPRINT(CURLOPT_USERNAME); + CASEPRINT(CURLOPT_PASSWORD); + CASEPRINT(CURLOPT_PROXYUSERNAME); + CASEPRINT(CURLOPT_PROXYPASSWORD); + CASEPRINT(CURLOPT_NOPROXY); + CASEPRINT(CURLOPT_TFTP_BLKSIZE); + CASEPRINT(CURLOPT_SOCKS5_GSSAPI_SERVICE); + CASEPRINT(CURLOPT_SOCKS5_GSSAPI_NEC); + CASEPRINT(CURLOPT_PROTOCOLS); + CASEPRINT(CURLOPT_REDIR_PROTOCOLS); + CASEPRINT(CURLOPT_SSH_KNOWNHOSTS); + CASEPRINT(CURLOPT_SSH_KEYFUNCTION); + CASEPRINT(CURLOPT_SSH_KEYDATA); + CASEPRINT(CURLOPT_MAIL_FROM); + CASEPRINT(CURLOPT_MAIL_RCPT); + CASEPRINT(CURLOPT_FTP_USE_PRET); + CASEPRINT(CURLOPT_RTSP_REQUEST); + CASEPRINT(CURLOPT_RTSP_SESSION_ID); + CASEPRINT(CURLOPT_RTSP_STREAM_URI); + CASEPRINT(CURLOPT_RTSP_TRANSPORT); + CASEPRINT(CURLOPT_RTSP_CLIENT_CSEQ); + CASEPRINT(CURLOPT_RTSP_SERVER_CSEQ); + CASEPRINT(CURLOPT_INTERLEAVEDATA); + CASEPRINT(CURLOPT_INTERLEAVEFUNCTION); + CASEPRINT(CURLOPT_WILDCARDMATCH); + CASEPRINT(CURLOPT_CHUNK_BGN_FUNCTION); + CASEPRINT(CURLOPT_CHUNK_END_FUNCTION); + CASEPRINT(CURLOPT_FNMATCH_FUNCTION); + CASEPRINT(CURLOPT_CHUNK_DATA); + CASEPRINT(CURLOPT_FNMATCH_DATA); +#if CURL_VERSION(7, 21, 3) + CASEPRINT(CURLOPT_RESOLVE); +#endif +#if CURL_VERSION(7, 21, 4) + CASEPRINT(CURLOPT_TLSAUTH_USERNAME); + CASEPRINT(CURLOPT_TLSAUTH_PASSWORD); + CASEPRINT(CURLOPT_TLSAUTH_TYPE); +#endif +#if CURL_VERSION(7, 21, 6) + CASEPRINT(CURLOPT_TRANSFER_ENCODING); +#endif +#if CURL_VERSION(7, 21, 7) + CASEPRINT(CURLOPT_CLOSESOCKETFUNCTION); + CASEPRINT(CURLOPT_CLOSESOCKETDATA); +#endif +#if CURL_VERSION(7, 22, 0) + CASEPRINT(CURLOPT_GSSAPI_DELEGATION); +#endif +#if CURL_VERSION(7, 24, 0) + CASEPRINT(CURLOPT_DNS_SERVERS); + CASEPRINT(CURLOPT_ACCEPTTIMEOUT_MS); +#endif +#if CURL_VERSION(7, 25, 0) + CASEPRINT(CURLOPT_TCP_KEEPALIVE); + CASEPRINT(CURLOPT_TCP_KEEPIDLE); + CASEPRINT(CURLOPT_TCP_KEEPINTVL); +#endif +#if CURL_VERSION(7, 25, 0) + CASEPRINT(CURLOPT_SSL_OPTIONS); + CASEPRINT(CURLOPT_MAIL_AUTH); +#endif + default: + os << ""; + } + return os; +} + +std::ostream& operator<<(std::ostream& os, CURLMoption option) +{ + switch(option) + { + CASEPRINT(CURLMOPT_SOCKETFUNCTION); + CASEPRINT(CURLMOPT_SOCKETDATA); + CASEPRINT(CURLMOPT_PIPELINING); + CASEPRINT(CURLMOPT_TIMERFUNCTION); + CASEPRINT(CURLMOPT_TIMERDATA); + CASEPRINT(CURLMOPT_MAXCONNECTS); + default: + os << ""; + } + return os; +} + +std::ostream& operator<<(std::ostream& os, CURLMsg* msg) +{ + if (msg) + { + os << "(CURLMsg*){"; + if (msg->msg == CURLMSG_DONE) + os << "CURLMSG_DONE"; + else + os << msg->msg; + os << ", " << (AICURL*)msg->easy_handle << ", 0x" << std::hex << (size_t)msg->data.whatever << std::dec << '}'; + } + else + os << "(CURLMsg*)NULL"; + return os; +} + +struct Socket { + curl_socket_t mSocket; + Socket(curl_socket_t sockfd) : mSocket(sockfd) { } +}; + +std::ostream& operator<<(std::ostream& os, Socket const& sock) +{ + if (sock.mSocket == CURL_SOCKET_TIMEOUT) + os << "CURL_SOCKET_TIMEOUT"; + else + os << sock.mSocket; + return os; +} + +struct EvBitmask { + int mBitmask; + EvBitmask(int mask) : mBitmask(mask) { } +}; + +std::ostream& operator<<(std::ostream& os, EvBitmask const& bitmask) +{ + int m = bitmask.mBitmask; + if (m == 0) + os << '0'; + if ((m & CURL_CSELECT_IN)) + { + os << "CURL_CSELECT_IN"; + if ((m & (CURL_CSELECT_OUT|CURL_CSELECT_ERR))) + os << '|'; + } + if ((m & CURL_CSELECT_OUT)) + { + os << "CURL_CSELECT_OUT"; + if ((m & CURL_CSELECT_ERR)) + os << '|'; + } + if ((m & CURL_CSELECT_ERR)) + { + os << "CURL_CSELECT_ERR"; + } + return os; +} + +extern "C" { + +void debug_curl_easy_cleanup(CURL* handle) +{ + curl_easy_cleanup(handle); + Dout(dc::curl, "curl_easy_cleanup(" << (AICURL*)handle << ")"); +} + +CURL* debug_curl_easy_duphandle(CURL* handle) +{ + CURL* ret; + ret = curl_easy_duphandle(handle); + Dout(dc::curl, "curl_easy_duphandle(" << (AICURL*)handle << ") = " << (AICURL*)ret); + return ret; +} + +char* debug_curl_easy_escape(CURL* curl, char* url, int length) +{ + char* ret; + ret = curl_easy_escape(curl, url, length); + Dout(dc::curl, "curl_easy_escape(" << curl << ", \"" << url << "\", " << length << ") = \"" << ret << '"'); + return ret; +} + +CURLcode debug_curl_easy_getinfo(CURL* curl, CURLINFO info, ...) +{ + CURLcode ret; + va_list ap; + union param_type { + void* some_ptr; + long* long_ptr; + char** char_ptr; + curl_slist** curl_slist_ptr; + double* double_ptr; + } param; + va_start(ap, info); + param.some_ptr = va_arg(ap, void*); + va_end(ap); + ret = curl_easy_getinfo(curl, info, param.some_ptr); + if (info == CURLINFO_PRIVATE) + { + Dout(dc::curl, "curl_easy_getinfo(" << curl << ", " << info << ", 0x" << std::hex << (size_t)param.some_ptr << std::dec << ") = " << ret); + } + else + { + switch((info & CURLINFO_TYPEMASK)) + { + case CURLINFO_STRING: + Dout(dc::curl, "curl_easy_getinfo(" << curl << ", " << info << ", (char**){ \"" << (ret == CURLE_OK ? *param.char_ptr : " ") << "\" }) = " << ret); + break; + case CURLINFO_LONG: + Dout(dc::curl, "curl_easy_getinfo(" << curl << ", " << info << ", (long*){ " << (ret == CURLE_OK ? *param.long_ptr : 0L) << "L }) = " << ret); + break; + case CURLINFO_DOUBLE: + Dout(dc::curl, "curl_easy_getinfo(" << curl << ", " << info << ", (double*){" << (ret == CURLE_OK ? *param.double_ptr : 0.) << "}) = " << ret); + break; + case CURLINFO_SLIST: + Dout(dc::curl, "curl_easy_getinfo(" << curl << ", " << info << ", (curl_slist**){ " << (ret == CURLE_OK ? **param.curl_slist_ptr : unchanged_slist) << " }) = " << ret); + break; + } + } + return ret; +} + +CURL* debug_curl_easy_init(void) +{ + CURL* ret; + ret = curl_easy_init(); + Dout(dc::curl, "curl_easy_init() = " << (AICURL*)ret); + return ret; +} + +CURLcode debug_curl_easy_pause(CURL* handle, int bitmask) +{ + CURLcode ret; + ret = curl_easy_pause(handle, bitmask); + Dout(dc::curl, "curl_easy_pause(" << (AICURL*)handle << ", 0x" << std::hex << bitmask << std::dec << ") = " << ret); + return ret; +} + +CURLcode debug_curl_easy_perform(CURL* handle) +{ + CURLcode ret; + ret = curl_easy_perform(handle); + Dout(dc::curl, "curl_easy_perform(" << (AICURL*)handle << ") = " << ret); + return ret; +} + +void debug_curl_easy_reset(CURL* handle) +{ + curl_easy_reset(handle); + Dout(dc::curl, "curl_easy_reset(" << (AICURL*)handle << ")"); +} + +CURLcode debug_curl_easy_setopt(CURL* handle, CURLoption option, ...) +{ + CURLcode ret; + va_list ap; + union param_type { + long along; + void* ptr; + curl_off_t offset; + } param; + unsigned int param_type = (option / 10000) * 10000; + va_start(ap, option); + switch (param_type) + { + case CURLOPTTYPE_LONG: + param.along = va_arg(ap, long); + break; + case CURLOPTTYPE_OBJECTPOINT: + case CURLOPTTYPE_FUNCTIONPOINT: + param.ptr = va_arg(ap, void*); + break; + case CURLOPTTYPE_OFF_T: + param.offset = va_arg(ap, curl_off_t); + break; + default: + std::cerr << "Extracting param_type failed; option = " << option << "; param_type = " << param_type << std::endl; + std::exit(EXIT_FAILURE); + } + va_end(ap); + static long postfieldsize; // Cache. Assumes only one thread sets CURLOPT_POSTFIELDSIZE. + switch (param_type) + { + case CURLOPTTYPE_LONG: + { + ret = curl_easy_setopt(handle, option, param.along); + Dout(dc::curl, "curl_easy_setopt(" << (AICURL*)handle << ", " << option << ", " << param.along << "L) = " << ret); + if (option == CURLOPT_POSTFIELDSIZE) + { + postfieldsize = param.along; + } + break; + } + case CURLOPTTYPE_OBJECTPOINT: + { + ret = curl_easy_setopt(handle, option, param.ptr); + LibcwDoutScopeBegin(LIBCWD_DEBUGCHANNELS, libcwd::libcw_do, dc::curl) + LibcwDoutStream << "curl_easy_setopt(" << (AICURL*)handle << ", " << option << ", "; + // For a subset of all options that take a char*, print the string passed. + if (option == CURLOPT_PROXY || // Set HTTP proxy to use. The parameter should be a char* to a zero terminated string holding the host name or dotted IP address. + option == CURLOPT_PROXYUSERPWD || // Pass a char* as parameter, which should be [user name]:[password] to use for the connection to the HTTP proxy. + option == CURLOPT_CAINFO || // Pass a char * to a zero terminated string naming a file holding one or more certificates to verify the peer with. + option == CURLOPT_URL || // Pass in a pointer to the actual URL to deal with. The parameter should be a char * to a zero terminated string [...] + option == CURLOPT_COOKIEFILE || // Pass a pointer to a zero terminated string as parameter. It should contain the name of your file holding cookie data to read. + option == CURLOPT_CUSTOMREQUEST || // Pass a pointer to a zero terminated string as parameter. It can be used to specify the request instead of GET or HEAD when performing HTTP based requests + option == CURLOPT_ENCODING || // Sets the contents of the Accept-Encoding: header sent in a HTTP request, and enables decoding of a response when a Content-Encoding: header is received. + option == CURLOPT_POSTFIELDS || + option == CURLOPT_COPYPOSTFIELDS) // Full data to post in a HTTP POST operation. + { + bool const is_postfield = option == CURLOPT_POSTFIELDS || option == CURLOPT_COPYPOSTFIELDS; + char* str = (char*)param.ptr; + long size; + LibcwDoutStream << "(char*)0x" << std::hex << (size_t)param.ptr << std::dec << ")"; + if (is_postfield) + { + size = postfieldsize < 32 ? postfieldsize : 32; // Only print first 32 characters (this was already written to debug output before). + } + else + { + size = strlen(str); + } + LibcwDoutStream << "["; + if (str) + { + LibcwDoutStream << libcwd::buf2str(str, size); + if (is_postfield && postfieldsize > 32) + LibcwDoutStream << "..."; + } + else + { + LibcwDoutStream << "NULL"; + } + LibcwDoutStream << "](" << (is_postfield ? postfieldsize : size) << " bytes))"; + } + else + { + LibcwDoutStream << "(object*)0x" << std::hex << (size_t)param.ptr << std::dec << ")"; + } + LibcwDoutStream << " = " << ret; + LibcwDoutScopeEnd; + if (option == CURLOPT_HTTPHEADER && param.ptr) + { + debug::Indent indent(2); + Dout(dc::curl, "HTTP Headers:"); + struct curl_slist* list = (struct curl_slist*)param.ptr; + while (list) + { + Dout(dc::curl, '"' << list->data << '"'); + list = list->next; + } + } + break; + } + case CURLOPTTYPE_FUNCTIONPOINT: + ret = curl_easy_setopt(handle, option, param.ptr); + Dout(dc::curl, "curl_easy_setopt(" << (AICURL*)handle << ", " << option << ", (function*)0x" << std::hex << (size_t)param.ptr << std::dec << ") = " << ret); + break; + case CURLOPTTYPE_OFF_T: + { + ret = curl_easy_setopt(handle, option, param.offset); + Dout(dc::curl, "curl_easy_setopt(" << (AICURL*)handle << ", " << option << ", (curl_off_t)" << param.offset << ") = " << ret); + if (option == CURLOPT_POSTFIELDSIZE_LARGE) + { + postfieldsize = (long)param.offset; + } + break; + } + default: + break; + } + return ret; +} + +char const* debug_curl_easy_strerror(CURLcode errornum) +{ + char const* ret; + ret = curl_easy_strerror(errornum); + Dout(dc::curl, "curl_easy_strerror(" << errornum << ") = \"" << ret << '"'); + return ret; +} + +char* debug_curl_easy_unescape(CURL* curl, char* url, int inlength, int* outlength) +{ + char* ret; + ret = curl_easy_unescape(curl, url, inlength, outlength); + Dout(dc::curl, "curl_easy_unescape(" << curl << ", \"" << url << "\", " << inlength << ", " << ((ret && outlength) ? *outlength : 1) << ") = \"" << ret << '"'); + return ret; +} + +void debug_curl_free(char* ptr) +{ + curl_free(ptr); + Dout(dc::curl, "curl_free(0x" << std::hex << (size_t)ptr << std::dec << ")"); +} + +time_t debug_curl_getdate(char const* datestring, time_t* now) +{ + time_t ret; + ret = curl_getdate(datestring, now); + Dout(dc::curl, "curl_getdate(\"" << datestring << "\", " << (now == NULL ? "NULL" : "") << ") = " << ret); + return ret; +} + +void debug_curl_global_cleanup(void) +{ + curl_global_cleanup(); + Dout(dc::curl, "curl_global_cleanup()"); +} + +CURLcode debug_curl_global_init(long flags) +{ + CURLcode ret; + ret = curl_global_init(flags); + Dout(dc::curl, "curl_global_init(0x" << std::hex << flags << std::dec << ") = " << ret); + return ret; +} + +CURLMcode debug_curl_multi_add_handle(CURLM* multi_handle, CURL* easy_handle) +{ + CURLMcode ret; + ret = curl_multi_add_handle(multi_handle, easy_handle); + Dout(dc::curl, "curl_multi_add_handle(" << (AICURLM*)multi_handle << ", " << (AICURL*)easy_handle << ") = " << ret); + return ret; +} + +CURLMcode debug_curl_multi_assign(CURLM* multi_handle, curl_socket_t sockfd, void* sockptr) +{ + CURLMcode ret; + ret = curl_multi_assign(multi_handle, sockfd, sockptr); + Dout(dc::curl, "curl_multi_assign(" << (AICURLM*)multi_handle << ", " << Socket(sockfd) << ", " << sockptr << ") = " << ret); + return ret; +} + +CURLMcode debug_curl_multi_cleanup(CURLM* multi_handle) +{ + CURLMcode ret; + ret = curl_multi_cleanup(multi_handle); + Dout(dc::curl, "curl_multi_cleanup(" << (AICURLM*)multi_handle << ") = " << ret); + return ret; +} + +CURLMsg* debug_curl_multi_info_read(CURLM* multi_handle, int* msgs_in_queue) +{ + CURLMsg* ret; + ret = curl_multi_info_read(multi_handle, msgs_in_queue); + Dout(dc::curl, "curl_multi_info_read(" << (AICURLM*)multi_handle << ", {" << *msgs_in_queue << "}) = " << ret); + return ret; +} + +CURLM* debug_curl_multi_init(void) +{ + CURLM* ret; + ret = curl_multi_init(); + Dout(dc::curl, "curl_multi_init() = " << (AICURLM*)ret); + return ret; +} + +CURLMcode debug_curl_multi_remove_handle(CURLM* multi_handle, CURL* easy_handle) +{ + CURLMcode ret; + ret = curl_multi_remove_handle(multi_handle, easy_handle); + Dout(dc::curl, "curl_multi_remove_handle(" << (AICURLM*)multi_handle << ", " << (AICURL*)easy_handle << ") = " << ret); + return ret; +} + +CURLMcode debug_curl_multi_setopt(CURLM* multi_handle, CURLMoption option, ...) +{ + CURLMcode ret; + va_list ap; + union param_type { + long along; + void* ptr; + curl_off_t offset; + } param; + unsigned int param_type = (option / 10000) * 10000; + va_start(ap, option); + switch (param_type) + { + case CURLOPTTYPE_LONG: + param.along = va_arg(ap, long); + break; + case CURLOPTTYPE_OBJECTPOINT: + case CURLOPTTYPE_FUNCTIONPOINT: + param.ptr = va_arg(ap, void*); + break; + case CURLOPTTYPE_OFF_T: + param.offset = va_arg(ap, curl_off_t); + break; + default: + std::cerr << "Extracting param_type failed; option = " << option << "; param_type = " << param_type << std::endl; + std::exit(EXIT_FAILURE); + } + va_end(ap); + switch (param_type) + { + case CURLOPTTYPE_LONG: + ret = curl_multi_setopt(multi_handle, option, param.along); + Dout(dc::curl, "curl_easy_setopt(" << (AICURLM*)multi_handle << ", " << option << ", " << param.along << "L) = " << ret); + break; + case CURLOPTTYPE_OBJECTPOINT: + ret = curl_multi_setopt(multi_handle, option, param.ptr); + Dout(dc::curl, "curl_easy_setopt(" << (AICURLM*)multi_handle << ", " << option << ", (object*)0x" << std::hex << (size_t)param.ptr << std::dec << ") = " << ret); + break; + case CURLOPTTYPE_FUNCTIONPOINT: + ret = curl_multi_setopt(multi_handle, option, param.ptr); + Dout(dc::curl, "curl_easy_setopt(" << (AICURLM*)multi_handle << ", " << option << ", (function*)0x" << std::hex << (size_t)param.ptr << std::dec << ") = " << ret); + break; + case CURLOPTTYPE_OFF_T: + ret = curl_multi_setopt(multi_handle, option, param.offset); + Dout(dc::curl, "curl_easy_setopt(" << (AICURLM*)multi_handle << ", " << option << ", (curl_off_t)" << param.offset << ") = " << ret); + break; + default: // Stop compiler complaining about no default. + break; + } + return ret; +} + +CURLMcode debug_curl_multi_socket_action(CURLM* multi_handle, curl_socket_t sockfd, int ev_bitmask, int* running_handles) +{ + CURLMcode ret; + ret = curl_multi_socket_action(multi_handle, sockfd, ev_bitmask, running_handles); + Dout(dc::curl, "curl_multi_socket_action(" << (AICURLM*)multi_handle << ", " << Socket(sockfd) << + ", " << EvBitmask(ev_bitmask) << ", {" << (ret == CURLM_OK ? *running_handles : 0) << "}) = " << ret); + return ret; +} + +char const* debug_curl_multi_strerror(CURLMcode errornum) +{ + char const* ret; + ret = curl_multi_strerror(errornum); + Dout(dc::curl, "curl_multi_strerror(" << errornum << ") = \"" << ret << '"'); + return ret; +} + +struct curl_slist* debug_curl_slist_append(struct curl_slist* list, char const* string) +{ + struct curl_slist* ret; + ret = curl_slist_append(list, string); + Dout(dc::curl, "curl_slist_append((curl_slist)@0x" << std::hex << (size_t)list << std::dec << ", \"" << string << "\") = " << *ret); + return ret; +} + +void debug_curl_slist_free_all(struct curl_slist* list) +{ + curl_slist_free_all(list); + Dout(dc::curl, "curl_slist_free_all((curl_slist)@0x" << std::hex << (size_t)list << std::dec << ")"); +} + +char* debug_curl_unescape(char const* url, int length) +{ + char* ret; + ret = curl_unescape(url, length); + Dout(dc::curl, "curl_unescape(\"" << url << "\", " << length << ") = \"" << ret << '"'); + return ret; +} + +char* debug_curl_version(void) +{ + char* ret; + ret = curl_version(); + Dout(dc::curl, "curl_version() = \"" << ret << '"'); + return ret; +} + +} + +#else // DEBUG_CURLIO +int debug_libcurl_dummy; // I thought some OS didn't like empty source files. +#endif // DEBUG_CURLIO + diff --git a/indra/llmessage/debug_libcurl.h b/indra/llmessage/debug_libcurl.h new file mode 100644 index 000000000..27becb1c6 --- /dev/null +++ b/indra/llmessage/debug_libcurl.h @@ -0,0 +1,89 @@ +#ifndef DEBUG_LIBCURL +#define DEBUG_LIBCURL + +#ifndef DEBUG_CURLIO +#error "Don't include debug_libcurl.h unless DEBUG_CURLIO is defined." +#endif + +#ifndef CURLINFO_TYPEMASK +#error " must be included before including debug_libcurl.h!" +#endif + +#ifndef LLPREPROCESSOR_H +// CURL_STATICLIB is needed on windows namely, which is defined in llpreprocessor.h (but only on windows). +#error "llpreprocessor.h must be included before ." +#endif + +extern "C" { + +extern void debug_curl_easy_cleanup(CURL* handle); +extern CURL* debug_curl_easy_duphandle(CURL* handle); +extern char* debug_curl_easy_escape(CURL* curl, char* url, int length); +extern CURLcode debug_curl_easy_getinfo(CURL* curl, CURLINFO info, ...); +extern CURL* debug_curl_easy_init(void); +extern CURLcode debug_curl_easy_pause(CURL* handle, int bitmask); +extern CURLcode debug_curl_easy_perform(CURL* handle); +extern void debug_curl_easy_reset(CURL* handle); +extern CURLcode debug_curl_easy_setopt(CURL* handle, CURLoption option, ...); +extern char const* debug_curl_easy_strerror(CURLcode errornum); +extern char* debug_curl_easy_unescape(CURL* curl, char* url, int inlength, int* outlength); +extern void debug_curl_free(char* ptr); +extern time_t debug_curl_getdate(char const* datestring, time_t* now); +extern void debug_curl_global_cleanup(void); +extern CURLcode debug_curl_global_init(long flags); +extern CURLMcode debug_curl_multi_add_handle(CURLM* multi_handle, CURL* easy_handle); +extern CURLMcode debug_curl_multi_assign(CURLM* multi_handle, curl_socket_t sockfd, void* sockptr); +extern CURLMcode debug_curl_multi_cleanup(CURLM* multi_handle); +extern CURLMsg* debug_curl_multi_info_read(CURLM* multi_handle, int* msgs_in_queue); +extern CURLM* debug_curl_multi_init(void); +extern CURLMcode debug_curl_multi_remove_handle(CURLM* multi_handle, CURL* easy_handle); +extern CURLMcode debug_curl_multi_setopt(CURLM* multi_handle, CURLMoption option, ...); +extern CURLMcode debug_curl_multi_socket_action(CURLM* multi_handle, curl_socket_t sockfd, int ev_bitmask, int* running_handles); +extern char const* debug_curl_multi_strerror(CURLMcode errornum); +extern struct curl_slist* debug_curl_slist_append(struct curl_slist* list, char const* string); +extern void debug_curl_slist_free_all(struct curl_slist* list); +extern char* debug_curl_unescape(char const* url, int length); +extern char* debug_curl_version(void); + +} + +#ifndef COMPILING_DEBUG_LIBCURL_CC + +#ifdef curl_easy_setopt +#undef curl_easy_setopt +#undef curl_easy_getinfo +#undef curl_multi_setopt +#endif + +#define curl_easy_cleanup(handle) debug_curl_easy_cleanup(handle) +#define curl_easy_duphandle(handle) debug_curl_easy_duphandle(handle) +#define curl_easy_escape(curl, url, length) debug_curl_easy_escape(curl, url, length) +#define curl_easy_getinfo(curl, info, param) debug_curl_easy_getinfo(curl, info, param) +#define curl_easy_init() debug_curl_easy_init() +#define curl_easy_pause(handle, bitmask) debug_curl_easy_pause(handle, bitmask) +#define curl_easy_perform(handle) debug_curl_easy_perform(handle) +#define curl_easy_reset(handle) debug_curl_easy_reset(handle) +#define curl_easy_setopt(handle, option, param) debug_curl_easy_setopt(handle, option, param) +#define curl_easy_strerror(errornum) debug_curl_easy_strerror(errornum) +#define curl_easy_unescape(curl, url, inlength, outlength) debug_curl_easy_unescape(curl, url, inlength, outlength) +#define curl_free(ptr) debug_curl_free(ptr) +#define curl_getdate(datestring, now) debug_curl_getdate(datestring, now) +#define curl_global_cleanup() debug_curl_global_cleanup() +#define curl_global_init(flags) debug_curl_global_init(flags) +#define curl_multi_add_handle(multi_handle, easy_handle) debug_curl_multi_add_handle(multi_handle, easy_handle) +#define curl_multi_assign(multi_handle, sockfd, sockptr) debug_curl_multi_assign(multi_handle, sockfd, sockptr) +#define curl_multi_cleanup(multi_handle) debug_curl_multi_cleanup(multi_handle) +#define curl_multi_info_read(multi_handle, msgs_in_queue) debug_curl_multi_info_read(multi_handle, msgs_in_queue) +#define curl_multi_init() debug_curl_multi_init() +#define curl_multi_remove_handle(multi_handle, easy_handle) debug_curl_multi_remove_handle(multi_handle, easy_handle) +#define curl_multi_setopt(multi_handle, option, param) debug_curl_multi_setopt(multi_handle, option, param) +#define curl_multi_socket_action(multi_handle, sockfd, ev_bitmask, running_handles) debug_curl_multi_socket_action(multi_handle, sockfd, ev_bitmask, running_handles) +#define curl_multi_strerror(errornum) debug_curl_multi_strerror(errornum) +#define curl_slist_append(list, string) debug_curl_slist_append(list, string) +#define curl_slist_free_all(list) debug_curl_slist_free_all(list) +#define curl_unescape(url, length) debug_curl_unescape(url, length) +#define curl_version() debug_curl_version() + +#endif // !COMPILING_DEBUG_LIBCURL_CC + +#endif // DEBUG_LIBCURL diff --git a/indra/llmessage/llares.cpp b/indra/llmessage/llares.cpp index 48072d0e4..91d13dac3 100644 --- a/indra/llmessage/llares.cpp +++ b/indra/llmessage/llares.cpp @@ -28,7 +28,6 @@ #include "linden_common.h" #include "llares.h" -#include "llscopedvolatileaprpool.h" #include #include @@ -38,6 +37,7 @@ #include "apr_poll.h" #include "llapr.h" +#include "llaprpool.h" #include "llareslistener.h" #if defined(LL_WINDOWS) diff --git a/indra/llmessage/llassetstorage.cpp b/indra/llmessage/llassetstorage.cpp index 2729f199d..fd30f9249 100644 --- a/indra/llmessage/llassetstorage.cpp +++ b/indra/llmessage/llassetstorage.cpp @@ -401,7 +401,7 @@ bool LLAssetStorage::findInStaticVFSAndInvokeCallback(const LLUUID& uuid, LLAsse if (user_data) { // The *user_data should not be passed without a callback to clean it up. - llassert(callback != NULL) + llassert(callback != NULL); } BOOL exists = mStaticVFS->getExists(uuid, type); @@ -441,7 +441,7 @@ void LLAssetStorage::getAssetData(const LLUUID uuid, LLAssetType::EType type, LL if (user_data) { // The *user_data should not be passed without a callback to clean it up. - llassert(callback != NULL) + llassert(callback != NULL); } if (mShutDown) diff --git a/indra/llmessage/llcircuit.cpp b/indra/llmessage/llcircuit.cpp index 4b41abd45..56249db41 100644 --- a/indra/llmessage/llcircuit.cpp +++ b/indra/llmessage/llcircuit.cpp @@ -56,6 +56,7 @@ #include "llstl.h" #include "lltransfermanager.h" #include "llmodularmath.h" +#include "llpacketring.h" const S32 PING_START_BLOCK = 3; // How many pings behind we have to be to consider ourself blocked. const S32 PING_RELEASE_BLOCK = 2; // How many pings behind we have to be to consider ourself unblocked. @@ -346,7 +347,7 @@ S32 LLCircuitData::resendUnackedPackets(const F64 now) packetp->mBuffer[0] |= LL_RESENT_FLAG; // tag packet id as being a resend - gMessageSystem->mPacketRing.sendPacket(packetp->mSocket, + gMessageSystem->mPacketRing->sendPacket(packetp->mSocket, (char *)packetp->mBuffer, packetp->mBufferLength, packetp->mHost); diff --git a/indra/llmessage/llcurl.cpp b/indra/llmessage/llcurl.cpp index be35e97f4..585e4644b 100644 --- a/indra/llmessage/llcurl.cpp +++ b/indra/llmessage/llcurl.cpp @@ -26,32 +26,6 @@ * $/LicenseInfo$ */ -#if LL_WINDOWS -#define SAFE_SSL 1 -#elif LL_DARWIN -#define SAFE_SSL 1 -#else -#define SAFE_SSL 1 -#endif - -#include "linden_common.h" - -#include "llcurl.h" - -#include -#include -#include -#if SAFE_SSL -#include -#endif - -#include "llbufferstream.h" -#include "llproxy.h" -#include "llsdserialize.h" -#include "llstl.h" -#include "llthread.h" -#include "lltimer.h" - ////////////////////////////////////////////////////////////////////////////// /* The trick to getting curl to do keep-alives is to reuse the @@ -72,177 +46,40 @@ static const U32 EASY_HANDLE_POOL_SIZE = 5; static const S32 MULTI_PERFORM_CALL_REPEAT = 5; -static const S32 CURL_REQUEST_TIMEOUT = 30; // seconds +static const S32 CURL_REQUEST_TIMEOUT = 30; // seconds per operation static const S32 MAX_ACTIVE_REQUEST_COUNT = 100; -static -// DEBUG // -S32 gCurlEasyCount = 0; -S32 gCurlMultiCount = 0; +//static +F32 LLCurl::sCurlRequestTimeOut = 120.f; //seconds +S32 LLCurl::sMaxHandles = 256; //max number of handles, (multi handles and easy handles combined). ////////////////////////////////////////////////////////////////////////////// -//static -std::vector LLCurl::sSSLMutex; -std::string LLCurl::sCAPath; -std::string LLCurl::sCAFile; - -bool LLCurl::sMultiThreaded = false; -static U32 sMainThreadID = 0; - -void check_curl_code(CURLcode code) -{ - if (code != CURLE_OK) - { - // linux appears to throw a curl error once per session for a bad initialization - // at a pretty random time (when enabling cookies). - llinfos << "curl error detected: " << curl_easy_strerror(code) << llendl; - } -} - -void check_curl_multi_code(CURLMcode code) -{ - if (code != CURLM_OK) - { - // linux appears to throw a curl error once per session for a bad initialization - // at a pretty random time (when enabling cookies). - llinfos << "curl multi error detected: " << curl_multi_strerror(code) << llendl; - } -} - -//static -void LLCurl::setCAPath(const std::string& path) -{ - sCAPath = path; -} - -//static -void LLCurl::setCAFile(const std::string& file) -{ - sCAFile = file; -} - -//static -std::string LLCurl::getVersionString() -{ - return std::string(curl_version()); -} - -////////////////////////////////////////////////////////////////////////////// - -LLCurl::Responder::Responder() - : mReferenceCount(0) -{ -} - -LLCurl::Responder::~Responder() -{ -} - -// virtual -void LLCurl::Responder::errorWithContent( - U32 status, - const std::string& reason, - const LLSD&) -{ - error(status, reason); -} - -// virtual -void LLCurl::Responder::error(U32 status, const std::string& reason) -{ - llinfos << mURL << " [" << status << "]: " << reason << llendl; -} - -// virtual -void LLCurl::Responder::result(const LLSD& content) -{ -} - -void LLCurl::Responder::setURL(const std::string& url) -{ - mURL = url; -} - -// virtual -void LLCurl::Responder::completedRaw( - U32 status, - const std::string& reason, - const LLChannelDescriptors& channels, - const LLIOPipe::buffer_ptr_t& buffer) -{ - LLSD content; - LLBufferStream istr(channels, buffer.get()); - if (!LLSDSerialize::fromXML(content, istr)) - { - llinfos << "Failed to deserialize LLSD. " << mURL << " [" << status << "]: " << reason << llendl; - } - completed(status, reason, content); -} - -// virtual -void LLCurl::Responder::completed(U32 status, const std::string& reason, const LLSD& content) -{ - if (isGoodStatus(status)) - { - result(content); - } - else - { - errorWithContent(status, reason, content); - } -} - -//virtual -void LLCurl::Responder::completedHeader(U32 status, const std::string& reason, const LLSD& content) -{ - -} - -// DONT UNCOMMENT THIS IT BREAKS GCC47 -//namespace boost -//{ - void intrusive_ptr_add_ref(LLCurl::Responder* p) - { - ++p->mReferenceCount; - } - - void intrusive_ptr_release(LLCurl::Responder* p) - { - if(p && 0 == --p->mReferenceCount) - { - delete p; - } - } -//}; - - -////////////////////////////////////////////////////////////////////////////// - -std::set LLCurl::Easy::sFreeHandles; -std::set LLCurl::Easy::sActiveHandles; -LLMutex* LLCurl::Easy::sHandleMutex = NULL; -LLMutex* LLCurl::Easy::sMultiMutex = NULL; +AIThreadSafeSimpleDC LLCurl::Easy::sHandles; //static CURL* LLCurl::Easy::allocEasyHandle() { + llassert(*AIAccess(LLCurl::getCurlThread())) ; + CURL* ret = NULL; - LLMutexLock lock(sHandleMutex); - if (sFreeHandles.empty()) + + //*** Multi-threaded. + AIAccess handles_w(sHandles); + + if (handles_w->free.empty()) { - ret = curl_easy_init(); + ret = LLCurl::newEasyHandle(); } else { - ret = *(sFreeHandles.begin()); - sFreeHandles.erase(ret); - curl_easy_reset(ret); + ret = *(handles_w->free.begin()); + handles_w->free.erase(ret); } if (ret) { - sActiveHandles.insert(ret); + handles_w->active.insert(ret); } return ret; @@ -251,17 +88,33 @@ CURL* LLCurl::Easy::allocEasyHandle() //static void LLCurl::Easy::releaseEasyHandle(CURL* handle) { + DoutEntering(dc::curl, "LLCurl::Easy::releaseEasyHandle(" << (void*)handle << ")"); + BACKTRACE; + + static const S32 MAX_NUM_FREE_HANDLES = 32 ; + if (!handle) { - llerrs << "handle cannot be NULL!" << llendl; + return ; //handle allocation failed. + //llerrs << "handle cannot be NULL!" << llendl; } - LLMutexLock lock(sHandleMutex); - - if (sActiveHandles.find(handle) != sActiveHandles.end()) + //*** Multi-Threaded (logout only?) + AIAccess handles_w(sHandles); + + if (handles_w->active.find(handle) != handles_w->active.end()) { - sActiveHandles.erase(handle); - sFreeHandles.insert(handle); + handles_w->active.erase(handle); + + if (handles_w->free.size() < MAX_NUM_FREE_HANDLES) + { + curl_easy_reset(handle); + handles_w->free.insert(handle); + } + else + { + LLCurl::deleteEasyHandle(handle) ; + } } else { @@ -269,550 +122,16 @@ void LLCurl::Easy::releaseEasyHandle(CURL* handle) } } -LLCurl::Easy::Easy() - : mHeaders(NULL), - mCurlEasyHandle(NULL) -{ - mErrorBuffer[0] = 0; -} - -LLCurl::Easy* LLCurl::Easy::getEasy() -{ - Easy* easy = new Easy(); - easy->mCurlEasyHandle = allocEasyHandle(); - - if (!easy->mCurlEasyHandle) - { - // this can happen if we have too many open files (fails in c-ares/ares_init.c) - llwarns << "allocEasyHandle() returned NULL! Easy handles: " << gCurlEasyCount << " Multi handles: " << gCurlMultiCount << llendl; - delete easy; - return NULL; - } - - // set no DNS caching as default for all easy handles. This prevents them adopting a - // multi handles cache if they are added to one. - CURLcode result = curl_easy_setopt(easy->mCurlEasyHandle, CURLOPT_DNS_CACHE_TIMEOUT, 10); - check_curl_code(result); - result = curl_easy_setopt(easy->mCurlEasyHandle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); - check_curl_code(result); - - // Disable SSL/TLS session caching. Some servers refuse to talk to us when session ids are enabled. - // id.secondlife.com is such a server, when greeted with a SSL HELLO and a session id, it immediatly returns a RST packet and closes - // the connections. - // Fixes: FIRE-5368, FIRE-5756, VWR-28039, VWR-28629 - result = curl_easy_setopt(easy->mCurlEasyHandle, CURLOPT_SSL_SESSIONID_CACHE, 0); - check_curl_code(result); - - ++gCurlEasyCount; - return easy; -} - LLCurl::Easy::~Easy() { - releaseEasyHandle(mCurlEasyHandle); - --gCurlEasyCount; - curl_slist_free_all(mHeaders); - for_each(mStrings.begin(), mStrings.end(), DeletePointerArray()); -} - -void LLCurl::Easy::resetState() -{ - curl_easy_reset(mCurlEasyHandle); - - if (mHeaders) - { - curl_slist_free_all(mHeaders); - mHeaders = NULL; - } - - mRequest.str(""); - mRequest.clear(); - - mOutput.reset(); - - mInput.str(""); - mInput.clear(); - - mErrorBuffer[0] = 0; - - mHeaderOutput.str(""); - mHeaderOutput.clear(); -} - -void LLCurl::Easy::setErrorBuffer() -{ - setopt(CURLOPT_ERRORBUFFER, &mErrorBuffer); -} - -const char* LLCurl::Easy::getErrorBuffer() -{ - return mErrorBuffer; -} - -void LLCurl::Easy::setCA() -{ - if(!sCAPath.empty()) - { - setoptString(CURLOPT_CAPATH, sCAPath); - } - if(!sCAFile.empty()) - { - setoptString(CURLOPT_CAINFO, sCAFile); - } -} - -void LLCurl::Easy::setHeaders() -{ - setopt(CURLOPT_HTTPHEADER, mHeaders); -} - -void LLCurl::Easy::getTransferInfo(LLCurl::TransferInfo* info) -{ - check_curl_code(curl_easy_getinfo(mCurlEasyHandle, CURLINFO_SIZE_DOWNLOAD, &info->mSizeDownload)); - check_curl_code(curl_easy_getinfo(mCurlEasyHandle, CURLINFO_TOTAL_TIME, &info->mTotalTime)); - check_curl_code(curl_easy_getinfo(mCurlEasyHandle, CURLINFO_SPEED_DOWNLOAD, &info->mSpeedDownload)); -} - -U32 LLCurl::Easy::report(CURLcode code) -{ - U32 responseCode = 0; - std::string responseReason; - - if (code == CURLE_OK) - { - check_curl_code(curl_easy_getinfo(mCurlEasyHandle, CURLINFO_RESPONSE_CODE, &responseCode)); - // *TODO: get reason from first line of mHeaderOutput - } - else - { - responseCode = 499; - responseReason = strerror(code) + " : " + mErrorBuffer; - setopt(CURLOPT_FRESH_CONNECT, TRUE); - } - - if (mResponder) + AISTAccess responder_w(mResponder); + if (*responder_w && LLCurl::getNotQuitting()) //aborted { - mResponder->completedRaw(responseCode, responseReason, mChannels, mOutput); - mResponder = NULL; + std::string reason("Request timeout, aborted.") ; + (*responder_w)->completedRaw(408, //HTTP_REQUEST_TIME_OUT, timeout, abort + reason, mChannels, mOutput); } - - resetState(); - return responseCode; -} - -// Note: these all assume the caller tracks the value (i.e. keeps it persistant) -void LLCurl::Easy::setopt(CURLoption option, S32 value) -{ - CURLcode result = curl_easy_setopt(mCurlEasyHandle, option, value); - check_curl_code(result); -} - -void LLCurl::Easy::setopt(CURLoption option, void* value) -{ - CURLcode result = curl_easy_setopt(mCurlEasyHandle, option, value); - check_curl_code(result); -} - -void LLCurl::Easy::setopt(CURLoption option, char* value) -{ - CURLcode result = curl_easy_setopt(mCurlEasyHandle, option, value); - check_curl_code(result); -} - -// Note: this copies the string so that the caller does not have to keep it around -void LLCurl::Easy::setoptString(CURLoption option, const std::string& value) -{ - char* tstring = new char[value.length()+1]; - strcpy(tstring, value.c_str()); - mStrings.push_back(tstring); - CURLcode result = curl_easy_setopt(mCurlEasyHandle, option, tstring); - check_curl_code(result); -} - -void LLCurl::Easy::slist_append(const char* str) -{ - mHeaders = curl_slist_append(mHeaders, str); -} - -size_t curlReadCallback(char* data, size_t size, size_t nmemb, void* user_data) -{ - LLCurl::Easy* easy = (LLCurl::Easy*)user_data; - - S32 n = size * nmemb; - S32 startpos = (S32)easy->getInput().tellg(); - easy->getInput().seekg(0, std::ios::end); - S32 endpos = (S32)easy->getInput().tellg(); - easy->getInput().seekg(startpos, std::ios::beg); - S32 maxn = endpos - startpos; - n = llmin(n, maxn); - easy->getInput().read((char*)data, n); - - return n; -} - -size_t curlWriteCallback(char* data, size_t size, size_t nmemb, void* user_data) -{ - LLCurl::Easy* easy = (LLCurl::Easy*)user_data; - - S32 n = size * nmemb; - easy->getOutput()->append(easy->getChannels().in(), (const U8*)data, n); - - return n; -} - -size_t curlHeaderCallback(void* data, size_t size, size_t nmemb, void* user_data) -{ - LLCurl::Easy* easy = (LLCurl::Easy*)user_data; - - size_t n = size * nmemb; - easy->getHeaderOutput().write((const char*)data, n); - - return n; -} - -void LLCurl::Easy::prepRequest(const std::string& url, - const std::vector& headers, - ResponderPtr responder, S32 time_out, bool post) -{ - resetState(); - - if (post) setoptString(CURLOPT_ENCODING, ""); - - //setopt(CURLOPT_VERBOSE, 1); // useful for debugging - setopt(CURLOPT_NOSIGNAL, 1); - - setopt(CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); - - // Set the CURL options for either Socks or HTTP proxy - LLProxy::getInstance()->applyProxySettings(this); - - mOutput.reset(new LLBufferArray); - setopt(CURLOPT_WRITEFUNCTION, (void*)&curlWriteCallback); - setopt(CURLOPT_WRITEDATA, (void*)this); - - setopt(CURLOPT_READFUNCTION, (void*)&curlReadCallback); - setopt(CURLOPT_READDATA, (void*)this); - - setopt(CURLOPT_HEADERFUNCTION, (void*)&curlHeaderCallback); - setopt(CURLOPT_HEADERDATA, (void*)this); - // Allow up to five redirects - if(responder && responder->followRedir()) - { - setopt(CURLOPT_FOLLOWLOCATION, 1); - setopt(CURLOPT_MAXREDIRS, MAX_REDIRECTS); - } - - setErrorBuffer(); - setCA(); - - setopt(CURLOPT_SSL_VERIFYPEER, true); - - //don't verify host name so urls with scrubbed host names will work (improves DNS performance) - setopt(CURLOPT_SSL_VERIFYHOST, 0); - setopt(CURLOPT_TIMEOUT, llmax(time_out, CURL_REQUEST_TIMEOUT)); - - setoptString(CURLOPT_URL, url); - - mResponder = responder; - - if (!post) - { - slist_append("Connection: keep-alive"); - slist_append("Keep-alive: 300"); - // Accept and other headers - for (std::vector::const_iterator iter = headers.begin(); - iter != headers.end(); ++iter) - { - slist_append((*iter).c_str()); - } - } -} - -//////////////////////////////////////////////////////////////////////////// - -LLCurl::Multi::Multi() - : LLThread("Curl Multi"), - mQueued(0), - mErrorCount(0), - mPerformState(PERFORM_STATE_READY) -{ - mQuitting = false; - - mThreaded = LLCurl::sMultiThreaded && LLThread::currentID() == sMainThreadID; - if (mThreaded) - { - mSignal = new LLCondition; - } - else - { - mSignal = NULL; - } - - mCurlMultiHandle = curl_multi_init(); - if (!mCurlMultiHandle) - { - llwarns << "curl_multi_init() returned NULL! Easy handles: " << gCurlEasyCount << " Multi handles: " << gCurlMultiCount << llendl; - mCurlMultiHandle = curl_multi_init(); - } - - llassert_always(mCurlMultiHandle); - ++gCurlMultiCount; -} - -LLCurl::Multi::~Multi() -{ - llassert(isStopped()); - - if (LLCurl::sMultiThreaded) - { - LLCurl::Easy::sMultiMutex->lock(); - } - - delete mSignal; - mSignal = NULL; - - // Clean up active - for(easy_active_list_t::iterator iter = mEasyActiveList.begin(); - iter != mEasyActiveList.end(); ++iter) - { - Easy* easy = *iter; - check_curl_multi_code(curl_multi_remove_handle(mCurlMultiHandle, easy->getCurlHandle())); - delete easy; - } - mEasyActiveList.clear(); - mEasyActiveMap.clear(); - - // Clean up freed - for_each(mEasyFreeList.begin(), mEasyFreeList.end(), DeletePointer()); - mEasyFreeList.clear(); - - check_curl_multi_code(curl_multi_cleanup(mCurlMultiHandle)); - --gCurlMultiCount; - - if (LLCurl::sMultiThreaded) - { - LLCurl::Easy::sMultiMutex->unlock(); - } -} - -CURLMsg* LLCurl::Multi::info_read(S32* msgs_in_queue) -{ - CURLMsg* curlmsg = curl_multi_info_read(mCurlMultiHandle, msgs_in_queue); - return curlmsg; -} - -void LLCurl::Multi::perform() -{ - if (mThreaded) - { - mSignal->lock(); - if (mPerformState == PERFORM_STATE_READY) - { - mSignal->signal(); - } - mSignal->unlock(); - } - else - { - doPerform(); - } -} - -void LLCurl::Multi::run() -{ - llassert(mThreaded); - - mSignal->lock(); - while (!mQuitting) - { - mSignal->wait(); - mPerformState = PERFORM_STATE_PERFORMING; - if (!mQuitting) - { - LLMutexLock lock(LLCurl::Easy::sMultiMutex); - doPerform(); - } - } - mSignal->unlock(); -} - -void LLCurl::Multi::doPerform() -{ - S32 q = 0; - if (mThreaded) - mSignal->unlock(); - for (S32 call_count = 0; - call_count < MULTI_PERFORM_CALL_REPEAT; - call_count += 1) - { - CURLMcode code = curl_multi_perform(mCurlMultiHandle, &q); - if (CURLM_CALL_MULTI_PERFORM != code || q == 0) - { - check_curl_multi_code(code); - break; - } - - } - if (mThreaded) - mSignal->lock(); - mQueued = q; - mPerformState = PERFORM_STATE_COMPLETED; -} - -S32 LLCurl::Multi::process() -{ - perform(); - - if (mPerformState != PERFORM_STATE_COMPLETED) - { - return 0; - } - - CURLMsg* msg; - int msgs_in_queue; - - S32 processed = 0; - while ((msg = info_read(&msgs_in_queue))) - { - ++processed; - if (msg->msg == CURLMSG_DONE) - { - U32 response = 0; - easy_active_map_t::iterator iter = mEasyActiveMap.find(msg->easy_handle); - if (iter != mEasyActiveMap.end()) - { - Easy* easy = iter->second; - response = easy->report(msg->data.result); - removeEasy(easy); - } - else - { - response = 499; - // *TODO: change to llwarns - llerrs << "cleaned up curl request completed!" << llendl; - } - if (response >= 400) - { - // failure of some sort, inc mErrorCount for debugging and flagging multi for destruction - ++mErrorCount; - } - } - } - - mPerformState = PERFORM_STATE_READY; - return processed; -} - -LLCurl::Easy* LLCurl::Multi::allocEasy() -{ - Easy* easy = 0; - - if (mEasyFreeList.empty()) - { - easy = Easy::getEasy(); - } - else - { - easy = *(mEasyFreeList.begin()); - mEasyFreeList.erase(easy); - } - if (easy) - { - mEasyActiveList.insert(easy); - mEasyActiveMap[easy->getCurlHandle()] = easy; - } - return easy; -} - -bool LLCurl::Multi::addEasy(Easy* easy) -{ - CURLMcode mcode = curl_multi_add_handle(mCurlMultiHandle, easy->getCurlHandle()); - check_curl_multi_code(mcode); - //if (mcode != CURLM_OK) - //{ - // llwarns << "Curl Error: " << curl_multi_strerror(mcode) << llendl; - // return false; - //} - return true; -} - -void LLCurl::Multi::easyFree(Easy* easy) -{ - mEasyActiveList.erase(easy); - mEasyActiveMap.erase(easy->getCurlHandle()); - if (mEasyFreeList.size() < EASY_HANDLE_POOL_SIZE) - { - easy->resetState(); - mEasyFreeList.insert(easy); - } - else - { - delete easy; - } -} - -void LLCurl::Multi::removeEasy(Easy* easy) -{ - check_curl_multi_code(curl_multi_remove_handle(mCurlMultiHandle, easy->getCurlHandle())); - easyFree(easy); -} - -//static -std::string LLCurl::strerror(CURLcode errorcode) -{ - return std::string(curl_easy_strerror(errorcode)); -} - -//////////////////////////////////////////////////////////////////////////// -// For generating a simple request for data -// using one multi and one easy per request - -LLCurlRequest::LLCurlRequest() : - mActiveMulti(NULL), - mActiveRequestCount(0) -{ - mThreadID = LLThread::currentID(); - mProcessing = FALSE; -} - -LLCurlRequest::~LLCurlRequest() -{ - llassert_always(mThreadID == LLThread::currentID()); - - //stop all Multi handle background threads - for (curlmulti_set_t::iterator iter = mMultiSet.begin(); iter != mMultiSet.end(); ++iter) - { - LLCurl::Multi* multi = *iter; - if (multi->mThreaded) - multi->mSignal->lock(); - multi->mQuitting = true; - if (multi->mThreaded) - { - while (!multi->isStopped()) - { - multi->mSignal->signal(); - multi->mSignal->unlock(); - apr_sleep(1000); - multi->mSignal->lock(); - } - } - if (multi->mThreaded) - multi->mSignal->unlock(); - } - for_each(mMultiSet.begin(), mMultiSet.end(), DeletePointer()); -} - -void LLCurlRequest::addMulti() -{ - llassert_always(mThreadID == LLThread::currentID()); - LLCurl::Multi* multi = new LLCurl::Multi(); - if (multi->mThreaded) - { - multi->start(); - } - mMultiSet.insert(multi); - mActiveMulti = multi; - mActiveRequestCount = 0; + *responder_w = NULL; } LLCurl::Easy* LLCurlRequest::allocEasy() @@ -823,451 +142,13 @@ LLCurl::Easy* LLCurlRequest::allocEasy() { addMulti(); } - llassert_always(mActiveMulti); + if(!mActiveMulti) + { + return NULL ; + } + + //llassert_always(mActiveMulti); ++mActiveRequestCount; LLCurl::Easy* easy = mActiveMulti->allocEasy(); return easy; } - -bool LLCurlRequest::addEasy(LLCurl::Easy* easy) -{ - llassert_always(mActiveMulti); - - if (mProcessing) - { - llerrs << "Posting to a LLCurlRequest instance from within a responder is not allowed (causes DNS timeouts)." << llendl; - } - bool res = mActiveMulti->addEasy(easy); - return res; -} - -void LLCurlRequest::get(const std::string& url, LLCurl::ResponderPtr responder) -{ - getByteRange(url, headers_t(), 0, -1, responder); -} - -bool LLCurlRequest::getByteRange(const std::string& url, - const headers_t& headers, - S32 offset, S32 length, - LLCurl::ResponderPtr responder) -{ - LLCurl::Easy* easy = allocEasy(); - if (!easy) - { - return false; - } - easy->prepRequest(url, headers, responder); - easy->setopt(CURLOPT_HTTPGET, 1); - if (length > 0) - { - std::string range = llformat("Range: bytes=%d-%d", offset,offset+length-1); - easy->slist_append(range.c_str()); - } - easy->setHeaders(); - bool res = addEasy(easy); - return res; -} - -bool LLCurlRequest::post(const std::string& url, - const headers_t& headers, - const LLSD& data, - LLCurl::ResponderPtr responder, S32 time_out) -{ - LLCurl::Easy* easy = allocEasy(); - if (!easy) - { - return false; - } - easy->prepRequest(url, headers, responder, time_out); - - LLSDSerialize::toXML(data, easy->getInput()); - S32 bytes = easy->getInput().str().length(); - - easy->setopt(CURLOPT_POST, 1); - easy->setopt(CURLOPT_POSTFIELDS, (void*)NULL); - easy->setopt(CURLOPT_POSTFIELDSIZE, bytes); - - easy->slist_append("Content-Type: application/llsd+xml"); - easy->setHeaders(); - - lldebugs << "POSTING: " << bytes << " bytes." << llendl; - bool res = addEasy(easy); - return res; -} - -bool LLCurlRequest::post(const std::string& url, - const headers_t& headers, - const std::string& data, - LLCurl::ResponderPtr responder, S32 time_out) -{ - LLCurl::Easy* easy = allocEasy(); - if (!easy) - { - return false; - } - easy->prepRequest(url, headers, responder, time_out); - - easy->getInput().write(data.data(), data.size()); - S32 bytes = easy->getInput().str().length(); - - easy->setopt(CURLOPT_POST, 1); - easy->setopt(CURLOPT_POSTFIELDS, (void*)NULL); - easy->setopt(CURLOPT_POSTFIELDSIZE, bytes); - - easy->slist_append("Content-Type: application/octet-stream"); - easy->setHeaders(); - - lldebugs << "POSTING: " << bytes << " bytes." << llendl; - bool res = addEasy(easy); - return res; -} - -// Note: call once per frame -S32 LLCurlRequest::process() -{ - llassert_always(mThreadID == LLThread::currentID()); - S32 res = 0; - - mProcessing = TRUE; - for (curlmulti_set_t::iterator iter = mMultiSet.begin(); - iter != mMultiSet.end(); ) - { - curlmulti_set_t::iterator curiter = iter++; - LLCurl::Multi* multi = *curiter; - S32 tres = multi->process(); - res += tres; - if (multi != mActiveMulti && tres == 0 && multi->mQueued == 0) - { - mMultiSet.erase(curiter); - if (multi->mThreaded) - multi->mSignal->lock(); - multi->mQuitting = true; - if (multi->mThreaded) - { - while (!multi->isStopped()) - { - multi->mSignal->signal(); - multi->mSignal->unlock(); - apr_sleep(1000); - multi->mSignal->unlock(); - } - } - if (multi->mThreaded) - multi->mSignal->unlock(); - - delete multi; - } - } - mProcessing = FALSE; - return res; -} - -S32 LLCurlRequest::getQueued() -{ - llassert_always(mThreadID == LLThread::currentID()); - S32 queued = 0; - for (curlmulti_set_t::iterator iter = mMultiSet.begin(); - iter != mMultiSet.end(); ) - { - curlmulti_set_t::iterator curiter = iter++; - LLCurl::Multi* multi = *curiter; - queued += multi->mQueued; - if (multi->mPerformState != LLCurl::Multi::PERFORM_STATE_READY) - { - ++queued; - } - } - return queued; -} - -//////////////////////////////////////////////////////////////////////////// -// For generating one easy request -// associated with a single multi request - -LLCurlEasyRequest::LLCurlEasyRequest() - : mRequestSent(false), - mResultReturned(false) -{ - mMulti = new LLCurl::Multi(); - if (mMulti->mThreaded) - { - mMulti->start(); - } - mEasy = mMulti->allocEasy(); - if (mEasy) - { - mEasy->setErrorBuffer(); - mEasy->setCA(); - } -} - -LLCurlEasyRequest::~LLCurlEasyRequest() -{ - if (mMulti->mThreaded) - mMulti->mSignal->lock(); - mMulti->mQuitting = true; - if (mMulti->mThreaded) - { - while (!mMulti->isStopped()) - { - mMulti->mSignal->signal(); - mMulti->mSignal->unlock(); - apr_sleep(1000); - mMulti->mSignal->lock(); - } - } - if (mMulti->mThreaded) - mMulti->mSignal->unlock(); - delete mMulti; -} - -void LLCurlEasyRequest::setopt(CURLoption option, S32 value) -{ - if (mEasy) - { - mEasy->setopt(option, value); - } -} - -void LLCurlEasyRequest::setoptString(CURLoption option, const std::string& value) -{ - if (mEasy) - { - mEasy->setoptString(option, value); - } -} - -void LLCurlEasyRequest::setPost(char* postdata, S32 size) -{ - if (mEasy) - { - mEasy->setopt(CURLOPT_POST, 1); - mEasy->setopt(CURLOPT_POSTFIELDS, postdata); - mEasy->setopt(CURLOPT_POSTFIELDSIZE, size); - } -} - -void LLCurlEasyRequest::setHeaderCallback(curl_header_callback callback, void* userdata) -{ - if (mEasy) - { - mEasy->setopt(CURLOPT_HEADERFUNCTION, (void*)callback); - mEasy->setopt(CURLOPT_HEADERDATA, userdata); // aka CURLOPT_WRITEHEADER - } -} - -void LLCurlEasyRequest::setWriteCallback(curl_write_callback callback, void* userdata) -{ - if (mEasy) - { - mEasy->setopt(CURLOPT_WRITEFUNCTION, (void*)callback); - mEasy->setopt(CURLOPT_WRITEDATA, userdata); - } -} - -void LLCurlEasyRequest::setReadCallback(curl_read_callback callback, void* userdata) -{ - if (mEasy) - { - mEasy->setopt(CURLOPT_READFUNCTION, (void*)callback); - mEasy->setopt(CURLOPT_READDATA, userdata); - } -} - -void LLCurlEasyRequest::setSSLCtxCallback(curl_ssl_ctx_callback callback, void* userdata) -{ - if (mEasy) - { - mEasy->setopt(CURLOPT_SSL_CTX_FUNCTION, (void*)callback); - mEasy->setopt(CURLOPT_SSL_CTX_DATA, userdata); - } -} - -void LLCurlEasyRequest::slist_append(const char* str) -{ - if (mEasy) - { - mEasy->slist_append(str); - } -} - -void LLCurlEasyRequest::sendRequest(const std::string& url) -{ - llassert_always(!mRequestSent); - mRequestSent = true; - lldebugs << url << llendl; - if (mEasy) - { - mEasy->setHeaders(); - mEasy->setoptString(CURLOPT_URL, url); - mMulti->addEasy(mEasy); - } -} - -void LLCurlEasyRequest::requestComplete() -{ - llassert_always(mRequestSent); - mRequestSent = false; - if (mEasy) - { - mMulti->removeEasy(mEasy); - } -} - -void LLCurlEasyRequest::perform() -{ - mMulti->perform(); -} - -// Usage: Call getRestult until it returns false (no more messages) -bool LLCurlEasyRequest::getResult(CURLcode* result, LLCurl::TransferInfo* info) -{ - if (mMulti->mPerformState != LLCurl::Multi::PERFORM_STATE_COMPLETED) - { //we're busy, try again later - return false; - } - mMulti->mPerformState = LLCurl::Multi::PERFORM_STATE_READY; - - if (!mEasy) - { - // Special case - we failed to initialize a curl_easy (can happen if too many open files) - // Act as though the request failed to connect - if (mResultReturned) - { - return false; - } - else - { - *result = CURLE_FAILED_INIT; - mResultReturned = true; - return true; - } - } - // In theory, info_read might return a message with a status other than CURLMSG_DONE - // In practice for all messages returned, msg == CURLMSG_DONE - // Ignore other messages just in case - while(1) - { - S32 q; - CURLMsg* curlmsg = info_read(&q, info); - if (curlmsg) - { - if (curlmsg->msg == CURLMSG_DONE) - { - *result = curlmsg->data.result; - return true; - } - // else continue - } - else - { - return false; - } - } -} - -// private -CURLMsg* LLCurlEasyRequest::info_read(S32* q, LLCurl::TransferInfo* info) -{ - if (mEasy) - { - CURLMsg* curlmsg = mMulti->info_read(q); - if (curlmsg && curlmsg->msg == CURLMSG_DONE) - { - if (info) - { - mEasy->getTransferInfo(info); - } - } - return curlmsg; - } - return NULL; -} - -std::string LLCurlEasyRequest::getErrorString() -{ - return mEasy ? std::string(mEasy->getErrorBuffer()) : std::string(); -} - -//////////////////////////////////////////////////////////////////////////// - -#if SAFE_SSL -//static -void LLCurl::ssl_locking_callback(int mode, int type, const char *file, int line) -{ - if (mode & CRYPTO_LOCK) - { - LLCurl::sSSLMutex[type]->lock(); - } - else - { - LLCurl::sSSLMutex[type]->unlock(); - } -} - -//static -unsigned long LLCurl::ssl_thread_id(void) -{ - return LLThread::currentID(); -} -#endif - -void LLCurl::initClass(bool multi_threaded) -{ - sMainThreadID = LLThread::currentID(); - sMultiThreaded = multi_threaded; - // Do not change this "unless you are familiar with and mean to control - // internal operations of libcurl" - // - http://curl.haxx.se/libcurl/c/curl_global_init.html - CURLcode code = curl_global_init(CURL_GLOBAL_ALL); - - check_curl_code(code); - - Easy::sHandleMutex = new LLMutex; - Easy::sMultiMutex = new LLMutex; - -#if SAFE_SSL - S32 mutex_count = CRYPTO_num_locks(); - for (S32 i=0; i::iterator iter = Easy::sFreeHandles.begin(); iter != Easy::sFreeHandles.end(); ++iter) - { - CURL* curl = *iter; - curl_easy_cleanup(curl); - } - - Easy::sFreeHandles.clear(); - - llassert(Easy::sActiveHandles.empty()); -} - -const unsigned int LLCurl::MAX_REDIRECTS = 5; - -// Provide access to LLCurl free functions outside of llcurl.cpp without polluting the global namespace. -void LLCurlFF::check_easy_code(CURLcode code) -{ - check_curl_code(code); -} -void LLCurlFF::check_multi_code(CURLMcode code) -{ - check_curl_multi_code(code); -} diff --git a/indra/llmessage/llcurl.h b/indra/llmessage/llcurl.h index 67abb30ff..32bb6e45f 100644 --- a/indra/llmessage/llcurl.h +++ b/indra/llmessage/llcurl.h @@ -1,383 +1,39 @@ -/** +/** * @file llcurl.h - * @author Zero / Donovan - * @date 2006-10-15 - * @brief A wrapper around libcurl. + * @brief Drop in replacement for old llcurl.h. * - * $LicenseInfo:firstyear=2006&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, + * Copyright (c) 2012, Aleric Inglewood. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program 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$ + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution. + * + * CHANGELOG + * and additional copyright holders. + * + * 22/06/2012 + * Initial version, written by Aleric Inglewood @ SL */ - + #ifndef LL_LLCURL_H #define LL_LLCURL_H -#include "linden_common.h" +#include "aicurl.h" -#include -#include -#include - -#include -#include // TODO: remove dependency - -#include "llbuffer.h" -#include "lliopipe.h" -#include "llsd.h" -#include "llthread.h" - -class LLMutex; - -// For whatever reason, this is not typedef'd in curl.h -typedef size_t (*curl_header_callback)(void *ptr, size_t size, size_t nmemb, void *stream); - -class LLCurl -{ - LOG_CLASS(LLCurl); - -public: - class Easy; - class Multi; - - static bool sMultiThreaded; - - struct TransferInfo - { - TransferInfo() : mSizeDownload(0.0), mTotalTime(0.0), mSpeedDownload(0.0) {} - F64 mSizeDownload; - F64 mTotalTime; - F64 mSpeedDownload; - }; - - class Responder - { - //LOG_CLASS(Responder); - public: - - Responder(); - virtual ~Responder(); - - /** - * @brief return true if the status code indicates success. - */ - static bool isGoodStatus(U32 status) - { - return((200 <= status) && (status < 300)); - } - - virtual void errorWithContent( - U32 status, - const std::string& reason, - const LLSD& content); - //< called by completed() on bad status - - virtual void error(U32 status, const std::string& reason); - //< called by default error(status, reason, content) - - virtual void result(const LLSD& content); - //< called by completed for good status codes. - - virtual void completedRaw( - U32 status, - const std::string& reason, - const LLChannelDescriptors& channels, - const LLIOPipe::buffer_ptr_t& buffer); - /**< Override point for clients that may want to use this - class when the response is some other format besides LLSD - */ - - virtual void completed( - U32 status, - const std::string& reason, - const LLSD& content); - /**< The default implemetnation calls - either: - * result(), or - * error() - */ - - // Override to handle parsing of the header only. Note: this is the only place where the contents - // of the header can be parsed. In the ::completed call above only the body is contained in the LLSD. - virtual void completedHeader(U32 status, const std::string& reason, const LLSD& content); - - // Used internally to set the url for debugging later. - void setURL(const std::string& url); - - virtual bool followRedir() - { - return false; - } - public: /* but not really -- don't touch this */ - U32 mReferenceCount; - - private: - std::string mURL; - }; - typedef boost::intrusive_ptr ResponderPtr; - - - /** - * @ brief Set certificate authority file used to verify HTTPS certs. - */ - static void setCAFile(const std::string& file); - - /** - * @ brief Set certificate authority path used to verify HTTPS certs. - */ - static void setCAPath(const std::string& path); - - /** - * @ brief Return human-readable string describing libcurl version. - */ - static std::string getVersionString(); - - /** - * @ brief Get certificate authority file used to verify HTTPS certs. - */ - static const std::string& getCAFile() { return sCAFile; } - - /** - * @ brief Get certificate authority path used to verify HTTPS certs. - */ - static const std::string& getCAPath() { return sCAPath; } - - /** - * @ brief Initialize LLCurl class - */ - static void initClass(bool multi_threaded = false); - - /** - * @ brief Cleanup LLCurl class - */ - static void cleanupClass(); - - /** - * @ brief curl error code -> string - */ - static std::string strerror(CURLcode errorcode); - - // For OpenSSL callbacks - static std::vector sSSLMutex; - - // OpenSSL callbacks - static void ssl_locking_callback(int mode, int type, const char *file, int line); - static unsigned long ssl_thread_id(void); - -private: - static std::string sCAPath; - static std::string sCAFile; - static const unsigned int MAX_REDIRECTS; -}; - -class LLCurl::Easy -{ - LOG_CLASS(Easy); - -private: - Easy(); - -public: - static Easy* getEasy(); - ~Easy(); - - CURL* getCurlHandle() const { return mCurlEasyHandle; } - - void setErrorBuffer(); - void setCA(); - - void setopt(CURLoption option, S32 value); - // These assume the setter does not free value! - void setopt(CURLoption option, void* value); - void setopt(CURLoption option, char* value); - // Copies the string so that it is gauranteed to stick around - void setoptString(CURLoption option, const std::string& value); - - void slist_append(const char* str); - void setHeaders(); - - U32 report(CURLcode); - void getTransferInfo(LLCurl::TransferInfo* info); - - void prepRequest(const std::string& url, const std::vector& headers, ResponderPtr, S32 time_out = 0, bool post = false); - - const char* getErrorBuffer(); - - std::stringstream& getInput() { return mInput; } - std::stringstream& getHeaderOutput() { return mHeaderOutput; } - LLIOPipe::buffer_ptr_t& getOutput() { return mOutput; } - const LLChannelDescriptors& getChannels() { return mChannels; } - - void resetState(); - - static CURL* allocEasyHandle(); - static void releaseEasyHandle(CURL* handle); - -private: - friend class LLCurl; - friend class LLCurl::Multi; - - CURL* mCurlEasyHandle; - struct curl_slist* mHeaders; - - std::stringstream mRequest; - LLChannelDescriptors mChannels; - LLIOPipe::buffer_ptr_t mOutput; - std::stringstream mInput; - std::stringstream mHeaderOutput; - char mErrorBuffer[CURL_ERROR_SIZE]; - - // Note: char*'s not strings since we pass pointers to curl - std::vector mStrings; - - ResponderPtr mResponder; - - static std::set sFreeHandles; - static std::set sActiveHandles; - static LLMutex* sHandleMutex; - static LLMutex* sMultiMutex; -}; - -class LLCurl::Multi : public LLThread -{ - LOG_CLASS(Multi); -public: - - typedef enum - { - PERFORM_STATE_READY=0, - PERFORM_STATE_PERFORMING=1, - PERFORM_STATE_COMPLETED=2 - } ePerformState; - - Multi(); - ~Multi(); - - Easy* allocEasy(); - bool addEasy(Easy* easy); - - void removeEasy(Easy* easy); - - S32 process(); - void perform(); - void doPerform(); - - virtual void run(); - - CURLMsg* info_read(S32* msgs_in_queue); - - S32 mQueued; - S32 mErrorCount; - - S32 mPerformState; - - LLCondition* mSignal; - bool mQuitting; - bool mThreaded; - -private: - void easyFree(Easy*); - - CURLM* mCurlMultiHandle; - - typedef std::set easy_active_list_t; - easy_active_list_t mEasyActiveList; - typedef std::map easy_active_map_t; - easy_active_map_t mEasyActiveMap; - typedef std::set easy_free_list_t; - easy_free_list_t mEasyFreeList; -}; - -// DONT UNCOMMENT BREAKS GCC47 -//namespace boost -//{ - void intrusive_ptr_add_ref(LLCurl::Responder* p); - void intrusive_ptr_release(LLCurl::Responder* p); -//}; - - -class LLCurlRequest -{ -public: - typedef std::vector headers_t; - - LLCurlRequest(); - ~LLCurlRequest(); - - void get(const std::string& url, LLCurl::ResponderPtr responder); - bool getByteRange(const std::string& url, const headers_t& headers, S32 offset, S32 length, LLCurl::ResponderPtr responder); - bool post(const std::string& url, const headers_t& headers, const LLSD& data, LLCurl::ResponderPtr responder, S32 time_out = 0); - bool post(const std::string& url, const headers_t& headers, const std::string& data, LLCurl::ResponderPtr responder, S32 time_out = 0); - - S32 process(); - S32 getQueued(); - -private: - void addMulti(); - LLCurl::Easy* allocEasy(); - bool addEasy(LLCurl::Easy* easy); - -private: - typedef std::set curlmulti_set_t; - curlmulti_set_t mMultiSet; - LLCurl::Multi* mActiveMulti; - S32 mActiveRequestCount; - BOOL mProcessing; - U32 mThreadID; // debug -}; - -class LLCurlEasyRequest -{ -public: - LLCurlEasyRequest(); - ~LLCurlEasyRequest(); - void setopt(CURLoption option, S32 value); - void setoptString(CURLoption option, const std::string& value); - void setPost(char* postdata, S32 size); - void setHeaderCallback(curl_header_callback callback, void* userdata); - void setWriteCallback(curl_write_callback callback, void* userdata); - void setReadCallback(curl_read_callback callback, void* userdata); - void setSSLCtxCallback(curl_ssl_ctx_callback callback, void* userdata); - void slist_append(const char* str); - void sendRequest(const std::string& url); - void requestComplete(); - void perform(); - bool getResult(CURLcode* result, LLCurl::TransferInfo* info = NULL); - std::string getErrorString(); - - LLCurl::Easy* getEasy() const { return mEasy; } - -private: - CURLMsg* info_read(S32* queue, LLCurl::TransferInfo* info); - -private: - LLCurl::Multi* mMulti; - LLCurl::Easy* mEasy; - bool mRequestSent; - bool mResultReturned; -}; - -// Provide access to LLCurl free functions outside of llcurl.cpp without polluting the global namespace. -namespace LLCurlFF -{ - void check_easy_code(CURLcode code); - void check_multi_code(CURLMcode code); -} +// Map interface to old LLCurl names so this can be used as a drop-in replacement. +namespace LLCurl = AICurlInterface; #endif // LL_LLCURL_H diff --git a/indra/llmessage/llhttpclient.cpp b/indra/llmessage/llhttpclient.cpp index 2662eb349..c04bad5e4 100644 --- a/indra/llmessage/llhttpclient.cpp +++ b/indra/llmessage/llhttpclient.cpp @@ -25,7 +25,6 @@ */ #include "linden_common.h" - #include "llhttpclient.h" #include "llassetstorage.h" @@ -38,10 +37,8 @@ #include "lluri.h" #include "message.h" -#include const F32 HTTP_REQUEST_EXPIRY_SECS = 60.0f; - //////////////////////////////////////////////////////////////////////////// // Responder class moved to LLCurl @@ -122,7 +119,7 @@ namespace { public: RawInjector(const U8* data, S32 size) : mData(data), mSize(size) {} - virtual ~RawInjector() {delete mData;} + virtual ~RawInjector() {delete [] mData;} const char* contentType() { return "application/octet-stream"; } @@ -156,9 +153,9 @@ namespace if(fstream.is_open()) { fstream.seekg(0, std::ios::end); - U32 fileSize = (U32)fstream.tellg(); + U32 fileSize = fstream.tellg(); fstream.seekg(0, std::ios::beg); - std::vector fileBuffer(fileSize); //Mem leak fix'd + std::vector fileBuffer(fileSize); fstream.read(&fileBuffer[0], fileSize); ostream.write(&fileBuffer[0], fileSize); fstream.close(); @@ -209,20 +206,45 @@ static void request( const F32 timeout = HTTP_REQUEST_EXPIRY_SECS, const LLSD& headers = LLSD()) { + if (responder) + { + // For possible debug output from within the responder. + responder->setURL(url); + } + if (!LLHTTPClient::hasPump()) { - responder->completed(U32_MAX, "No pump", LLSD()); + responder->fatalError("No pump"); return; } LLPumpIO::chain_t chain; - LLURLRequest* req = new LLURLRequest(method, url); + LLURLRequest* req; + try + { + req = new LLURLRequest(method, url); + } + catch(AICurlNoEasyHandle& error) + { + llwarns << "Failed to create LLURLRequest: " << error.what() << llendl; + // This is what the old LL code did: no recovery whatsoever (but also no leaks or crash). + return ; + } req->checkRootCertificate(true); - // Insert custom headers is the caller sent any - if (headers.isMap()) - { + + lldebugs << LLURLRequest::actionAsVerb(method) << " " << url << " " + << headers << llendl; + + // Insert custom headers if the caller sent any + if (headers.isMap()) + { + if (headers.has("Cookie")) + { + req->allowCookies(); + } + LLSD::map_const_iterator iter = headers.beginMap(); LLSD::map_const_iterator end = headers.endMap(); @@ -257,11 +279,6 @@ static void request( } } - if (responder) - { - responder->setURL(url); - } - req->setCallback(new LLHTTPClientURLAdaptor(responder)); if (method == LLURLRequest::HTTP_POST && gMessageSystem) @@ -308,7 +325,7 @@ void LLHTTPClient::getByteRange( std::string range = llformat("bytes=%d-%d", offset, offset+bytes-1); headers["Range"] = range; } - request(url,LLURLRequest::HTTP_GET, NULL, responder, timeout, headers); + request(url, LLURLRequest::HTTP_GET, NULL, responder, timeout, headers); } void LLHTTPClient::head( @@ -347,12 +364,12 @@ class LLHTTPBuffer public: LLHTTPBuffer() { } - static size_t curl_write( void *ptr, size_t size, size_t nmemb, void *user_data) + static size_t curl_write(char* ptr, size_t size, size_t nmemb, void* user_data) { LLHTTPBuffer* self = (LLHTTPBuffer*)user_data; size_t bytes = (size * nmemb); - self->mBuffer.append((char*)ptr,bytes); + self->mBuffer.append(ptr,bytes); return nmemb; } @@ -403,99 +420,82 @@ static LLSD blocking_request( ) { lldebugs << "blockingRequest of " << url << llendl; - char curl_error_buffer[CURL_ERROR_SIZE] = "\0"; - CURL* curlp = curl_easy_init(); - LLHTTPBuffer http_buffer; - std::string body_str; - - // other request method checks root cert first, we skip? - - // * Set curl handle options - curl_easy_setopt(curlp, CURLOPT_NOSIGNAL, 1); // don't use SIGALRM for timeouts - curl_easy_setopt(curlp, CURLOPT_TIMEOUT, timeout); // seconds, see warning at top of function. - curl_easy_setopt(curlp, CURLOPT_WRITEFUNCTION, LLHTTPBuffer::curl_write); - curl_easy_setopt(curlp, CURLOPT_WRITEDATA, &http_buffer); - curl_easy_setopt(curlp, CURLOPT_URL, url.c_str()); - curl_easy_setopt(curlp, CURLOPT_ERRORBUFFER, curl_error_buffer); - - // * Setup headers (don't forget to free them after the call!) - curl_slist* headers_list = NULL; - if (headers.isMap()) + + S32 http_status = 499; + LLSD response = LLSD::emptyMap(); + + try { - LLSD::map_const_iterator iter = headers.beginMap(); - LLSD::map_const_iterator end = headers.endMap(); - for (; iter != end; ++iter) + AICurlEasyRequest easy_request(false); + AICurlEasyRequest_wat curlEasyRequest_w(*easy_request); + + LLHTTPBuffer http_buffer; + + // * Set curl handle options + curlEasyRequest_w->setopt(CURLOPT_TIMEOUT, (long)timeout); // seconds, see warning at top of function. + curlEasyRequest_w->setWriteCallback(&LLHTTPBuffer::curl_write, &http_buffer); + + // * Setup headers. + if (headers.isMap()) { - std::ostringstream header; - header << iter->first << ": " << iter->second.asString() ; - lldebugs << "header = " << header.str() << llendl; - headers_list = curl_slist_append(headers_list, header.str().c_str()); + LLSD::map_const_iterator iter = headers.beginMap(); + LLSD::map_const_iterator end = headers.endMap(); + for (; iter != end; ++iter) + { + std::ostringstream header; + header << iter->first << ": " << iter->second.asString() ; + lldebugs << "header = " << header.str() << llendl; + curlEasyRequest_w->addHeader(header.str().c_str()); + } + } + + // Needs to stay alive until after the call to perform(). + std::ostringstream ostr; + + // * Setup specific method / "verb" for the URI (currently only GET and POST supported + poppy) + if (method == LLURLRequest::HTTP_GET) + { + curlEasyRequest_w->setopt(CURLOPT_HTTPGET, 1); + } + else if (method == LLURLRequest::HTTP_POST) + { + //copied from PHP libs, correct? + curlEasyRequest_w->addHeader("Content-Type: application/llsd+xml"); + LLSDSerialize::toXML(body, ostr); + curlEasyRequest_w->setPost(ostr.str().c_str(), ostr.str().length()); + } + + // * Do the action using curl, handle results + curlEasyRequest_w->addHeader("Accept: application/llsd+xml"); + curlEasyRequest_w->finalizeRequest(url); + + S32 curl_success = curlEasyRequest_w->perform(); + curlEasyRequest_w->getinfo(CURLINFO_RESPONSE_CODE, &http_status); + // if we get a non-404 and it's not a 200 OR maybe it is but you have error bits, + if ( http_status != 404 && (http_status != 200 || curl_success != 0) ) + { + // We expect 404s, don't spam for them. + llwarns << "CURL REQ URL: " << url << llendl; + llwarns << "CURL REQ METHOD TYPE: " << method << llendl; + llwarns << "CURL REQ HEADERS: " << headers.asString() << llendl; + llwarns << "CURL REQ BODY: " << ostr.str() << llendl; + llwarns << "CURL HTTP_STATUS: " << http_status << llendl; + llwarns << "CURL ERROR: " << curlEasyRequest_w->getErrorString() << llendl; + llwarns << "CURL ERROR BODY: " << http_buffer.asString() << llendl; + response["body"] = http_buffer.asString(); + } + else + { + response["body"] = http_buffer.asLLSD(); + lldebugs << "CURL response: " << http_buffer.asString() << llendl; } } - - // * Setup specific method / "verb" for the URI (currently only GET and POST supported + poppy) - if (method == LLURLRequest::HTTP_GET) + catch(AICurlNoEasyHandle const& error) { - curl_easy_setopt(curlp, CURLOPT_HTTPGET, 1); - } - else if (method == LLURLRequest::HTTP_POST) - { - curl_easy_setopt(curlp, CURLOPT_POST, 1); - //serialize to ostr then copy to str - need to because ostr ptr is unstable :( - std::ostringstream ostr; - LLSDSerialize::toXML(body, ostr); - body_str = ostr.str(); - curl_easy_setopt(curlp, CURLOPT_POSTFIELDS, body_str.c_str()); - //copied from PHP libs, correct? - headers_list = curl_slist_append(headers_list, "Content-Type: application/llsd+xml"); - - // copied from llurlrequest.cpp - // it appears that apache2.2.3 or django in etch is busted. If - // we do not clear the expect header, we get a 500. May be - // limited to django/mod_wsgi. - headers_list = curl_slist_append(headers_list, "Expect:"); - } - - // * Do the action using curl, handle results - lldebugs << "HTTP body: " << body_str << llendl; - headers_list = curl_slist_append(headers_list, "Accept: application/llsd+xml"); - CURLcode curl_result = curl_easy_setopt(curlp, CURLOPT_HTTPHEADER, headers_list); - if ( curl_result != CURLE_OK ) - { - llinfos << "Curl is hosed - can't add headers" << llendl; + response["body"] = error.what(); } - LLSD response = LLSD::emptyMap(); - S32 curl_success = curl_easy_perform(curlp); - S32 http_status = 499; - curl_easy_getinfo(curlp, CURLINFO_RESPONSE_CODE, &http_status); response["status"] = http_status; - // if we get a non-404 and it's not a 200 OR maybe it is but you have error bits, - if ( http_status != 404 && (http_status != 200 || curl_success != 0) ) - { - // We expect 404s, don't spam for them. - llwarns << "CURL REQ URL: " << url << llendl; - llwarns << "CURL REQ METHOD TYPE: " << method << llendl; - llwarns << "CURL REQ HEADERS: " << headers.asString() << llendl; - llwarns << "CURL REQ BODY: " << body_str << llendl; - llwarns << "CURL HTTP_STATUS: " << http_status << llendl; - llwarns << "CURL ERROR: " << curl_error_buffer << llendl; - llwarns << "CURL ERROR BODY: " << http_buffer.asString() << llendl; - response["body"] = http_buffer.asString(); - } - else - { - response["body"] = http_buffer.asLLSD(); - lldebugs << "CURL response: " << http_buffer.asString() << llendl; - } - - if(headers_list) - { // free the header list - curl_slist_free_all(headers_list); - } - - // * Cleanup - curl_easy_cleanup(curlp); return response; } @@ -595,7 +595,8 @@ bool LLHTTPClient::hasPump() return theClientPump != NULL; } -LLPumpIO &LLHTTPClient::getPump() +//static +LLPumpIO& LLHTTPClient::getPump() { return *theClientPump; } diff --git a/indra/llmessage/llhttpclient.h b/indra/llmessage/llhttpclient.h index b4541c99e..ae22fd30a 100644 --- a/indra/llmessage/llhttpclient.h +++ b/indra/llmessage/llhttpclient.h @@ -34,7 +34,6 @@ #include #include - #include "llassettype.h" #include "llcurl.h" #include "lliopipe.h" @@ -56,6 +55,9 @@ public: typedef LLCurl::Responder Responder; typedef LLCurl::ResponderPtr ResponderPtr; + // The default actually already ignores responses. + class ResponderIgnore : public Responder { }; + /** @name non-blocking API */ //@{ static void head( diff --git a/indra/llmessage/lliopipe.cpp b/indra/llmessage/lliopipe.cpp index 8f827f7a3..c8bef05c6 100644 --- a/indra/llmessage/lliopipe.cpp +++ b/indra/llmessage/lliopipe.cpp @@ -76,7 +76,14 @@ LLIOPipe::~LLIOPipe() } //virtual -bool LLIOPipe::isValid() +bool LLIOPipe::hasExpiration(void) const +{ + // LLIOPipe::hasNotExpired always returns true. + return false; +} + +//virtual +bool LLIOPipe::hasNotExpired(void) const { return true ; } diff --git a/indra/llmessage/lliopipe.h b/indra/llmessage/lliopipe.h index 2def3229f..0e71d7a00 100644 --- a/indra/llmessage/lliopipe.h +++ b/indra/llmessage/lliopipe.h @@ -149,7 +149,7 @@ public: // The connection was lost. STATUS_LOST_CONNECTION = -5, - // The totoal process time has exceeded the timeout. + // The total process time has exceeded the timeout. STATUS_EXPIRED = -6, // Keep track of the count of codes here. @@ -231,7 +231,16 @@ public: */ virtual ~LLIOPipe(); - virtual bool isValid() ; + /** + * @brief External expiration facility. + * + * If hasExpiration() returns true, then we need to check hasNotExpired() + * to see if the LLIOPipe is still valid. In the legacy LL code the + * latter was called isValid() and was overloaded for two purposes: + * either expiration or failure to initialize. + */ + virtual bool hasExpiration(void) const; + virtual bool hasNotExpired(void) const; protected: /** diff --git a/indra/llmessage/llpacketring.h b/indra/llmessage/llpacketring.h index b214271e7..63d272308 100644 --- a/indra/llmessage/llpacketring.h +++ b/indra/llmessage/llpacketring.h @@ -32,7 +32,7 @@ #include "llhost.h" #include "llpacketbuffer.h" -#include "llproxy.h" +//#include "llproxy.h" #include "llthrottle.h" #include "net.h" diff --git a/indra/llmessage/llproxy.cpp b/indra/llmessage/llproxy.cpp index 4a7d326c0..448b6606b 100644 --- a/indra/llmessage/llproxy.cpp +++ b/indra/llmessage/llproxy.cpp @@ -29,7 +29,6 @@ #include "llproxy.h" #include -#include #include "llapr.h" #include "llcurl.h" @@ -47,23 +46,22 @@ static apr_status_t tcp_blocking_handshake(LLSocket::ptr_t handle, char * dataou static LLSocket::ptr_t tcp_open_channel(LLHost host); // Open a TCP channel to a given host static void tcp_close_channel(LLSocket::ptr_t* handle_ptr); // Close an open TCP channel -LLProxy::LLProxy(): - mHTTPProxyEnabled(false), - mProxyMutex(), - mUDPProxy(), - mTCPProxy(), - mHTTPProxy(), +ProxyShared::ProxyShared(void): mProxyType(LLPROXY_SOCKS), - mAuthMethodSelected(METHOD_NOAUTH), - mSocksUsername(), - mSocksPassword() + mAuthMethodSelected(METHOD_NOAUTH) +{ +} + +LLProxy::LLProxy(): + mHTTPProxyEnabled(false) { } LLProxy::~LLProxy() { stopSOCKSProxy(); - disableHTTPProxy(); + Shared_wat shared_w(mShared); + disableHTTPProxy(shared_w); } /** @@ -78,15 +76,18 @@ S32 LLProxy::proxyHandshake(LLHost proxy) { S32 result; + Unshared_rat unshared_r(mUnshared); + Shared_rat shared_r(mShared); + /* SOCKS 5 Auth request */ socks_auth_request_t socks_auth_request; socks_auth_response_t socks_auth_response; socks_auth_request.version = SOCKS_VERSION; // SOCKS version 5 socks_auth_request.num_methods = 1; // Sending 1 method. - socks_auth_request.methods = getSelectedAuthMethod(); // Send only the selected method. + socks_auth_request.methods = getSelectedAuthMethod(shared_r); // Send only the selected method. - result = tcp_blocking_handshake(mProxyControlChannel, + result = tcp_blocking_handshake(unshared_r->mProxyControlChannel, static_cast(static_cast(&socks_auth_request)), sizeof(socks_auth_request), static_cast(static_cast(&socks_auth_response)), @@ -109,8 +110,8 @@ S32 LLProxy::proxyHandshake(LLHost proxy) if (socks_auth_response.method == METHOD_PASSWORD) { // The server has requested a username/password combination - std::string socks_username(getSocksUser()); - std::string socks_password(getSocksPwd()); + std::string socks_username(getSocksUser(shared_r)); + std::string socks_password(getSocksPwd(shared_r)); U32 request_size = socks_username.size() + socks_password.size() + 3; char * password_auth = new char[request_size]; password_auth[0] = 0x01; @@ -121,7 +122,7 @@ S32 LLProxy::proxyHandshake(LLHost proxy) authmethod_password_reply_t password_reply; - result = tcp_blocking_handshake(mProxyControlChannel, + result = tcp_blocking_handshake(unshared_r->mProxyControlChannel, password_auth, request_size, static_cast(static_cast(&password_reply)), @@ -157,7 +158,7 @@ S32 LLProxy::proxyHandshake(LLHost proxy) // "If the client is not in possession of the information at the time of the UDP ASSOCIATE, // the client MUST use a port number and address of all zeros. RFC 1928" - result = tcp_blocking_handshake(mProxyControlChannel, + result = tcp_blocking_handshake(unshared_r->mProxyControlChannel, static_cast(static_cast(&connect_request)), sizeof(connect_request), static_cast(static_cast(&connect_reply)), @@ -176,10 +177,14 @@ S32 LLProxy::proxyHandshake(LLHost proxy) return SOCKS_UDP_FWD_NOT_GRANTED; } - mUDPProxy.setPort(ntohs(connect_reply.port)); // reply port is in network byte order - mUDPProxy.setAddress(proxy.getAddress()); + { + // Write access type and read access type are really the same, so unshared_w must be simply a reference. + Unshared_wat& unshared_w = unshared_r; + unshared_w->mUDPProxy.setPort(ntohs(connect_reply.port)); // reply port is in network byte order + unshared_w->mUDPProxy.setAddress(proxy.getAddress()); + } // The connection was successful. We now have the UDP port to send requests that need forwarding to. - LL_INFOS("Proxy") << "SOCKS 5 UDP proxy connected on " << mUDPProxy << LL_ENDL; + LL_INFOS("Proxy") << "SOCKS 5 UDP proxy connected on " << unshared_r->mUDPProxy << LL_ENDL; return SOCKS_OK; } @@ -197,9 +202,11 @@ S32 LLProxy::proxyHandshake(LLHost proxy) */ S32 LLProxy::startSOCKSProxy(LLHost host) { + Unshared_wat unshared_w(mUnshared); + if (host.isOk()) { - mTCPProxy = host; + unshared_w->mTCPProxy = host; } else { @@ -209,13 +216,13 @@ S32 LLProxy::startSOCKSProxy(LLHost host) // Close any running SOCKS connection. stopSOCKSProxy(); - mProxyControlChannel = tcp_open_channel(mTCPProxy); - if (!mProxyControlChannel) + unshared_w->mProxyControlChannel = tcp_open_channel(unshared_w->mTCPProxy); + if (!unshared_w->mProxyControlChannel) { return SOCKS_HOST_CONNECT_FAILED; } - S32 status = proxyHandshake(mTCPProxy); + S32 status = proxyHandshake(unshared_w->mTCPProxy); if (status != SOCKS_OK) { @@ -246,14 +253,16 @@ void LLProxy::stopSOCKSProxy() // then we must shut down any HTTP proxy operations. But it is allowable if web // proxy is being used to continue proxying HTTP. - if (LLPROXY_SOCKS == getHTTPProxyType()) + Shared_rat shared_r(mShared); + if (LLPROXY_SOCKS == getHTTPProxyType(shared_r)) { - disableHTTPProxy(); + Shared_wat shared_w(shared_r); + disableHTTPProxy(shared_w); } - - if (mProxyControlChannel) + Unshared_wat unshared_w(mUnshared); + if (unshared_w->mProxyControlChannel) { - tcp_close_channel(&mProxyControlChannel); + tcp_close_channel(&unshared_w->mProxyControlChannel); } } @@ -262,9 +271,7 @@ void LLProxy::stopSOCKSProxy() */ void LLProxy::setAuthNone() { - LLMutexLock lock(&mProxyMutex); - - mAuthMethodSelected = METHOD_NOAUTH; + Shared_wat(mShared)->mAuthMethodSelected = METHOD_NOAUTH; } /** @@ -288,11 +295,10 @@ bool LLProxy::setAuthPassword(const std::string &username, const std::string &pa return false; } - LLMutexLock lock(&mProxyMutex); - - mAuthMethodSelected = METHOD_PASSWORD; - mSocksUsername = username; - mSocksPassword = password; + Shared_wat shared_w(mShared); + shared_w->mAuthMethodSelected = METHOD_PASSWORD; + shared_w->mSocksUsername = username; + shared_w->mSocksPassword = password; return true; } @@ -314,12 +320,10 @@ bool LLProxy::enableHTTPProxy(LLHost httpHost, LLHttpProxyType type) return false; } - LLMutexLock lock(&mProxyMutex); - - mHTTPProxy = httpHost; - mProxyType = type; - + Shared_wat shared_w(mShared); mHTTPProxyEnabled = true; + shared_w->mHTTPProxy = httpHost; + shared_w->mProxyType = type; return true; } @@ -335,9 +339,8 @@ bool LLProxy::enableHTTPProxy() { bool ok; - LLMutexLock lock(&mProxyMutex); - - ok = (mHTTPProxy.isOk()); + Shared_rat shared_r(mShared); + ok = (shared_r->mHTTPProxy.isOk()); if (ok) { mHTTPProxyEnabled = true; @@ -346,54 +349,6 @@ bool LLProxy::enableHTTPProxy() return ok; } -/** - * @brief Disable the HTTP proxy. - */ -void LLProxy::disableHTTPProxy() -{ - LLMutexLock lock(&mProxyMutex); - - mHTTPProxyEnabled = false; -} - -/** - * @brief Get the currently selected HTTP proxy type - */ -LLHttpProxyType LLProxy::getHTTPProxyType() const -{ - LLMutexLock lock(&mProxyMutex); - return mProxyType; -} - -/** - * @brief Get the SOCKS 5 password. - */ -std::string LLProxy::getSocksPwd() const -{ - LLMutexLock lock(&mProxyMutex); - return mSocksPassword; -} - -/** - * @brief Get the SOCKS 5 username. - */ -std::string LLProxy::getSocksUser() const -{ - LLMutexLock lock(&mProxyMutex); - return mSocksUsername; -} - -/** - * @brief Get the currently selected SOCKS 5 authentication method. - * - * @return Returns either none or password. - */ -LLSocks5AuthType LLProxy::getSelectedAuthMethod() const -{ - LLMutexLock lock(&mProxyMutex); - return mAuthMethodSelected; -} - /** * @brief Stop the LLProxy and make certain that any APR pools and classes are deleted before terminating APR. * @@ -406,57 +361,6 @@ void LLProxy::cleanupClass() deleteSingleton(); } -void LLProxy::applyProxySettings(LLCurlEasyRequest* handle) -{ - applyProxySettings(handle->getEasy()); -} - -void LLProxy::applyProxySettings(LLCurl::Easy* handle) -{ - applyProxySettings(handle->getCurlHandle()); -} - -/** - * @brief Apply proxy settings to a CuRL request if an HTTP proxy is enabled. - * - * This method has been designed to be safe to call from - * any thread in the viewer. This allows requests in the - * texture fetch thread to be aware of the proxy settings. - * When the HTTP proxy is enabled, the proxy mutex will - * be locked every time this method is called. - * - * @param handle A pointer to a valid CURL request, before it has been performed. - */ -void LLProxy::applyProxySettings(CURL* handle) -{ - // Do a faster unlocked check to see if we are supposed to proxy. - if (mHTTPProxyEnabled) - { - // We think we should proxy, lock the proxy mutex. - LLMutexLock lock(&mProxyMutex); - // Now test again to verify that the proxy wasn't disabled between the first check and the lock. - if (mHTTPProxyEnabled) - { - LLCurlFF::check_easy_code(curl_easy_setopt(handle, CURLOPT_PROXY, mHTTPProxy.getIPString().c_str())); - LLCurlFF::check_easy_code(curl_easy_setopt(handle, CURLOPT_PROXYPORT, mHTTPProxy.getPort())); - - if (mProxyType == LLPROXY_SOCKS) - { - LLCurlFF::check_easy_code(curl_easy_setopt(handle, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5)); - if (mAuthMethodSelected == METHOD_PASSWORD) - { - std::string auth_string = mSocksUsername + ":" + mSocksPassword; - LLCurlFF::check_easy_code(curl_easy_setopt(handle, CURLOPT_PROXYUSERPWD, auth_string.c_str())); - } - } - else - { - LLCurlFF::check_easy_code(curl_easy_setopt(handle, CURLOPT_PROXYTYPE, CURLPROXY_HTTP)); - } - } - } -} - /** * @brief Send one TCP packet and receive one in return. * diff --git a/indra/llmessage/llproxy.h b/indra/llmessage/llproxy.h index a91937054..00bfa1ad4 100644 --- a/indra/llmessage/llproxy.h +++ b/indra/llmessage/llproxy.h @@ -33,6 +33,7 @@ #include "llmemory.h" #include "llsingleton.h" #include "llthread.h" +#include "aithreadsafe.h" #include // SOCKS error codes returned from the StartProxy method @@ -206,41 +207,92 @@ enum LLSocks5AuthType * The implementation of HTTP proxying is handled by libcurl. LLProxy * is responsible for managing the HTTP proxy options and provides a * thread-safe method to apply those options to a curl request - * (LLProxy::applyProxySettings()). This method is overloaded - * to accommodate the various abstraction libcurl layers that exist - * throughout the viewer (LLCurlEasyRequest, LLCurl::Easy, and CURL). - * - * If you are working with LLCurl or LLCurlEasyRequest objects, - * the configured proxy settings will be applied in the constructors - * of those request handles. If you are working with CURL objects - * directly, you will need to pass the handle of the request to - * applyProxySettings() before issuing the request. + * (LLProxy::applyProxySettings()). * * To ensure thread safety, all LLProxy members that relate to the HTTP * proxy require the LLProxyMutex to be locked before accessing. */ + +struct ProxyUnshared +{ + /*########################################################################################### + MEMBERS READ AND WRITTEN ONLY IN THE MAIN THREAD. + ###########################################################################################*/ + + // UDP proxy address and port + LLHost mUDPProxy; + + // TCP proxy control channel address and port + LLHost mTCPProxy; + + // socket handle to proxy TCP control channel + LLSocket::ptr_t mProxyControlChannel; + + /*########################################################################################### + END OF UNSHARED MEMBERS + ###########################################################################################*/ +}; + +struct ProxyShared +{ + ProxyShared(void); + + /*########################################################################################### + MEMBERS WRITTEN IN MAIN THREAD AND READ IN ANY THREAD. + ###########################################################################################*/ + + // HTTP proxy address and port + LLHost mHTTPProxy; + + // Currently selected HTTP proxy type. Can be web or SOCKS. + LLHttpProxyType mProxyType; + + // SOCKS 5 selected authentication method. + LLSocks5AuthType mAuthMethodSelected; + + // SOCKS 5 username + std::string mSocksUsername; + // SOCKS 5 password + std::string mSocksPassword; + + /*########################################################################################### + END OF SHARED MEMBERS + ###########################################################################################*/ +}; + class LLProxy: public LLSingleton { LOG_CLASS(LLProxy); + public: + typedef AISTAccessConst Unshared_crat; // Constant Read Access Type for Unshared (cannot be converted to write access). + typedef AISTAccess Unshared_rat; // Read Access Type for Unshared (same as write access type, since we don't lock at all). + typedef AISTAccess Unshared_wat; // Write Access Type, for Unshared. + typedef AIReadAccessConst Shared_crat; // Constant Read Access Type for Shared (cannot be converted to write access). + typedef AIReadAccess Shared_rat; // Read Access Type for Shared. + typedef AIWriteAccess Shared_wat; // Write Access Type for Shared. + /*########################################################################################### - METHODS THAT DO NOT LOCK mProxyMutex! + Public methods that only access variables not shared between threads. ###########################################################################################*/ // Constructor, cannot have parameters due to LLSingleton parent class. Call from main thread only. LLProxy(); - // Static check for enabled status for UDP packets. Call from main thread only. - static bool isSOCKSProxyEnabled() { return sUDPProxyEnabled; } + // Static check for enabled status for UDP packets. Called from main thread only. + static bool isSOCKSProxyEnabled(void) { llassert(is_main_thread()); return sUDPProxyEnabled; } - // Get the UDP proxy address and port. Call from main thread only. - LLHost getUDPProxy() const { return mUDPProxy; } + // Get the UDP proxy address and port. Called from main thread only. + LLHost getUDPProxy(void) const { return Unshared_crat(mUnshared)->mUDPProxy; } /*########################################################################################### - END OF NON-LOCKING METHODS + End of methods that only access variables not shared between threads. ###########################################################################################*/ + // Return true if there is a good chance that the HTTP proxy is currently enabled. + bool HTTPProxyEnabled(void) const { return mHTTPProxyEnabled; } + /*########################################################################################### - METHODS THAT LOCK mProxyMutex! DO NOT CALL WHILE mProxyMutex IS LOCKED! + Public methods that access variables shared between threads. ###########################################################################################*/ // Destructor, closes open connections. Do not call directly, use cleanupClass(). ~LLProxy(); @@ -251,9 +303,7 @@ public: // Apply the current proxy settings to a curl request. Doesn't do anything if mHTTPProxyEnabled is false. // Safe to call from any thread. - void applyProxySettings(CURL* handle); - void applyProxySettings(LLCurl::Easy* handle); - void applyProxySettings(LLCurlEasyRequest* handle); + void applyProxySettings(AICurlEasyRequest_wat const& curlEasyRequest_w); // Start a connection to the SOCKS 5 proxy. Call from main thread only. S32 startSOCKSProxy(LLHost host); @@ -273,30 +323,37 @@ public: bool enableHTTPProxy(); // Stop proxying HTTP packets. Call from main thread only. - void disableHTTPProxy(); + // Note that this needs shared_w to be passed because we want the shared members to be locked when this is reset to false. + void disableHTTPProxy(Shared_wat const& shared_w) { mHTTPProxyEnabled = false; } + void disableHTTPProxy(void) { disableHTTPProxy(Shared_wat(mShared)); } + + // Get the currently selected HTTP proxy address and port + LLHost const& getHTTPProxy(Shared_crat const& shared_r) const { return shared_r->mHTTPProxy; } + + // Get the currently selected HTTP proxy type + LLHttpProxyType getHTTPProxyType(Shared_crat const& shared_r) const { return shared_r->mProxyType; } + + // Get the currently selected auth method. + LLSocks5AuthType getSelectedAuthMethod(Shared_crat const& shared_r) const { return shared_r->mAuthMethodSelected; } + + // SOCKS 5 username and password accessors. + std::string getSocksUser(Shared_crat const& shared_r) const { return shared_r->mSocksUsername; } + std::string getSocksPwd(Shared_crat const& shared_r) const { return shared_r->mSocksPassword; } /*########################################################################################### - END OF LOCKING METHODS + End of methods that access variables shared between threads. ###########################################################################################*/ + private: /*########################################################################################### - METHODS THAT LOCK mProxyMutex! DO NOT CALL WHILE mProxyMutex IS LOCKED! + Private methods that access variables shared between threads. ###########################################################################################*/ // Perform a SOCKS 5 authentication and UDP association with the proxy server. S32 proxyHandshake(LLHost proxy); - // Get the currently selected auth method. - LLSocks5AuthType getSelectedAuthMethod() const; - - // Get the currently selected HTTP proxy type - LLHttpProxyType getHTTPProxyType() const; - - std::string getSocksPwd() const; - std::string getSocksUser() const; - /*########################################################################################### - END OF LOCKING METHODS + End of methods that access variables shared between threads. ###########################################################################################*/ private: @@ -304,49 +361,16 @@ private: // Instead use enableHTTPProxy() and disableHTTPProxy() instead. mutable LLAtomic32 mHTTPProxyEnabled; - // Mutex to protect shared members in non-main thread calls to applyProxySettings(). - mutable LLMutex mProxyMutex; - - /*########################################################################################### - MEMBERS READ AND WRITTEN ONLY IN THE MAIN THREAD. DO NOT SHARE! - ###########################################################################################*/ - // Is the UDP proxy enabled? static bool sUDPProxyEnabled; - // UDP proxy address and port - LLHost mUDPProxy; - // TCP proxy control channel address and port - LLHost mTCPProxy; + AIThreadSafeSingleThreadDC mUnshared; + AIThreadSafeDC mShared; - // socket handle to proxy TCP control channel - LLSocket::ptr_t mProxyControlChannel; - - /*########################################################################################### - END OF UNSHARED MEMBERS - ###########################################################################################*/ - - /*########################################################################################### - MEMBERS WRITTEN IN MAIN THREAD AND READ IN ANY THREAD. ONLY READ OR WRITE AFTER LOCKING mProxyMutex! - ###########################################################################################*/ - - // HTTP proxy address and port - LLHost mHTTPProxy; - - // Currently selected HTTP proxy type. Can be web or socks. - LLHttpProxyType mProxyType; - - // SOCKS 5 selected authentication method. - LLSocks5AuthType mAuthMethodSelected; - - // SOCKS 5 username - std::string mSocksUsername; - // SOCKS 5 password - std::string mSocksPassword; - - /*########################################################################################### - END OF SHARED MEMBERS - ###########################################################################################*/ +public: + // For thread-safe read access. Use the _crat access types with these. + AIThreadSafeSingleThreadDC const& unshared_lockobj(void) const { return mUnshared; } + AIThreadSafeDC const& shared_lockobj(void) const { return mShared; } }; #endif diff --git a/indra/llmessage/llpumpio.cpp b/indra/llmessage/llpumpio.cpp index 48e581c64..e6c6a42d6 100644 --- a/indra/llmessage/llpumpio.cpp +++ b/indra/llmessage/llpumpio.cpp @@ -4,31 +4,25 @@ * @date 2004-11-21 * @brief Implementation of the i/o pump and related functions. * - * $LicenseInfo:firstyear=2004&license=viewergpl$ - * - * Copyright (c) 2004-2009, Linden Research, Inc. - * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ * Second Life Viewer Source Code - * The source code in this file ("Source Code") is provided by Linden Lab - * to you under the terms of the GNU General Public License, version 2.0 - * ("GPL"), unless you have obtained a separate licensing agreement - * ("Other License"), formally executed by you and Linden Lab. Terms of - * the GPL can be found in doc/GPL-license.txt in this distribution, or - * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * Copyright (C) 2010, Linden Research, Inc. * - * There are special exceptions to the terms and conditions of the GPL as - * it is applied to this Source Code. View the full text of the exception - * in the file doc/FLOSS-exception.txt in this software distribution, or - * online at - * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * 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. * - * By copying, modifying or distributing this software, you acknowledge - * that you have read and understood your obligations described above, - * and agree to abide by those obligations. + * 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. * - * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO - * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, - * COMPLETENESS OR PERFORMANCE. + * 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$ */ @@ -40,12 +34,11 @@ #include "apr_poll.h" #include "llapr.h" +#include "llfasttimer.h" #include "llmemtype.h" #include "llstl.h" #include "llstat.h" #include "llthread.h" -#include "llfasttimer.h" -#include //VS2010 // These should not be enabled in production, but they can be // intensely useful during development for finding certain kinds of @@ -191,17 +184,28 @@ LLPumpIO::LLPumpIO(void) : LLPumpIO::~LLPumpIO() { LLMemType m1(LLMemType::MTYPE_IO_PUMP); - cleanup(); +#if LL_THREADS_APR + if (mChainsMutex) apr_thread_mutex_destroy(mChainsMutex); + if (mCallbackMutex) apr_thread_mutex_destroy(mCallbackMutex); +#endif + mChainsMutex = NULL; + mCallbackMutex = NULL; + if(mPollset) + { +// lldebugs << "cleaning up pollset" << llendl; + apr_pollset_destroy(mPollset); + mPollset = NULL; + } } -bool LLPumpIO::addChain(const chain_t& chain, F32 timeout) +bool LLPumpIO::addChain(chain_t const& chain, F32 timeout) { LLMemType m1(LLMemType::MTYPE_IO_PUMP); - if(chain.empty()) return false; -#if LL_THREADS_APR - LLScopedLock lock(mChainsMutex); -#endif + chain_t::const_iterator it = chain.begin(); + chain_t::const_iterator const end = chain.end(); + if (it == end) return false; + LLChainInfo info; info.setTimeoutSeconds(timeout); info.mData = LLIOPipe::buffer_ptr_t(new LLBufferArray); @@ -212,14 +216,17 @@ bool LLPumpIO::addChain(const chain_t& chain, F32 timeout) #else lldebugs << "LLPumpIO::addChain() " << chain[0] <hasExpiration(); link.mPipe = (*it); link.mChannels = info.mData->nextChannel(); info.mChainLinks.push_back(link); } + +#if LL_THREADS_APR + LLScopedLock lock(mChainsMutex); +#endif mPendingChains.push_back(info); return true; } @@ -236,11 +243,10 @@ bool LLPumpIO::addChain( // description, we need to have that description matched to a // particular buffer. if(!data) return false; - if(links.empty()) return false; + links_t::const_iterator link = links.begin(); + links_t::const_iterator const end = links.end(); + if (link == end) return false; -#if LL_THREADS_APR - LLScopedLock lock(mChainsMutex); -#endif #if LL_DEBUG_PIPE_TYPE_IN_PUMP lldebugs << "LLPumpIO::addChain() " << links[0].mPipe << " '" << typeid(*(links[0].mPipe)).name() << "'" << llendl; @@ -252,6 +258,17 @@ bool LLPumpIO::addChain( info.mChainLinks = links; info.mData = data; info.mContext = context; + for (; link != end; ++link) + { + if (link->mPipe->hasExpiration()) + { + info.mHasExpiration = true; + break; + } + } +#if LL_THREADS_APR + LLScopedLock lock(mChainsMutex); +#endif mPendingChains.push_back(info); return true; } @@ -438,6 +455,15 @@ void LLPumpIO::pump() static LLFastTimer::DeclareTimer FTM_PUMP_IO("Pump IO"); +LLPumpIO::current_chain_t LLPumpIO::removeRunningChain(LLPumpIO::current_chain_t& run_chain) +{ + std::for_each( + (*run_chain).mDescriptors.begin(), + (*run_chain).mDescriptors.end(), + ll_delete_apr_pollset_fd_client_data()); + return mRunningChains.erase(run_chain); +} + //timeout is in microseconds void LLPumpIO::pump(const S32& poll_timeout) { @@ -583,10 +609,16 @@ void LLPumpIO::pump(const S32& poll_timeout) // << (*run_chain).mChainLinks[0].mPipe // << " because we reached the end." << llendl; #endif - run_chain = mRunningChains.erase(run_chain); + run_chain = removeRunningChain(run_chain); continue; } } + else if(isChainExpired(*run_chain)) + { + run_chain = removeRunningChain(run_chain); + continue; + } + PUMP_DEBUG; if((*run_chain).mLock) { @@ -694,11 +726,7 @@ void LLPumpIO::pump(const S32& poll_timeout) PUMP_DEBUG; // This chain is done. Clean up any allocated memory and // erase the chain info. - std::for_each( - (*run_chain).mDescriptors.begin(), - (*run_chain).mDescriptors.end(), - ll_delete_apr_pollset_fd_client_data()); - run_chain = mRunningChains.erase(run_chain); + run_chain = removeRunningChain(run_chain); // *NOTE: may not always need to rebuild the pollset. mRebuildPollset = true; @@ -833,22 +861,6 @@ void LLPumpIO::initialize(void) apr_thread_mutex_create(&mCallbackMutex, APR_THREAD_MUTEX_UNNESTED, mPool()); #endif } -void LLPumpIO::cleanup() -{ - LLMemType m1(LLMemType::MTYPE_IO_PUMP); -#if LL_THREADS_APR - if (mChainsMutex) apr_thread_mutex_destroy(mChainsMutex); - if (mCallbackMutex) apr_thread_mutex_destroy(mCallbackMutex); -#endif - mChainsMutex = NULL; - mCallbackMutex = NULL; - if(mPollset) - { -// lldebugs << "cleaning up pollset" << llendl; - apr_pollset_destroy(mPollset); - mPollset = NULL; - } -} void LLPumpIO::rebuildPollset() { @@ -1083,10 +1095,30 @@ void LLPumpIO::processChain(LLChainInfo& chain) PUMP_DEBUG; } +bool LLPumpIO::isChainExpired(LLChainInfo& chain) +{ + if(!chain.mHasExpiration) + { + return false ; + } + + for(links_t::iterator iter = chain.mChainLinks.begin(); iter != chain.mChainLinks.end(); ++iter) + { + if(!(*iter).mPipe->hasNotExpired()) + { + return true ; + } + } + + return false ; +} + bool LLPumpIO::handleChainError( LLChainInfo& chain, LLIOPipe::EStatus error) { + DoutEntering(dc::notice, "LLPumpIO::handleChainError(" << (void*)&chain << ", " << LLIOPipe::lookupStatusString(error) << ")"); + LLMemType m1(LLMemType::MTYPE_IO_PUMP); links_t::reverse_iterator rit; if(chain.mHead == chain.mChainLinks.end()) @@ -1124,6 +1156,9 @@ bool LLPumpIO::handleChainError( #endif keep_going = false; break; + case LLIOPipe::STATUS_EXPIRED: + keep_going = false; + break ; default: if(LLIOPipe::isSuccess(error)) { @@ -1146,6 +1181,7 @@ LLPumpIO::LLChainInfo::LLChainInfo() : mInit(false), mLock(0), mEOS(false), + mHasExpiration(false), mDescriptorsPool(new LLAPRPool(LLThread::tldata().mRootPool)) { LLMemType m1(LLMemType::MTYPE_IO_PUMP); diff --git a/indra/llmessage/llpumpio.h b/indra/llmessage/llpumpio.h index 4e6d59b39..0d1387257 100644 --- a/indra/llmessage/llpumpio.h +++ b/indra/llmessage/llpumpio.h @@ -4,31 +4,25 @@ * @date 2004-11-19 * @brief Declaration of pump class which manages io chains. * - * $LicenseInfo:firstyear=2004&license=viewergpl$ - * - * Copyright (c) 2004-2009, Linden Research, Inc. - * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ * Second Life Viewer Source Code - * The source code in this file ("Source Code") is provided by Linden Lab - * to you under the terms of the GNU General Public License, version 2.0 - * ("GPL"), unless you have obtained a separate licensing agreement - * ("Other License"), formally executed by you and Linden Lab. Terms of - * the GPL can be found in doc/GPL-license.txt in this distribution, or - * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * Copyright (C) 2010, Linden Research, Inc. * - * There are special exceptions to the terms and conditions of the GPL as - * it is applied to this Source Code. View the full text of the exception - * in the file doc/FLOSS-exception.txt in this software distribution, or - * online at - * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * 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. * - * By copying, modifying or distributing this software, you acknowledge - * that you have read and understood your obligations described above, - * and agree to abide by those obligations. + * 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. * - * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO - * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, - * COMPLETENESS OR PERFORMANCE. + * 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$ */ @@ -351,12 +345,13 @@ protected: // basic member data bool mInit; + bool mEOS; + bool mHasExpiration; S32 mLock; LLFrameTimer mTimer; links_t::iterator mHead; links_t mChainLinks; - LLIOPipe::buffer_ptr_t mData; - bool mEOS; + LLIOPipe::buffer_ptr_t mData; LLSD mContext; // tracking inside the pump @@ -397,8 +392,8 @@ protected: protected: void initialize(); - void cleanup(); + current_chain_t removeRunningChain(current_chain_t& chain) ; /** * @brief Given the internal state of the chains, rebuild the pollset * @see setConditional() @@ -425,6 +420,9 @@ protected: */ bool handleChainError(LLChainInfo& chain, LLIOPipe::EStatus error); + //if the chain is expired, remove it + bool isChainExpired(LLChainInfo& chain) ; + public: /** * @brief Return number of running chains. diff --git a/indra/llmessage/llsdmessage.cpp b/indra/llmessage/llsdmessage.cpp index 1c93c12d9..67da908b7 100644 --- a/indra/llmessage/llsdmessage.cpp +++ b/indra/llmessage/llsdmessage.cpp @@ -151,11 +151,11 @@ bool LLSDMessage::ResponderAdapter::listener(const LLSD& payload, bool success) { if (success) { - mResponder->result(payload); + mResponder->pubResult(payload); } else { - mResponder->errorWithContent(payload["status"].asInteger(), payload["reason"], payload["content"]); + mResponder->pubErrorWithContent(payload["status"].asInteger(), payload["reason"], payload["content"]); } /*---------------- MUST BE LAST STATEMENT BEFORE RETURN ----------------*/ diff --git a/indra/llmessage/llsdrpcclient.cpp b/indra/llmessage/llsdrpcclient.cpp deleted file mode 100644 index dbc511f9f..000000000 --- a/indra/llmessage/llsdrpcclient.cpp +++ /dev/null @@ -1,255 +0,0 @@ -/** - * @file llsdrpcclient.cpp - * @author Phoenix - * @date 2005-11-05 - * @brief Implementation of the llsd client classes. - * - * $LicenseInfo:firstyear=2005&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 "linden_common.h" -#include "llsdrpcclient.h" - -#include "llbufferstream.h" -#include "llfasttimer.h" -#include "llfiltersd2xmlrpc.h" -#include "llmemtype.h" -#include "llpumpio.h" -#include "llsd.h" -#include "llsdserialize.h" -#include "llurlrequest.h" - -/** - * String constants - */ -static std::string LLSDRPC_RESPONSE_NAME("response"); -static std::string LLSDRPC_FAULT_NAME("fault"); - -/** - * LLSDRPCResponse - */ -LLSDRPCResponse::LLSDRPCResponse() : - mIsError(false), - mIsFault(false) -{ - LLMemType m1(LLMemType::MTYPE_IO_SD_CLIENT); -} - -// virtual -LLSDRPCResponse::~LLSDRPCResponse() -{ - LLMemType m1(LLMemType::MTYPE_IO_SD_CLIENT); -} - -bool LLSDRPCResponse::extractResponse(const LLSD& sd) -{ - LLMemType m1(LLMemType::MTYPE_IO_SD_CLIENT); - bool rv = true; - if(sd.has(LLSDRPC_RESPONSE_NAME)) - { - mReturnValue = sd[LLSDRPC_RESPONSE_NAME]; - mIsFault = false; - } - else if(sd.has(LLSDRPC_FAULT_NAME)) - { - mReturnValue = sd[LLSDRPC_FAULT_NAME]; - mIsFault = true; - } - else - { - mReturnValue.clear(); - mIsError = true; - rv = false; - } - return rv; -} - -static LLFastTimer::DeclareTimer FTM_SDRPC_RESPONSE("SDRPC Response"); - -// virtual -LLIOPipe::EStatus LLSDRPCResponse::process_impl( - const LLChannelDescriptors& channels, - buffer_ptr_t& buffer, - bool& eos, - LLSD& context, - LLPumpIO* pump) -{ - LLFastTimer t(FTM_SDRPC_RESPONSE); - PUMP_DEBUG; - LLMemType m1(LLMemType::MTYPE_IO_SD_CLIENT); - if(mIsError) - { - error(pump); - } - else if(mIsFault) - { - fault(pump); - } - else - { - response(pump); - } - PUMP_DEBUG; - return STATUS_DONE; -} - -/** - * LLSDRPCClient - */ - -LLSDRPCClient::LLSDRPCClient() : - mState(STATE_NONE), - mQueue(EPBQ_PROCESS) -{ - LLMemType m1(LLMemType::MTYPE_IO_SD_CLIENT); -} - -// virtual -LLSDRPCClient::~LLSDRPCClient() -{ - LLMemType m1(LLMemType::MTYPE_IO_SD_CLIENT); -} - -bool LLSDRPCClient::call( - const std::string& uri, - const std::string& method, - const LLSD& parameter, - LLSDRPCResponse* response, - EPassBackQueue queue) -{ - LLMemType m1(LLMemType::MTYPE_IO_SD_CLIENT); - //llinfos << "RPC: " << uri << "." << method << "(" << *parameter << ")" - // << llendl; - if(method.empty() || !response) - { - return false; - } - mState = STATE_READY; - mURI.assign(uri); - std::stringstream req; - req << LLSDRPC_REQUEST_HEADER_1 << method - << LLSDRPC_REQUEST_HEADER_2; - LLSDSerialize::toNotation(parameter, req); - req << LLSDRPC_REQUEST_FOOTER; - mRequest = req.str(); - mQueue = queue; - mResponse = response; - return true; -} - -bool LLSDRPCClient::call( - const std::string& uri, - const std::string& method, - const std::string& parameter, - LLSDRPCResponse* response, - EPassBackQueue queue) -{ - LLMemType m1(LLMemType::MTYPE_IO_SD_CLIENT); - //llinfos << "RPC: " << uri << "." << method << "(" << parameter << ")" - // << llendl; - if(method.empty() || parameter.empty() || !response) - { - return false; - } - mState = STATE_READY; - mURI.assign(uri); - std::stringstream req; - req << LLSDRPC_REQUEST_HEADER_1 << method - << LLSDRPC_REQUEST_HEADER_2 << parameter - << LLSDRPC_REQUEST_FOOTER; - mRequest = req.str(); - mQueue = queue; - mResponse = response; - return true; -} - -static LLFastTimer::DeclareTimer FTM_PROCESS_SDRPC_CLIENT("SDRPC Client"); - -// virtual -LLIOPipe::EStatus LLSDRPCClient::process_impl( - const LLChannelDescriptors& channels, - buffer_ptr_t& buffer, - bool& eos, - LLSD& context, - LLPumpIO* pump) -{ - LLFastTimer t(FTM_PROCESS_SDRPC_CLIENT); - PUMP_DEBUG; - LLMemType m1(LLMemType::MTYPE_IO_SD_CLIENT); - if((STATE_NONE == mState) || (!pump)) - { - // You should have called the call() method already. - return STATUS_PRECONDITION_NOT_MET; - } - EStatus rv = STATUS_DONE; - switch(mState) - { - case STATE_READY: - { - PUMP_DEBUG; -// lldebugs << "LLSDRPCClient::process_impl STATE_READY" << llendl; - buffer->append( - channels.out(), - (U8*)mRequest.c_str(), - mRequest.length()); - context[CONTEXT_DEST_URI_SD_LABEL] = mURI; - mState = STATE_WAITING_FOR_RESPONSE; - break; - } - case STATE_WAITING_FOR_RESPONSE: - { - PUMP_DEBUG; - // The input channel has the sd response in it. - //lldebugs << "LLSDRPCClient::process_impl STATE_WAITING_FOR_RESPONSE" - // << llendl; - LLBufferStream resp(channels, buffer.get()); - LLSD sd; - LLSDSerialize::fromNotation(sd, resp, buffer->count(channels.in())); - LLSDRPCResponse* response = (LLSDRPCResponse*)mResponse.get(); - if (!response) - { - mState = STATE_DONE; - break; - } - response->extractResponse(sd); - if(EPBQ_PROCESS == mQueue) - { - LLPumpIO::chain_t chain; - chain.push_back(mResponse); - pump->addChain(chain, DEFAULT_CHAIN_EXPIRY_SECS); - } - else - { - pump->respond(mResponse.get()); - } - mState = STATE_DONE; - break; - } - case STATE_DONE: - default: - PUMP_DEBUG; - llinfos << "invalid state to process" << llendl; - rv = STATUS_ERROR; - break; - } - return rv; -} diff --git a/indra/llmessage/llsdrpcclient.h b/indra/llmessage/llsdrpcclient.h deleted file mode 100644 index 0cecf4f68..000000000 --- a/indra/llmessage/llsdrpcclient.h +++ /dev/null @@ -1,323 +0,0 @@ -/** - * @file llsdrpcclient.h - * @author Phoenix - * @date 2005-11-05 - * @brief Implementation and helpers for structure data RPC clients. - * - * $LicenseInfo:firstyear=2005&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$ - */ - -#ifndef LL_LLSDRPCCLIENT_H -#define LL_LLSDRPCCLIENT_H - -/** - * This file declares classes to encapsulate a basic structured data - * remote procedure client. - */ - -#include "llchainio.h" -#include "llfiltersd2xmlrpc.h" -#include "lliopipe.h" -#include "llurlrequest.h" - -/** - * @class LLSDRPCClientResponse - * @brief Abstract base class to represent a response from an SD server. - * - * This is used as a base class for callbacks generated from an - * structured data remote procedure call. The - * extractResponse method will deal with the llsdrpc method - * call overhead, and keep track of what to call during the next call - * into process. If you use this as a base class, you - * need to implement response, fault, and - * error to do something useful. When in those methods, - * you can parse and utilize the mReturnValue member data. - */ -class LLSDRPCResponse : public LLIOPipe -{ -public: - LLSDRPCResponse(); - virtual ~LLSDRPCResponse(); - - /** - * @brief This method extracts the response out of the sd passed in - * - * Any appropriate data found in the sd passed in will be - * extracted and managed by this object - not copied or cloned. It - * will still be up to the caller to delete the pointer passed in. - * @param sd The raw structured data response from the remote server. - * @return Returns true if this was able to parse the structured data. - */ - bool extractResponse(const LLSD& sd); - -protected: - /** - * @brief Method called when the response is ready. - */ - virtual bool response(LLPumpIO* pump) = 0; - - /** - * @brief Method called when a fault is generated by the remote server. - */ - virtual bool fault(LLPumpIO* pump) = 0; - - /** - * @brief Method called when there was an error - */ - virtual bool error(LLPumpIO* pump) = 0; - -protected: - /* @name LLIOPipe virtual implementations - */ - //@{ - /** - * @brief Process the data in buffer - */ - virtual EStatus process_impl( - const LLChannelDescriptors& channels, - buffer_ptr_t& buffer, - bool& eos, - LLSD& context, - LLPumpIO* pump); - //@} - -protected: - LLSD mReturnValue; - bool mIsError; - bool mIsFault; -}; - -/** - * @class LLSDRPCClient - * @brief Client class for a structured data remote procedure call. - * - * This class helps deal with making structured data calls to a remote - * server. You can visualize the calls as: - * - * response = uri.method(parameter) - * - * where you pass in everything to call and this class - * takes care of the rest of the details. - * In typical usage, you will derive a class from this class and - * provide an API more useful for the specific application at - * hand. For example, if you were writing a service to send an instant - * message, you could create an API for it to send the messsage, and - * that class would do the work of translating it into the method and - * parameter, find the destination, and invoke call with - * a useful implementation of LLSDRPCResponse passed in to handle the - * response from the network. - */ -class LLSDRPCClient : public LLIOPipe -{ -public: - LLSDRPCClient(); - virtual ~LLSDRPCClient(); - - /** - * @brief Enumeration for tracking which queue to process the - * response. - */ - enum EPassBackQueue - { - EPBQ_PROCESS, - EPBQ_CALLBACK, - }; - - /** - * @brief Call a method on a remote LLSDRPCServer - * - * @param uri The remote object to call, eg, - * http://localhost/usher. If you are using a factory with a fixed - * url, the uri passed in will probably be ignored. - * @param method The method to call on the remote object - * @param parameter The parameter to pass into the remote - * object. It is up to the caller to delete the value passed in. - * @param response The object which gets the response. - * @param queue Specifies to call the response on the process or - * callback queue. - * @return Returns true if this object will be able to make the RPC call. - */ - bool call( - const std::string& uri, - const std::string& method, - const LLSD& parameter, - LLSDRPCResponse* response, - EPassBackQueue queue); - - /** - * @brief Call a method on a remote LLSDRPCServer - * - * @param uri The remote object to call, eg, - * http://localhost/usher. If you are using a factory with a fixed - * url, the uri passed in will probably be ignored. - * @param method The method to call on the remote object - * @param parameter The seriailized parameter to pass into the - * remote object. - * @param response The object which gets the response. - * @param queue Specifies to call the response on the process or - * callback queue. - * @return Returns true if this object will be able to make the RPC call. - */ - bool call( - const std::string& uri, - const std::string& method, - const std::string& parameter, - LLSDRPCResponse* response, - EPassBackQueue queue); - -protected: - /** - * @brief Enumeration for tracking client state. - */ - enum EState - { - STATE_NONE, - STATE_READY, - STATE_WAITING_FOR_RESPONSE, - STATE_DONE - }; - - /* @name LLIOPipe virtual implementations - */ - //@{ - /** - * @brief Process the data in buffer - */ - virtual EStatus process_impl( - const LLChannelDescriptors& channels, - buffer_ptr_t& buffer, - bool& eos, - LLSD& context, - LLPumpIO* pump); - //@} - -protected: - EState mState; - std::string mURI; - std::string mRequest; - EPassBackQueue mQueue; - LLIOPipe::ptr_t mResponse; -}; - -/** - * @class LLSDRPCClientFactory - * @brief Basic implementation for making an SD RPC client factory - * - * This class eases construction of a basic sd rpc client. Here is an - * example of it's use: - * - * class LLUsefulService : public LLService { ... } - * LLService::registerCreator( - * "useful", - * LLService::creator_t(new LLSDRPCClientFactory)) - * - */ -template -class LLSDRPCClientFactory : public LLChainIOFactory -{ -public: - LLSDRPCClientFactory() {} - LLSDRPCClientFactory(const std::string& fixed_url) : mURL(fixed_url) {} - virtual bool build(LLPumpIO::chain_t& chain, LLSD context) const - { - lldebugs << "LLSDRPCClientFactory::build" << llendl; - LLURLRequest* http(new LLURLRequest(LLURLRequest::HTTP_POST)); - if(!http->isValid()) - { - llwarns << "Creating LLURLRequest failed." << llendl ; - delete http; - return false; - } - - LLIOPipe::ptr_t service(new Client); - chain.push_back(service); - LLIOPipe::ptr_t http_pipe(http); - http->addHeader("Content-Type: text/llsd"); - if(mURL.empty()) - { - chain.push_back(LLIOPipe::ptr_t(new LLContextURLExtractor(http))); - } - else - { - http->setURL(mURL); - } - chain.push_back(http_pipe); - chain.push_back(service); - return true; - } -protected: - std::string mURL; -}; - -/** - * @class LLXMLSDRPCClientFactory - * @brief Basic implementation for making an XMLRPC to SD RPC client factory - * - * This class eases construction of a basic sd rpc client which uses - * xmlrpc as a serialization grammar. Here is an example of it's use: - * - * class LLUsefulService : public LLService { ... } - * LLService::registerCreator( - * "useful", - * LLService::creator_t(new LLXMLSDRPCClientFactory)) - * - */ -template -class LLXMLSDRPCClientFactory : public LLChainIOFactory -{ -public: - LLXMLSDRPCClientFactory() {} - LLXMLSDRPCClientFactory(const std::string& fixed_url) : mURL(fixed_url) {} - virtual bool build(LLPumpIO::chain_t& chain, LLSD context) const - { - lldebugs << "LLXMLSDRPCClientFactory::build" << llendl; - - LLURLRequest* http(new LLURLRequest(LLURLRequest::HTTP_POST)); - if(!http->isValid()) - { - llwarns << "Creating LLURLRequest failed." << llendl ; - delete http; - return false ; - } - LLIOPipe::ptr_t service(new Client); - chain.push_back(service); - LLIOPipe::ptr_t http_pipe(http); - http->addHeader("Content-Type: text/xml"); - if(mURL.empty()) - { - chain.push_back(LLIOPipe::ptr_t(new LLContextURLExtractor(http))); - } - else - { - http->setURL(mURL); - } - chain.push_back(LLIOPipe::ptr_t(new LLFilterSD2XMLRPCRequest(NULL))); - chain.push_back(http_pipe); - chain.push_back(LLIOPipe::ptr_t(new LLFilterXMLRPCResponse2LLSD)); - chain.push_back(service); - return true; - } -protected: - std::string mURL; -}; - -#endif // LL_LLSDRPCCLIENT_H diff --git a/indra/llmessage/llsdrpcserver.cpp b/indra/llmessage/llsdrpcserver.cpp deleted file mode 100644 index 6ee5bb508..000000000 --- a/indra/llmessage/llsdrpcserver.cpp +++ /dev/null @@ -1,347 +0,0 @@ -/** - * @file llsdrpcserver.cpp - * @author Phoenix - * @date 2005-10-11 - * @brief Implementation of the LLSDRPCServer and related classes. - * - * $LicenseInfo:firstyear=2005&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 "linden_common.h" -#include "llsdrpcserver.h" - -#include "llbuffer.h" -#include "llbufferstream.h" -#include "llfasttimer.h" -#include "llmemtype.h" -#include "llpumpio.h" -#include "llsdserialize.h" -#include "llstl.h" - -static const char FAULT_PART_1[] = "{'fault':{'code':i"; -static const char FAULT_PART_2[] = ", 'description':'"; -static const char FAULT_PART_3[] = "'}}"; - -static const char RESPONSE_PART_1[] = "{'response':"; -static const char RESPONSE_PART_2[] = "}"; - -static const S32 FAULT_GENERIC = 1000; -static const S32 FAULT_METHOD_NOT_FOUND = 1001; - -static const std::string LLSDRPC_METHOD_SD_NAME("method"); -static const std::string LLSDRPC_PARAMETER_SD_NAME("parameter"); - - -/** - * LLSDRPCServer - */ -LLSDRPCServer::LLSDRPCServer() : - mState(LLSDRPCServer::STATE_NONE), - mPump(NULL), - mLock(0) -{ - LLMemType m1(LLMemType::MTYPE_IO_SD_SERVER); -} - -LLSDRPCServer::~LLSDRPCServer() -{ - LLMemType m1(LLMemType::MTYPE_IO_SD_SERVER); - std::for_each( - mMethods.begin(), - mMethods.end(), - llcompose1( - DeletePointerFunctor(), - llselect2nd())); - std::for_each( - mCallbackMethods.begin(), - mCallbackMethods.end(), - llcompose1( - DeletePointerFunctor(), - llselect2nd())); -} - - -// virtual -ESDRPCSStatus LLSDRPCServer::deferredResponse( - const LLChannelDescriptors& channels, - LLBufferArray* data) { - // subclass should provide a sane implementation - return ESDRPCS_DONE; -} - -void LLSDRPCServer::clearLock() -{ - if(mLock && mPump) - { - mPump->clearLock(mLock); - mPump = NULL; - mLock = 0; - } -} - -static LLFastTimer::DeclareTimer FTM_PROCESS_SDRPC_SERVER("SDRPC Server"); - -// virtual -LLIOPipe::EStatus LLSDRPCServer::process_impl( - const LLChannelDescriptors& channels, - buffer_ptr_t& buffer, - bool& eos, - LLSD& context, - LLPumpIO* pump) -{ - LLFastTimer t(FTM_PROCESS_SDRPC_SERVER); - PUMP_DEBUG; - LLMemType m1(LLMemType::MTYPE_IO_SD_SERVER); -// lldebugs << "LLSDRPCServer::process_impl" << llendl; - // Once we have all the data, We need to read the sd on - // the the in channel, and respond on the out channel - if(!eos) return STATUS_BREAK; - if(!pump || !buffer) return STATUS_PRECONDITION_NOT_MET; - - std::string method_name; - LLIOPipe::EStatus status = STATUS_DONE; - - switch(mState) - { - case STATE_DEFERRED: - PUMP_DEBUG; - if(ESDRPCS_DONE != deferredResponse(channels, buffer.get())) - { - buildFault( - channels, - buffer.get(), - FAULT_GENERIC, - "deferred response failed."); - } - mState = STATE_DONE; - return STATUS_DONE; - - case STATE_DONE: -// lldebugs << "STATE_DONE" << llendl; - break; - case STATE_CALLBACK: -// lldebugs << "STATE_CALLBACK" << llendl; - PUMP_DEBUG; - method_name = mRequest[LLSDRPC_METHOD_SD_NAME].asString(); - if(!method_name.empty() && mRequest.has(LLSDRPC_PARAMETER_SD_NAME)) - { - if(ESDRPCS_DONE != callbackMethod( - method_name, - mRequest[LLSDRPC_PARAMETER_SD_NAME], - channels, - buffer.get())) - { - buildFault( - channels, - buffer.get(), - FAULT_GENERIC, - "Callback method call failed."); - } - } - else - { - // this should never happen, since we should not be in - // this state unless we originally found a method and - // params during the first call to process. - buildFault( - channels, - buffer.get(), - FAULT_GENERIC, - "Invalid LLSDRPC sever state - callback without method."); - } - pump->clearLock(mLock); - mLock = 0; - mState = STATE_DONE; - break; - case STATE_NONE: -// lldebugs << "STATE_NONE" << llendl; - default: - { - // First time we got here - process the SD request, and call - // the method. - PUMP_DEBUG; - LLBufferStream istr(channels, buffer.get()); - mRequest.clear(); - LLSDSerialize::fromNotation( - mRequest, - istr, - buffer->count(channels.in())); - - // { 'method':'...', 'parameter': ... } - method_name = mRequest[LLSDRPC_METHOD_SD_NAME].asString(); - if(!method_name.empty() && mRequest.has(LLSDRPC_PARAMETER_SD_NAME)) - { - ESDRPCSStatus rv = callMethod( - method_name, - mRequest[LLSDRPC_PARAMETER_SD_NAME], - channels, - buffer.get()); - switch(rv) - { - case ESDRPCS_DEFERRED: - mPump = pump; - mLock = pump->setLock(); - mState = STATE_DEFERRED; - status = STATUS_BREAK; - break; - - case ESDRPCS_CALLBACK: - { - mState = STATE_CALLBACK; - LLPumpIO::LLLinkInfo link; - link.mPipe = LLIOPipe::ptr_t(this); - link.mChannels = channels; - LLPumpIO::links_t links; - links.push_back(link); - pump->respond(links, buffer, context); - mLock = pump->setLock(); - status = STATUS_BREAK; - break; - } - case ESDRPCS_DONE: - mState = STATE_DONE; - break; - case ESDRPCS_ERROR: - default: - buildFault( - channels, - buffer.get(), - FAULT_GENERIC, - "Method call failed."); - break; - } - } - else - { - // send a fault - buildFault( - channels, - buffer.get(), - FAULT_GENERIC, - "Unable to find method and parameter in request."); - } - break; - } - } - - PUMP_DEBUG; - return status; -} - -// virtual -ESDRPCSStatus LLSDRPCServer::callMethod( - const std::string& method, - const LLSD& params, - const LLChannelDescriptors& channels, - LLBufferArray* response) -{ - LLMemType m1(LLMemType::MTYPE_IO_SD_SERVER); - // Try to find the method in the method table. - ESDRPCSStatus rv = ESDRPCS_DONE; - method_map_t::iterator it = mMethods.find(method); - if(it != mMethods.end()) - { - rv = (*it).second->call(params, channels, response); - } - else - { - it = mCallbackMethods.find(method); - if(it == mCallbackMethods.end()) - { - // method not found. - std::ostringstream message; - message << "rpc server unable to find method: " << method; - buildFault( - channels, - response, - FAULT_METHOD_NOT_FOUND, - message.str()); - } - else - { - // we found it in the callback methods - tell the process - // to coordinate calling on the pump callback. - return ESDRPCS_CALLBACK; - } - } - return rv; -} - -// virtual -ESDRPCSStatus LLSDRPCServer::callbackMethod( - const std::string& method, - const LLSD& params, - const LLChannelDescriptors& channels, - LLBufferArray* response) -{ - LLMemType m1(LLMemType::MTYPE_IO_SD_SERVER); - // Try to find the method in the callback method table. - ESDRPCSStatus rv = ESDRPCS_DONE; - method_map_t::iterator it = mCallbackMethods.find(method); - if(it != mCallbackMethods.end()) - { - rv = (*it).second->call(params, channels, response); - } - else - { - std::ostringstream message; - message << "pcserver unable to find callback method: " << method; - buildFault( - channels, - response, - FAULT_METHOD_NOT_FOUND, - message.str()); - } - return rv; -} - -// static -void LLSDRPCServer::buildFault( - const LLChannelDescriptors& channels, - LLBufferArray* data, - S32 code, - const std::string& msg) -{ - LLMemType m1(LLMemType::MTYPE_IO_SD_SERVER); - LLBufferStream ostr(channels, data); - ostr << FAULT_PART_1 << code << FAULT_PART_2 << msg << FAULT_PART_3; - llinfos << "LLSDRPCServer::buildFault: " << code << ", " << msg << llendl; -} - -// static -void LLSDRPCServer::buildResponse( - const LLChannelDescriptors& channels, - LLBufferArray* data, - const LLSD& response) -{ - LLMemType m1(LLMemType::MTYPE_IO_SD_SERVER); - LLBufferStream ostr(channels, data); - ostr << RESPONSE_PART_1; - LLSDSerialize::toNotation(response, ostr); - ostr << RESPONSE_PART_2; -#if LL_DEBUG - std::ostringstream debug_ostr; - debug_ostr << "LLSDRPCServer::buildResponse: "; - LLSDSerialize::toNotation(response, debug_ostr); - llinfos << debug_ostr.str() << llendl; -#endif -} diff --git a/indra/llmessage/llsdrpcserver.h b/indra/llmessage/llsdrpcserver.h deleted file mode 100644 index 9e56e4ea4..000000000 --- a/indra/llmessage/llsdrpcserver.h +++ /dev/null @@ -1,360 +0,0 @@ -/** - * @file llsdrpcserver.h - * @author Phoenix - * @date 2005-10-11 - * @brief Declaration of the structured data remote procedure call server. - * - * $LicenseInfo:firstyear=2005&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$ - */ - -#ifndef LL_LLSDRPCSERVER_H -#define LL_LLSDRPCSERVER_H - -/** - * I've set this up to be pretty easy to use when you want to make a - * structured data rpc server which responds to methods by - * name. Derive a class from the LLSDRPCServer, and during - * construction (or initialization if you have the luxury) map method - * names to pointers to member functions. This will look a lot like: - * - * - * class LLMessageAgents : public LLSDRPCServer {
- * public:
- * typedef LLSDRPCServer mem_fn_t;
- * LLMessageAgents() {
- * mMethods["message"] = new mem_fn_t(this, &LLMessageAgents::rpc_IM);
- * mMethods["alert"] = new mem_fn_t(this, &LLMessageAgents::rpc_Alrt);
- * }
- * protected:
- * rpc_IM(const LLSD& params, - * const LLChannelDescriptors& channels, - * LLBufferArray* data) - * {...}
- * rpc_Alert(const LLSD& params, - * const LLChannelDescriptors& channels, - * LLBufferArray* data) - * {...}
- * };
- *
- * - * The params are an array where each element in the array is a single - * parameter in the call. - * - * It is up to you to pack a valid serialized llsd response into the - * data object passed into the method, but you can use the helper - * methods below to help. - */ - -#include -#include "lliopipe.h" -#include "lliohttpserver.h" -#include "llfiltersd2xmlrpc.h" - -class LLSD; - -/** - * @brief Enumeration for specifying server method call status. This - * enumeration controls how the server class will manage the pump - * process/callback mechanism. - */ -enum ESDRPCSStatus -{ - // The call went ok, but the response is not yet ready. The - // method will arrange for the clearLock() call to be made at - // a later date, after which, once the chain is being pumped - // again, deferredResponse() will be called to gather the result - ESDRPCS_DEFERRED, - - // The LLSDRPCServer would like to handle the method on the - // callback queue of the pump. - ESDRPCS_CALLBACK, - - // The method call finished and generated output. - ESDRPCS_DONE, - - // Method failed for some unspecified reason - you should avoid - // this. A generic fault will be sent to the output. - ESDRPCS_ERROR, - - ESDRPCS_COUNT, -}; - -/** - * @class LLSDRPCMethodCallBase - * @brief Base class for calling a member function in an sd rpcserver - * implementation. - */ -class LLSDRPCMethodCallBase -{ -public: - LLSDRPCMethodCallBase() {} - virtual ~LLSDRPCMethodCallBase() {} - - virtual ESDRPCSStatus call( - const LLSD& params, - const LLChannelDescriptors& channels, - LLBufferArray* response) = 0; -protected: -}; - -/** - * @class LLSDRPCMethodCall - * @brief Class which implements member function calls. - */ -template -class LLSDRPCMethodCall : public LLSDRPCMethodCallBase -{ -public: - typedef ESDRPCSStatus (Server::*mem_fn)( - const LLSD& params, - const LLChannelDescriptors& channels, - LLBufferArray* data); - LLSDRPCMethodCall(Server* s, mem_fn fn) : - mServer(s), - mMemFn(fn) - { - } - virtual ~LLSDRPCMethodCall() {} - virtual ESDRPCSStatus call( - const LLSD& params, - const LLChannelDescriptors& channels, - LLBufferArray* data) - { - return (*mServer.*mMemFn)(params, channels, data); - } - -protected: - Server* mServer; - mem_fn mMemFn; - //bool (Server::*mMemFn)(const LLSD& params, LLBufferArray& data); -}; - - -/** - * @class LLSDRPCServer - * @brief Basic implementation of a structure data rpc server - * - * The rpc server is also designed to appropriately straddle the pump - * process() and callback() to specify which - * thread you want to work on when handling a method call. The - * mMethods methods are called from - * process(), while the mCallbackMethods are - * called when a pump is in a callback() cycle. - */ -class LLSDRPCServer : public LLIOPipe -{ -public: - LLSDRPCServer(); - virtual ~LLSDRPCServer(); - - /** - * enumeration for generic fault codes - */ - enum - { - FAULT_BAD_REQUEST = 2000, - FAULT_NO_RESPONSE = 2001, - }; - - /** - * @brief Call this method to return an rpc fault. - * - * @param channel The channel for output on the data buffer - * @param data buffer which will recieve the final output - * @param code The fault code - * @param msg The fault message - */ - static void buildFault( - const LLChannelDescriptors& channels, - LLBufferArray* data, - S32 code, - const std::string& msg); - - /** - * @brief Call this method to build an rpc response. - * - * @param channel The channel for output on the data buffer - * @param data buffer which will recieve the final output - * @param response The return value from the method call - */ - static void buildResponse( - const LLChannelDescriptors& channels, - LLBufferArray* data, - const LLSD& response); - -protected: - /* @name LLIOPipe virtual implementations - */ - //@{ - /** - * @brief Process the data in buffer - */ - virtual EStatus process_impl( - const LLChannelDescriptors& channels, - buffer_ptr_t& buffer, - bool& eos, - LLSD& context, - LLPumpIO* pump); - //@} - -protected: - - /** - * @brief Enumeration to track the state of the rpc server instance - */ - enum EState - { - STATE_NONE, - STATE_CALLBACK, - STATE_DEFERRED, - STATE_DONE - }; - - /** - * @brief This method is called when an http post comes in. - * - * The default behavior is to look at the method name, look up the - * method in the method table, and call it. If the method is not - * found, this function will build a fault response. You can - * implement your own version of this function if you want to hard - * wire some behavior or optimize things a bit. - * @param method The method name being called - * @param params The parameters - * @param channel The channel for output on the data buffer - * @param data The http data - * @return Returns the status of the method call, done/deferred/etc - */ - virtual ESDRPCSStatus callMethod( - const std::string& method, - const LLSD& params, - const LLChannelDescriptors& channels, - LLBufferArray* data); - - /** - * @brief This method is called when a pump callback is processed. - * - * The default behavior is to look at the method name, look up the - * method in the callback method table, and call it. If the method - * is not found, this function will build a fault response. You - * can implement your own version of this function if you want to - * hard wire some behavior or optimize things a bit. - * @param method The method name being called - * @param params The parameters - * @param channel The channel for output on the data buffer - * @param data The http data - * @return Returns the status of the method call, done/deferred/etc - */ - virtual ESDRPCSStatus callbackMethod( - const std::string& method, - const LLSD& params, - const LLChannelDescriptors& channels, - LLBufferArray* data); - - /** - * @brief Called after a deferred service is unlocked - * - * If a method returns ESDRPCS_DEFERRED, then the service chain - * will be locked and not processed until some other system calls - * clearLock() on the service instance again. At that point, - * once the pump starts processing the chain again, this method - * will be called so the service can output the final result - * into the buffers. - */ - virtual ESDRPCSStatus deferredResponse( - const LLChannelDescriptors& channels, - LLBufferArray* data); - - // donovan put this public here 7/27/06 -public: - /** - * @brief unlock a service that as ESDRPCS_DEFERRED - */ - void clearLock(); - -protected: - EState mState; - LLSD mRequest; - LLPumpIO* mPump; - S32 mLock; - typedef std::map method_map_t; - method_map_t mMethods; - method_map_t mCallbackMethods; -}; - -/** - * @name Helper Templates for making LLHTTPNodes - * - * These templates help in creating nodes for handing a service from - * either SDRPC or XMLRPC, given a single implementation of LLSDRPCServer. - * - * To use it: - * \code - * class LLUsefulServer : public LLSDRPCServer { ... } - * - * LLHTTPNode& root = LLCreateHTTPWireServer(...); - * root.addNode("llsdrpc/useful", new LLSDRPCNode); - * root.addNode("xmlrpc/useful", new LLXMLRPCNode); - * \endcode - */ -//@{ - -template -class LLSDRPCServerFactory : public LLChainIOFactory -{ -public: - virtual bool build(LLPumpIO::chain_t& chain, LLSD context) const - { - lldebugs << "LLXMLSDRPCServerFactory::build" << llendl; - chain.push_back(LLIOPipe::ptr_t(new Server)); - return true; - } -}; - -template -class LLSDRPCNode : public LLHTTPNodeForFactory< - LLSDRPCServerFactory > -{ -}; - -template -class LLXMLRPCServerFactory : public LLChainIOFactory -{ -public: - virtual bool build(LLPumpIO::chain_t& chain, LLSD context) const - { - lldebugs << "LLXMLSDRPCServerFactory::build" << llendl; - chain.push_back(LLIOPipe::ptr_t(new LLFilterXMLRPCRequest2LLSD)); - chain.push_back(LLIOPipe::ptr_t(new Server)); - chain.push_back(LLIOPipe::ptr_t(new LLFilterSD2XMLRPCResponse)); - return true; - } -}; - -template -class LLXMLRPCNode : public LLHTTPNodeForFactory< - LLXMLRPCServerFactory > -{ -}; - -//@} - -#endif // LL_LLSDRPCSERVER_H diff --git a/indra/llmessage/llurlrequest.cpp b/indra/llmessage/llurlrequest.cpp index 65967085f..b0f2fb047 100644 --- a/indra/llmessage/llurlrequest.cpp +++ b/indra/llmessage/llurlrequest.cpp @@ -29,6 +29,10 @@ #include "linden_common.h" #include "llurlrequest.h" +#ifdef CWDEBUG +#include +#endif + #include #include #include @@ -48,13 +52,10 @@ static const U32 HTTP_STATUS_PIPE_ERROR = 499; /** * String constants */ -const std::string CONTEXT_DEST_URI_SD_LABEL("dest_uri"); const std::string CONTEXT_TRANSFERED_BYTES("transfered_bytes"); -static size_t headerCallback(void* data, size_t size, size_t nmemb, void* user); - - +static size_t headerCallback(char* data, size_t size, size_t nmemb, void* user); /** * class LLURLRequestDetail @@ -65,33 +66,26 @@ public: LLURLRequestDetail(); ~LLURLRequestDetail(); std::string mURL; - LLCurlEasyRequest* mCurlRequest; - LLBufferArray* mResponseBuffer; + AICurlEasyRequest mCurlEasyRequest; + LLIOPipe::buffer_ptr_t mResponseBuffer; LLChannelDescriptors mChannels; U8* mLastRead; U32 mBodyLimit; S32 mByteAccumulator; bool mIsBodyLimitSet; - LLURLRequest::SSLCertVerifyCallback mSSLVerifyCallback; }; LLURLRequestDetail::LLURLRequestDetail() : - mCurlRequest(NULL), - mResponseBuffer(NULL), + mCurlEasyRequest(false), mLastRead(NULL), mBodyLimit(0), mByteAccumulator(0), mIsBodyLimitSet(false) { - LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST); - mCurlRequest = new LLCurlEasyRequest(); } LLURLRequestDetail::~LLURLRequestDetail() { - LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST); - delete mCurlRequest; - mResponseBuffer = NULL; mLastRead = NULL; } @@ -123,6 +117,7 @@ LLURLRequest::LLURLRequest(LLURLRequest::ERequestAction action) : mAction(action) { LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST); + // This might throw AICurlNoEasyHandle. initialize(); } @@ -132,6 +127,7 @@ LLURLRequest::LLURLRequest( mAction(action) { LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST); + // This might throw AICurlNoEasyHandle. initialize(); setURL(url); } @@ -139,13 +135,16 @@ LLURLRequest::LLURLRequest( LLURLRequest::~LLURLRequest() { LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST); + { + AICurlEasyRequest_wat curl_easy_request_w(*mDetail->mCurlEasyRequest); + curl_easy_request_w->revokeCallbacks(); + curl_easy_request_w->send_events_to(NULL); + } delete mDetail; - mDetail = NULL ; } void LLURLRequest::setURL(const std::string& url) { - LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST); mDetail->mURL = url; } @@ -153,19 +152,21 @@ std::string LLURLRequest::getURL() const { return mDetail->mURL; } + void LLURLRequest::addHeader(const char* header) { LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST); - mDetail->mCurlRequest->slist_append(header); + AICurlEasyRequest_wat curlEasyRequest_w(*mDetail->mCurlEasyRequest); + curlEasyRequest_w->addHeader(header); } void LLURLRequest::checkRootCertificate(bool check) { - mDetail->mCurlRequest->setopt(CURLOPT_SSL_VERIFYPEER, (check? TRUE : FALSE)); - mDetail->mCurlRequest->setoptString(CURLOPT_ENCODING, ""); + AICurlEasyRequest_wat curlEasyRequest_w(*mDetail->mCurlEasyRequest); + curlEasyRequest_w->setopt(CURLOPT_SSL_VERIFYPEER, check ? 1L : 0L); + curlEasyRequest_w->setoptString(CURLOPT_ENCODING, ""); } - void LLURLRequest::setBodyLimit(U32 size) { mDetail->mBodyLimit = size; @@ -176,7 +177,8 @@ void LLURLRequest::setCallback(LLURLRequestComplete* callback) { LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST); mCompletionCallback = callback; - mDetail->mCurlRequest->setHeaderCallback(&headerCallback, (void*)callback); + AICurlEasyRequest_wat curlEasyRequest_w(*mDetail->mCurlEasyRequest); + curlEasyRequest_w->setHeaderCallback(&headerCallback, (void*)callback); } // Added to mitigate the effect of libcurl looking @@ -210,26 +212,41 @@ void LLURLRequest::useProxy(bool use_proxy) } } - LL_DEBUGS("Proxy") << "use_proxy = " << (use_proxy?'Y':'N') << ", env_proxy = " << (!env_proxy.empty() ? env_proxy : "(null)") << LL_ENDL; + LL_DEBUGS("Proxy") << "use_proxy = " << (use_proxy?'Y':'N') << ", env_proxy = " << (!env_proxy.empty() ? env_proxy : "(null)") << LL_ENDL; - if (use_proxy && !env_proxy.empty()) - { - mDetail->mCurlRequest->setoptString(CURLOPT_PROXY, env_proxy); - } - else - { - mDetail->mCurlRequest->setoptString(CURLOPT_PROXY, ""); - } + AICurlEasyRequest_wat curlEasyRequest_w(*mDetail->mCurlEasyRequest); + curlEasyRequest_w->setoptString(CURLOPT_PROXY, (use_proxy && !env_proxy.empty()) ? env_proxy : std::string("")); } void LLURLRequest::useProxy(const std::string &proxy) { - mDetail->mCurlRequest->setoptString(CURLOPT_PROXY, proxy); + AICurlEasyRequest_wat curlEasyRequest_w(*mDetail->mCurlEasyRequest); + curlEasyRequest_w->setoptString(CURLOPT_PROXY, proxy); } void LLURLRequest::allowCookies() { - mDetail->mCurlRequest->setoptString(CURLOPT_COOKIEFILE, ""); + AICurlEasyRequest_wat curlEasyRequest_w(*mDetail->mCurlEasyRequest); + curlEasyRequest_w->setoptString(CURLOPT_COOKIEFILE, ""); +} + +//virtual +bool LLURLRequest::hasExpiration(void) const +{ + // Currently, this ALWAYS returns false -- because only AICurlEasyRequestStateMachine uses buffered + // AICurlEasyRequest objects, and LLURLRequest uses (unbuffered) AICurlEasyRequest directly, which + // have no expiration facility. + return mDetail->mCurlEasyRequest.isBuffered(); +} + +//virtual +bool LLURLRequest::hasNotExpired(void) const +{ + if (!mDetail->mCurlEasyRequest.isBuffered()) + return true; + AICurlEasyRequest_wat buffered_easy_request_w(*mDetail->mCurlEasyRequest); + AICurlResponderBuffer_wat buffer_w(*mDetail->mCurlEasyRequest); + return buffer_w->isValid(); } // virtual @@ -237,7 +254,27 @@ LLIOPipe::EStatus LLURLRequest::handleError( LLIOPipe::EStatus status, LLPumpIO* pump) { + DoutEntering(dc::curl, "LLURLRequest::handleError(" << LLIOPipe::lookupStatusString(status) << ", " << (void*)pump << ")"); LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST); + + if (LL_LIKELY(!mDetail->mCurlEasyRequest.isBuffered())) // Currently always true. + { + // The last reference will be deleted when the pump that this chain belongs to + // is removed from the running chains vector, upon returning from this function. + // This keeps the CurlEasyRequest object alive until the curl thread cleanly removed it. + Dout(dc::curl, "Calling mDetail->mCurlEasyRequest.removeRequest()"); + mDetail->mCurlEasyRequest.removeRequest(); + } + else if (!hasNotExpired()) + { + // The buffered version has it's own time out handling, and that already expired, + // so we can ignore the expiration of this timer (currently never happens). + // I left it here because it's what LL did (in the form if (!isValid() ...), + // and it would be relevant if this characteristic of mDetail->mCurlEasyRequest + // would change. --Aleric + return STATUS_EXPIRED ; + } + if(mCompletionCallback && pump) { LLURLRequestComplete* complete = NULL; @@ -252,9 +289,21 @@ LLIOPipe::EStatus LLURLRequest::handleError( return status; } +void LLURLRequest::added_to_multi_handle(AICurlEasyRequest_wat&) +{ +} + +void LLURLRequest::finished(AICurlEasyRequest_wat&) +{ +} + +void LLURLRequest::removed_from_multi_handle(AICurlEasyRequest_wat&) +{ + mRemoved = true; +} + static LLFastTimer::DeclareTimer FTM_PROCESS_URL_REQUEST("URL Request"); static LLFastTimer::DeclareTimer FTM_PROCESS_URL_REQUEST_GET_RESULT("Get Result"); -static LLFastTimer::DeclareTimer FTM_URL_PERFORM("Perform"); // virtual LLIOPipe::EStatus LLURLRequest::process_impl( @@ -269,9 +318,10 @@ LLIOPipe::EStatus LLURLRequest::process_impl( LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST); //llinfos << "LLURLRequest::process_impl()" << llendl; if (!buffer) return STATUS_ERROR; + if (!mDetail) return STATUS_ERROR; //Seems to happen on occasion. Need to hunt down why. - // we're still waiting or prcessing, check how many + // we're still waiting or processing, check how many // bytes we have accumulated. const S32 MIN_ACCUMULATION = 100000; if(pump && (mDetail->mByteAccumulator > MIN_ACCUMULATION)) @@ -309,44 +359,42 @@ LLIOPipe::EStatus LLURLRequest::process_impl( // *FIX: bit of a hack, but it should work. The configure and // callback method expect this information to be ready. - mDetail->mResponseBuffer = buffer.get(); + mDetail->mResponseBuffer = buffer; mDetail->mChannels = channels; if(!configure()) { return STATUS_ERROR; } + mRemoved = false; mState = STATE_WAITING_FOR_RESPONSE; + mDetail->mCurlEasyRequest.addRequest(); // Add easy handle to multi handle. - // *FIX: Maybe we should just go to the next state now... return STATUS_BREAK; } case STATE_WAITING_FOR_RESPONSE: case STATE_PROCESSING_RESPONSE: { - PUMP_DEBUG; - LLIOPipe::EStatus status = STATUS_BREAK; + if (!mRemoved) // Not removed from multi handle yet? { - LLFastTimer t(FTM_URL_PERFORM); - mDetail->mCurlRequest->perform(); + // Easy handle is still being processed. + return STATUS_BREAK; } + // Curl thread finished with this easy handle. + mState = STATE_CURL_FINISHED; + } + case STATE_CURL_FINISHED: + { + PUMP_DEBUG; + LLIOPipe::EStatus status = STATUS_NO_CONNECTION; // Catch-all failure code. - while(1) + // Left braces in order not to change indentation. { CURLcode result; - bool newmsg = false; - { LLFastTimer t(FTM_PROCESS_URL_REQUEST_GET_RESULT); - newmsg = mDetail->mCurlRequest->getResult(&result); - } - - if(!newmsg) - { - // keep processing - break; - } - + AICurlEasyRequest_wat(*mDetail->mCurlEasyRequest)->getResult(&result); + mState = STATE_HAVE_RESPONSE; context[CONTEXT_REQUEST][CONTEXT_TRANSFERED_BYTES] = mRequestTransferedBytes; context[CONTEXT_RESPONSE][CONTEXT_TRANSFERED_BYTES] = mResponseTransferedBytes; @@ -378,6 +426,7 @@ LLIOPipe::EStatus LLURLRequest::process_impl( } mCompletionCallback = NULL; } + status = STATUS_BREAK; // This is what the old code returned. Does it make sense? break; case CURLE_FAILED_INIT: case CURLE_COULDNT_CONNECT: @@ -419,10 +468,15 @@ void LLURLRequest::initialize() { LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST); mState = STATE_INITIALIZED; + // This might throw AICurlNoEasyHandle. mDetail = new LLURLRequestDetail; - mDetail->mCurlRequest->setopt(CURLOPT_NOSIGNAL, 1); - mDetail->mCurlRequest->setWriteCallback(&downCallback, (void*)this); - mDetail->mCurlRequest->setReadCallback(&upCallback, (void*)this); + + { + AICurlEasyRequest_wat curlEasyRequest_w(*mDetail->mCurlEasyRequest); + curlEasyRequest_w->setWriteCallback(&downCallback, (void*)this); + curlEasyRequest_w->setReadCallback(&upCallback, (void*)this); + } + mRequestTransferedBytes = 0; mResponseTransferedBytes = 0; } @@ -437,70 +491,66 @@ bool LLURLRequest::configure() S32 bytes = mDetail->mResponseBuffer->countAfter( mDetail->mChannels.in(), NULL); - switch(mAction) { - case HTTP_HEAD: - mDetail->mCurlRequest->setopt(CURLOPT_HEADER, 1); - mDetail->mCurlRequest->setopt(CURLOPT_NOBODY, 1); - mDetail->mCurlRequest->setopt(CURLOPT_FOLLOWLOCATION, 1); - rv = true; - break; - case HTTP_GET: - mDetail->mCurlRequest->setopt(CURLOPT_HTTPGET, 1); - mDetail->mCurlRequest->setopt(CURLOPT_FOLLOWLOCATION, 1); + AICurlEasyRequest_wat curlEasyRequest_w(*mDetail->mCurlEasyRequest); + switch(mAction) + { + case HTTP_HEAD: + curlEasyRequest_w->setopt(CURLOPT_HEADER, 1); + curlEasyRequest_w->setopt(CURLOPT_NOBODY, 1); + curlEasyRequest_w->setopt(CURLOPT_FOLLOWLOCATION, 1); + rv = true; + break; - // Set Accept-Encoding to allow response compression - mDetail->mCurlRequest->setoptString(CURLOPT_ENCODING, ""); - rv = true; - break; + case HTTP_GET: + curlEasyRequest_w->setopt(CURLOPT_HTTPGET, 1); + curlEasyRequest_w->setopt(CURLOPT_FOLLOWLOCATION, 1); - case HTTP_PUT: - // Disable the expect http 1.1 extension. POST and PUT default - // to turning this on, and I am not too sure what it means. - addHeader("Expect:"); + // Set Accept-Encoding to allow response compression + curlEasyRequest_w->setoptString(CURLOPT_ENCODING, ""); + rv = true; + break; - mDetail->mCurlRequest->setopt(CURLOPT_UPLOAD, 1); - mDetail->mCurlRequest->setopt(CURLOPT_INFILESIZE, bytes); - rv = true; - break; + case HTTP_PUT: + // Disable the expect http 1.1 extension. POST and PUT default + // to turning this on, and I am not too sure what it means. + curlEasyRequest_w->addHeader("Expect:"); + curlEasyRequest_w->setopt(CURLOPT_UPLOAD, 1); + curlEasyRequest_w->setopt(CURLOPT_INFILESIZE, bytes); + rv = true; + break; - case HTTP_POST: - // Disable the expect http 1.1 extension. POST and PUT default - // to turning this on, and I am not too sure what it means. - addHeader("Expect:"); + case HTTP_POST: + // Set the handle for an http post + curlEasyRequest_w->setPost(bytes); - // Disable the content type http header. - // *FIX: what should it be? - addHeader("Content-Type:"); + // Set Accept-Encoding to allow response compression + curlEasyRequest_w->setoptString(CURLOPT_ENCODING, ""); + rv = true; + break; - // Set the handle for an http post - mDetail->mCurlRequest->setPost(NULL, bytes); + case HTTP_DELETE: + // Set the handle for an http post + curlEasyRequest_w->setoptString(CURLOPT_CUSTOMREQUEST, "DELETE"); + rv = true; + break; - // Set Accept-Encoding to allow response compression - mDetail->mCurlRequest->setoptString(CURLOPT_ENCODING, ""); - rv = true; - break; + case HTTP_MOVE: + // Set the handle for an http post + curlEasyRequest_w->setoptString(CURLOPT_CUSTOMREQUEST, "MOVE"); + // *NOTE: should we check for the Destination header? + rv = true; + break; - case HTTP_DELETE: - // Set the handle for an http post - mDetail->mCurlRequest->setoptString(CURLOPT_CUSTOMREQUEST, "DELETE"); - rv = true; - break; - - case HTTP_MOVE: - // Set the handle for an http post - mDetail->mCurlRequest->setoptString(CURLOPT_CUSTOMREQUEST, "MOVE"); - // *NOTE: should we check for the Destination header? - rv = true; - break; - - default: - llwarns << "Unhandled URLRequest action: " << mAction << llendl; - break; - } - if(rv) - { - mDetail->mCurlRequest->sendRequest(mDetail->mURL); + default: + llwarns << "Unhandled URLRequest action: " << mAction << llendl; + break; + } + if(rv) + { + curlEasyRequest_w->finalizeRequest(mDetail->mURL); + curlEasyRequest_w->send_events_to(this); + } } return rv; } @@ -564,9 +614,8 @@ size_t LLURLRequest::upCallback( return bytes; } -static size_t headerCallback(void* data, size_t size, size_t nmemb, void* user) +static size_t headerCallback(char* header_line, size_t size, size_t nmemb, void* user) { - const char* header_line = (const char*)data; size_t header_len = size * nmemb; LLURLRequestComplete* complete = (LLURLRequestComplete*)user; @@ -632,42 +681,6 @@ static size_t headerCallback(void* data, size_t size, size_t nmemb, void* user) return header_len; } -static LLFastTimer::DeclareTimer FTM_PROCESS_URL_EXTRACTOR("URL Extractor"); -/** - * LLContextURLExtractor - */ -// virtual -LLIOPipe::EStatus LLContextURLExtractor::process_impl( - const LLChannelDescriptors& channels, - buffer_ptr_t& buffer, - bool& eos, - LLSD& context, - LLPumpIO* pump) -{ - LLFastTimer t(FTM_PROCESS_URL_EXTRACTOR); - PUMP_DEBUG; - LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST); - // The destination host is in the context. - if(context.isUndefined() || !mRequest) - { - return STATUS_PRECONDITION_NOT_MET; - } - - // copy in to out, since this just extract the URL and does not - // actually change the data. - LLChangeChannel change(channels.in(), channels.out()); - std::for_each(buffer->beginSegment(), buffer->endSegment(), change); - - // find the context url - if(context.has(CONTEXT_DEST_URI_SD_LABEL)) - { - mRequest->setURL(context[CONTEXT_DEST_URI_SD_LABEL].asString()); - return STATUS_DONE; - } - return STATUS_ERROR; -} - - /** * LLURLRequestComplete */ diff --git a/indra/llmessage/llurlrequest.h b/indra/llmessage/llurlrequest.h index 625f11afd..fdf7d873e 100644 --- a/indra/llmessage/llurlrequest.h +++ b/indra/llmessage/llurlrequest.h @@ -41,7 +41,6 @@ #include "llcurl.h" extern const std::string CONTEXT_REQUEST; -extern const std::string CONTEXT_DEST_URI_SD_LABEL; extern const std::string CONTEXT_RESPONSE; extern const std::string CONTEXT_TRANSFERED_BYTES; @@ -65,7 +64,7 @@ typedef struct x509_store_ctx_st X509_STORE_CTX; * worth the time and effort to eventually port this to a raw client * socket. */ -class LLURLRequest : public LLIOPipe +class LLURLRequest : public LLIOPipe, protected AICurlEasyHandleEvents { LOG_CLASS(LLURLRequest); public: @@ -188,6 +187,9 @@ public: */ void allowCookies(); + /*virtual*/ bool hasExpiration(void) const; + /*virtual*/ bool hasNotExpired(void) const; + public: /** * @brief Give this pipe a chance to handle a generated error @@ -212,6 +214,7 @@ protected: STATE_INITIALIZED, STATE_WAITING_FOR_RESPONSE, STATE_PROCESSING_RESPONSE, + STATE_CURL_FINISHED, STATE_HAVE_RESPONSE, }; EState mState; @@ -221,6 +224,14 @@ protected: S32 mRequestTransferedBytes; S32 mResponseTransferedBytes; + // mRemoved is used instead of changing mState directly, because I'm not convinced the latter is atomic. + // Set to false before adding curl request and then only tested. + // Reset in removed_from_multi_handle (by another thread), this is thread-safe. + bool mRemoved; + /*virtual*/ void added_to_multi_handle(AICurlEasyRequest_wat&); + /*virtual*/ void finished(AICurlEasyRequest_wat&); + /*virtual*/ void removed_from_multi_handle(AICurlEasyRequest_wat&); + private: /** * @brief Initialize the object. Called during construction. @@ -259,42 +270,6 @@ private: LLURLRequest(const LLURLRequest&); }; - -/** - * @class LLContextURLExtractor - * @brief This class unpacks the url out of a agent usher service so - * it can be packed into a LLURLRequest object. - * @see LLIOPipe - * - * This class assumes that the context is a map that contains an entry - * named CONTEXT_DEST_URI_SD_LABEL. - */ -class LLContextURLExtractor : public LLIOPipe -{ -public: - LLContextURLExtractor(LLURLRequest* req) : mRequest(req) {} - ~LLContextURLExtractor() {} - -protected: - /* @name LLIOPipe virtual implementations - */ - //@{ - /** - * @brief Process the data in buffer - */ - virtual EStatus process_impl( - const LLChannelDescriptors& channels, - buffer_ptr_t& buffer, - bool& eos, - LLSD& context, - LLPumpIO* pump); - //@} - -protected: - LLURLRequest* mRequest; -}; - - /** * @class LLURLRequestComplete * @brief Class which can optionally be used with an LLURLRequest to @@ -367,11 +342,4 @@ protected: EStatus mRequestStatus; }; - - -/** - * External constants - */ -extern const std::string CONTEXT_DEST_URI_SD_LABEL; - #endif // LL_LLURLREQUEST_H diff --git a/indra/llmessage/message.cpp b/indra/llmessage/message.cpp index e50bb604a..1fe40f6bc 100644 --- a/indra/llmessage/message.cpp +++ b/indra/llmessage/message.cpp @@ -81,6 +81,7 @@ #include "v4math.h" #include "lltransfertargetvfile.h" #include "llmemtype.h" +#include "llpacketring.h" // Constants //const char* MESSAGE_LOG_FILENAME = "message.log"; @@ -243,7 +244,8 @@ LLMessageSystem::LLMessageSystem(const std::string& filename, U32 port, bool failure_is_fatal, const F32 circuit_heartbeat_interval, const F32 circuit_timeout) : mCircuitInfo(circuit_heartbeat_interval, circuit_timeout), - mLastMessageFromTrustedMessageService(false) + mLastMessageFromTrustedMessageService(false), + mPacketRing(new LLPacketRing) { init(); @@ -383,6 +385,9 @@ LLMessageSystem::~LLMessageSystem() delete mPollInfop; mPollInfop = NULL; + delete mPacketRing; + mPacketRing = NULL; + mIncomingCompressedSize = 0; mCurrentRecvPacketID = 0; } @@ -548,13 +553,13 @@ BOOL LLMessageSystem::checkMessages( S64 frame_count ) U8* buffer = mTrueReceiveBuffer; - mTrueReceiveSize = mPacketRing.receivePacket(mSocket, (char *)mTrueReceiveBuffer); + mTrueReceiveSize = mPacketRing->receivePacket(mSocket, (char *)mTrueReceiveBuffer); // If you want to dump all received packets into SecondLife.log, uncomment this //dumpPacketToLog(); receive_size = mTrueReceiveSize; - mLastSender = mPacketRing.getLastSender(); - mLastReceivingIF = mPacketRing.getLastReceivingInterface(); + mLastSender = mPacketRing->getLastSender(); + mLastReceivingIF = mPacketRing->getLastReceivingInterface(); if (receive_size < (S32) LL_MINIMUM_VALID_PACKET_SIZE) { @@ -1129,7 +1134,7 @@ S32 LLMessageSystem::flushReliable(const LLHost &host) return send_bytes; } -LLHTTPClient::ResponderPtr LLMessageSystem::createResponder(const std::string& name) +LLFnPtrResponder* LLMessageSystem::createResponder(const std::string& name) { if(mSendReliable) { @@ -1328,7 +1333,7 @@ S32 LLMessageSystem::sendMessage(const LLHost &host) } BOOL success; - success = mPacketRing.sendPacket(mSocket, (char *)buf_ptr, buffer_length, host); + success = mPacketRing->sendPacket(mSocket, (char *)buf_ptr, buffer_length, host); if (!success) { @@ -3361,7 +3366,7 @@ void LLMessageSystem::establishBidirectionalTrust(const LLHost &host, S64 frame_ void LLMessageSystem::dumpPacketToLog() { - LL_WARNS("Messaging") << "Packet Dump from:" << mPacketRing.getLastSender() << llendl; + LL_WARNS("Messaging") << "Packet Dump from:" << mPacketRing->getLastSender() << llendl; LL_WARNS("Messaging") << "Packet Size:" << mTrueReceiveSize << llendl; char line_buffer[256]; /* Flawfinder: ignore */ S32 i; diff --git a/indra/llmessage/message.h b/indra/llmessage/message.h index 1589ea29c..dee687d47 100644 --- a/indra/llmessage/message.h +++ b/indra/llmessage/message.h @@ -48,9 +48,9 @@ #include "string_table.h" #include "llcircuit.h" #include "lltimer.h" -#include "llpacketring.h" +//#include "llpacketring.h" #include "llhost.h" -#include "llhttpclient.h" +//#include "llhttpclient.h" #include "llhttpnode.h" #include "llpacketack.h" #include "llsingleton.h" @@ -61,6 +61,12 @@ #include "llstoredmessage.h" +class LLPacketRing; +namespace +{ + class LLFnPtrResponder; +} + const U32 MESSAGE_MAX_STRINGS_LENGTH = 64; const U32 MESSAGE_NUMBER_OF_HASH_BUCKETS = 8192; @@ -213,7 +219,7 @@ class LLMessageSystem : public LLMessageSenderInterface LLHost mUntrustedInterface; public: - LLPacketRing mPacketRing; + LLPacketRing* mPacketRing; LLReliablePacketParams mReliablePacketParams; // Set this flag to TRUE when you want *very* verbose logs. @@ -494,7 +500,7 @@ public: void (*callback)(void **,S32), void ** callback_data); - LLHTTPClient::ResponderPtr createResponder(const std::string& name); + LLFnPtrResponder* createResponder(const std::string& name); S32 sendMessage(const LLHost &host); S32 sendMessage(const U32 circuit); private: diff --git a/indra/llplugin/llplugininstance.cpp b/indra/llplugin/llplugininstance.cpp index a53f67087..19d41f201 100644 --- a/indra/llplugin/llplugininstance.cpp +++ b/indra/llplugin/llplugininstance.cpp @@ -125,7 +125,7 @@ int LLPluginInstance::load(const std::string& plugin_dir, std::string &plugin_fi buf[0] = 0; if (error) { - strncpy(buf, dlerror(), sizeof(buf)); + strncpy(buf, error, sizeof(buf)); } buf[sizeof(buf) - 1] = 0; } diff --git a/indra/llplugin/llpluginprocessparent.cpp b/indra/llplugin/llpluginprocessparent.cpp index cdb8ec88a..c3fa0fb53 100644 --- a/indra/llplugin/llpluginprocessparent.cpp +++ b/indra/llplugin/llpluginprocessparent.cpp @@ -379,6 +379,7 @@ void LLPluginProcessParent::idle(void) } else { + // Set PluginAttachDebuggerToPlugins to TRUE to use this. You might also want to set DebugPluginDisableTimeout to TRUE. if(mDebug) { // If we're set to debug, start up a gdb instance in a new terminal window and have it attach to the plugin process and continue. @@ -400,14 +401,37 @@ void LLPluginProcessParent::idle(void) mDebugger.launch(); #elif LL_LINUX // The command we're constructing would look like this on the command line: - // /usr/bin/xterm -geometry 160x24-0+0 -e '/usr/bin/gdb -n /proc/12345/exe 12345' - // This can be changed by setting the following environment variables, for example: - // export LL_DEBUG_TERMINAL_COMMAND="/usr/bin/gnome-terminal --geometry=165x24-0+0 -e %s" + // /usr/bin/xterm -geometry 160x24-0+0 -e /usr/bin/gdb -n /proc/12345/exe 12345 + // Note that most terminals demand that all arguments to the process that is + // started with -e are passed as arguments to the terminal: there are no quotes + // around '/usr/bin/gdb -n /proc/12345/exe 12345'. This is the case for xterm, + // uxterm, konsole etc. The exception might be gnome-terminal. + // + // The constructed command can be changed by setting the following environment + // variables, for example: + // // export LL_DEBUG_GDB_PATH=/usr/bin/gdb + // export LL_DEBUG_TERMINAL_COMMAND='/usr/bin/gnome-terminal --geometry=165x24-0+0 -e "%s"' + // + // Or, as second example, if you are running the viewer on host 'A', and you want + // to open the gdb terminal on the X display of host 'B', you would run on host B: + // 'ssh -X A' (and then start the viewer, or just leave the terminal open), and + // then use: + // + // export LL_DEBUG_TERMINAL_COMMAND="/usr/bin/uxterm -fs 9 -fa 'DejaVu Sans Mono' -display localhost:10 -geometry 209x31+0-50 -e %s" + // + // which would open the terminal on B (no quotes around the %s, since this uses uxterm!). + // For a list of available strings to pass to the -fa, run in a terminal: fc-list :scalable=true:spacing=mono: family + char const* env; - std::string const terminal_command = (env = getenv("LL_DEBUG_TERMINAL_COMMAND")) ? env : "/usr/bin/xterm -geometry 160x24+0+0 -e %s"; + std::string terminal_command = (env = getenv("LL_DEBUG_TERMINAL_COMMAND")) ? env : "/usr/bin/xterm -geometry 160x24+0+0 -e %s"; char const* const gdb_path = (env = getenv("LL_DEBUG_GDB_PATH")) ? env : "/usr/bin/gdb"; cmd << gdb_path << " -n /proc/" << mProcess.getProcessID() << "/exe " << mProcess.getProcessID(); + std::string::size_type pos = terminal_command.find("%s"); + if (pos != std::string::npos) + { + terminal_command.replace(pos, 2, cmd.str()); + } typedef boost::tokenizer< boost::escaped_list_separator< char>, std::basic_string< @@ -429,14 +453,7 @@ void LLPluginProcessParent::idle(void) mDebugger.setExecutable(*token); while (++token != tokens.end()) { - if (*token == "%s") - { - mDebugger.addArgument(cmd.str()); - } - else - { - mDebugger.addArgument(*token); - } + mDebugger.addArgument(*token); } mDebugger.launch(); #endif diff --git a/indra/llwindow/llwindowwin32.cpp b/indra/llwindow/llwindowwin32.cpp index 70c6f771f..53ba137d0 100644 --- a/indra/llwindow/llwindowwin32.cpp +++ b/indra/llwindow/llwindowwin32.cpp @@ -1416,7 +1416,7 @@ BOOL LLWindowWin32::switchContext(BOOL fullscreen, const LLCoordScreen &size, BO { found_format = TRUE; } - else if(cur_format >= num_formats-1) + else if(cur_format >= (S32)num_formats-1) { cur_format = 0; found_format = TRUE; diff --git a/indra/llxml/llcontrol.h b/indra/llxml/llcontrol.h index 47df59d5e..6ce9745cc 100644 --- a/indra/llxml/llcontrol.h +++ b/indra/llxml/llcontrol.h @@ -42,6 +42,7 @@ #include "v4color.h" #include "v4coloru.h" #include "llinstancetracker.h" +#include "llrefcount.h" #include "llcontrolgroupreader.h" diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 494a3d3bb..b9449ed62 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -133,6 +133,7 @@ set(viewer_SOURCE_FILES llconfirmationmanager.cpp llconsole.cpp llcontainerview.cpp + llcurlrequest.cpp llcurrencyuimanager.cpp llcylinder.cpp lldaycyclemanager.cpp @@ -634,6 +635,7 @@ set(viewer_HEADER_FILES llconfirmationmanager.h llconsole.h llcontainerview.h + llcurlrequest.h llcurrencyuimanager.h llcylinder.h lldaycyclemanager.h diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 0687366e4..c0ec36c53 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -4227,17 +4227,6 @@ Value 120.0 - CurlUseMultipleThreads - - Comment - Use background threads for executing curl_multi_perform (requires restart) - Persist - 1 - Type - Boolean - Value - 1 - Cursor3D Comment diff --git a/indra/newview/hipporestrequest.cpp b/indra/newview/hipporestrequest.cpp index 8953dec87..12e112bcf 100644 --- a/indra/newview/hipporestrequest.cpp +++ b/indra/newview/hipporestrequest.cpp @@ -6,15 +6,18 @@ #ifndef CURL_STATICLIB #define CURL_STATICLIB 1 #endif -#include #include -#include -#include -#include -#include -#include +#include "llbufferstream.h" +#include "llerror.h" +#include "llhttpclient.h" +#include "llurlrequest.h" +#include "llxmltree.h" +#include +#ifdef DEBUG_CURLIO +#include "debug_libcurl.h" +#endif // ******************************************************************** @@ -255,7 +258,16 @@ static void request(const std::string &url, } LLPumpIO::chain_t chain; - LLURLRequest *req = new LLURLRequest(method, url); + LLURLRequest *req; + try + { + req = new LLURLRequest(method, url); + } + catch(AICurlNoEasyHandle const& error) + { + llwarns << "Failed to create LLURLRequest: " << error.what() << llendl; + return; + } req->checkRootCertificate(true); /* @@ -324,10 +336,11 @@ int HippoRestRequest::getBlocking(const std::string &url, std::string *result) char curlErrorBuffer[CURL_ERROR_SIZE]; CURL* curlp = curl_easy_init(); + llassert_always(curlp); curl_easy_setopt(curlp, CURLOPT_NOSIGNAL, 1); // don't use SIGALRM for timeouts curl_easy_setopt(curlp, CURLOPT_TIMEOUT, 5); // seconds - curl_easy_setopt(curlp, CURLOPT_CAINFO, gDirUtilp->getCAFile().c_str()); + curl_easy_setopt(curlp, CURLOPT_CAINFO, gDirUtilp->getCAFile().c_str()); curl_easy_setopt(curlp, CURLOPT_WRITEFUNCTION, curlWrite); curl_easy_setopt(curlp, CURLOPT_WRITEDATA, result); @@ -337,7 +350,7 @@ int HippoRestRequest::getBlocking(const std::string &url, std::string *result) *result = ""; S32 curlSuccess = curl_easy_perform(curlp); - S32 httpStatus = 499; + long httpStatus = 499L; // curl_easy_getinfo demands pointer to long. curl_easy_getinfo(curlp, CURLINFO_RESPONSE_CODE, &httpStatus); if (curlSuccess != 0) { diff --git a/indra/newview/linux_tools/wrapper.sh b/indra/newview/linux_tools/wrapper.sh index 04edf2a76..e406e3c71 100755 --- a/indra/newview/linux_tools/wrapper.sh +++ b/indra/newview/linux_tools/wrapper.sh @@ -139,15 +139,11 @@ fi export VIEWER_BINARY='singularity-do-not-run-directly' BINARY_TYPE=$(expr match "$(file -b bin/$VIEWER_BINARY)" '\(.*executable\)') -QPP=qt4/plugins/imageformats/ if [ "${BINARY_TYPE}" == "ELF 64-bit LSB executable" ]; then - QTPLUGINS=/usr/lib64/$QPP:/lib64/$QPP:/usr/local/lib64/$QPP - SL_ENV+='LD_LIBRARY_PATH="`pwd`/lib64:`pwd`/lib32:$QTPLUGINS:$LD_LIBRARY_PATH"' + SL_ENV+='LD_LIBRARY_PATH="`pwd`/lib64:`pwd`/lib32:$LD_LIBRARY_PATH"' else - QTPLUGINS=/usr/lib/$QPP:/lib/$QPP:/usr/local/lib/$QPP - SL_ENV+='LD_LIBRARY_PATH="`pwd`/lib:$QTPLUGINS:$LD_LIBRARY_PATH"' + SL_ENV+='LD_LIBRARY_PATH="`pwd`/lib:$LD_LIBRARY_PATH"' fi - export SL_CMD='$LL_WRAPPER bin/$VIEWER_BINARY' export SL_OPT="`cat gridargs.dat` $@" diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp index 9f85f680f..a0798dcd8 100644 --- a/indra/newview/llagent.cpp +++ b/indra/newview/llagent.cpp @@ -2401,7 +2401,7 @@ bool LLAgent::sendMaturityPreferenceToServer(int preferredMaturity) body["access_prefs"] = access_prefs; llinfos << "Sending access prefs update to " << (access_prefs["max"].asString()) << " via capability to: " << url << llendl; - LLHTTPClient::post(url, body, new LLHTTPClient::Responder()); // Ignore response + LLHTTPClient::post(url, body, new LLHTTPClient::ResponderIgnore); // Ignore response return true; } return false; diff --git a/indra/newview/llagentlanguage.cpp b/indra/newview/llagentlanguage.cpp index 04e1228c7..4dbc7eb70 100644 --- a/indra/newview/llagentlanguage.cpp +++ b/indra/newview/llagentlanguage.cpp @@ -61,7 +61,7 @@ bool LLAgentLanguage::update() body["language"] = language; body["language_is_public"] = gSavedSettings.getBOOL("LanguageIsPublic"); - LLHTTPClient::post(url, body, new LLHTTPClient::Responder); + LLHTTPClient::post(url, body, new LLHTTPClient::ResponderIgnore); } return true; } diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 73ebdac82..72d94258d 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -63,7 +63,6 @@ #include "llviewerjoystick.h" #include "llfloaterjoystick.h" #include "llares.h" -#include "llcurl.h" #include "llfloatersnapshot.h" #include "lltexturestats.h" #include "llviewerwindow.h" @@ -611,6 +610,9 @@ bool LLAppViewer::init() initLogging(); + // Curl must be initialized before any thread is running. + AICurlInterface::initCurl(&AIStateMachine::flush); + // Logging is initialized. Now it's safe to start the error thread. startErrorThread(); @@ -635,11 +637,6 @@ bool LLAppViewer::init() LLPrivateMemoryPoolManager::initClass((BOOL)gSavedSettings.getBOOL("MemoryPrivatePoolEnabled"), (U32)gSavedSettings.getU32("MemoryPrivatePoolSize")) ; mAlloc.setProfilingEnabled(gSavedSettings.getBOOL("MemProfiling")); - // *NOTE:Mani - LLCurl::initClass is not thread safe. - // Called before threads are created. - LLCurl::initClass(gSavedSettings.getF32("CurlRequestTimeOut")); - - LL_INFOS("InitInfo") << "LLCurl initialized." << LL_ENDL ; initThreads(); LL_INFOS("InitInfo") << "Threads initialized." << LL_ENDL ; @@ -1742,22 +1739,20 @@ bool LLAppViewer::cleanup() delete gVFS; gVFS = NULL; - // Cleanup settings last in case other clases reference them - gSavedSettings.cleanup(); - gColors.cleanup(); - gCrashSettings.cleanup(); - LLWatchdog::getInstance()->cleanup(); llinfos << "Shutting down message system" << llendflush; end_messaging_system(); llinfos << "Message system deleted." << llendflush; - LLUserAuth::getInstance()->reset(); //reset before LLCurl::cleanupClass, else LLCURL::sHandleMutex == NULL - // *NOTE:Mani - The following call is not thread safe. - LLCurl::cleanupClass(); - llinfos << "LLCurl cleaned up." << llendflush; + LLApp::stopErrorThread(); // The following call is not thread-safe. Have to stop all threads. + AICurlInterface::cleanupCurl(); + // Cleanup settings last in case other classes reference them. + gSavedSettings.cleanup(); + gColors.cleanup(); + gCrashSettings.cleanup(); + // If we're exiting to launch an URL, do that here so the screen // is at the right resolution before we launch IE. if (!gLaunchFileOnQuit.empty()) @@ -1825,6 +1820,8 @@ bool LLAppViewer::initThreads() LLWatchdog::getInstance()->init(watchdog_killer_callback); } + AICurlInterface::startCurlThread(); + LLImage::initClass(); LLVFSThread::initClass(enable_threads && false); diff --git a/indra/newview/llcurlrequest.cpp b/indra/newview/llcurlrequest.cpp new file mode 100644 index 000000000..4084b729f --- /dev/null +++ b/indra/newview/llcurlrequest.cpp @@ -0,0 +1,146 @@ +/** + * @file llcurlrequest.cpp + * @brief Implementation of Request. + * + * Copyright (c) 2012, Aleric Inglewood. + * Copyright (C) 2010, Linden Research, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution. + * + * CHANGELOG + * and additional copyright holders. + * + * 17/03/2012 + * Initial version, written by Aleric Inglewood @ SL + * + * 20/03/2012 + * Added copyright notice for Linden Lab for those parts that were + * copied or derived from llcurl.cpp. The code of those parts are + * already in their own llcurl.cpp, so they do not ever need to + * even look at this file; the reason I added the copyright notice + * is to make clear that I am not the author of 100% of this code + * and hence I cannot change the license of it. + */ + +#include "llviewerprecompiledheaders.h" + +#include "llsdserialize.h" +#include "llcurlrequest.h" +#include "llbuffer.h" +#include "llbufferstream.h" +#include "statemachine/aicurleasyrequeststatemachine.h" + +//----------------------------------------------------------------------------- +// class Request +// + +namespace AICurlInterface { + +bool Request::get(std::string const& url, ResponderPtr responder) +{ + return getByteRange(url, headers_t(), 0, -1, responder); +} + +bool Request::getByteRange(std::string const& url, headers_t const& headers, S32 offset, S32 length, ResponderPtr responder) +{ + DoutEntering(dc::curl, "Request::getByteRange(" << url << ", ...)"); + + // This might throw AICurlNoEasyHandle. + AICurlEasyRequestStateMachine* buffered_easy_request = new AICurlEasyRequestStateMachine(true); + + { + AICurlEasyRequest_wat buffered_easy_request_w(*buffered_easy_request->mCurlEasyRequest); + + AICurlResponderBuffer_wat(*buffered_easy_request->mCurlEasyRequest)->prepRequest(buffered_easy_request_w, headers, responder); + + buffered_easy_request_w->setopt(CURLOPT_HTTPGET, 1); + if (length > 0) + { + std::string range = llformat("Range: bytes=%d-%d", offset, offset + length - 1); + buffered_easy_request_w->addHeader(range.c_str()); + } + + buffered_easy_request_w->finalizeRequest(url); + } + + buffered_easy_request->run(); + + return true; // We throw in case of problems. +} + +bool Request::post(std::string const& url, headers_t const& headers, std::string const& data, ResponderPtr responder, S32 time_out) +{ + DoutEntering(dc::curl, "Request::post(" << url << ", ...)"); + + // This might throw AICurlNoEasyHandle. + AICurlEasyRequestStateMachine* buffered_easy_request = new AICurlEasyRequestStateMachine(true); + + { + AICurlEasyRequest_wat buffered_easy_request_w(*buffered_easy_request->mCurlEasyRequest); + AICurlResponderBuffer_wat buffer_w(*buffered_easy_request->mCurlEasyRequest); + + buffer_w->prepRequest(buffered_easy_request_w, headers, responder); + + U32 bytes = data.size(); + bool success = buffer_w->getInput()->append(buffer_w->sChannels.out(), (U8 const*)data.data(), bytes); + llassert_always(success); // AIFIXME: Maybe throw an error. + if (!success) + return false; + buffered_easy_request_w->setPost(bytes); + buffered_easy_request_w->addHeader("Content-Type: application/octet-stream"); + buffered_easy_request_w->finalizeRequest(url); + + lldebugs << "POSTING: " << bytes << " bytes." << llendl; + } + + buffered_easy_request->run(); + + return true; // We throw in case of problems. +} + +bool Request::post(std::string const& url, headers_t const& headers, LLSD const& data, ResponderPtr responder, S32 time_out) +{ + DoutEntering(dc::curl, "Request::post(" << url << ", ...)"); + + // This might throw AICurlNoEasyHandle. + AICurlEasyRequestStateMachine* buffered_easy_request = new AICurlEasyRequestStateMachine(true); + + { + AICurlEasyRequest_wat buffered_easy_request_w(*buffered_easy_request->mCurlEasyRequest); + AICurlResponderBuffer_wat buffer_w(*buffered_easy_request->mCurlEasyRequest); + + buffer_w->prepRequest(buffered_easy_request_w, headers, responder); + + LLBufferStream buffer_stream(buffer_w->sChannels, buffer_w->getInput().get()); + LLSDSerialize::toXML(data, buffer_stream); + S32 bytes = buffer_w->getInput()->countAfter(buffer_w->sChannels.out(), NULL); + buffered_easy_request_w->setPost(bytes); + buffered_easy_request_w->addHeader("Content-Type: application/llsd+xml"); + buffered_easy_request_w->finalizeRequest(url); + + lldebugs << "POSTING: " << bytes << " bytes." << llendl; + } + + buffered_easy_request->run(); + + return true; // We throw in case of problems. +} + +} // namespace AICurlInterface +//================================================================================== + diff --git a/indra/newview/llcurlrequest.h b/indra/newview/llcurlrequest.h new file mode 100644 index 000000000..d10c20b98 --- /dev/null +++ b/indra/newview/llcurlrequest.h @@ -0,0 +1,57 @@ +/** + * @file llcurlrequest.h + * @brief Declaration of class Request + * + * Copyright (c) 2012, Aleric Inglewood. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution. + * + * CHANGELOG + * and additional copyright holders. + * + * 17/03/2012 + * Initial version, written by Aleric Inglewood @ SL + */ + +#ifndef AICURLREQUEST_H +#define AICURLREQUEST_H + +#include +#include +#include + +// Things defined in this namespace are called from elsewhere in the viewer code. +namespace AICurlInterface { + +// Forward declaration. +class Responder; +typedef boost::intrusive_ptr ResponderPtr; + +class Request { + public: + typedef std::vector headers_t; + + bool get(std::string const& url, ResponderPtr responder); + bool getByteRange(std::string const& url, headers_t const& headers, S32 offset, S32 length, ResponderPtr responder); + bool post(std::string const& url, headers_t const& headers, std::string const& data, ResponderPtr responder, S32 time_out = 0); + bool post(std::string const& url, headers_t const& headers, LLSD const& data, ResponderPtr responder, S32 time_out = 0); +}; + +} // namespace AICurlInterface + +#endif diff --git a/indra/newview/llcurrencyuimanager.cpp b/indra/newview/llcurrencyuimanager.cpp index 6b72cc922..e31e9d114 100644 --- a/indra/newview/llcurrencyuimanager.cpp +++ b/indra/newview/llcurrencyuimanager.cpp @@ -266,7 +266,7 @@ bool LLCurrencyUIManager::Impl::checkTransaction() return false; } - if (!mTransaction->process()) + if (!mTransaction->is_finished()) { return false; } diff --git a/indra/newview/llfloaterbuyland.cpp b/indra/newview/llfloaterbuyland.cpp index 852b439fe..909111ee3 100644 --- a/indra/newview/llfloaterbuyland.cpp +++ b/indra/newview/llfloaterbuyland.cpp @@ -866,7 +866,7 @@ bool LLFloaterBuyLandUI::checkTransaction() return false; } - if (!mTransaction->process()) + if (!mTransaction->is_finished()) { return false; } diff --git a/indra/newview/llfloaterregioninfo.cpp b/indra/newview/llfloaterregioninfo.cpp index 7bf346769..8395f842c 100644 --- a/indra/newview/llfloaterregioninfo.cpp +++ b/indra/newview/llfloaterregioninfo.cpp @@ -762,7 +762,7 @@ BOOL LLPanelRegionGeneralInfo::sendUpdate() body["allow_parcel_changes"] = childGetValue("allow_parcel_changes_check"); body["block_parcel_search"] = childGetValue("block_parcel_search_check"); - LLHTTPClient::post(url, body, new LLHTTPClient::Responder()); + LLHTTPClient::post(url, body, new LLHTTPClient::ResponderIgnore); } else { diff --git a/indra/newview/llfloaterurlentry.cpp b/indra/newview/llfloaterurlentry.cpp index 35dc9ddda..c56e49206 100644 --- a/indra/newview/llfloaterurlentry.cpp +++ b/indra/newview/llfloaterurlentry.cpp @@ -43,6 +43,7 @@ #include "lluictrlfactory.h" #include "llwindow.h" #include "llviewerwindow.h" +#include "llhttpclient.h" static LLFloaterURLEntry* sInstance = NULL; diff --git a/indra/newview/lllogchat.cpp b/indra/newview/lllogchat.cpp index 574d70f4b..e80032239 100644 --- a/indra/newview/lllogchat.cpp +++ b/indra/newview/lllogchat.cpp @@ -99,7 +99,7 @@ std::string LLLogChat::timestamp(bool withdate) //static -void LLLogChat::saveHistory(std::string filename, std::string line) +void LLLogChat::saveHistory(std::string const& filename, std::string line) { if(!filename.size()) { @@ -120,7 +120,7 @@ void LLLogChat::saveHistory(std::string filename, std::string line) } } -void LLLogChat::loadHistory(std::string filename , void (*callback)(ELogLineType,std::string,void*), void* userdata) +void LLLogChat::loadHistory(std::string const& filename , void (*callback)(ELogLineType,std::string,void*), void* userdata) { if(!filename.size()) { diff --git a/indra/newview/lllogchat.h b/indra/newview/lllogchat.h index ad903b66f..84f6760ab 100644 --- a/indra/newview/lllogchat.h +++ b/indra/newview/lllogchat.h @@ -34,6 +34,8 @@ #ifndef LL_LLLOGCHAT_H #define LL_LLLOGCHAT_H +#include + class LLLogChat { public: @@ -44,9 +46,9 @@ public: LOG_END }; static std::string timestamp(bool withdate = false); - static std::string makeLogFileName(std::string(filename)); - static void saveHistory(std::string filename, std::string line); - static void loadHistory(std::string filename, + static std::string makeLogFileName(std::string filename); + static void saveHistory(std::string const& filename, std::string line); + static void loadHistory(std::string const& filename, void (*callback)(ELogLineType,std::string,void*), void* userdata); private: diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index 5367711ce..33439f10e 100644 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -35,7 +35,7 @@ #include "llappviewer.h" #include "llbufferstream.h" #include "llcallbacklist.h" -#include "llcurl.h" +#include "llcurlrequest.h" #include "lldatapacker.h" #include "llfasttimer.h" #if MESH_IMPORT @@ -465,7 +465,6 @@ public: LLMeshRepoThread::LLMeshRepoThread() : LLThread("mesh repo") { - mWaiting = false; mMutex = new LLMutex(); mHeaderMutex = new LLMutex(); mSignal = new LLCondition(); @@ -483,7 +482,7 @@ LLMeshRepoThread::~LLMeshRepoThread() void LLMeshRepoThread::run() { - mCurlRequest = new LLCurlRequest(); + mCurlRequest = new AICurlInterface::Request; #if MESH_IMPORT LLCDResult res = LLConvexDecomposition::initThread(); if (res != LLCD_OK) @@ -492,13 +491,10 @@ void LLMeshRepoThread::run() } #endif //MESH_IMPORT + mSignal->lock(); while (!LLApp::isQuitting()) { - mWaiting = true; - mSignal->wait(); - mWaiting = false; - - if (!LLApp::isQuitting()) + // Left braces in order not to change the indentation. { static U32 count = 0; @@ -511,38 +507,53 @@ void LLMeshRepoThread::run() } // NOTE: throttling intentionally favors LOD requests over header requests - + while (!mLODReqQ.empty() && count < MAX_MESH_REQUESTS_PER_SECOND && sActiveLODRequests < (S32)sMaxConcurrentRequests) { - if (mMutex) { mMutex->lock(); LODRequest req = mLODReqQ.front(); mLODReqQ.pop(); LLMeshRepository::sLODProcessing--; mMutex->unlock(); - if (!fetchMeshLOD(req.mMeshParams, req.mLOD, count))//failed, resubmit + try { + fetchMeshLOD(req.mMeshParams, req.mLOD, count); + } + catch(AICurlNoEasyHandle const& error) + { + llwarns << "fetchMeshLOD() failed: " << error.what() << llendl; mMutex->lock(); - mLODReqQ.push(req) ; + LLMeshRepository::sLODProcessing++; + mLODReqQ.push(req); mMutex->unlock(); + break; } } } while (!mHeaderReqQ.empty() && count < MAX_MESH_REQUESTS_PER_SECOND && sActiveHeaderRequests < (S32)sMaxConcurrentRequests) { - if (mMutex) { mMutex->lock(); HeaderRequest req = mHeaderReqQ.front(); mHeaderReqQ.pop(); mMutex->unlock(); - if (!fetchMeshHeader(req.mMeshParams, count))//failed, resubmit + bool success = false; + try + { + success = fetchMeshHeader(req.mMeshParams, count); + } + catch(AICurlNoEasyHandle const& error) + { + llwarns << "fetchMeshHeader() failed: " << error.what() << llendl; + } + if (!success) { mMutex->lock(); mHeaderReqQ.push(req) ; mMutex->unlock(); + break; } } } @@ -552,7 +563,16 @@ void LLMeshRepoThread::run() for (std::set::iterator iter = mSkinRequests.begin(); iter != mSkinRequests.end(); ++iter) { LLUUID mesh_id = *iter; - if (!fetchMeshSkinInfo(mesh_id)) + bool success = false; + try + { + success = fetchMeshSkinInfo(mesh_id); + } + catch(AICurlNoEasyHandle const& error) + { + llwarns << "fetchMeshSkinInfo(" << mesh_id << ") failed: " << error.what() << llendl; + } + if (!success) { incomplete.insert(mesh_id); } @@ -565,7 +585,16 @@ void LLMeshRepoThread::run() for (std::set::iterator iter = mDecompositionRequests.begin(); iter != mDecompositionRequests.end(); ++iter) { LLUUID mesh_id = *iter; - if (!fetchMeshDecomposition(mesh_id)) + bool success = false; + try + { + success = fetchMeshDecomposition(mesh_id); + } + catch(AICurlNoEasyHandle const& error) + { + llwarns << "fetchMeshDecomposition(" << mesh_id << ") failed: " << error.what() << llendl; + } + if (!success) { incomplete.insert(mesh_id); } @@ -578,7 +607,16 @@ void LLMeshRepoThread::run() for (std::set::iterator iter = mPhysicsShapeRequests.begin(); iter != mPhysicsShapeRequests.end(); ++iter) { LLUUID mesh_id = *iter; - if (!fetchMeshPhysicsShape(mesh_id)) + bool success = false; + try + { + success = fetchMeshPhysicsShape(mesh_id); + } + catch(AICurlNoEasyHandle const& error) + { + llwarns << "fetchMeshPhysicsShape(" << mesh_id << ") failed: " << error.what() << llendl; + } + if (!success) { incomplete.insert(mesh_id); } @@ -586,14 +624,11 @@ void LLMeshRepoThread::run() mPhysicsShapeRequests = incomplete; } - mCurlRequest->process(); } + + mSignal->wait(); } - - if (mSignal->isLocked()) - { //make sure to let go of the mutex associated with the given signal before shutting down - mSignal->unlock(); - } + mSignal->unlock(); #if MESH_IMPORT res = LLConvexDecomposition::quitThread(); @@ -645,7 +680,7 @@ void LLMeshRepoThread::loadMeshLOD(const LLVolumeParams& mesh_params, S32 lod) if (pending != mPendingLOD.end()) { //append this lod request to existing header request pending->second.push_back(lod); - llassert(pending->second.size() <= LLModel::NUM_LODS) + llassert(pending->second.size() <= LLModel::NUM_LODS); } else { //if no header request is pending, fetch header @@ -681,21 +716,15 @@ std::string LLMeshRepoThread::constructUrl(LLUUID mesh_id) bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id) { //protected by mMutex - - if (!mHeaderMutex) - { - return false; - } - mHeaderMutex->lock(); if (mMeshHeader.find(mesh_id) == mMeshHeader.end()) - { //we have no header info for this mesh, do nothing + { + // We have no header info for this mesh, try again later. mHeaderMutex->unlock(); return false; } - bool ret = true ; U32 header_size = mMeshHeaderSize[mesh_id]; if (header_size > 0) @@ -743,12 +772,10 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id) std::string http_url = constructUrl(mesh_id); if (!http_url.empty()) { - ret = mCurlRequest->getByteRange(http_url, headers, offset, size, - new LLMeshSkinInfoResponder(mesh_id, offset, size)); - if(ret) - { - LLMeshRepository::sHTTPRequestCount++; - } + // This might throw AICurlNoEasyHandle. + mCurlRequest->getByteRange(http_url, headers, offset, size, + new LLMeshSkinInfoResponder(mesh_id, offset, size)); + LLMeshRepository::sHTTPRequestCount++; } } } @@ -758,26 +785,22 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id) } //early out was not hit, effectively fetched - return ret; + return true; } +//return false if failed to get header bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id) { //protected by mMutex - if (!mHeaderMutex) - { - return false; - } - mHeaderMutex->lock(); if (mMeshHeader.find(mesh_id) == mMeshHeader.end()) - { //we have no header info for this mesh, do nothing + { + // We have no header info for this mesh, try again later. mHeaderMutex->unlock(); return false; } U32 header_size = mMeshHeaderSize[mesh_id]; - bool ret = true ; if (header_size > 0) { @@ -824,12 +847,10 @@ bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id) std::string http_url = constructUrl(mesh_id); if (!http_url.empty()) { - ret = mCurlRequest->getByteRange(http_url, headers, offset, size, - new LLMeshDecompositionResponder(mesh_id, offset, size)); - if(ret) - { - LLMeshRepository::sHTTPRequestCount++; - } + // This might throw AICurlNoEasyHandle. + mCurlRequest->getByteRange(http_url, headers, offset, size, + new LLMeshDecompositionResponder(mesh_id, offset, size)); + LLMeshRepository::sHTTPRequestCount++; } } } @@ -839,26 +860,22 @@ bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id) } //early out was not hit, effectively fetched - return ret; + return true; } +//return false if failed to get header bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id) { //protected by mMutex - if (!mHeaderMutex) - { - return false; - } - mHeaderMutex->lock(); if (mMeshHeader.find(mesh_id) == mMeshHeader.end()) - { //we have no header info for this mesh, do nothing + { + // We have no header info for this mesh, retry later. mHeaderMutex->unlock(); return false; } U32 header_size = mMeshHeaderSize[mesh_id]; - bool ret = true ; if (header_size > 0) { @@ -905,13 +922,10 @@ bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id) std::string http_url = constructUrl(mesh_id); if (!http_url.empty()) { - ret = mCurlRequest->getByteRange(http_url, headers, offset, size, - new LLMeshPhysicsShapeResponder(mesh_id, offset, size)); - - if(ret) - { - LLMeshRepository::sHTTPRequestCount++; - } + // This might throw AICurlNoEasyHandle. + mCurlRequest->getByteRange(http_url, headers, offset, size, + new LLMeshPhysicsShapeResponder(mesh_id, offset, size)); + LLMeshRepository::sHTTPRequestCount++; } } else @@ -925,7 +939,7 @@ bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id) } //early out was not hit, effectively fetched - return ret; + return true; } //return false if failed to get header @@ -944,14 +958,14 @@ bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params, U32& c LLMeshRepository::sCacheBytesRead += bytes; file.read(buffer, bytes); if (headerReceived(mesh_params, buffer, bytes)) - { //did not do an HTTP request, return false + { + // Already have header, no need to retry. return true; } } } //either cache entry doesn't exist or is corrupt, request header from simulator - bool retval = true ; std::vector headers; headers.push_back("Accept: application/octet-stream"); @@ -961,29 +975,19 @@ bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params, U32& c //grab first 4KB if we're going to bother with a fetch. Cache will prevent future fetches if a full mesh fits //within the first 4KB //NOTE -- this will break of headers ever exceed 4KB - retval = mCurlRequest->getByteRange(http_url, headers, 0, 4096, new LLMeshHeaderResponder(mesh_params)); - if(retval) - { - LLMeshRepository::sHTTPRequestCount++; - } + // This might throw AICurlNoEasyHandle. + mCurlRequest->getByteRange(http_url, headers, 0, 4096, new LLMeshHeaderResponder(mesh_params)); + LLMeshRepository::sHTTPRequestCount++; count++; } - return retval; + return true; } -//return false if failed to get mesh lod. -bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, U32& count) +void LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, U32& count) { //protected by mMutex - if (!mHeaderMutex) - { - return false; - } - mHeaderMutex->lock(); - bool retval = true; - LLUUID mesh_id = mesh_params.getSculptID(); U32 header_size = mMeshHeaderSize[mesh_id]; @@ -1019,7 +1023,7 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, if (lodReceived(mesh_params, lod, buffer, size)) { delete[] buffer; - return true; + return; } } @@ -1033,13 +1037,10 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, std::string http_url = constructUrl(mesh_id); if (!http_url.empty()) { - retval = mCurlRequest->getByteRange(constructUrl(mesh_id), headers, offset, size, + // This might throw AICurlNoEasyHandle. + mCurlRequest->getByteRange(constructUrl(mesh_id), headers, offset, size, new LLMeshLODResponder(mesh_params, lod, offset, size)); - - if(retval) - { - LLMeshRepository::sHTTPRequestCount++; - } + LLMeshRepository::sHTTPRequestCount++; count++; } else @@ -1056,8 +1057,6 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, { mHeaderMutex->unlock(); } - - return retval; } bool LLMeshRepoThread::headerReceived(const LLVolumeParams& mesh_params, U8* data, S32 data_size) @@ -1102,7 +1101,7 @@ bool LLMeshRepoThread::headerReceived(const LLVolumeParams& mesh_params, U8* dat LLMutexLock lock(mHeaderMutex); mMeshHeaderSize[mesh_id] = header_size; mMeshHeader[mesh_id] = header; - } + } LLMutexLock lock(mMutex); // FIRE-7182, make sure only one thread access mPendingLOD at the same time. @@ -1604,7 +1603,7 @@ void LLMeshUploadThread::generateHulls() void LLMeshUploadThread::doWholeModelUpload() { - mCurlRequest = new LLCurlRequest(); + mCurlRequest = new AICurlInterface::Request(); if (mWholeModelUploadURL.empty()) { @@ -1619,11 +1618,14 @@ void LLMeshUploadThread::doWholeModelUpload() LLSD body = full_model_data["asset_resources"]; dump_llsd_to_file(body,make_dump_name("whole_model_body_",dump_num)); LLCurlRequest::headers_t headers; + // This might throw AICurlNoEasyHandle. mCurlRequest->post(mWholeModelUploadURL, headers, body, new LLWholeModelUploadResponder(this, full_model_data, mUploadObserverHandle), mMeshUploadTimeOut); do { - mCurlRequest->process(); + mCurlRequest->process(); // FIXME: This function does not exist anymore. The post() gets CPU time from AICurlEasyRequestStateMachine. + // Therefore, if we do not want to continue here unless this upload is done... no wait, that would + // be blocking and we don't want blocking... //sleep for 10ms to prevent eating a whole core apr_sleep(10000); } while (mCurlRequest->getQueued() > 0); @@ -1640,7 +1642,7 @@ void LLMeshUploadThread::requestWholeModelFee() { dump_num++; - mCurlRequest = new LLCurlRequest(); + mCurlRequest = new AICurlInterface::Request; generateHulls(); @@ -1650,6 +1652,7 @@ void LLMeshUploadThread::requestWholeModelFee() mPendingUploads++; LLCurlRequest::headers_t headers; + // This might throw AICurlNoEasyHandle. mCurlRequest->post(mWholeModelFeeCapability, headers, model_data, new LLWholeModelFeeResponder(this,model_data, mFeeObserverHandle), mMeshUploadTimeOut); @@ -1670,11 +1673,6 @@ void LLMeshUploadThread::requestWholeModelFee() void LLMeshRepoThread::notifyLoadedMeshes() {//called via gMeshRepo.notifyLoadedMeshes(). mMutex already locked - if (!mMutex) - { - return; - } - while (!mLoadedQ.empty()) { mMutex->lock(); @@ -2378,17 +2376,17 @@ void LLMeshRepository::notifyLoadedMeshes() mInventoryQ.pop(); } - } - #endif //MESH_IMPORT //call completed callbacks on finished decompositions mDecompThread->notifyCompleted(); - if (!mThread->mWaiting) - { //curl thread is churning, wait for it to go idle + if (!mThread->mSignal->tryLock()) + { + // Curl thread is churning, wait for it to go idle. return; } + mThread->mSignal->unlock(); static std::string region_name("never name a region this"); @@ -3334,6 +3332,7 @@ void LLPhysicsDecomp::run() mStageID[stages[i].mName] = i; } + mSignal->lock(); while (!mQuitting) { mSignal->wait(); @@ -3362,14 +3361,10 @@ void LLPhysicsDecomp::run() } } } + mSignal->unlock(); decomp->quitThread(); - if (mSignal->isLocked()) - { //let go of mSignal's associated mutex - mSignal->unlock(); - } - mDone = true; #endif //MESH_IMPORT } diff --git a/indra/newview/llmeshrepository.h b/indra/newview/llmeshrepository.h index c106f810f..5d225cfe4 100644 --- a/indra/newview/llmeshrepository.h +++ b/indra/newview/llmeshrepository.h @@ -32,6 +32,7 @@ #include "lluuid.h" #include "llviewertexture.h" #include "llvolume.h" +#include "llcurlrequest.h" #if MESH_IMPORT #define LLCONVEXDECOMPINTER_STATIC 1 @@ -67,7 +68,6 @@ struct LLCDHull class LLVOVolume; class LLMeshResponder; -class LLCurlRequest; class LLMutex; class LLCondition; class LLVFS; @@ -246,13 +246,11 @@ public: static S32 sActiveLODRequests; static U32 sMaxConcurrentRequests; - LLCurlRequest* mCurlRequest; + AICurlInterface::Request* mCurlRequest; LLMutex* mMutex; LLMutex* mHeaderMutex; LLCondition* mSignal; - bool mWaiting; - //map of known mesh headers typedef std::map mesh_header_map; mesh_header_map mMeshHeader; @@ -351,7 +349,7 @@ public: void loadMeshLOD(const LLVolumeParams& mesh_params, S32 lod); bool fetchMeshHeader(const LLVolumeParams& mesh_params, U32& count); - bool fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, U32& count); + void fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, U32& count); bool headerReceived(const LLVolumeParams& mesh_params, U8* data, S32 data_size); bool lodReceived(const LLVolumeParams& mesh_params, S32 lod, U8* data, S32 data_size); bool skinInfoReceived(const LLUUID& mesh_id, U8* data, S32 data_size); diff --git a/indra/newview/llpanelclassified.cpp b/indra/newview/llpanelclassified.cpp index 97c162cd8..1d3e50360 100644 --- a/indra/newview/llpanelclassified.cpp +++ b/indra/newview/llpanelclassified.cpp @@ -999,7 +999,7 @@ void LLPanelClassified::sendClassifiedClickMessage(const std::string& type) std::string url = gAgent.getRegion()->getCapability("SearchStatTracking"); llinfos << "LLPanelClassified::sendClassifiedClickMessage via capability" << llendl; - LLHTTPClient::post(url, body, new LLHTTPClient::Responder()); + LLHTTPClient::post(url, body, new LLHTTPClient::ResponderIgnore); } //////////////////////////////////////////////////////////////////////////////////////////// diff --git a/indra/newview/llpanellogin.cpp b/indra/newview/llpanellogin.cpp index 045b722a4..3483f2577 100644 --- a/indra/newview/llpanellogin.cpp +++ b/indra/newview/llpanellogin.cpp @@ -49,7 +49,6 @@ #include "llcheckboxctrl.h" #include "llcommandhandler.h" // for secondlife:///app/login/ #include "llcombobox.h" -#include "llcurl.h" #include "llviewercontrol.h" #include "llfloaterabout.h" #include "llfloatertest.h" diff --git a/indra/newview/llspatialpartition.cpp b/indra/newview/llspatialpartition.cpp index 20c390931..d081f51de 100644 --- a/indra/newview/llspatialpartition.cpp +++ b/indra/newview/llspatialpartition.cpp @@ -1998,7 +1998,7 @@ public: virtual void processGroup(LLSpatialGroup* group) { - llassert(!group->isState(LLSpatialGroup::DIRTY) && !group->isEmpty()) + llassert(!group->isState(LLSpatialGroup::DIRTY) && !group->isEmpty()); if (mRes < 2) { diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index 596d8d154..d0a5a6364 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -218,6 +218,7 @@ #include "llfloaterblacklist.h" #include "scriptcounter.h" #include "shfloatermediaticker.h" +#include "llpacketring.h" // #include "llpathfindingmanager.h" @@ -256,7 +257,9 @@ extern S32 gStartImageHeight; // local globals // - +#if defined(CWDEBUG) || defined(DEBUG_CURLIO) +static bool gCurlIo; +#endif static LLHost gAgentSimHost; static BOOL gSkipOptionalUpdate = FALSE; @@ -628,21 +631,21 @@ bool idle_startup() F32 dropPercent = gSavedSettings.getF32("PacketDropPercentage"); - msg->mPacketRing.setDropPercentage(dropPercent); + msg->mPacketRing->setDropPercentage(dropPercent); F32 inBandwidth = gSavedSettings.getF32("InBandwidth"); F32 outBandwidth = gSavedSettings.getF32("OutBandwidth"); if (inBandwidth != 0.f) { LL_DEBUGS("AppInit") << "Setting packetring incoming bandwidth to " << inBandwidth << LL_ENDL; - msg->mPacketRing.setUseInThrottle(TRUE); - msg->mPacketRing.setInBandwidth(inBandwidth); + msg->mPacketRing->setUseInThrottle(TRUE); + msg->mPacketRing->setInBandwidth(inBandwidth); } if (outBandwidth != 0.f) { LL_DEBUGS("AppInit") << "Setting packetring outgoing bandwidth to " << outBandwidth << LL_ENDL; - msg->mPacketRing.setUseOutThrottle(TRUE); - msg->mPacketRing.setOutBandwidth(outBandwidth); + msg->mPacketRing->setUseOutThrottle(TRUE); + msg->mPacketRing->setOutBandwidth(outBandwidth); } } @@ -1348,6 +1351,9 @@ bool idle_startup() llinfos << "Authenticating with " << grid_uri << llendl; + // Always write curl I/O debug info for the login attempt. + Debug(gCurlIo = dc::curl.is_on() && !dc::curlio.is_on(); if (gCurlIo) dc::curlio.on()); + // TODO if statement here to use web_login_key // OGPX : which routine would this end up in? the LLSD or XMLRPC, or ....? LLUserAuth::getInstance()->authenticate( @@ -1422,6 +1428,7 @@ bool idle_startup() LL_DEBUGS("AppInit") << "downloading..." << LL_ENDL; return FALSE; } + Debug(if (gCurlIo) dc::curlio.off()); // Login succeeded: restore dc::curlio to original state. LLStartUp::setStartupState( STATE_LOGIN_PROCESS_RESPONSE ); progress += 0.01f; set_startup_status(progress, LLTrans::getString("LoginProcessingResponse"), auth_message); @@ -3607,6 +3614,7 @@ std::string LLStartUp::startupStateToString(EStartupState state) #define RTNENUM(E) case E: return #E switch(state){ RTNENUM( STATE_FIRST ); + RTNENUM( STATE_BROWSER_INIT ); RTNENUM( STATE_LOGIN_SHOW ); RTNENUM( STATE_LOGIN_WAIT ); RTNENUM( STATE_LOGIN_CLEANUP ); @@ -3614,10 +3622,13 @@ std::string LLStartUp::startupStateToString(EStartupState state) RTNENUM( STATE_UPDATE_CHECK ); RTNENUM( STATE_LOGIN_AUTH_INIT ); RTNENUM( STATE_LOGIN_AUTHENTICATE ); + RTNENUM( STATE_WAIT_LEGACY_LOGIN ); + RTNENUM( STATE_XMLRPC_LEGACY_LOGIN ); RTNENUM( STATE_LOGIN_NO_DATA_YET ); RTNENUM( STATE_LOGIN_DOWNLOADING ); RTNENUM( STATE_LOGIN_PROCESS_RESPONSE ); RTNENUM( STATE_WORLD_INIT ); + RTNENUM( STATE_MULTIMEDIA_INIT ); RTNENUM( STATE_FONT_INIT ); RTNENUM( STATE_SEED_GRANTED_WAIT ); RTNENUM( STATE_SEED_CAP_GRANTED ); @@ -3630,10 +3641,10 @@ std::string LLStartUp::startupStateToString(EStartupState state) RTNENUM( STATE_WEARABLES_WAIT ); RTNENUM( STATE_CLEANUP ); RTNENUM( STATE_STARTED ); - default: - return llformat("(state #%d)", state); } #undef RTNENUM + // Never reached. + return llformat("(state #%d)", state); } diff --git a/indra/newview/lltexturefetch.cpp b/indra/newview/lltexturefetch.cpp index f507d22ba..2a4abcf1b 100644 --- a/indra/newview/lltexturefetch.cpp +++ b/indra/newview/lltexturefetch.cpp @@ -779,7 +779,7 @@ LLTextureFetchWorker::LLTextureFetchWorker(LLTextureFetch* fetcher, calcWorkPriority(); mType = host.isOk() ? LLImageBase::TYPE_AVATAR_BAKE : LLImageBase::TYPE_NORMAL; -// llinfos << "Create: " << mID << " mHost:" << host << " Discard=" << discard << llendl; + llinfos << "Create: " << mID << " mHost:" << host << " Discard=" << discard << " URL:"<< mUrl << llendl; if (!mFetcher->mDebugPause) { U32 work_priority = mWorkPriority | LLWorkerThread::PRIORITY_HIGH; @@ -1320,8 +1320,15 @@ bool LLTextureFetchWorker::doWork(S32 param) // Will call callbackHttpGet when curl request completes std::vector headers; headers.push_back("Accept: image/x-j2c"); - res = mFetcher->mCurlGetRequest->getByteRange(mUrl, headers, offset, mRequestedSize, - new HTTPGetResponder(mFetcher, mID, LLTimer::getTotalTime(), mRequestedSize, offset, true)); + try + { + res = mFetcher->mCurlGetRequest->getByteRange(mUrl, headers, offset, mRequestedSize, + new HTTPGetResponder(mFetcher, mID, LLTimer::getTotalTime(), mRequestedSize, offset, true)); + } + catch(AICurlNoEasyHandle const& error) + { + llwarns << error.what() << llendl; + } } if (!res) { @@ -2100,7 +2107,7 @@ bool LLTextureFetch::createRequest(const std::string& url, const LLUUID& id, con worker->unlockWorkMutex(); } -// llinfos << "REQUESTED: " << id << " Discard: " << desired_discard << llendl; + llinfos << "REQUESTED: " << id << " Discard: " << desired_discard << llendl; return true; } @@ -2357,14 +2364,6 @@ void LLTextureFetch::commonUpdate() // Run a cross-thread command, if any. cmdDoWork(); #endif - - // Update Curl on same thread as mCurlGetRequest was constructed - llassert_always(mCurlGetRequest); - S32 processed = mCurlGetRequest->process(); - if (processed > 0) - { - lldebugs << "processed: " << processed << " messages." << llendl; - } } @@ -2429,7 +2428,7 @@ void LLTextureFetch::shutDownImageDecodeThread() void LLTextureFetch::startThread() { // Construct mCurlGetRequest from Worker Thread - mCurlGetRequest = new LLCurlRequest(); + mCurlGetRequest = new AICurlInterface::Request; } // WORKER THREAD diff --git a/indra/newview/lltexturefetch.h b/indra/newview/lltexturefetch.h index ee3cd2c75..03073d906 100644 --- a/indra/newview/lltexturefetch.h +++ b/indra/newview/lltexturefetch.h @@ -37,7 +37,7 @@ #include "llimage.h" #include "lluuid.h" #include "llworkerthread.h" -#include "llcurl.h" +#include "llcurlrequest.h" #include "lltextureinfo.h" #include "llapr.h" @@ -107,7 +107,7 @@ public: LLViewerAssetStats * main_stats); void commandDataBreak(); - LLCurlRequest & getCurlRequest() { return *mCurlGetRequest; } + AICurlInterface::Request& getCurlRequest() { return *mCurlGetRequest; } bool isQAMode() const { return mQAMode; } @@ -178,7 +178,7 @@ private: LLTextureCache* mTextureCache; LLImageDecodeThread* mImageDecodeThread; - LLCurlRequest* mCurlGetRequest; + AICurlInterface::Request* mCurlGetRequest; // Map of all requests by UUID typedef std::map map_t; diff --git a/indra/newview/lltexturestatsuploader.cpp b/indra/newview/lltexturestatsuploader.cpp index e0358e1fc..7f14a4254 100644 --- a/indra/newview/lltexturestatsuploader.cpp +++ b/indra/newview/lltexturestatsuploader.cpp @@ -33,6 +33,7 @@ #include "llviewerprecompiledheaders.h" #include "lltexturestatsuploader.h" +#include "llhttpclient.h" LLTextureStatsUploader::LLTextureStatsUploader() { diff --git a/indra/newview/llurlsimstring.cpp b/indra/newview/llurlsimstring.cpp index a28988db5..200345cae 100644 --- a/indra/newview/llurlsimstring.cpp +++ b/indra/newview/llurlsimstring.cpp @@ -38,7 +38,10 @@ #include "llpanellogin.h" #include "llviewercontrol.h" -#include "curl/curl.h" +#include // curl_unescape, curl_free +#ifdef DEBUG_CURLIO +#include "debug_libcurl.h" +#endif //static LLURLSimString LLURLSimString::sInstance; diff --git a/indra/newview/lluserauth.cpp b/indra/newview/lluserauth.cpp index f558a3170..a10c66fca 100644 --- a/indra/newview/lluserauth.cpp +++ b/indra/newview/lluserauth.cpp @@ -47,9 +47,12 @@ #include "stringize.h" // NOTE: MUST include these after otherincludes since queue gets redefined!?!! -#include #include +#include +#ifdef DEBUG_CURLIO +#include "debug_libcurl.h" +#endif // Don't define PLATFORM_STRING for unknown platforms - they need // to get added to the login cgi script, so we want this to cause an @@ -270,7 +273,7 @@ LLUserAuth::UserAuthcode LLUserAuth::authResponse() return mAuthResponse; } - bool done = mTransaction->process(); + bool done = mTransaction->is_finished(); if (!done) { if (LLXMLRPCTransaction::StatusDownloading == mTransaction->status(0)) diff --git a/indra/newview/llviewerassetstorage.h b/indra/newview/llviewerassetstorage.h index 15d9a748e..7b85f55d7 100644 --- a/indra/newview/llviewerassetstorage.h +++ b/indra/newview/llviewerassetstorage.h @@ -34,7 +34,6 @@ #define LLVIEWERASSETSTORAGE_H #include "llassetstorage.h" -//#include "curl/curl.h" class LLVFile; diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index d17067d96..0c0b12075 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -250,6 +250,7 @@ #include "llfloatervfs.h" #include "llfloatervfsexplorer.h" #include "shfloatermediaticker.h" +#include "llpacketring.h" // #include "scriptcounter.h" @@ -4048,7 +4049,7 @@ void print_packets_lost(void*) void drop_packet(void*) { - gMessageSystem->mPacketRing.dropPackets(1); + gMessageSystem->mPacketRing->dropPackets(1); } diff --git a/indra/newview/llviewerparcelmedia.cpp b/indra/newview/llviewerparcelmedia.cpp index 1349429ee..49cd7a1a3 100644 --- a/indra/newview/llviewerparcelmedia.cpp +++ b/indra/newview/llviewerparcelmedia.cpp @@ -467,7 +467,7 @@ void LLViewerParcelMedia::sendMediaNavigateMessage(const std::string& url) body["agent-id"] = gAgent.getID(); body["local-id"] = LLViewerParcelMgr::getInstance()->getAgentParcel()->getLocalID(); body["url"] = url; - LLHTTPClient::post(region_url, body, new LLHTTPClient::Responder); + LLHTTPClient::post(region_url, body, new LLHTTPClient::ResponderIgnore); } else { diff --git a/indra/newview/llviewerparcelmgr.cpp b/indra/newview/llviewerparcelmgr.cpp index a15722cda..9ed016d71 100644 --- a/indra/newview/llviewerparcelmgr.cpp +++ b/indra/newview/llviewerparcelmgr.cpp @@ -1310,7 +1310,7 @@ void LLViewerParcelMgr::sendParcelPropertiesUpdate(LLParcel* parcel, bool use_ag parcel->packMessage(body); llinfos << "Sending parcel properties update via capability to: " << url << llendl; - LLHTTPClient::post(url, body, new LLHTTPClient::Responder()); + LLHTTPClient::post(url, body, new LLHTTPClient::ResponderIgnore); } else { diff --git a/indra/newview/llviewerprecompiledheaders.h b/indra/newview/llviewerprecompiledheaders.h index 9fb55c7ee..3b8444b54 100644 --- a/indra/newview/llviewerprecompiledheaders.h +++ b/indra/newview/llviewerprecompiledheaders.h @@ -151,7 +151,7 @@ #include "llnamevalue.h" #include "llpacketack.h" #include "llpacketbuffer.h" -#include "llpacketring.h" +//#include "llpacketring.h" #include "llpartdata.h" #include "llregionhandle.h" #include "lltaskname.h" diff --git a/indra/newview/llviewertexturelist.cpp b/indra/newview/llviewertexturelist.cpp index 90806a724..d685b951e 100644 --- a/indra/newview/llviewertexturelist.cpp +++ b/indra/newview/llviewertexturelist.cpp @@ -277,7 +277,7 @@ void LLViewerTextureList::shutdown() break; } - if (count > 0 && !gDirUtilp->getLindenUserDir(true).empty()) + if (count > 0 && !gDirUtilp->getLindenUserDir(true).empty()) { std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, get_texture_list_name()); llofstream file; diff --git a/indra/newview/llwaterparammanager.cpp b/indra/newview/llwaterparammanager.cpp index ab95ee14d..307edffdd 100644 --- a/indra/newview/llwaterparammanager.cpp +++ b/indra/newview/llwaterparammanager.cpp @@ -76,8 +76,6 @@ #include "llagentcamera.h" -#include "curl/curl.h" - LLWaterParamManager::LLWaterParamManager() : mFogColor(22.f/255.f, 43.f/255.f, 54.f/255.f, 0.0f, 0.0f, "waterFogColor", "WaterFogColor"), mFogDensity(4, "waterFogDensity", 2), diff --git a/indra/newview/llwlparammanager.cpp b/indra/newview/llwlparammanager.cpp index a87487f55..284da40cd 100644 --- a/indra/newview/llwlparammanager.cpp +++ b/indra/newview/llwlparammanager.cpp @@ -77,7 +77,6 @@ #include "llviewerregion.h" #include "llassetuploadresponders.h" -#include "curl/curl.h" #include "llstreamtools.h" // [RLVa:KB] - Checked: 2011-09-04 (RLVa-1.4.1a) | Added: RLVa-1.4.1a diff --git a/indra/newview/llworld.cpp b/indra/newview/llworld.cpp index e4605ec9c..959f07f0e 100644 --- a/indra/newview/llworld.cpp +++ b/indra/newview/llworld.cpp @@ -61,6 +61,7 @@ #include "message.h" #include "pipeline.h" #include "llappviewer.h" // for do_disconnect() +#include "llpacketring.h" #include #include @@ -779,8 +780,8 @@ void LLWorld::updateNetStats() S32 packets_out = gMessageSystem->mPacketsOut - mLastPacketsOut; S32 packets_lost = gMessageSystem->mDroppedPackets - mLastPacketsLost; - S32 actual_in_bits = gMessageSystem->mPacketRing.getAndResetActualInBits(); - S32 actual_out_bits = gMessageSystem->mPacketRing.getAndResetActualOutBits(); + S32 actual_in_bits = gMessageSystem->mPacketRing->getAndResetActualInBits(); + S32 actual_out_bits = gMessageSystem->mPacketRing->getAndResetActualOutBits(); LLViewerStats::getInstance()->mActualInKBitStat.addValue(actual_in_bits/1024.f); LLViewerStats::getInstance()->mActualOutKBitStat.addValue(actual_out_bits/1024.f); LLViewerStats::getInstance()->mKBitStat.addValue(bits/1024.f); diff --git a/indra/newview/llxmlrpctransaction.cpp b/indra/newview/llxmlrpctransaction.cpp index 11dc653aa..227904f90 100644 --- a/indra/newview/llxmlrpctransaction.cpp +++ b/indra/newview/llxmlrpctransaction.cpp @@ -43,6 +43,11 @@ #include "llappviewer.h" #include "hippogridmanager.h" +#include "statemachine/aicurleasyrequeststatemachine.h" + +#ifdef CWDEBUG +#include +#endif LLXMLRPCValue LLXMLRPCValue::operator[](const char* id) const { @@ -154,7 +159,7 @@ class LLXMLRPCTransaction::Impl public: typedef LLXMLRPCTransaction::Status Status; - LLCurlEasyRequest* mCurlRequest; + AICurlEasyRequestStateMachine* mCurlEasyRequestStateMachinePtr; Status mStatus; CURLcode mCurlCode; @@ -163,8 +168,6 @@ public: LLCurl::TransferInfo mTransferInfo; std::string mURI; - char* mRequestText; - int mRequestTextSize; std::string mProxyAddress; @@ -176,7 +179,8 @@ public: const std::string& method, LLXMLRPCValue params, bool useGzip); ~Impl(); - bool process(); + bool is_finished(void) const; + void curlEasyRequestCallback(bool success); void setStatus(Status code, const std::string& message = "", const std::string& uri = ""); @@ -191,10 +195,9 @@ private: LLXMLRPCTransaction::Impl::Impl(const std::string& uri, XMLRPC_REQUEST request, bool useGzip) - : mCurlRequest(0), + : mCurlEasyRequestStateMachinePtr(NULL), mStatus(LLXMLRPCTransaction::StatusNotStarted), mURI(uri), - mRequestText(0), mResponse(0) { init(request, useGzip); @@ -203,10 +206,9 @@ LLXMLRPCTransaction::Impl::Impl(const std::string& uri, LLXMLRPCTransaction::Impl::Impl(const std::string& uri, const std::string& method, LLXMLRPCValue params, bool useGzip) - : mCurlRequest(0), + : mCurlEasyRequestStateMachinePtr(NULL), mStatus(LLXMLRPCTransaction::StatusNotStarted), mURI(uri), - mRequestText(0), mResponse(0) { XMLRPC_REQUEST request = XMLRPC_RequestNew(); @@ -217,177 +219,170 @@ LLXMLRPCTransaction::Impl::Impl(const std::string& uri, init(request, useGzip); } - - +// Store pointer to data allocated with XMLRPC_REQUEST_ToXML and call XMLRPC_Free to free it upon destruction. +class AIXMLRPCData : public AIPostField +{ + public: + AIXMLRPCData(char const* allocated_data) : AIPostField(allocated_data) { } + /*virtual*/ ~AIXMLRPCData() { XMLRPC_Free(const_cast(mData)); mData = NULL; } +}; void LLXMLRPCTransaction::Impl::init(XMLRPC_REQUEST request, bool useGzip) { - if (!mCurlRequest) { - mCurlRequest = new LLCurlEasyRequest(); + try + { + mCurlEasyRequestStateMachinePtr = new AICurlEasyRequestStateMachine(false); + } + catch(AICurlNoEasyHandle const& error) + { + llwarns << "Failed to initialize LLXMLRPCTransaction: " << error.what() << llendl; + setStatus(StatusOtherError, "No curl easy handle"); + return; + } + AICurlEasyRequest_wat curlEasyRequest_w(*mCurlEasyRequestStateMachinePtr->mCurlEasyRequest); + + curlEasyRequest_w->setWriteCallback(&curlDownloadCallback, (void*)this); + BOOL vefifySSLCert = !gSavedSettings.getBOOL("NoVerifySSLCert"); + curlEasyRequest_w->setopt(CURLOPT_SSL_VERIFYPEER, vefifySSLCert); + curlEasyRequest_w->setopt(CURLOPT_SSL_VERIFYHOST, vefifySSLCert ? 2 : 0); + // Be a little impatient about establishing connections. + curlEasyRequest_w->setopt(CURLOPT_CONNECTTIMEOUT, 40L); + + /* Setting the DNS cache timeout to -1 disables it completely. + This might help with bug #503 */ + curlEasyRequest_w->setopt(CURLOPT_DNS_CACHE_TIMEOUT, -1L); + + curlEasyRequest_w->addHeader("Content-Type: text/xml"); + + if (useGzip) + { + curlEasyRequest_w->setoptString(CURLOPT_ENCODING, ""); + } + + int requestTextSize; + char* requestText = XMLRPC_REQUEST_ToXML(request, &requestTextSize); + if (requestText) + { + curlEasyRequest_w->setPost(new AIXMLRPCData(requestText), requestTextSize); + } + else + { + setStatus(StatusOtherError); + } + + curlEasyRequest_w->finalizeRequest(mURI); } - - LLProxy::getInstance()->applyProxySettings(mCurlRequest); - -// mCurlRequest->setopt(CURLOPT_VERBOSE, 1); // usefull for debugging - mCurlRequest->setopt(CURLOPT_NOSIGNAL, 1); - mCurlRequest->setWriteCallback(&curlDownloadCallback, (void*)this); - BOOL vefifySSLCert = !gSavedSettings.getBOOL("NoVerifySSLCert"); - mCurlRequest->setopt(CURLOPT_SSL_VERIFYPEER, vefifySSLCert); - mCurlRequest->setopt(CURLOPT_SSL_VERIFYHOST, vefifySSLCert ? 2 : 0); - // Be a little impatient about establishing connections. - mCurlRequest->setopt(CURLOPT_CONNECTTIMEOUT, 40L); - - /* Setting the DNS cache timeout to -1 disables it completely. - This might help with bug #503 */ - mCurlRequest->setopt(CURLOPT_DNS_CACHE_TIMEOUT, -1); - - mCurlRequest->slist_append("Content-Type: text/xml"); - - if (useGzip) + if (mStatus == LLXMLRPCTransaction::StatusNotStarted) // It could be LLXMLRPCTransaction::StatusOtherError. { - mCurlRequest->setoptString(CURLOPT_ENCODING, ""); - } - - mRequestText = XMLRPC_REQUEST_ToXML(request, &mRequestTextSize); - if (mRequestText) - { - mCurlRequest->setoptString(CURLOPT_POSTFIELDS, mRequestText); - mCurlRequest->setopt(CURLOPT_POSTFIELDSIZE, mRequestTextSize); + mCurlEasyRequestStateMachinePtr->run(boost::bind(&LLXMLRPCTransaction::Impl::curlEasyRequestCallback, this, _1)); + setStatus(LLXMLRPCTransaction::StatusStarted); } else { - setStatus(StatusOtherError); + // This deletes the statemachine immediately. + mCurlEasyRequestStateMachinePtr->kill(); + mCurlEasyRequestStateMachinePtr = NULL; } - - mCurlRequest->sendRequest(mURI); } - LLXMLRPCTransaction::Impl::~Impl() { + if (mCurlEasyRequestStateMachinePtr && mCurlEasyRequestStateMachinePtr->running()) + { + llwarns << "Calling LLXMLRPCTransaction::Impl::~Impl while mCurlEasyRequestStateMachinePtr is still running" << llendl; + mCurlEasyRequestStateMachinePtr->abort(); + } + if (mResponse) { XMLRPC_RequestFree(mResponse, 1); } - - if (mRequestText) - { - XMLRPC_Free(mRequestText); - } - - delete mCurlRequest; - mCurlRequest = NULL ; } -bool LLXMLRPCTransaction::Impl::process() +bool LLXMLRPCTransaction::Impl::is_finished(void) const { - if(!mCurlRequest || !mCurlRequest->getEasy()) - { - llwarns << "transaction failed." << llendl ; + // Nothing to process anymore. Just wait till the statemachine finished. + return mStatus != LLXMLRPCTransaction::StatusNotStarted && + mStatus != LLXMLRPCTransaction::StatusStarted && + mStatus != LLXMLRPCTransaction::StatusDownloading; +} - delete mCurlRequest ; - mCurlRequest = NULL ; - return true ; //failed, quit. +void LLXMLRPCTransaction::Impl::curlEasyRequestCallback(bool success) +{ + llassert(mStatus == LLXMLRPCTransaction::StatusStarted || mStatus == LLXMLRPCTransaction::StatusDownloading); + + AICurlEasyRequestStateMachine* state_machine = mCurlEasyRequestStateMachinePtr; + // We're done with the statemachine, one way or another. + // Set mCurlEasyRequestStateMachinePtr to NULL so we won't call mCurlEasyRequestStateMachinePtr->running() in the destructor. + // Note that the state machine auto-cleaning: it will be deleted by the main-thread after this function returns. + mCurlEasyRequestStateMachinePtr = NULL; + + if (!success) + { + // AICurlEasyRequestStateMachine did abort. + // This currently only happens when libcurl didn't finish before the timer expired. + std::ostringstream msg; + F32 timeout_value = gSavedSettings.getF32("CurlRequestTimeOut"); + msg << "Connection to " << mURI << " timed out (" << timeout_value << " s)!"; + if (timeout_value < 40) + { + msg << "\nTry increasing CurlRequestTimeOut in Debug Settings."; + } + setStatus(LLXMLRPCTransaction::StatusOtherError, msg.str()); + return; } - switch(mStatus) + AICurlEasyRequest_wat curlEasyRequest_w(*state_machine->mCurlEasyRequest); + CURLcode result; + curlEasyRequest_w->getResult(&result, &mTransferInfo); + + if (result != CURLE_OK) { - case LLXMLRPCTransaction::StatusComplete: - case LLXMLRPCTransaction::StatusCURLError: - case LLXMLRPCTransaction::StatusXMLRPCError: - case LLXMLRPCTransaction::StatusOtherError: - { - return true; - } - - case LLXMLRPCTransaction::StatusNotStarted: - { - setStatus(LLXMLRPCTransaction::StatusStarted); - break; - } - - default: - { - // continue onward - } + setCurlStatus(result); + llwarns << "LLXMLRPCTransaction CURL error " + << mCurlCode << ": " << curlEasyRequest_w->getErrorString() << llendl; + llwarns << "LLXMLRPCTransaction request URI: " + << mURI << llendl; + + return; } - //const F32 MAX_PROCESSING_TIME = 0.05f; - //LLTimer timer; + setStatus(LLXMLRPCTransaction::StatusComplete); - mCurlRequest->perform(); + mResponse = XMLRPC_REQUEST_FromXML( + mResponseText.data(), mResponseText.size(), NULL); - /*while (mCurlRequest->perform() > 0) + bool hasError = false; + bool hasFault = false; + int faultCode = 0; + std::string faultString; + + LLXMLRPCValue error(XMLRPC_RequestGetError(mResponse)); + if (error.isValid()) { - if (timer.getElapsedTimeF32() >= MAX_PROCESSING_TIME) - { - return false; - } - }*/ - - while(1) - { - CURLcode result; - bool newmsg = mCurlRequest->getResult(&result, &mTransferInfo); - if (newmsg) - { - if (result != CURLE_OK) - { - setCurlStatus(result); - llwarns << "LLXMLRPCTransaction CURL error " - << mCurlCode << ": " << mCurlRequest->getErrorString() << llendl; - llwarns << "LLXMLRPCTransaction request URI: " - << mURI << llendl; - - return true; - } - - setStatus(LLXMLRPCTransaction::StatusComplete); - - mResponse = XMLRPC_REQUEST_FromXML( - mResponseText.data(), mResponseText.size(), NULL); - - bool hasError = false; - bool hasFault = false; - int faultCode = 0; - std::string faultString; - - LLXMLRPCValue error(XMLRPC_RequestGetError(mResponse)); - if (error.isValid()) - { - hasError = true; - faultCode = error["faultCode"].asInt(); - faultString = error["faultString"].asString(); - } - else if (XMLRPC_ResponseIsFault(mResponse)) - { - hasFault = true; - faultCode = XMLRPC_GetResponseFaultCode(mResponse); - faultString = XMLRPC_GetResponseFaultString(mResponse); - } - - if (hasError || hasFault) - { - setStatus(LLXMLRPCTransaction::StatusXMLRPCError); - - llwarns << "LLXMLRPCTransaction XMLRPC " - << (hasError ? "error " : "fault ") - << faultCode << ": " - << faultString << llendl; - llwarns << "LLXMLRPCTransaction request URI: " - << mURI << llendl; - } - - return true; - } - else - { - break; // done - } + hasError = true; + faultCode = error["faultCode"].asInt(); + faultString = error["faultString"].asString(); + } + else if (XMLRPC_ResponseIsFault(mResponse)) + { + hasFault = true; + faultCode = XMLRPC_GetResponseFaultCode(mResponse); + faultString = XMLRPC_GetResponseFaultString(mResponse); + } + + if (hasError || hasFault) + { + setStatus(LLXMLRPCTransaction::StatusXMLRPCError); + + llwarns << "LLXMLRPCTransaction XMLRPC " + << (hasError ? "error " : "fault ") + << faultCode << ": " + << faultString << llendl; + llwarns << "LLXMLRPCTransaction request URI: " + << mURI << llendl; } - - return false; } void LLXMLRPCTransaction::Impl::setStatus(Status status, @@ -483,6 +478,13 @@ size_t LLXMLRPCTransaction::Impl::curlDownloadCallback( size_t n = size * nmemb; +#ifdef CWDEBUG + if (n < 80) + Dout(dc::curl, "Entering LLXMLRPCTransaction::Impl::curlDownloadCallback(\"" << buf2str(data, n) << "\", " << size << ", " << nmemb << ", " << user_data << ")"); + else + Dout(dc::curl, "Entering LLXMLRPCTransaction::Impl::curlDownloadCallback(\"" << buf2str(data, 40) << "\"...\"" << buf2str(data + n - 40, 40) << "\", " << size << ", " << nmemb << ", " << user_data << ")"); +#endif + impl.mResponseText.append(data, n); if (impl.mStatus == LLXMLRPCTransaction::StatusStarted) @@ -511,9 +513,9 @@ LLXMLRPCTransaction::~LLXMLRPCTransaction() delete &impl; } -bool LLXMLRPCTransaction::process() +bool LLXMLRPCTransaction::is_finished(void) const { - return impl.process(); + return impl.is_finished(); } LLXMLRPCTransaction::Status LLXMLRPCTransaction::status(int* curlCode) diff --git a/indra/newview/llxmlrpctransaction.h b/indra/newview/llxmlrpctransaction.h index 528451fcb..6d70f8ff1 100644 --- a/indra/newview/llxmlrpctransaction.h +++ b/indra/newview/llxmlrpctransaction.h @@ -110,8 +110,8 @@ public: StatusOtherError } Status; - bool process(); - // run the request a little, returns true when done + bool is_finished(void) const; + // Returns true when done. Status status(int* curlCode); // return status, and extended CURL code, if code isn't null @@ -127,7 +127,7 @@ public: // retains ownership of the result object, don't free it F64 transferRate(); - // only valid if StsatusComplete, otherwise 0.0 + // only valid if StatusComplete, otherwise 0.0 private: class Impl; diff --git a/indra/newview/scriptcounter.cpp b/indra/newview/scriptcounter.cpp index 37acc1e37..3da258c28 100644 --- a/indra/newview/scriptcounter.cpp +++ b/indra/newview/scriptcounter.cpp @@ -43,6 +43,7 @@ #include "llviewercontrol.h" #include "llviewernetwork.h" #include "llviewerobject.h" +#include "llpacketring.h" #include ScriptCounter* ScriptCounter::sInstance; @@ -201,8 +202,8 @@ void ScriptCounter::serializeSelection(bool delScript) F32 throttle = gSavedSettings.getF32("OutBandwidth"); if((throttle == 0.f) || (throttle > 128000.f)) { - gMessageSystem->mPacketRing.setOutBandwidth(128000); - gMessageSystem->mPacketRing.setUseOutThrottle(TRUE); + gMessageSystem->mPacketRing->setOutBandwidth(128000); + gMessageSystem->mPacketRing->setUseOutThrottle(TRUE); } showResult(llformat("Counting scripts, please wait...")); if((objectCount == 1) && !(foo->isAvatar())) @@ -340,13 +341,13 @@ void ScriptCounter::completechk() F32 throttle = gSavedSettings.getF32("OutBandwidth"); if(throttle != 0.f) { - gMessageSystem->mPacketRing.setOutBandwidth(throttle); - gMessageSystem->mPacketRing.setUseOutThrottle(TRUE); + gMessageSystem->mPacketRing->setOutBandwidth(throttle); + gMessageSystem->mPacketRing->setUseOutThrottle(TRUE); } else { - gMessageSystem->mPacketRing.setOutBandwidth(0.0); - gMessageSystem->mPacketRing.setUseOutThrottle(FALSE); + gMessageSystem->mPacketRing->setOutBandwidth(0.0); + gMessageSystem->mPacketRing->setUseOutThrottle(FALSE); } llinfos << "Sending readout to chat..." << llendl; showResult(user_msg); diff --git a/indra/newview/statemachine/CMakeLists.txt b/indra/newview/statemachine/CMakeLists.txt index 86f19107e..34d362064 100644 --- a/indra/newview/statemachine/CMakeLists.txt +++ b/indra/newview/statemachine/CMakeLists.txt @@ -5,7 +5,7 @@ project(statemachine) include(00-Common) include(LLCommon) include(LLPlugin) -include(LLMessage) # This is needed by LLPlugin. +include(LLMessage) include(LLMath) include(LLVFS) include(LLXML) @@ -36,6 +36,7 @@ include_directories( set(statemachine_SOURCE_FILES aistatemachine.cpp + aicurleasyrequeststatemachine.cpp aifilepicker.cpp aifetchinventoryfolder.cpp aievent.cpp @@ -45,6 +46,7 @@ set(statemachine_SOURCE_FILES set(statemachine_HEADER_FILES CMakeLists.txt aistatemachine.h + aicurleasyrequeststatemachine.h aifilepicker.h aidirpicker.h aifetchinventoryfolder.h diff --git a/indra/newview/statemachine/aicurleasyrequeststatemachine.cpp b/indra/newview/statemachine/aicurleasyrequeststatemachine.cpp new file mode 100644 index 000000000..0c2f0dbc5 --- /dev/null +++ b/indra/newview/statemachine/aicurleasyrequeststatemachine.cpp @@ -0,0 +1,249 @@ +/** + * @file aicurleasyrequeststatemachine.cpp + * @brief Implementation of AICurlEasyRequestStateMachine + * + * Copyright (c) 2012, Aleric Inglewood. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution. + * + * CHANGELOG + * and additional copyright holders. + * + * 06/05/2012 + * Initial version, written by Aleric Inglewood @ SL + */ + +#include "linden_common.h" +#include "aicurleasyrequeststatemachine.h" +#include "llcontrol.h" + +enum curleasyrequeststatemachine_state_type { + AICurlEasyRequestStateMachine_addRequest = AIStateMachine::max_state, + AICurlEasyRequestStateMachine_waitAdded, + AICurlEasyRequestStateMachine_added, + AICurlEasyRequestStateMachine_timedOut, // This must be smaller than the rest, so they always overrule. + AICurlEasyRequestStateMachine_finished, + AICurlEasyRequestStateMachine_removed, // The removed states must be largest two, so they are never ignored. + AICurlEasyRequestStateMachine_removed_after_finished +}; + +char const* AICurlEasyRequestStateMachine::state_str_impl(state_type run_state) const +{ + switch(run_state) + { + AI_CASE_RETURN(AICurlEasyRequestStateMachine_addRequest); + AI_CASE_RETURN(AICurlEasyRequestStateMachine_waitAdded); + AI_CASE_RETURN(AICurlEasyRequestStateMachine_added); + AI_CASE_RETURN(AICurlEasyRequestStateMachine_timedOut); + AI_CASE_RETURN(AICurlEasyRequestStateMachine_finished); + AI_CASE_RETURN(AICurlEasyRequestStateMachine_removed); + AI_CASE_RETURN(AICurlEasyRequestStateMachine_removed_after_finished); + } + return "UNKNOWN STATE"; +} + +void AICurlEasyRequestStateMachine::initialize_impl(void) +{ + { + AICurlEasyRequest_wat curlEasyRequest_w(*mCurlEasyRequest); + llassert(curlEasyRequest_w->is_finalized()); // Call finalizeRequest(url) before calling run(). + curlEasyRequest_w->send_events_to(this); + } + mAdded = false; + mTimedOut = false; + mFinished = false; + mHandled = false; + set_state(AICurlEasyRequestStateMachine_addRequest); +} + +// CURL-THREAD +void AICurlEasyRequestStateMachine::added_to_multi_handle(AICurlEasyRequest_wat&) +{ + set_state(AICurlEasyRequestStateMachine_added); +} + +// CURL-THREAD +void AICurlEasyRequestStateMachine::finished(AICurlEasyRequest_wat&) +{ + mFinished = true; + set_state(AICurlEasyRequestStateMachine_finished); +} + +// CURL-THREAD +void AICurlEasyRequestStateMachine::removed_from_multi_handle(AICurlEasyRequest_wat&) +{ + set_state(mFinished ? AICurlEasyRequestStateMachine_removed_after_finished : AICurlEasyRequestStateMachine_removed); +} + +void AICurlEasyRequestStateMachine::multiplex_impl(void) +{ + mSetStateLock.lock(); + state_type current_state = mRunState; + mSetStateLock.unlock(); + switch (current_state) + { + case AICurlEasyRequestStateMachine_addRequest: + { + set_state(AICurlEasyRequestStateMachine_waitAdded); + idle(AICurlEasyRequestStateMachine_waitAdded); // Wait till AICurlEasyRequestStateMachine::added_to_multi_handle() is called. + // Only AFTER going idle, add request to curl thread; this is needed because calls to set_state() are + // ignored when the statemachine is not idle, and theoretically the callbacks could be called + // immediately after this call. + mAdded = true; + mCurlEasyRequest.addRequest(); // This causes the state to be changed, now or later, to + // AICurlEasyRequestStateMachine_added, then + // AICurlEasyRequestStateMachine_finished and then + // AICurlEasyRequestStateMachine_removed_after_finished. + + // The first two states might be skipped thus, and the state at this point is one of + // 1) AICurlEasyRequestStateMachine_waitAdded (idle) + // 2) AICurlEasyRequestStateMachine_added (running) + // 3) AICurlEasyRequestStateMachine_finished (running) + // 4) AICurlEasyRequestStateMachine_removed_after_finished (running) + + // Set an inactivity timer. + // This shouldn't really be necessary, except in the case of a bug + // in libcurl; but lets be sure and set a timer for inactivity. + static LLCachedControl CurlRequestTimeOut("CurlRequestTimeOut", 40.f); + mTimer = new AIPersistentTimer; // Do not delete timer upon expiration. + mTimer->setInterval(CurlRequestTimeOut); + mTimer->run(this, AICurlEasyRequestStateMachine_timedOut, false, false); + break; + } + case AICurlEasyRequestStateMachine_added: + { + // The request was added to the multi handle. This is a no-op, which is good cause + // this state might be skipped anyway ;). + idle(current_state); // Wait for the next event. + + // The state at this point is one of + // 1) AICurlEasyRequestStateMachine_added (idle) + // 2) AICurlEasyRequestStateMachine_finished (running) + // 3) AICurlEasyRequestStateMachine_removed_after_finished (running) + break; + } + case AICurlEasyRequestStateMachine_timedOut: + { + // It is possible that exactly at this point the state changes into + // AICurlEasyRequestStateMachine_finished, with as result that mTimedOut + // is set while we will continue with that state. Hence that mTimedOut + // is explicitly reset in that state. + + // Libcurl failed to deliver within a reasonable time... Abort operation in order + // to free this curl easy handle and to notify the application that it didn't work. + mTimedOut = true; + llassert(mAdded); + mAdded = false; + mCurlEasyRequest.removeRequest(); + idle(current_state); // Wait till AICurlEasyRequestStateMachine::removed_from_multi_handle() is called. + break; + } + case AICurlEasyRequestStateMachine_finished: + case AICurlEasyRequestStateMachine_removed_after_finished: + { + if (!mHandled) + { + // Only do this once. + mHandled = true; + + // Stop the timer. Note that it's the main thread that generates timer events, + // so we're certain that there will be no time out anymore if we reach this point. + mTimer->abort(); + + // The request finished and either data or an error code is available. + if (mBuffered) + { + AICurlEasyRequest_wat easy_request_w(*mCurlEasyRequest); + AICurlResponderBuffer_wat buffered_easy_request_w(*mCurlEasyRequest); + buffered_easy_request_w->processOutput(easy_request_w); + } + } + + if (current_state == AICurlEasyRequestStateMachine_finished) + { + idle(current_state); // Wait till AICurlEasyRequestStateMachine::removed_from_multi_handle() is called. + break; + } + + // See above. + mTimedOut = false; + /* Fall-Through */ + } + case AICurlEasyRequestStateMachine_removed: + { + // The request was removed from the multi handle. + if (mBuffered && mTimedOut) + { + AICurlEasyRequest_wat easy_request_w(*mCurlEasyRequest); + AICurlResponderBuffer_wat buffered_easy_request_w(*mCurlEasyRequest); + buffered_easy_request_w->timed_out(); + } + + // We're done. If we timed out, abort -- or else the application will + // think that getResult() will return a valid error code from libcurl. + if (mTimedOut) + abort(); + else + finish(); + + break; + } + } +} + +void AICurlEasyRequestStateMachine::abort_impl(void) +{ + DoutEntering(dc::curl, "AICurlEasyRequestStateMachine::abort_impl() [" << (void*)this << "] [" << (void*)mCurlEasyRequest.get() << "]"); + // Revert call to addRequest() if that was already called (and the request wasn't removed again already). + if (mAdded) + { + // Note that it's safe to call this even if the curl thread already removed it, or will removes it + // after we called this, before processing the remove command; only the curl thread calls + // MultiHandle::remove_easy_request, which is a no-op when called twice for the same easy request. + mAdded = false; + mCurlEasyRequest.removeRequest(); + } +} + +void AICurlEasyRequestStateMachine::finish_impl(void) +{ + DoutEntering(dc::curl, "AICurlEasyRequestStateMachine::finish_impl() [" << (void*)this << "] [" << (void*)mCurlEasyRequest.get() << "]"); + // Revoke callbacks. + { + AICurlEasyRequest_wat curl_easy_request_w(*mCurlEasyRequest); + curl_easy_request_w->send_events_to(NULL); + curl_easy_request_w->revokeCallbacks(); + } + // Note that even if the timer expired, it wasn't deleted because we used AIPersistentTimer; so mTimer is still valid. + // Stop the timer, if it's still running. + if (!mHandled) + mTimer->abort(); + // Auto clean up ourselves. + kill(); +} + +AICurlEasyRequestStateMachine::AICurlEasyRequestStateMachine(bool buffered) : mBuffered(buffered), mCurlEasyRequest(buffered) +{ + Dout(dc::statemachine, "Calling AICurlEasyRequestStateMachine(" << (buffered ? "true" : "false") << ") [" << (void*)this << "] [" << (void*)mCurlEasyRequest.get() << "]"); +} + +AICurlEasyRequestStateMachine::~AICurlEasyRequestStateMachine() +{ + Dout(dc::statemachine, "Calling ~AICurlEasyRequestStateMachine() [" << (void*)this << "] [" << (void*)mCurlEasyRequest.get() << "]"); +} + diff --git a/indra/newview/statemachine/aicurleasyrequeststatemachine.h b/indra/newview/statemachine/aicurleasyrequeststatemachine.h new file mode 100644 index 000000000..e5f875138 --- /dev/null +++ b/indra/newview/statemachine/aicurleasyrequeststatemachine.h @@ -0,0 +1,102 @@ +/** + * @file aicurleasyrequest.h + * @brief Perform a curl easy request. + * + * Copyright (c) 2012, Aleric Inglewood. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution. + * + * CHANGELOG + * and additional copyright holders. + * + * 06/05/2012 + * Initial version, written by Aleric Inglewood @ SL + */ + +#ifndef AICURLEASYREQUEST_H +#define AICURLEASYREQUEST_H + +#include "aistatemachine.h" +#include "aitimer.h" +#include "aicurl.h" + +// A curl easy request state machine. +// +// Before calling cersm.run() initialize the object (cersm) as follows: +// +// AICurlEasyRequest_wat cersm_w(cersm); +// cersm_w->setopt(...); // etc, see the interface of AICurlPrivate::CurlEasyRequest and it's base class AICurlPrivate::CurlEasyHandle. +// +// When the state machine finishes, call aborted() to check +// whether or not the statemachine succeeded in fetching +// the URL or not. +// +// Objects of this type can be reused multiple times, see +// also the documentation of AIStateMachine. +// +// Construction of a AICurlEasyRequestStateMachine might throw AICurlNoEasyHandle. +class AICurlEasyRequestStateMachine : public AIStateMachine, public AICurlEasyHandleEvents { + public: + AICurlEasyRequestStateMachine(bool buffered); + + // Transparent access. + AICurlEasyRequest mCurlEasyRequest; + + private: + bool mBuffered; // Argument used for construction of mCurlEasyRequest. + bool mAdded; // Set when the last command to the curl thread was to add the request. + bool mTimedOut; // Set if the expiration timer timed out. + bool mFinished; // Set by the curl thread to signal it finished. + bool mHandled; // Set when we processed the received data. + AITimer* mTimer; // Expiration timer. + + protected: + // AICurlEasyRequest Events. + + // Called when this curl easy handle was added to a multi handle. + /*virtual*/ void added_to_multi_handle(AICurlEasyRequest_wat&); + + // Called when this curl easy handle finished processing (right before it is removed from the multi handle). + /*virtual*/ void finished(AICurlEasyRequest_wat&); + + // Called after this curl easy handle was removed from a multi handle. + /*virtual*/ void removed_from_multi_handle(AICurlEasyRequest_wat&); + + protected: + // AIStateMachine implementations. + + // Call finish() (or abort()), not delete. + /*virtual*/ ~AICurlEasyRequestStateMachine(); + + // Handle initializing the object. + /*virtual*/ void initialize_impl(void); + + // Handle mRunState. + /*virtual*/ void multiplex_impl(void); + + // Handle aborting from current bs_run state. + /*virtual*/ void abort_impl(void); + + // Handle cleaning up from initialization (or post abort) state. + /*virtual*/ void finish_impl(void); + + // Implemenation of state_str for run states. + /*virtual*/ char const* state_str_impl(state_type run_state) const; +}; + +#endif diff --git a/indra/newview/statemachine/aistatemachine.cpp b/indra/newview/statemachine/aistatemachine.cpp index 3cd5c056c..a6a646ec9 100644 --- a/indra/newview/statemachine/aistatemachine.cpp +++ b/indra/newview/statemachine/aistatemachine.cpp @@ -44,35 +44,35 @@ extern LLControlGroup gSavedSettings; // Local variables. namespace { - struct QueueElementComp; + struct QueueElementComp; - class QueueElement { - private: - AIStateMachine* mStateMachine; - U64 mRuntime; + class QueueElement { + private: + AIStateMachine* mStateMachine; + U64 mRuntime; - public: - QueueElement(AIStateMachine* statemachine) : mStateMachine(statemachine), mRuntime(0) { } - friend bool operator==(QueueElement const& e1, QueueElement const& e2) { return e1.mStateMachine == e2.mStateMachine; } - friend struct QueueElementComp; + public: + QueueElement(AIStateMachine* statemachine) : mStateMachine(statemachine), mRuntime(0) { } + friend bool operator==(QueueElement const& e1, QueueElement const& e2) { return e1.mStateMachine == e2.mStateMachine; } + friend struct QueueElementComp; - AIStateMachine& statemachine(void) const { return *mStateMachine; } - void add(U64 count) { mRuntime += count; } - }; + AIStateMachine& statemachine(void) const { return *mStateMachine; } + void add(U64 count) { mRuntime += count; } + }; - struct QueueElementComp { - bool operator()(QueueElement const& e1, QueueElement const& e2) const { return e1.mRuntime < e2.mRuntime; } - }; + struct QueueElementComp { + bool operator()(QueueElement const& e1, QueueElement const& e2) const { return e1.mRuntime < e2.mRuntime; } + }; - typedef std::vector active_statemachines_type; - active_statemachines_type active_statemachines; - typedef std::vector continued_statemachines_type; - struct cscm_type - { - continued_statemachines_type continued_statemachines; - bool calling_mainloop; - }; - AIThreadSafeDC continued_statemachines_and_calling_mainloop; + typedef std::vector active_statemachines_type; + active_statemachines_type active_statemachines; + typedef std::vector continued_statemachines_type; + struct cscm_type + { + continued_statemachines_type continued_statemachines; + bool calling_mainloop; + }; + AIThreadSafeDC continued_statemachines_and_calling_mainloop; } // static @@ -80,8 +80,14 @@ AIThreadSafeSimpleDC AIStateMachine::sMaxCount; void AIStateMachine::updateSettings(void) { + static const LLCachedControl StateMachineMaxTime("StateMachineMaxTime", 20); + static U32 last_StateMachineMaxTime = 0; + if (last_StateMachineMaxTime != StateMachineMaxTime) + { Dout(dc::statemachine, "Initializing AIStateMachine::sMaxCount"); - *AIAccess(sMaxCount) = calc_clock_frequency() * gSavedSettings.getU32("StateMachineMaxTime") / 1000; + *AIAccess(sMaxCount) = calc_clock_frequency() * StateMachineMaxTime / 1000; + last_StateMachineMaxTime = StateMachineMaxTime; + } } //---------------------------------------------------------------------------- @@ -89,228 +95,366 @@ void AIStateMachine::updateSettings(void) // Public methods // -void AIStateMachine::run(AIStateMachine* parent, state_type new_parent_state, bool abort_parent) +void AIStateMachine::run(AIStateMachine* parent, state_type new_parent_state, bool abort_parent, bool on_abort_signal_parent) { - DoutEntering(dc::statemachine, "AIStateMachine::run(" << (void*)parent << ", " << (parent ? parent->state_str(new_parent_state) : "NA") << ", " << abort_parent << ") [" << (void*)this << "]"); - // Must be the first time we're being run, or we must be called from a callback function. - llassert(!mParent || mState == bs_callback); - llassert(!mCallback || mState == bs_callback); - // Can only be run when in this state. - llassert(mState == bs_initialize || mState == bs_callback); + DoutEntering(dc::statemachine, "AIStateMachine::run(" << (void*)parent << ", " << (parent ? parent->state_str(new_parent_state) : "NA") << ", " << abort_parent << ") [" << (void*)this << "]"); + // Must be the first time we're being run, or we must be called from a callback function. + llassert(!mParent || mState == bs_callback); + llassert(!mCallback || mState == bs_callback); + // Can only be run when in this state. + llassert(mState == bs_initialize || mState == bs_callback); - // Allow NULL to be passed as parent to signal that we want to reuse the old one. - if (parent) - { - mParent = parent; - // In that case remove any old callback! - if (mCallback) - { - delete mCallback; - mCallback = NULL; - } + // Allow NULL to be passed as parent to signal that we want to reuse the old one. + if (parent) + { + mParent = parent; + // In that case remove any old callback! + if (mCallback) + { + delete mCallback; + mCallback = NULL; + } - mNewParentState = new_parent_state; - mAbortParent = abort_parent; - } + mNewParentState = new_parent_state; + mAbortParent = abort_parent; + mOnAbortSignalParent = on_abort_signal_parent; + } - // If abort_parent is requested then a parent must be provided. - llassert(!abort_parent || mParent); - // If a parent is provided, it must be running. - llassert(!mParent || mParent->mState == bs_run); + // If abort_parent is requested then a parent must be provided. + llassert(!abort_parent || mParent); + // If a parent is provided, it must be running. + llassert(!mParent || mParent->mState == bs_run); - // Mark that run() has been called, in case we're being called from a callback function. - mState = bs_initialize; + // Mark that run() has been called, in case we're being called from a callback function. + mState = bs_initialize; - cont(); + // Set mIdle to false and add statemachine to continued_statemachines. + mSetStateLock.lock(); + locked_cont(); } void AIStateMachine::run(callback_type::signal_type::slot_type const& slot) { - DoutEntering(dc::statemachine, "AIStateMachine::run() [" << (void*)this << "]"); - // Must be the first time we're being run, or we must be called from a callback function. - llassert(!mParent || mState == bs_callback); - llassert(!mCallback || mState == bs_callback); - // Can only be run when in this state. - llassert(mState == bs_initialize || mState == bs_callback); + DoutEntering(dc::statemachine, "AIStateMachine::run() [" << (void*)this << "]"); + // Must be the first time we're being run, or we must be called from a callback function. + llassert(!mParent || mState == bs_callback); + llassert(!mCallback || mState == bs_callback); + // Can only be run when in this state. + llassert(mState == bs_initialize || mState == bs_callback); - // Clean up any old callbacks. - mParent = NULL; - if (mCallback) - { - delete mCallback; - mCallback = NULL; - } + // Clean up any old callbacks. + mParent = NULL; + if (mCallback) + { + delete mCallback; + mCallback = NULL; + } - mCallback = new callback_type(slot); + mCallback = new callback_type(slot); - // Mark that run() has been called, in case we're being called from a callback function. - mState = bs_initialize; + // Mark that run() has been called, in case we're being called from a callback function. + mState = bs_initialize; - cont(); + // Set mIdle to false and add statemachine to continued_statemachines. + mSetStateLock.lock(); + locked_cont(); } void AIStateMachine::idle(void) { - DoutEntering(dc::statemachine, "AIStateMachine::idle() [" << (void*)this << "]"); - llassert(!mIdle); - mIdle = true; - mSleep = 0; + DoutEntering(dc::statemachine, "AIStateMachine::idle() [" << (void*)this << "]"); + llassert(is_main_thread()); + llassert(!mIdle); + mIdle = true; + mSleep = 0; +#ifdef SHOW_ASSERT + mCalledThreadUnsafeIdle = true; +#endif +} + +void AIStateMachine::idle(state_type current_run_state) +{ + DoutEntering(dc::statemachine, "AIStateMachine::idle(" << state_str(current_run_state) << ") [" << (void*)this << "]"); + llassert(is_main_thread()); + llassert(!mIdle); + mSetStateLock.lock(); + // Only go idle if the run state is (still) what we expect it to be. + // Otherwise assume that another thread called set_state() and continue running. + if (current_run_state == mRunState) + { + mIdle = true; + mSleep = 0; + } + mSetStateLock.unlock(); } // About thread safeness: // // The main thread initializes a statemachine and calls run, so a statemachine // runs in the main thread. However, it is allowed that a state calls idle() -// and then allows one and only one other thread to call cont() upon some +// and then allows one or more other threads to call cont() upon some // event (only once, of course, as idle() has to be called before cont() -// can be called again-- and another thread is not allowed to call idle()). -// Instead of cont(), the other thread may also call set_state(). - -void AIStateMachine::cont(void) +// can be called again-- and a non-main thread is not allowed to call idle()). +// Instead of cont() one may also call set_state(). +// Of course, this may give rise to a race condition; if that happens then +// the thread that calls cont() (set_state()) first is serviced, and the other +// thread(s) are ignored, as if they never called cont(). +void AIStateMachine::locked_cont(void) { - DoutEntering(dc::statemachine, "AIStateMachine::cont() [" << (void*)this << "]"); - llassert(mIdle); - // Atomic test mActive and change mIdle. - mIdleActive.lock(); - mIdle = false; - bool not_active = mActive == as_idle; - mIdleActive.unlock(); - if (not_active) - { - AIWriteAccess cscm_w(continued_statemachines_and_calling_mainloop); - // We only get here when the statemachine was idle (set by the main thread), - // see first assertion. Hence, the main thread is not changing this, as the - // statemachine is not running. Thus, mActive can have changed when a THIRD - // thread called cont(), which is not allowed: if two threads can call cont() - // at any moment then the first assertion can't hold. - llassert_always(mActive == as_idle); - cscm_w->continued_statemachines.push_back(this); - if (!cscm_w->calling_mainloop) - { - Dout(dc::statemachine, "Adding AIStateMachine::mainloop to gIdleCallbacks"); - cscm_w->calling_mainloop = true; - gIdleCallbacks.addFunction(&AIStateMachine::mainloop); - } - mActive = as_queued; - llassert_always(!mIdle); // It should never happen that one thread calls cont() while another calls idle() concurrently. - } + DoutEntering(dc::statemachine, "AIStateMachine::locked_cont() [" << (void*)this << "]"); + llassert(mIdle); + // Atomic test mActive and change mIdle. + mIdleActive.lock(); +#ifdef SHOW_ASSERT + mContThread.reset(); +#endif + mIdle = false; + bool not_active = mActive == as_idle; + mIdleActive.unlock(); + // mActive is only changed in AIStateMachine::mainloop, by the main-thread, and + // here, possibly by any thread. However, after setting mIdle to false above, it + // is impossible for any thread to come here, until after the main-thread called + // idle(). So, if this is the main thread then that certainly isn't going to + // happen until we left this function, while if this is another thread and the + // state machine is already running in the main thread then not_active is false + // and we're already at the end of this function. + // If not_active is true then main-thread is not running this statemachine. + // It might call cont() (or set_state()) but never locked_cont(), and will never + // start actually running until we are done here and release the lock on + // continued_statemachines_and_calling_mainloop again. It is therefore safe + // to release mSetStateLock here, with as advantage that if we're not the main- + // thread and not_active is true, then the main-thread won't block when it has + // a timer running that times out and calls set_state(). + mSetStateLock.unlock(); + if (not_active) + { + AIWriteAccess cscm_w(continued_statemachines_and_calling_mainloop); + // See above: it is not possible that mActive was changed since not_active + // was set to true above. + llassert_always(mActive == as_idle); + Dout(dc::statemachine, "Adding " << (void*)this << " to continued_statemachines"); + cscm_w->continued_statemachines.push_back(this); + if (!cscm_w->calling_mainloop) + { + Dout(dc::statemachine, "Adding AIStateMachine::mainloop to gIdleCallbacks"); + cscm_w->calling_mainloop = true; + gIdleCallbacks.addFunction(&AIStateMachine::mainloop); + } + mActive = as_queued; + llassert_always(!mIdle); // It should never happen that the main thread calls idle(), while another thread calls cont() concurrently. + } } void AIStateMachine::set_state(state_type state) { - DoutEntering(dc::statemachine, "AIStateMachine::set_state(" << state_str(state) << ") [" << (void*)this << "]"); - llassert(mState == bs_run); - if (mRunState != state) - { - mRunState = state; - Dout(dc::statemachine, "mRunState set to " << state_str(mRunState)); - } - if (mIdle) - cont(); + DoutEntering(dc::statemachine, "AIStateMachine::set_state(" << state_str(state) << ") [" << (void*)this << "]"); + + // Stop race condition of multiple threads calling cont() or set_state() here. + mSetStateLock.lock(); + + // Do not call set_state() unless running. + llassert(mState == bs_run || !is_main_thread()); + + // If this function is called from another thread than the main thread, then we have to ignore + // it if we're not idle and the state is less than the current state. The main thread must + // be able to change the state to anything (also smaller values). Note that that only can work + // if the main thread itself at all times cancels thread callbacks that call set_state() + // before calling idle() again! + // + // Thus: main thead calls idle(), and tells one or more threads to do callbacks on events, + // which (might) call set_state(). If the main thread calls set_state first (currently only + // possible as a result of the use of a timer) it will set mIdle to false (here) then cancel + // the call backs from the other threads and only then call idle() again. + // Thus if you want other threads get here while mIdle is false to be ignored then the + // main thread should use a large value for the new run state. + // + // If a non-main thread calls set_state first, then the state is changed but the main thread + // can still override it if it calls set_state before handling the new state; in the latter + // case it would still be as if the call from the non-main thread was ignored. + // + // Concurrent calls from non-main threads however, always result in the largest state + // to prevail. + + // If the state machine is already running, and we are not the main-thread and the new + // state is less than the current state, ignore it. + // Also, if abort() or finish() was called, then we should just ignore it. + if (mState != bs_run || + (!mIdle && state <= mRunState && !AIThreadID::in_main_thread())) + { +#ifdef SHOW_ASSERT + // It's a bit weird if the same thread does two calls on a row where the second call + // has a smaller value: warn about that. + if (mState == bs_run && mContThread.equals_current_thread()) + { + llwarns << "Ignoring call to set_state(" << state_str(state) << + ") by non-main thread before main-thread could react on previous call, " + "because new state is smaller than old state (" << state_str(mRunState) << ")." << llendl; + } +#endif + mSetStateLock.unlock(); + return; // Ignore. + } + + // Do not call idle() when set_state is called from another thread; use idle(state_type) instead. + llassert(!mCalledThreadUnsafeIdle || is_main_thread()); + + // Change mRunState to the requested value. + if (mRunState != state) + { + mRunState = state; + Dout(dc::statemachine, "mRunState set to " << state_str(mRunState)); + } + + // Continue the state machine if appropriate. + if (mIdle) + locked_cont(); // This unlocks mSetStateLock. + else + mSetStateLock.unlock(); + + // If we get here then mIdle is false, so only mRunState can still be changed but we won't + // call locked_cont() anymore. When the main thread finally picks up on the state change, + // it will cancel any possible callbacks from other threads and process the largest state + // that this function was called with in the meantime. } void AIStateMachine::abort(void) { - DoutEntering(dc::statemachine, "AIStateMachine::abort() [" << (void*)this << "]"); - llassert(mState == bs_run); - mState = bs_abort; - abort_impl(); - mAborted = true; - finish(); + DoutEntering(dc::statemachine, "AIStateMachine::abort() [" << (void*)this << "]"); + // It's possible that abort() is called before calling AIStateMachine::multiplex. + // In that case the statemachine wasn't initialized yet and we should just kill() it. + if (LL_UNLIKELY(mState == bs_initialize)) + { + // It's ok to use the thread-unsafe idle() here, because if the statemachine + // wasn't started yet, then other threads won't call set_state() on it. + if (!mIdle) + idle(); + // run() calls locked_cont() after which the top of the mainloop adds this + // state machine to active_statemachines. Therefore, if the following fails + // then either the same statemachine called run() immediately followed by abort(), + // which is not allowed; or there were two active statemachines running, + // the first created a new statemachine and called run() on it, and then + // the other (before reaching the top of the mainloop) called abort() on + // that freshly created statemachine. Obviously, this is highly unlikely, + // but if that is the case then here we bump the statemachine into + // continued_statemachines to prevent kill() to delete this statemachine: + // the caller of abort() does not expect that. + if (LL_UNLIKELY(mActive == as_idle)) + { + mSetStateLock.lock(); + locked_cont(); + idle(); + } + kill(); + } + else + { + llassert(mState == bs_run); + mSetStateLock.lock(); + mState = bs_abort; // Causes additional calls to set_state to be ignored. + mSetStateLock.unlock(); + abort_impl(); + mAborted = true; + finish(); + } } void AIStateMachine::finish(void) { - DoutEntering(dc::statemachine, "AIStateMachine::finish() [" << (void*)this << "]"); - llassert(mState == bs_run || mState == bs_abort); - // It is possible that mIdle is true when abort or finish was called from - // outside multiplex_impl. However, that only may be done by the main thread. - llassert(!mIdle || is_main_thread()); - if (!mIdle) - idle(); - mState = bs_finish; - finish_impl(); - // Did finish_impl call kill()? Then that is only the default. Remember it. - bool default_delete = (mState == bs_killed); - mState = bs_finish; - if (mParent) - { - // It is possible that the parent is not running when the parent is in fact aborting and called - // abort on this object from it's abort_impl function. It that case we don't want to recursively - // call abort again (or change it's state). - if (mParent->running()) - { - if (mAborted && mAbortParent) - { - mParent->abort(); - mParent = NULL; - } - else - { - mParent->set_state(mNewParentState); - } - } - } - // After this (bool)*this evaluates to true and we can call the callback, which then is allowed to call run(). - mState = bs_callback; - if (mCallback) - { - // This can/may call kill() that sets mState to bs_kill and in which case the whole AIStateMachine - // will be deleted from the mainloop, or it may call run() that sets mState is set to bs_initialize - // and might change or reuse mCallback or mParent. - mCallback->callback(!mAborted); - if (mState != bs_initialize) - { - delete mCallback; - mCallback = NULL; - mParent = NULL; - } - } - else - { - // Not restarted by callback. Allow run() to be called later on. - mParent = NULL; - } - // Fix the final state. - if (mState == bs_callback) - mState = default_delete ? bs_killed : bs_initialize; - if (mState == bs_killed && mActive == as_idle) - { - // Bump the statemachine onto the active statemachine list, or else it won't be deleted. - cont(); - idle(); - } + DoutEntering(dc::statemachine, "AIStateMachine::finish() [" << (void*)this << "]"); + mSetStateLock.lock(); + llassert(mState == bs_run || mState == bs_abort); + // It is possible that mIdle is true when abort or finish was called from + // outside multiplex_impl. However, that only may be done by the main thread. + llassert(!mIdle || is_main_thread()); + if (!mIdle) + idle(); // After calling this, we don't want other threads to call set_state() anymore. + mState = bs_finish; // Causes additional calls to set_state to be ignored. + mSetStateLock.unlock(); + finish_impl(); + // Did finish_impl call kill()? Then that is only the default. Remember it. + bool default_delete = (mState == bs_killed); + mState = bs_finish; + if (mParent) + { + // It is possible that the parent is not running when the parent is in fact aborting and called + // abort on this object from it's abort_impl function. It that case we don't want to recursively + // call abort again (or change it's state). + if (mParent->running()) + { + if (mAborted && mAbortParent) + { + mParent->abort(); + mParent = NULL; + } + else if (!mAborted || mOnAbortSignalParent) + { + mParent->set_state(mNewParentState); + } + } + } + // After this (bool)*this evaluates to true and we can call the callback, which then is allowed to call run(). + mState = bs_callback; + if (mCallback) + { + // This can/may call kill() that sets mState to bs_kill and in which case the whole AIStateMachine + // will be deleted from the mainloop, or it may call run() that sets mState is set to bs_initialize + // and might change or reuse mCallback or mParent. + mCallback->callback(!mAborted); + if (mState != bs_initialize) + { + delete mCallback; + mCallback = NULL; + mParent = NULL; + } + } + else + { + // Not restarted by callback. Allow run() to be called later on. + mParent = NULL; + } + // Fix the final state. + if (mState == bs_callback) + mState = default_delete ? bs_killed : bs_initialize; + if (mState == bs_killed && mActive == as_idle) + { + // Bump the statemachine onto the active statemachine list, or else it won't be deleted. + mSetStateLock.lock(); + locked_cont(); + idle(); + } } void AIStateMachine::kill(void) { - // Should only be called from finish() (or when not running (bs_initialize)). - llassert(mIdle && (mState == bs_callback || mState == bs_finish || mState == bs_initialize)); - base_state_type prev_state = mState; - mState = bs_killed; - if (prev_state == bs_initialize) - { - // We're not running (ie being deleted by a parent statemachine), delete it immediately. - delete this; - } + DoutEntering(dc::statemachine, "AIStateMachine::kill() [" << (void*)this << "]"); + // Should only be called from finish() (or when not running (bs_initialize)). + // However, also allow multiple calls to kill() on a row (bs_killed) (which effectively don't do anything). + llassert(mIdle && (mState == bs_callback || mState == bs_finish || mState == bs_initialize || mState == bs_killed)); + base_state_type prev_state = mState; + mState = bs_killed; + if (prev_state == bs_initialize && mActive == as_idle) + { + // We're not running (ie being deleted by a parent statemachine), delete it immediately. + delete this; + } } // Return stringified 'state'. char const* AIStateMachine::state_str(state_type state) { - if (state >= min_state && state < max_state) - { - switch (state) - { - AI_CASE_RETURN(bs_initialize); - AI_CASE_RETURN(bs_run); - AI_CASE_RETURN(bs_abort); - AI_CASE_RETURN(bs_finish); - AI_CASE_RETURN(bs_callback); - AI_CASE_RETURN(bs_killed); - } - } - return state_str_impl(state); + if (state >= min_state && state < max_state) + { + switch (state) + { + AI_CASE_RETURN(bs_initialize); + AI_CASE_RETURN(bs_run); + AI_CASE_RETURN(bs_abort); + AI_CASE_RETURN(bs_finish); + AI_CASE_RETURN(bs_callback); + AI_CASE_RETURN(bs_killed); + } + } + return state_str_impl(state); } //---------------------------------------------------------------------------- @@ -320,135 +464,183 @@ char const* AIStateMachine::state_str(state_type state) void AIStateMachine::multiplex(U64 current_time) { - // Return immediately when this state machine is sleeping. - // A negative value of mSleep means we're counting frames, - // a positive value means we're waiting till a certain - // amount of time has passed. - if (mSleep != 0) - { - if (mSleep < 0) - { - if (++mSleep) - return; - } - else - { - if (current_time < (U64)mSleep) - return; - mSleep = 0; - } - } + // Return immediately when this state machine is sleeping. + // A negative value of mSleep means we're counting frames, + // a positive value means we're waiting till a certain + // amount of time has passed. + if (mSleep != 0) + { + if (mSleep < 0) + { + if (++mSleep) + return; + } + else + { + if (current_time < (U64)mSleep) + return; + mSleep = 0; + } + } - DoutEntering(dc::statemachine, "AIStateMachine::multiplex() [" << (void*)this << "] [with state: " << state_str(mState == bs_run ? mRunState : mState) << "]"); - llassert(mState == bs_initialize || mState == bs_run); + DoutEntering(dc::statemachine, "AIStateMachine::multiplex() [" << (void*)this << "] [with state: " << state_str(mState == bs_run ? mRunState : mState) << "]"); + llassert(mState == bs_initialize || mState == bs_run); - // Real state machine starts here. - if (mState == bs_initialize) - { - mAborted = false; - mState = bs_run; - initialize_impl(); - if (mAborted || mState != bs_run) - return; - } - multiplex_impl(); + // Real state machine starts here. + if (mState == bs_initialize) + { + mAborted = false; + mState = bs_run; + initialize_impl(); + if (mAborted || mState != bs_run) + return; + } + multiplex_impl(); +} + +//static +void AIStateMachine::add_continued_statemachines(void) +{ + AIReadAccess cscm_r(continued_statemachines_and_calling_mainloop); + bool nonempty = false; + for (continued_statemachines_type::const_iterator iter = cscm_r->continued_statemachines.begin(); iter != cscm_r->continued_statemachines.end(); ++iter) + { + nonempty = true; + active_statemachines.push_back(QueueElement(*iter)); + Dout(dc::statemachine, "Adding " << (void*)*iter << " to active_statemachines"); + (*iter)->mActive = as_active; + } + if (nonempty) + AIWriteAccess(cscm_r)->continued_statemachines.clear(); } static LLFastTimer::DeclareTimer FTM_STATEMACHINE("State Machine"); // static void AIStateMachine::mainloop(void*) { - LLFastTimer t(FTM_STATEMACHINE); - // Add continued state machines. - { - AIReadAccess cscm_r(continued_statemachines_and_calling_mainloop); - bool nonempty = false; - for (continued_statemachines_type::const_iterator iter = cscm_r->continued_statemachines.begin(); iter != cscm_r->continued_statemachines.end(); ++iter) - { - nonempty = true; - active_statemachines.push_back(QueueElement(*iter)); - Dout(dc::statemachine, "Adding " << (void*)*iter << " to active_statemachines"); - (*iter)->mActive = as_active; - } - if (nonempty) - AIWriteAccess(cscm_r)->continued_statemachines.clear(); - } - llassert(!active_statemachines.empty()); - // Run one or more state machines. - U64 total_clocks = 0; - U64 max_count = *AIAccess(sMaxCount); - for (active_statemachines_type::iterator iter = active_statemachines.begin(); iter != active_statemachines.end(); ++iter) - { - AIStateMachine& statemachine(iter->statemachine()); - if (!statemachine.mIdle) - { - U64 start = get_clock_count(); - // This might call idle() and then pass the statemachine to another thread who then may call cont(). - // Hence, after this isn't not sure what mIdle is, and it can change from true to false at any moment, - // if it is true after this function returns. - iter->statemachine().multiplex(start); - U64 delta = get_clock_count() - start; - iter->add(delta); - total_clocks += delta; - if (total_clocks >= max_count) - { + LLFastTimer t(FTM_STATEMACHINE); + add_continued_statemachines(); + llassert(!active_statemachines.empty()); + // Run one or more state machines. + U64 total_clocks = 0; + U64 max_count = *AIAccess(sMaxCount); + for (active_statemachines_type::iterator iter = active_statemachines.begin(); iter != active_statemachines.end(); ++iter) + { + AIStateMachine& statemachine(iter->statemachine()); + if (!statemachine.mIdle) + { + U64 start = get_clock_count(); + // This might call idle() and then pass the statemachine to another thread who then may call cont(). + // Hence, after this isn't not sure what mIdle is, and it can change from true to false at any moment, + // if it is true after this function returns. + statemachine.multiplex(start); + U64 delta = get_clock_count() - start; + iter->add(delta); + total_clocks += delta; + if (total_clocks >= max_count) + { #ifndef LL_RELEASE_FOR_DOWNLOAD - llwarns << "AIStateMachine::mainloop did run for " << (total_clocks * 1000 / calc_clock_frequency()) << " ms." << llendl; + llwarns << "AIStateMachine::mainloop did run for " << (total_clocks * 1000 / calc_clock_frequency()) << " ms." << llendl; #endif - std::sort(active_statemachines.begin(), active_statemachines.end(), QueueElementComp()); - break; - } - } + std::sort(active_statemachines.begin(), active_statemachines.end(), QueueElementComp()); + break; + } } - // Remove idle state machines from the loop. - active_statemachines_type::iterator iter = active_statemachines.begin(); - while (iter != active_statemachines.end()) + } + // Remove idle state machines from the loop. + active_statemachines_type::iterator iter = active_statemachines.begin(); + while (iter != active_statemachines.end()) + { + AIStateMachine& statemachine(iter->statemachine()); + // Atomic test mIdle and change mActive. + bool locked = statemachine.mIdleActive.tryLock(); + // If the lock failed, then another thread is in the middle of calling cont(), + // thus mIdle will end up false. So, there is no reason to block here; just + // treat mIdle as false already. + if (locked && statemachine.mIdle) { - AIStateMachine& statemachine(iter->statemachine()); - // Atomic test mIdle and change mActive. - bool locked = statemachine.mIdleActive.tryLock(); - // If the lock failed, then another thread is in the middle of calling cont(), - // thus mIdle will end up false. So, there is no reason to block here; just - // treat mIdle as false already. - if (locked && statemachine.mIdle) - { - // Without the lock, it would be possible that another thread called cont() right here, - // changing mIdle to false again but NOT adding the statemachine to continued_statemachines, - // thinking it is in active_statemachines (and it is), while immediately below it is - // erased from active_statemachines. - statemachine.mActive = as_idle; - // Now, calling cont() is ok -- as that will cause the statemachine to be added to - // continued_statemachines, so it's fine in that case-- even necessary-- to remove it from - // active_statemachines regardless, and we can release the lock here. - statemachine.mIdleActive.unlock(); - Dout(dc::statemachine, "Erasing " << (void*)&statemachine << " from active_statemachines"); - iter = active_statemachines.erase(iter); - if (statemachine.mState == bs_killed) - { - Dout(dc::statemachine, "Deleting " << (void*)&statemachine); - delete &statemachine; - } - } - else - { - if (locked) - { - statemachine.mIdleActive.unlock(); - } - llassert(statemachine.mActive == as_active); // It should not be possible that another thread called cont() and changed this when we are we are not idle. - llassert(statemachine.mState == bs_run || statemachine.mState == bs_initialize); - ++iter; - } + // Without the lock, it would be possible that another thread called cont() right here, + // changing mIdle to false again but NOT adding the statemachine to continued_statemachines, + // thinking it is in active_statemachines (and it is), while immediately below it is + // erased from active_statemachines. + statemachine.mActive = as_idle; + // Now, calling cont() is ok -- as that will cause the statemachine to be added to + // continued_statemachines, so it's fine in that case-- even necessary-- to remove it from + // active_statemachines regardless, and we can release the lock here. + statemachine.mIdleActive.unlock(); + Dout(dc::statemachine, "Erasing " << (void*)&statemachine << " from active_statemachines"); + iter = active_statemachines.erase(iter); + if (statemachine.mState == bs_killed) + { + Dout(dc::statemachine, "Deleting " << (void*)&statemachine); + delete &statemachine; + } } - if (active_statemachines.empty()) + else { - // If this was the last state machine, remove mainloop from the IdleCallbacks. - AIReadAccess cscm_r(continued_statemachines_and_calling_mainloop); - if (cscm_r->continued_statemachines.empty() && cscm_r->calling_mainloop) - { - Dout(dc::statemachine, "Removing AIStateMachine::mainloop from gIdleCallbacks"); - AIWriteAccess(cscm_r)->calling_mainloop = false; - gIdleCallbacks.deleteFunction(&AIStateMachine::mainloop); - } + if (locked) + { + statemachine.mIdleActive.unlock(); + } + llassert(statemachine.mActive == as_active); // It should not be possible that another thread called cont() and changed this when we are we are not idle. + llassert(statemachine.mState == bs_run || statemachine.mState == bs_initialize); + ++iter; } + } + if (active_statemachines.empty()) + { + // If this was the last state machine, remove mainloop from the IdleCallbacks. + AIReadAccess cscm_r(continued_statemachines_and_calling_mainloop); + if (cscm_r->continued_statemachines.empty() && cscm_r->calling_mainloop) + { + Dout(dc::statemachine, "Removing AIStateMachine::mainloop from gIdleCallbacks"); + AIWriteAccess(cscm_r)->calling_mainloop = false; + gIdleCallbacks.deleteFunction(&AIStateMachine::mainloop); + } + } +} + +// static +void AIStateMachine::flush(void) +{ + DoutEntering(dc::curl, "AIStateMachine::flush(void)"); + add_continued_statemachines(); + // Abort all state machines. + for (active_statemachines_type::iterator iter = active_statemachines.begin(); iter != active_statemachines.end(); ++iter) + { + AIStateMachine& statemachine(iter->statemachine()); + if (statemachine.abortable()) + { + // We can't safely call abort() here for non-running (run() was called, but they we're initialized yet) statemachines, + // because that might call kill() which in some cases is undesirable (ie, when it is owned by a partent that will + // also call abort() on it when it is aborted itself). + if (statemachine.running()) + statemachine.abort(); + else + statemachine.idle(); // Stop the statemachine from starting, in the next loop with batch == 0. + } + } + for (int batch = 0;; ++batch) + { + // Run mainloop until all state machines are idle (batch == 0) or deleted (batch == 1). + for(;;) + { + { + AIReadAccess cscm_r(continued_statemachines_and_calling_mainloop); + if (!cscm_r->calling_mainloop) + break; + } + mainloop(NULL); + } + if (batch == 1) + break; + add_continued_statemachines(); + // Kill all state machines. + for (active_statemachines_type::iterator iter = active_statemachines.begin(); iter != active_statemachines.end(); ++iter) + { + AIStateMachine& statemachine(iter->statemachine()); + if (statemachine.running()) + statemachine.kill(); + } + } } diff --git a/indra/newview/statemachine/aistatemachine.h b/indra/newview/statemachine/aistatemachine.h index 48effea9a..f3dddef1d 100644 --- a/indra/newview/statemachine/aistatemachine.h +++ b/indra/newview/statemachine/aistatemachine.h @@ -207,12 +207,17 @@ class AIStateMachine { active_type mActive; //!< Whether statemachine is idle, queued to be added to the active list, or already on the active list. S64 mSleep; //!< Non-zero while the state machine is sleeping. LLMutex mIdleActive; //!< Used for atomic operations on the pair mIdle / mActive. +#ifdef SHOW_ASSERT + AIThreadID mContThread; //!< Thread that last called locked_cont(). + bool mCalledThreadUnsafeIdle; //!< Set to true when idle() is called. +#endif // Callback facilities. // From within an other state machine: AIStateMachine* mParent; //!< The parent object that started this state machine, or NULL if there isn't any. state_type mNewParentState; //!< The state at which the parent should continue upon a successful finish. bool mAbortParent; //!< If true, abort parent on abort(). Otherwise continue as normal. + bool mOnAbortSignalParent; //!< If true and mAbortParent is false, change state of parent even on abort. // From outside a state machine: struct callback_type { typedef boost::signals2::signal signal_type; @@ -228,21 +233,30 @@ class AIStateMachine { static AIThreadSafeSimpleDC sMaxCount; //!< Number of cpu clocks below which we start a new state machine within the same frame. protected: + LLMutex mSetStateLock; //!< For critical areas in set_state() and locked_cont(). + //! State of the derived class. Only valid if mState == bs_run. Call set_state to change. - state_type mRunState; + volatile state_type mRunState; public: //! Create a non-running state machine. - AIStateMachine(void) : mState(bs_initialize), mIdle(true), mAborted(true), mActive(as_idle), mSleep(0), mParent(NULL), mCallback(NULL) { updateSettings(); } + AIStateMachine(void) : mState(bs_initialize), mIdle(true), mAborted(true), mActive(as_idle), mSleep(0), mParent(NULL), mCallback(NULL) +#ifdef SHOW_ASSERT + , mContThread(AIThreadID::none), mCalledThreadUnsafeIdle(false) +#endif + { updateSettings(); } protected: //! The user should call 'kill()', not delete a AIStateMachine (derived) directly. - virtual ~AIStateMachine() { llassert(mState == bs_killed && mActive == as_idle); } + virtual ~AIStateMachine() { llassert((mState == bs_killed && mActive == as_idle) || mState == bs_initialize); } public: - //! Halt the state machine until cont() is called. + //! Halt the state machine until cont() is called (not thread-safe). void idle(void); + //! Halt the state machine until cont() is called, provided it is still in 'cur_run_state'. + void idle(state_type current_run_state); + //! Temporarily halt the state machine. void yield_frame(unsigned int frames) { mSleep = -(S64)frames; } @@ -250,15 +264,25 @@ class AIStateMachine { void yield_ms(unsigned int ms) { mSleep = get_clock_count() + calc_clock_frequency() * ms / 1000; } //! Continue running after calling idle. - void cont(void); + void cont(void) + { + mSetStateLock.lock(); + // Ignore calls to cont() if the statemachine isn't idle. See comments in set_state(). + // Calling cont() twice or after calling set_state(), without first calling idle(), is an error. + if (mState != bs_run || !mIdle) { llassert(mState != bs_run || !mContThread.equals_current_thread()); mSetStateLock.unlock(); return; } + locked_cont(); + } + private: + void locked_cont(void); + public: //--------------------------------------- // Changing the state. //! Change state to bs_run. May only be called after creation or after returning from finish(). // If parent is non-NULL, change the parent state machine's state to new_parent_state // upon finish, or in the case of an abort and when abort_parent is true, call parent->abort() instead. - void run(AIStateMachine* parent, state_type new_parent_state, bool abort_parent = true); + void run(AIStateMachine* parent, state_type new_parent_state, bool abort_parent = true, bool on_abort_signal_parent = true); //! Change state to 'bs_run'. May only be called after creation or after returning from finish(). // Does not cause a callback. @@ -326,11 +350,14 @@ class AIStateMachine { //! Return true if the derived class is running (also when we are idle). bool running(void) const { return mState == bs_run; } + //! Return true if it's safe to call abort. + bool abortable(void) const { return mState == bs_run || mState == bs_initialize; } + //! Return true if the derived class is running but idle. bool waiting(void) const { return mState == bs_run && mIdle; } // Use some safebool idiom (http://www.artima.com/cppsource/safebool.html) rather than operator bool. - typedef state_type AIStateMachine::* const bool_type; + typedef volatile state_type AIStateMachine::* const bool_type; //! Return true if state machine successfully finished. operator bool_type() const { return ((mState == bs_initialize || mState == bs_callback) && !mAborted) ? &AIStateMachine::mRunState : 0; } @@ -338,9 +365,14 @@ class AIStateMachine { char const* state_str(state_type state); private: + static void add_continued_statemachines(void); static void mainloop(void*); void multiplex(U64 current_time); + public: + //! Abort all running state machines and then run mainloop until all state machines are idle (called when application is exiting). + static void flush(void); + protected: //--------------------------------------- // Derived class implementations. diff --git a/indra/newview/statemachine/aitimer.h b/indra/newview/statemachine/aitimer.h index 06d3e2ef4..c10559429 100644 --- a/indra/newview/statemachine/aitimer.h +++ b/indra/newview/statemachine/aitimer.h @@ -70,7 +70,6 @@ class AITimer : public AIStateMachine { * @brief Set the interval after which the timer should expire. * * @param interval Amount of time in seconds before the timer will expire. - * @param True if the timer should be deleted after it expires; false means it will keep firing at regular intervals. * * Call abort() at any time to stop the timer (and delete the AITimer object). */ diff --git a/indra/plugins/base_media/CMakeLists.txt b/indra/plugins/base_media/CMakeLists.txt index 249a95bfc..f7917a794 100644 --- a/indra/plugins/base_media/CMakeLists.txt +++ b/indra/plugins/base_media/CMakeLists.txt @@ -2,6 +2,9 @@ project(media_plugin_base) +# Fail at configure, not link time. +set(OpenGL_FIND_REQUIRED ON) + include(00-Common) include(LLCommon) include(LLImage) diff --git a/indra/plugins/webkit/linux_volume_catcher.cpp b/indra/plugins/webkit/linux_volume_catcher.cpp index 0cb73ca1e..5298056fd 100644 --- a/indra/plugins/webkit/linux_volume_catcher.cpp +++ b/indra/plugins/webkit/linux_volume_catcher.cpp @@ -36,9 +36,10 @@ */ #include "linden_common.h" -# include +#include #include "volume_catcher.h" +#include "llaprpool.h" #ifndef LL_WINDOWS #include @@ -53,7 +54,6 @@ extern "C" { #include #include // There's no special reason why we want the *glib* PA mainloop, but the generic polling implementation seems broken. -#include "llaprpool.h" #include "apr_dso.h" #ifdef LL_STANDALONE #include diff --git a/indra/plugins/webkit/media_plugin_webkit.cpp b/indra/plugins/webkit/media_plugin_webkit.cpp index ebd183305..b1d0b00b0 100644 --- a/indra/plugins/webkit/media_plugin_webkit.cpp +++ b/indra/plugins/webkit/media_plugin_webkit.cpp @@ -25,8 +25,10 @@ * $/LicenseInfo$ * @endcond */ -#include "llqtwebkit.h" #include "linden_common.h" + +#include "llqtwebkit.h" + #include "indra_constants.h" // for indra keyboard codes #include "lltimer.h" @@ -160,7 +162,7 @@ private: mVolumeCatcher.pump(); checkEditState(); - + if(mInitState >= INIT_STATE_NAVIGATE_COMPLETE) { if(!mInitialNavigateURL.empty()) diff --git a/indra/test/CMakeLists.txt b/indra/test/CMakeLists.txt index 2033d6762..de440d2df 100644 --- a/indra/test/CMakeLists.txt +++ b/indra/test/CMakeLists.txt @@ -28,7 +28,6 @@ include_directories( set(test_SOURCE_FILES common.cpp inventory.cpp - io.cpp # llapp_tut.cpp # Temporarily removed until thread issues can be solved llbase64_tut.cpp llblowfish_tut.cpp diff --git a/indra/test/io.cpp b/indra/test/io.cpp deleted file mode 100644 index 9f3adb28b..000000000 --- a/indra/test/io.cpp +++ /dev/null @@ -1,1604 +0,0 @@ -/** - * @file io.cpp - * @author Phoenix - * @date 2005-10-02 - * @brief Tests for io classes and helpers - * - * $LicenseInfo:firstyear=2005&license=viewergpl$ - * - * Copyright (c) 2005-2009, Linden Research, Inc. - * - * Second Life Viewer Source Code - * The source code in this file ("Source Code") is provided by Linden Lab - * to you under the terms of the GNU General Public License, version 2.0 - * ("GPL"), unless you have obtained a separate licensing agreement - * ("Other License"), formally executed by you and Linden Lab. Terms of - * the GPL can be found in doc/GPL-license.txt in this distribution, or - * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 - * - * There are special exceptions to the terms and conditions of the GPL as - * it is applied to this Source Code. View the full text of the exception - * in the file doc/FLOSS-exception.txt in this software distribution, or - * online at - * http://secondlifegrid.net/programs/open_source/licensing/flossexception - * - * By copying, modifying or distributing this software, you acknowledge - * that you have read and understood your obligations described above, - * and agree to abide by those obligations. - * - * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO - * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, - * COMPLETENESS OR PERFORMANCE. - * $/LicenseInfo$ - */ - -#include "linden_common.h" -#include "lltut.h" - -#include - -#include "apr_pools.h" - -#include "llbuffer.h" -#include "llbufferstream.h" -#include "lliosocket.h" -#include "llioutil.h" -#include "llmemorystream.h" -#include "llpipeutil.h" -#include "llpumpio.h" -#include "llsd.h" -#include "llsdrpcclient.h" -#include "llsdrpcserver.h" -#include "llsdserialize.h" -#include "lluuid.h" -#include "llinstantmessage.h" - -namespace tut -{ - struct heap_buffer_data - { - heap_buffer_data() : mBuffer(NULL) {} - ~heap_buffer_data() { if(mBuffer) delete mBuffer; } - LLHeapBuffer* mBuffer; - }; - typedef test_group heap_buffer_test; - typedef heap_buffer_test::object heap_buffer_object; - tut::heap_buffer_test thb("heap_buffer"); - - template<> template<> - void heap_buffer_object::test<1>() - { - const S32 BUF_SIZE = 100; - mBuffer = new LLHeapBuffer(BUF_SIZE); - ensure_equals("empty buffer capacity", mBuffer->capacity(), BUF_SIZE); - const S32 SEGMENT_SIZE = 50; - LLSegment segment; - mBuffer->createSegment(0, SEGMENT_SIZE, segment); - ensure_equals("used buffer capacity", mBuffer->capacity(), BUF_SIZE); - } - - template<> template<> - void heap_buffer_object::test<2>() - { - const S32 BUF_SIZE = 10; - mBuffer = new LLHeapBuffer(BUF_SIZE); - LLSegment segment; - mBuffer->createSegment(0, BUF_SIZE, segment); - ensure("segment is in buffer", mBuffer->containsSegment(segment)); - ensure_equals("buffer consumed", mBuffer->bytesLeft(), 0); - bool created; - created = mBuffer->createSegment(0, 0, segment); - ensure("Create zero size segment fails", !created); - created = mBuffer->createSegment(0, BUF_SIZE, segment); - ensure("Create segment fails", !created); - } - - template<> template<> - void heap_buffer_object::test<3>() - { - const S32 BUF_SIZE = 10; - mBuffer = new LLHeapBuffer(BUF_SIZE); - LLSegment segment; - mBuffer->createSegment(0, BUF_SIZE, segment); - ensure("segment is in buffer", mBuffer->containsSegment(segment)); - ensure_equals("buffer consumed", mBuffer->bytesLeft(), 0); - bool reclaimed = mBuffer->reclaimSegment(segment); - ensure("buffer reclaimed.", reclaimed); - ensure_equals("buffer available", mBuffer->bytesLeft(), BUF_SIZE); - bool created; - created = mBuffer->createSegment(0, 0, segment); - ensure("Create zero size segment fails", !created); - created = mBuffer->createSegment(0, BUF_SIZE, segment); - ensure("Create another segment succeeds", created); - } - - template<> template<> - void heap_buffer_object::test<4>() - { - const S32 BUF_SIZE = 10; - const S32 SEGMENT_SIZE = 4; - mBuffer = new LLHeapBuffer(BUF_SIZE); - LLSegment seg1; - mBuffer->createSegment(0, SEGMENT_SIZE, seg1); - ensure("segment is in buffer", mBuffer->containsSegment(seg1)); - LLSegment seg2; - mBuffer->createSegment(0, SEGMENT_SIZE, seg2); - ensure("segment is in buffer", mBuffer->containsSegment(seg2)); - LLSegment seg3; - mBuffer->createSegment(0, SEGMENT_SIZE, seg3); - ensure("segment is in buffer", mBuffer->containsSegment(seg3)); - ensure_equals("segment is truncated", seg3.size(), 2); - LLSegment seg4; - bool created; - created = mBuffer->createSegment(0, SEGMENT_SIZE, seg4); - ensure("Create segment fails", !created); - bool reclaimed; - reclaimed = mBuffer->reclaimSegment(seg1); - ensure("buffer reclaim succeed.", reclaimed); - ensure_equals("no buffer available", mBuffer->bytesLeft(), 0); - reclaimed = mBuffer->reclaimSegment(seg2); - ensure("buffer reclaim succeed.", reclaimed); - ensure_equals("buffer reclaimed", mBuffer->bytesLeft(), 0); - reclaimed = mBuffer->reclaimSegment(seg3); - ensure("buffer reclaim succeed.", reclaimed); - ensure_equals("buffer reclaimed", mBuffer->bytesLeft(), BUF_SIZE); - created = mBuffer->createSegment(0, SEGMENT_SIZE, seg1); - ensure("segment is in buffer", mBuffer->containsSegment(seg1)); - ensure("Create segment succeds", created); - } -} - -namespace tut -{ - struct buffer_data - { - LLBufferArray mBuffer; - }; - typedef test_group buffer_test; - typedef buffer_test::object buffer_object; - tut::buffer_test tba("buffer_array"); - - template<> template<> - void buffer_object::test<1>() - { - const char HELLO_WORLD[] = "hello world"; - const S32 str_len = strlen(HELLO_WORLD); - LLChannelDescriptors ch = mBuffer.nextChannel(); - mBuffer.append(ch.in(), (U8*)HELLO_WORLD, str_len); - S32 count = mBuffer.countAfter(ch.in(), NULL); - ensure_equals("total append size", count, str_len); - LLBufferArray::segment_iterator_t it = mBuffer.beginSegment(); - U8* first = (*it).data(); - count = mBuffer.countAfter(ch.in(), first); - ensure_equals("offset append size", count, str_len - 1); - } - - template<> template<> - void buffer_object::test<2>() - { - const char HELLO_WORLD[] = "hello world"; - const S32 str_len = strlen(HELLO_WORLD); /* Flawfinder: ignore */ - LLChannelDescriptors ch = mBuffer.nextChannel(); - mBuffer.append(ch.in(), (U8*)HELLO_WORLD, str_len); - mBuffer.append(ch.in(), (U8*)HELLO_WORLD, str_len); - S32 count = mBuffer.countAfter(ch.in(), NULL); - ensure_equals("total append size", count, 2 * str_len); - LLBufferArray::segment_iterator_t it = mBuffer.beginSegment(); - U8* first = (*it).data(); - count = mBuffer.countAfter(ch.in(), first); - ensure_equals("offset append size", count, (2 * str_len) - 1); - } - - template<> template<> - void buffer_object::test<3>() - { - const char ONE[] = "one"; - const char TWO[] = "two"; - std::string expected(ONE); - expected.append(TWO); - LLChannelDescriptors ch = mBuffer.nextChannel(); - mBuffer.append(ch.in(), (U8*)ONE, 3); - mBuffer.append(ch.in(), (U8*)TWO, 3); - char buffer[255]; /* Flawfinder: ignore */ - S32 len = 6; - mBuffer.readAfter(ch.in(), NULL, (U8*)buffer, len); - ensure_equals(len, 6); - buffer[len] = '\0'; - std::string actual(buffer); - ensure_equals("read", actual, expected); - } - - template<> template<> - void buffer_object::test<4>() - { - const char ONE[] = "one"; - const char TWO[] = "two"; - std::string expected(ONE); - expected.append(TWO); - LLChannelDescriptors ch = mBuffer.nextChannel(); - mBuffer.append(ch.in(), (U8*)TWO, 3); - mBuffer.prepend(ch.in(), (U8*)ONE, 3); - char buffer[255]; /* Flawfinder: ignore */ - S32 len = 6; - mBuffer.readAfter(ch.in(), NULL, (U8*)buffer, len); - ensure_equals(len, 6); - buffer[len] = '\0'; - std::string actual(buffer); - ensure_equals("read", actual, expected); - } - - template<> template<> - void buffer_object::test<5>() - { - const char ONE[] = "one"; - const char TWO[] = "two"; - std::string expected("netwo"); - LLChannelDescriptors ch = mBuffer.nextChannel(); - mBuffer.append(ch.in(), (U8*)TWO, 3); - mBuffer.prepend(ch.in(), (U8*)ONE, 3); - char buffer[255]; /* Flawfinder: ignore */ - S32 len = 5; - LLBufferArray::segment_iterator_t it = mBuffer.beginSegment(); - U8* addr = (*it).data(); - mBuffer.readAfter(ch.in(), addr, (U8*)buffer, len); - ensure_equals(len, 5); - buffer[len] = '\0'; - std::string actual(buffer); - ensure_equals("read", actual, expected); - } - - template<> template<> - void buffer_object::test<6>() - { - std::string request("The early bird catches the worm."); - std::string response("If you're a worm, sleep late."); - std::ostringstream expected; - expected << "ContentLength: " << response.length() << "\r\n\r\n" - << response; - LLChannelDescriptors ch = mBuffer.nextChannel(); - mBuffer.append(ch.in(), (U8*)request.c_str(), request.length()); - mBuffer.append(ch.out(), (U8*)response.c_str(), response.length()); - S32 count = mBuffer.countAfter(ch.out(), NULL); - std::ostringstream header; - header << "ContentLength: " << count << "\r\n\r\n"; - std::string head(header.str()); - mBuffer.prepend(ch.out(), (U8*)head.c_str(), head.length()); - char buffer[1024]; /* Flawfinder: ignore */ - S32 len = response.size() + head.length(); - ensure_equals("same length", len, (S32)expected.str().length()); - mBuffer.readAfter(ch.out(), NULL, (U8*)buffer, len); - buffer[len] = '\0'; - std::string actual(buffer); - ensure_equals("threaded writes", actual, expected.str()); - } - - template<> template<> - void buffer_object::test<7>() - { - const S32 LINE_COUNT = 3; - std::string lines[LINE_COUNT] = - { - std::string("GET /index.htm HTTP/1.0\r\n"), - std::string("User-Agent: Wget/1.9.1\r\n"), - std::string("Host: localhost:8008\r\n") - }; - std::string text; - S32 i; - for(i = 0; i < LINE_COUNT; ++i) - { - text.append(lines[i]); - } - LLChannelDescriptors ch = mBuffer.nextChannel(); - mBuffer.append(ch.in(), (U8*)text.c_str(), text.length()); - const S32 BUFFER_LEN = 1024; - char buf[BUFFER_LEN]; - S32 len; - U8* last = NULL; - std::string last_line; - for(i = 0; i < LINE_COUNT; ++i) - { - len = BUFFER_LEN; - last = mBuffer.readAfter(ch.in(), last, (U8*)buf, len); - char* newline = strchr((char*)buf, '\n'); - S32 offset = -((len - 1) - (newline - buf)); - ++newline; - *newline = '\0'; - last_line.assign(buf); - std::ostringstream message; - message << "line reads in line[" << i << "]"; - ensure_equals(message.str().c_str(), last_line, lines[i]); - last = mBuffer.seek(ch.in(), last, offset); - } - } - - template<> template<> - void buffer_object::test<8>() - { - LLChannelDescriptors ch = mBuffer.nextChannel(); - mBuffer.append(ch.in(), (U8*)"1", 1); - LLBufferArray buffer; - buffer.append(ch.in(), (U8*)"2", 1); - mBuffer.takeContents(buffer); - mBuffer.append(ch.in(), (U8*)"3", 1); - S32 count = mBuffer.countAfter(ch.in(), NULL); - ensure_equals("buffer size", count, 3); - U8* temp = new U8[count]; - mBuffer.readAfter(ch.in(), NULL, temp, count); - ensure("buffer content", (0 == memcmp(temp, (void*)"123", 3))); - delete[] temp; - } - - template<> template<> - void buffer_object::test<9>() - { - LLChannelDescriptors ch = mBuffer.nextChannel(); - mBuffer.append(ch.in(), (U8*)"1", 1); - S32 capacity = mBuffer.capacity(); - ensure("has capacity", capacity > 0); - U8* temp = new U8[capacity - 1]; - mBuffer.append(ch.in(), temp, capacity - 1); - capacity = mBuffer.capacity(); - ensure("has capacity when full", capacity > 0); - S32 used = mBuffer.countAfter(ch.in(), NULL); - ensure_equals("used equals capacity", used, capacity); - - LLBufferArray::segment_iterator_t iter = mBuffer.beginSegment(); - while(iter != mBuffer.endSegment()) - { - mBuffer.eraseSegment(iter++); - } - - used = mBuffer.countAfter(ch.in(), NULL); - ensure_equals("used is zero", used, 0); - S32 capacity2 = mBuffer.capacity(); - ensure_equals("capacity the same after erase", capacity2, capacity); - mBuffer.append(ch.in(), temp, capacity - 1); - capacity2 = mBuffer.capacity(); - ensure_equals("capacity the same after append", capacity2, capacity); - - delete[] temp; - } - -#if 0 - template<> template<> - void buffer_object::test<9>() - { - char buffer[1024]; /* Flawfinder: ignore */ - S32 size = sprintf(buffer, - "%d|%d|%s|%s|%s|%s|%s|%x|%x|%x|%x|%x|%s|%s|%d|%d|%x", - 7, - 7, - "Hang Glider INFO", - "18e84d1e-04a4-4c0d-8cb6-6c73477f0a9a", - "0e346d8b-4433-4d66-a6b0-fd37083abc4c", - "0e346d8b-4433-4d66-a6b0-fd37083abc4c", - "00000000-0000-0000-0000-000000000000", - 0x7fffffff, - 0x7fffffff, - 0, - 0, - 0x7fffffff, - "69e0d357-2e7c-8990-a2bc-7f61c868e5a3", - "2004-06-04 16:09:17 note card", - 0, - 10, - 0) + 1; - - //const char* expected = "7|7|Hang Glider INFO|18e84d1e-04a4-4c0d-8cb6-6c73477f0a9a|0e346d8b-4433-4d66-a6b0-fd37083abc4c|0e346d8b-4433-4d66-a6b0-fd37083abc4c|00000000-0000-0000-0000-000000000000|7fffffff|7fffffff|0|0|7fffffff|69e0d357-2e7c-8990-a2bc-7f61c868e5a3|2004-06-04 16:09:17 note card|0|10|0\0"; - - LLSD* bin_bucket = LLIMInfo::buildSDfrombuffer((U8*)buffer,size); - - char post_buffer[1024]; - U32 post_size; - LLIMInfo::getBinaryBucket(bin_bucket,(U8*)post_buffer,post_size); - ensure_equals("Buffer sizes",size,(S32)post_size); - ensure("Buffer content",!strcmp(buffer,post_buffer)); - } -#endif - - /* - template<> template<> - void buffer_object::test<>() - { - } - */ -} - -namespace tut -{ - struct buffer_and_stream_data - { - LLBufferArray mBuffer; - }; - typedef test_group bas_test; - typedef bas_test::object bas_object; - tut::bas_test tbs("buffer_stream"); - - template<> template<> - void bas_object::test<1>() - { - const char HELLO_WORLD[] = "hello world"; - const S32 str_len = strlen(HELLO_WORLD); /* Flawfinder: ignore */ - LLChannelDescriptors ch = mBuffer.nextChannel(); - LLBufferStream str(ch, &mBuffer); - mBuffer.append(ch.in(), (U8*)HELLO_WORLD, str_len); - std::string hello; - std::string world; - str >> hello >> world; - ensure_equals("first word", hello, std::string("hello")); - ensure_equals("second word", world, std::string("world")); - } - - template<> template<> - void bas_object::test<2>() - { - std::string part1("Eat my shor"); - std::string part2("ts ho"); - std::string part3("mer"); - std::string ignore("ignore me"); - LLChannelDescriptors ch = mBuffer.nextChannel(); - LLBufferStream str(ch, &mBuffer); - mBuffer.append(ch.in(), (U8*)part1.c_str(), part1.length()); - mBuffer.append(ch.in(), (U8*)part2.c_str(), part2.length()); - mBuffer.append(ch.out(), (U8*)ignore.c_str(), ignore.length()); - mBuffer.append(ch.in(), (U8*)part3.c_str(), part3.length()); - std::string eat; - std::string my; - std::string shorts; - std::string homer; - str >> eat >> my >> shorts >> homer; - ensure_equals("word1", eat, std::string("Eat")); - ensure_equals("word2", my, std::string("my")); - ensure_equals("word3", shorts, std::string("shorts")); - ensure_equals("word4", homer, std::string("homer")); - } - - template<> template<> - void bas_object::test<3>() - { - std::string part1("junk in "); - std::string part2("the trunk"); - const S32 CHANNEL = 0; - mBuffer.append(CHANNEL, (U8*)part1.c_str(), part1.length()); - mBuffer.append(CHANNEL, (U8*)part2.c_str(), part2.length()); - U8* last = 0; - const S32 BUF_LEN = 128; - char buf[BUF_LEN]; - S32 len = 11; - last = mBuffer.readAfter(CHANNEL, last, (U8*)buf, len); - buf[len] = '\0'; - std::string actual(buf); - ensure_equals("first read", actual, std::string("junk in the")); - last = mBuffer.seek(CHANNEL, last, -6); - len = 12; - last = mBuffer.readAfter(CHANNEL, last, (U8*)buf, len); - buf[len] = '\0'; - actual.assign(buf); - ensure_equals("seek and read", actual, std::string("in the trunk")); - } - - template<> template<> - void bas_object::test<4>() - { - std::string phrase("zippity do da!"); - const S32 CHANNEL = 0; - mBuffer.append(CHANNEL, (U8*)phrase.c_str(), phrase.length()); - const S32 BUF_LEN = 128; - char buf[BUF_LEN]; - S32 len = 7; - U8* last = mBuffer.readAfter(CHANNEL, NULL, (U8*)buf, len); - mBuffer.splitAfter(last); - LLBufferArray::segment_iterator_t it = mBuffer.beginSegment(); - LLBufferArray::segment_iterator_t end = mBuffer.endSegment(); - std::string first((char*)((*it).data()), (*it).size()); - ensure_equals("first part", first, std::string("zippity")); - ++it; - std::string second((char*)((*it).data()), (*it).size()); - ensure_equals("second part", second, std::string(" do da!")); - ++it; - ensure("iterators equal", (it == end)); - } - - template<> template<> - void bas_object::test<5>() - { - LLChannelDescriptors ch = mBuffer.nextChannel(); - LLBufferStream str(ch, &mBuffer); - std::string h1("hello"); - std::string h2(", how are you doing?"); - std::string expected(h1); - expected.append(h2); - str << h1 << h2; - str.flush(); - const S32 BUF_LEN = 128; - char buf[BUF_LEN]; - S32 actual_len = BUF_LEN; - S32 expected_len = h1.size() + h2.size(); - (void) mBuffer.readAfter(ch.out(), NULL, (U8*)buf, actual_len); - ensure_equals("streamed size", actual_len, expected_len); - buf[actual_len] = '\0'; - std::string actual(buf); - ensure_equals("streamed to buf", actual, expected); - } - - template<> template<> - void bas_object::test<6>() - { - LLChannelDescriptors ch = mBuffer.nextChannel(); - LLBufferStream bstr(ch, &mBuffer); - std::ostringstream ostr; - std::vector ids; - LLUUID id; - for(int i = 0; i < 5; ++i) - { - id.generate(); - ids.push_back(id); - } - bstr << "SELECT concat(u.username, ' ', l.name) " - << "FROM user u, user_last_name l " - << "WHERE u.last_name_id = l.last_name_id" - << " AND u.agent_id IN ('"; - ostr << "SELECT concat(u.username, ' ', l.name) " - << "FROM user u, user_last_name l " - << "WHERE u.last_name_id = l.last_name_id" - << " AND u.agent_id IN ('"; - std::copy( - ids.begin(), - ids.end(), - std::ostream_iterator(bstr, "','")); - std::copy( - ids.begin(), - ids.end(), - std::ostream_iterator(ostr, "','")); - bstr.seekp(-2, std::ios::cur); - ostr.seekp(-2, std::ios::cur); - bstr << ") "; - ostr << ") "; - bstr.flush(); - const S32 BUF_LEN = 512; - char buf[BUF_LEN]; /* Flawfinder: ignore */ - S32 actual_len = BUF_LEN; - (void) mBuffer.readAfter(ch.out(), NULL, (U8*)buf, actual_len); - buf[actual_len] = '\0'; - std::string actual(buf); - std::string expected(ostr.str()); - ensure_equals("size of string in seek",actual.size(),expected.size()); - ensure_equals("seek in ostream", actual, expected); - } - - template<> template<> - void bas_object::test<7>() - { - LLChannelDescriptors ch = mBuffer.nextChannel(); - LLBufferStream bstr(ch, &mBuffer); - bstr << "1"; - bstr.flush(); - S32 count = mBuffer.countAfter(ch.out(), NULL); - ensure_equals("buffer size 1", count, 1); - LLBufferArray buffer; - buffer.append(ch.out(), (U8*)"2", 1); - mBuffer.takeContents(buffer); - count = mBuffer.countAfter(ch.out(), NULL); - ensure_equals("buffer size 2", count, 2); - bstr << "3"; - bstr.flush(); - count = mBuffer.countAfter(ch.out(), NULL); - ensure_equals("buffer size 3", count, 3); - U8* temp = new U8[count]; - mBuffer.readAfter(ch.out(), NULL, temp, count); - ensure("buffer content", (0 == memcmp(temp, (void*)"123", 3))); - delete[] temp; - } - - template<> template<> - void bas_object::test<8>() - { - LLChannelDescriptors ch = mBuffer.nextChannel(); - LLBufferStream ostr(ch, &mBuffer); - typedef std::vector buf_t; - typedef std::vector actual_t; - actual_t actual; - buf_t source; - bool need_comma = false; - ostr << "["; - S32 total_size = 1; - for(S32 i = 2000; i < 2003; ++i) - { - if(need_comma) - { - ostr << ","; - ++total_size; - } - need_comma = true; - srand(69 + i); /* Flawfinder: ignore */ - S32 size = rand() % 1000 + 1000; - std::generate_n( - std::back_insert_iterator(source), - size, - rand); - actual.push_back(source); - ostr << "b(" << size << ")\""; - total_size += 8; - ostr.write((const char*)(&source[0]), size); - total_size += size; - source.clear(); - ostr << "\""; - ++total_size; - } - ostr << "]"; - ++total_size; - ostr.flush(); - - // now that we have a bunch of data on a stream, parse it all. - ch = mBuffer.nextChannel(); - S32 count = mBuffer.countAfter(ch.in(), NULL); - ensure_equals("size of buffer", count, total_size); - LLBufferStream istr(ch, &mBuffer); - LLSD data; - count = LLSDSerialize::fromNotation(data, istr, total_size); - ensure("sd parsed", data.isDefined()); - - for(S32 j = 0; j < 3; ++j) - { - std::ostringstream name; - LLSD child(data[j]); - name << "found buffer " << j; - ensure(name.str(), child.isDefined()); - source = child.asBinary(); - name.str(""); - name << "buffer " << j << " size"; - ensure_equals(name.str().c_str(), source.size(), actual[j].size()); - name.str(""); - name << "buffer " << j << " contents"; - ensure( - name.str(), - (0 == memcmp(&source[0], &actual[j][0], source.size()))); - } - } - - template<> template<> - void bas_object::test<9>() - { - LLChannelDescriptors ch = mBuffer.nextChannel(); - LLBufferStream ostr(ch, &mBuffer); - typedef std::vector buf_t; - buf_t source; - bool need_comma = false; - ostr << "{"; - S32 total_size = 1; - for(S32 i = 1000; i < 3000; ++i) - { - if(need_comma) - { - ostr << ","; - ++total_size; - } - need_comma = true; - ostr << "'" << i << "':"; - total_size += 7; - srand(69 + i); /* Flawfinder: ignore */ - S32 size = rand() % 1000 + 1000; - std::generate_n( - std::back_insert_iterator(source), - size, - rand); - ostr << "b(" << size << ")\""; - total_size += 8; - ostr.write((const char*)(&source[0]), size); - total_size += size; - source.clear(); - ostr << "\""; - ++total_size; - } - ostr << "}"; - ++total_size; - ostr.flush(); - - // now that we have a bunch of data on a stream, parse it all. - ch = mBuffer.nextChannel(); - S32 count = mBuffer.countAfter(ch.in(), NULL); - ensure_equals("size of buffer", count, total_size); - LLBufferStream istr(ch, &mBuffer); - LLSD data; - count = LLSDSerialize::fromNotation(data, istr, total_size); - ensure("sd parsed", data.isDefined()); - } - - template<> template<> - void bas_object::test<10>() - { -//#if LL_WINDOWS && _MSC_VER >= 1400 -// skip_fail("Fails on VS2005 due to broken LLSDSerialize::fromNotation() parser."); -//#endif - const char LOGIN_STREAM[] = "{'method':'login', 'parameter': [ {" - "'uri': 'sl-am:kellys.region.siva.lindenlab.com/location?start=url&px=128&py=128&pz=128&lx=0&ly=0&lz=0'}, " - "{'version': i1}, {'texture_data': [ '61d724fb-ad79-f637-2186-5cf457560daa', '6e38b9be-b7cc-e77a-8aec-029a42b0b416', " - "'a9073524-e89b-2924-ca6e-a81944109a1a', '658f18b5-5f1e-e593-f5d5-36c3abc7249a', '0cc799f4-8c99-6b91-bd75-b179b12429e2', " - "'59fd9b64-8300-a425-aad8-2ffcbe9a49d2', '59fd9b64-8300-a425-aad8-2ffcbe9a49d2', '5748decc-f629-461c-9a36-a35a221fe21f', " - "'b8fc9be2-26a6-6b47-690b-0e902e983484', 'a13ca0fe-3802-dc97-e79a-70d12171c724', 'dd9643cf-fd5d-0376-ed4a-b1cc646a97d5', " - "'4ad13ae9-a112-af09-210a-cf9353a7a9e7', 'c228d1cf-4b5d-4ba8-84f4-899a0796aa97', 'c228d1cf-4b5d-4ba8-84f4-899a0796aa97', " - "'c228d1cf-4b5d-4ba8-84f4-899a0796aa97', 'c228d1cf-4b5d-4ba8-84f4-899a0796aa97', 'c228d1cf-4b5d-4ba8-84f4-899a0796aa97', " - "'5748decc-f629-461c-9a36-a35a221fe21f', 'c228d1cf-4b5d-4ba8-84f4-899a0796aa97', 'c228d1cf-4b5d-4ba8-84f4-899a0796aa97']," - "'session_id': '324cfa9f-fe5d-4d1c-a317-35f20a86a4d1','position': [ i128, i128, i128],'last_name': 'Linden','group_title': '-> !BLING! <-','group_name': 'test!','agent_access': 'M'," - "'attachment_data': [ {'asset_id': 'aaede2b1-9955-09d4-5c93-2b557c778cf3','attachment_point': i6,'item_id': 'f3694abc-5122-db33-73d9-e0f4288dc2bf'}]," - "'buddy_ids': [ '101358d5-469d-4b24-9b85-4dc3c05e635d', '1b00fec7-6265-4875-acac-80d9cfe9295c', '203ad6df-b522-491d-ba48-4e24eb57aeff', " - "'22d4dcdb-aebb-47fa-b925-a871cc75ee48','27da3df5-1339-4463-80aa-40504ee3b3e5', '299d1720-b61f-4268-8c29-9614aa2d44c2', " - "'2b048a24-2737-4994-9fa5-becc8e466253', '2cd5dc14-a853-49a4-be3c-a5a7178e37bc', '3de548e1-57be-cfea-2b78-83ae3ad95998', " - "'3dee98e4-a6a3-4543-91c3-bbd528447ba7', '3e2d81a3-6263-6ffe-ad5c-8ce04bee07e9', '40e70b98-fed7-47f3-9700-1bce93f9350b', " - "'50a9b68e-b5aa-4d35-9137-3cfebda0a15c', '54295571-9357-43ff-ae74-a83b5138160f', '6191e2d7-5f96-4856-bdab-af0f79f47ae4', " - "'63e577d8-cd34-4235-a0a3-de0500133364', '79cfb666-4fd0-4af7-95df-fb7d96b4e24d', '8121c2f3-4a88-4c33-9899-8fc1273f47ee', " - "'909da964-ef23-4f2a-ba13-f2a8cfd454b6','a2e76fcd-9360-4f6d-a924-000000000001', 'aaa6d664-527e-4d83-9cbb-7ef79ccc7cc8', " - "'b79bfb6c-23be-49eb-b35b-30ff2f501b37', 'ba0d9c79-148c-4a79-8e3c-0665eebe2427', 'bc9bda98-57cd-498f-b993-4ff1ac9dec93', " - "'c62d16f6-81cb-419d-9cac-e46dc394084d', 'd48f8fa7-2512-4fe5-80c8-c0a923412e07', 'd77e3e24-7e6c-4c3f-96d0-a1746337f8fb', " - "'da615c63-a84b-4592-a3d6-a90dd3e92e6e', 'df47190a-7eb7-4aff-985f-2d1d3ad6c6e9', 'e3380196-72cd-499c-a2ba-caa180bd5fe4', " - "'e937863f-f134-4207-803b-d6e686651d6c', 'efcdf98b-5269-45ef-ac7a-0671f09ea9d9']," - "'circuit_code': i124,'group_id': '8615c885-9cf0-bf0a-6e40-0c11462aa652','limited_to_estate': i1,'look_at': [ i0, i0, i0]," - "'agent_id': '0e346d8b-4433-4d66-a6b0-fd37083abc4c','first_name': 'Kelly','start': 'url'}]}"; - LLChannelDescriptors ch = mBuffer.nextChannel(); - mBuffer.append(ch.out(), (U8*)LOGIN_STREAM, strlen(LOGIN_STREAM)); /* Flawfinder: ignore */ - ch = mBuffer.nextChannel(); - LLBufferStream istr(ch, &mBuffer); - LLSD data; - S32 count = LLSDSerialize::fromNotation( - data, - istr, - mBuffer.count(ch.in())); - ensure("parsed something", (count > 0)); - ensure("sd parsed", data.isDefined()); - ensure_equals("sd type", data.type(), LLSD::TypeMap); - ensure("has method", data.has("method")); - ensure("has parameter", data.has("parameter")); - LLSD parameter = data["parameter"]; - ensure_equals("parameter is array", parameter.type(), LLSD::TypeArray); - LLSD agent_params = parameter[2]; - std::string s_value; - s_value = agent_params["last_name"].asString(); - ensure_equals("last name", s_value, std::string("Linden")); - s_value = agent_params["first_name"].asString(); - ensure_equals("first name", s_value, std::string("Kelly")); - s_value = agent_params["agent_access"].asString(); - ensure_equals("agent access", s_value, std::string("M")); - s_value = agent_params["group_name"].asString(); - ensure_equals("group name", s_value, std::string("test!")); - s_value = agent_params["group_title"].asString(); - ensure_equals("group title", s_value, std::string("-> !BLING! <-")); - - LLUUID agent_id("0e346d8b-4433-4d66-a6b0-fd37083abc4c"); - LLUUID id = agent_params["agent_id"]; - ensure_equals("agent id", id, agent_id); - LLUUID session_id("324cfa9f-fe5d-4d1c-a317-35f20a86a4d1"); - id = agent_params["session_id"]; - ensure_equals("session id", id, session_id); - LLUUID group_id ("8615c885-9cf0-bf0a-6e40-0c11462aa652"); - id = agent_params["group_id"]; - ensure_equals("group id", id, group_id); - - S32 i_val = agent_params["limited_to_estate"]; - ensure_equals("limited to estate", i_val, 1); - i_val = agent_params["circuit_code"]; - ensure_equals("circuit code", i_val, 124); - } - - - template<> template<> - void bas_object::test<11>() - { - std::string val = "{!'foo'@:#'bar'}"; - std::istringstream istr; - istr.str(val); - LLSD sd; - S32 count = LLSDSerialize::fromNotation(sd, istr, val.size()); - ensure_equals("parser error return value", count, -1); - ensure("data undefined", sd.isUndefined()); - } - - template<> template<> - void bas_object::test<12>() - { -//#if LL_WINDOWS && _MSC_VER >= 1400 -// skip_fail("Fails on VS2005 due to broken LLSDSerialize::fromNotation() parser."); -//#endif - std::string val = "{!'foo':[i1,'hi',{@'bar'#:[$i2%,^'baz'&]*}+]=}"; - std::istringstream istr; - istr.str(val); - LLSD sd; - S32 count = LLSDSerialize::fromNotation(sd, istr, val.size()); - ensure_equals("parser error return value", count, -1); - ensure("data undefined", sd.isUndefined()); - } - -/* - template<> template<> - void bas_object::test<13>() - { - } - template<> template<> - void bas_object::test<14>() - { - } - template<> template<> - void bas_object::test<15>() - { - } -*/ -} - - -namespace tut -{ - class PumpAndChainTestData - { - protected: - apr_pool_t* mPool; - LLPumpIO* mPump; - LLPumpIO::chain_t mChain; - - public: - PumpAndChainTestData() - { - apr_pool_create(&mPool, NULL); - mPump = new LLPumpIO(mPool); - } - - ~PumpAndChainTestData() - { - mChain.clear(); - delete mPump; - apr_pool_destroy(mPool); - } - }; - typedef test_group PumpAndChainTestGroup; - typedef PumpAndChainTestGroup::object PumpAndChainTestObject; - PumpAndChainTestGroup pumpAndChainTestGroup("pump_and_chain"); - - template<> template<> - void PumpAndChainTestObject::test<1>() - { - LLPipeStringExtractor* extractor = new LLPipeStringExtractor(); - - mChain.push_back(LLIOPipe::ptr_t(new LLIOFlush)); - mChain.push_back(LLIOPipe::ptr_t(extractor)); - - LLTimer timer; - timer.setTimerExpirySec(100.0f); - - mPump->addChain(mChain, DEFAULT_CHAIN_EXPIRY_SECS); - while(!extractor->done() && !timer.hasExpired()) - { - mPump->pump(); - mPump->callback(); - } - - ensure("reading string finished", extractor->done()); - ensure_equals("string was empty", extractor->string(), ""); - } -} - -/* -namespace tut -{ - struct double_construct - { - public: - double_construct() - { - llinfos << "constructed" << llendl; - } - ~double_construct() - { - llinfos << "destroyed" << llendl; - } - }; - typedef test_group double_construct_test_group; - typedef double_construct_test_group::object dc_test_object; - double_construct_test_group dctest("double construct"); - template<> template<> - void dc_test_object::test<1>() - { - ensure("test 1", true); - } -} -*/ - -namespace tut -{ - /** - * @brief we want to test the pipes & pumps under bad conditions. - */ - struct pipe_and_pump_fitness - { - public: - enum - { - SERVER_LISTEN_PORT = 13050 - }; - - pipe_and_pump_fitness() - { - LLFrameTimer::updateFrameTime(); - apr_pool_create(&mPool, NULL); - mPump = new LLPumpIO(mPool); - mSocket = LLSocket::create( - mPool, - LLSocket::STREAM_TCP, - SERVER_LISTEN_PORT); - } - - ~pipe_and_pump_fitness() - { - mSocket.reset(); - delete mPump; - apr_pool_destroy(mPool); - } - - protected: - apr_pool_t* mPool; - LLPumpIO* mPump; - LLSocket::ptr_t mSocket; - }; - typedef test_group fitness_test_group; - typedef fitness_test_group::object fitness_test_object; - fitness_test_group fitness("pipe and pump fitness"); - - template<> template<> - void fitness_test_object::test<1>() - { - lldebugs << "fitness_test_object::test<1>()" << llendl; - - // Set up the server - //lldebugs << "fitness_test_object::test<1> - setting up server." - // << llendl; - LLPumpIO::chain_t chain; - typedef LLCloneIOFactory emitter_t; - emitter_t* emitter = new emitter_t( - new LLPipeStringInjector("suckers never play me")); - boost::shared_ptr factory(emitter); - LLIOServerSocket* server = new LLIOServerSocket( - mPool, - mSocket, - factory); - server->setResponseTimeout(SHORT_CHAIN_EXPIRY_SECS); - chain.push_back(LLIOPipe::ptr_t(server)); - mPump->addChain(chain, NEVER_CHAIN_EXPIRY_SECS); - - // We need to tickle the pump a little to set up the listen() - //lldebugs << "fitness_test_object::test<1> - initializing server." - // << llendl; - pump_loop(mPump, 0.1f); - - // Set up the client - //lldebugs << "fitness_test_object::test<1> - connecting client." - // << llendl; - LLSocket::ptr_t client = LLSocket::create(mPool, LLSocket::STREAM_TCP); - LLHost server_host("127.0.0.1", SERVER_LISTEN_PORT); - bool connected = client->blockingConnect(server_host); - ensure("Connected to server", connected); - lldebugs << "connected" << llendl; - - // We have connected, since the socket reader does not block, - // the first call to read data will return EAGAIN, so we need - // to write something. - chain.clear(); - chain.push_back(LLIOPipe::ptr_t(new LLPipeStringInjector("hi"))); - chain.push_back(LLIOPipe::ptr_t(new LLIOSocketWriter(client))); - chain.push_back(LLIOPipe::ptr_t(new LLIONull)); - mPump->addChain(chain, 1.0f); - - // Now, the server should immediately send the data, but we'll - // never read it. pump for a bit - F32 elapsed = pump_loop(mPump, 2.0f); - ensure("Did not take too long", (elapsed < 3.0f)); - } - - template<> template<> - void fitness_test_object::test<2>() - { - lldebugs << "fitness_test_object::test<2>()" << llendl; - - // Set up the server - LLPumpIO::chain_t chain; - typedef LLCloneIOFactory emitter_t; - emitter_t* emitter = new emitter_t(new LLIOFuzz(1000000)); - boost::shared_ptr factory(emitter); - LLIOServerSocket* server = new LLIOServerSocket( - mPool, - mSocket, - factory); - server->setResponseTimeout(SHORT_CHAIN_EXPIRY_SECS); - chain.push_back(LLIOPipe::ptr_t(server)); - mPump->addChain(chain, NEVER_CHAIN_EXPIRY_SECS); - - // We need to tickle the pump a little to set up the listen() - pump_loop(mPump, 0.1f); - - // Set up the client - LLSocket::ptr_t client = LLSocket::create(mPool, LLSocket::STREAM_TCP); - LLHost server_host("127.0.0.1", SERVER_LISTEN_PORT); - bool connected = client->blockingConnect(server_host); - ensure("Connected to server", connected); - lldebugs << "connected" << llendl; - - // We have connected, since the socket reader does not block, - // the first call to read data will return EAGAIN, so we need - // to write something. - chain.clear(); - chain.push_back(LLIOPipe::ptr_t(new LLPipeStringInjector("hi"))); - chain.push_back(LLIOPipe::ptr_t(new LLIOSocketWriter(client))); - chain.push_back(LLIOPipe::ptr_t(new LLIONull)); - mPump->addChain(chain, SHORT_CHAIN_EXPIRY_SECS / 2.0f); - - // Now, the server should immediately send the data, but we'll - // never read it. pump for a bit - F32 elapsed = pump_loop(mPump, SHORT_CHAIN_EXPIRY_SECS * 2.0f); - ensure("Did not take too long", (elapsed < 3.0f)); - } - - template<> template<> - void fitness_test_object::test<3>() - { - lldebugs << "fitness_test_object::test<3>()" << llendl; - - // Set up the server - LLPumpIO::chain_t chain; - typedef LLCloneIOFactory emitter_t; - emitter_t* emitter = new emitter_t(new LLIOFuzz(1000000)); - boost::shared_ptr factory(emitter); - LLIOServerSocket* server = new LLIOServerSocket( - mPool, - mSocket, - factory); - server->setResponseTimeout(SHORT_CHAIN_EXPIRY_SECS); - chain.push_back(LLIOPipe::ptr_t(server)); - mPump->addChain(chain, NEVER_CHAIN_EXPIRY_SECS); - - // We need to tickle the pump a little to set up the listen() - pump_loop(mPump, 0.1f); - - // Set up the client - LLSocket::ptr_t client = LLSocket::create(mPool, LLSocket::STREAM_TCP); - LLHost server_host("127.0.0.1", SERVER_LISTEN_PORT); - bool connected = client->blockingConnect(server_host); - ensure("Connected to server", connected); - lldebugs << "connected" << llendl; - - // We have connected, since the socket reader does not block, - // the first call to read data will return EAGAIN, so we need - // to write something. - chain.clear(); - chain.push_back(LLIOPipe::ptr_t(new LLPipeStringInjector("hi"))); - chain.push_back(LLIOPipe::ptr_t(new LLIOSocketWriter(client))); - chain.push_back(LLIOPipe::ptr_t(new LLIONull)); - mPump->addChain(chain, SHORT_CHAIN_EXPIRY_SECS * 2.0f); - - // Now, the server should immediately send the data, but we'll - // never read it. pump for a bit - F32 elapsed = pump_loop(mPump, SHORT_CHAIN_EXPIRY_SECS * 2.0f + 1.0f); - ensure("Did not take too long", (elapsed < 4.0f)); - } - - template<> template<> - void fitness_test_object::test<4>() - { - lldebugs << "fitness_test_object::test<4>()" << llendl; - - // Set up the server - LLPumpIO::chain_t chain; - typedef LLCloneIOFactory emitter_t; - emitter_t* emitter = new emitter_t(new LLIOFuzz(1000000)); - boost::shared_ptr factory(emitter); - LLIOServerSocket* server = new LLIOServerSocket( - mPool, - mSocket, - factory); - server->setResponseTimeout(SHORT_CHAIN_EXPIRY_SECS + 1.80f); - chain.push_back(LLIOPipe::ptr_t(server)); - mPump->addChain(chain, NEVER_CHAIN_EXPIRY_SECS); - - // We need to tickle the pump a little to set up the listen() - pump_loop(mPump, 0.1f); - - // Set up the client - LLSocket::ptr_t client = LLSocket::create(mPool, LLSocket::STREAM_TCP); - LLHost server_host("127.0.0.1", SERVER_LISTEN_PORT); - bool connected = client->blockingConnect(server_host); - ensure("Connected to server", connected); - lldebugs << "connected" << llendl; - - // We have connected, since the socket reader does not block, - // the first call to read data will return EAGAIN, so we need - // to write something. - chain.clear(); - chain.push_back(LLIOPipe::ptr_t(new LLPipeStringInjector("hi"))); - chain.push_back(LLIOPipe::ptr_t(new LLIOSocketWriter(client))); - chain.push_back(LLIOPipe::ptr_t(new LLIONull)); - mPump->addChain(chain, NEVER_CHAIN_EXPIRY_SECS); - - // Now, the server should immediately send the data, but we'll - // never read it. pump for a bit - F32 elapsed = pump_loop(mPump, SHORT_CHAIN_EXPIRY_SECS + 3.0f); - ensure("Did not take too long", (elapsed < DEFAULT_CHAIN_EXPIRY_SECS)); - } - - template<> template<> - void fitness_test_object::test<5>() - { - // Set up the server - LLPumpIO::chain_t chain; - typedef LLCloneIOFactory sleeper_t; - sleeper_t* sleeper = new sleeper_t(new LLIOSleeper); - boost::shared_ptr factory(sleeper); - LLIOServerSocket* server = new LLIOServerSocket( - mPool, - mSocket, - factory); - server->setResponseTimeout(1.0); - chain.push_back(LLIOPipe::ptr_t(server)); - mPump->addChain(chain, NEVER_CHAIN_EXPIRY_SECS); - // We need to tickle the pump a little to set up the listen() - pump_loop(mPump, 0.1f); - U32 count = mPump->runningChains(); - ensure_equals("server chain onboard", count, 1); - lldebugs << "** Server is up." << llendl; - - // Set up the client - LLSocket::ptr_t client = LLSocket::create(mPool, LLSocket::STREAM_TCP); - LLHost server_host("127.0.0.1", SERVER_LISTEN_PORT); - bool connected = client->blockingConnect(server_host); - ensure("Connected to server", connected); - lldebugs << "connected" << llendl; - F32 elapsed = pump_loop(mPump,0.1f); - count = mPump->runningChains(); - ensure_equals("server chain onboard", count, 2); - lldebugs << "** Client is connected." << llendl; - - // We have connected, since the socket reader does not block, - // the first call to read data will return EAGAIN, so we need - // to write something. - chain.clear(); - chain.push_back(LLIOPipe::ptr_t(new LLPipeStringInjector("hi"))); - chain.push_back(LLIOPipe::ptr_t(new LLIOSocketWriter(client))); - chain.push_back(LLIOPipe::ptr_t(new LLIONull)); - mPump->addChain(chain, 0.2f); - chain.clear(); - - // pump for a bit and make sure all 3 chains are running - elapsed = pump_loop(mPump,0.1f); - count = mPump->runningChains(); - ensure_equals("client chain onboard", count, 3); - lldebugs << "** request should have been sent." << llendl; - - // pump for long enough the the client socket closes, and the - // server socket should not be closed yet. - elapsed = pump_loop(mPump,0.2f); - count = mPump->runningChains(); - ensure_equals("client chain timed out ", count, 2); - lldebugs << "** client chain should be closed." << llendl; - - // At this point, the socket should be closed by the timeout - elapsed = pump_loop(mPump,1.0f); - count = mPump->runningChains(); - ensure_equals("accepted socked close", count, 1); - lldebugs << "** Sleeper should have timed out.." << llendl; - } -} - -namespace tut -{ - struct rpc_server_data - { - class LLSimpleRPCResponse : public LLSDRPCResponse - { - public: - LLSimpleRPCResponse(LLSD* response) : - mResponsePtr(response) - { - } - ~LLSimpleRPCResponse() {} - virtual bool response(LLPumpIO* pump) - { - *mResponsePtr = mReturnValue; - return true; - } - virtual bool fault(LLPumpIO* pump) - { - *mResponsePtr = mReturnValue; - return false; - } - virtual bool error(LLPumpIO* pump) - { - ensure("LLSimpleRPCResponse::error()", false); - return false; - } - public: - LLSD* mResponsePtr; - }; - - class LLSimpleRPCClient : public LLSDRPCClient - { - public: - LLSimpleRPCClient(LLSD* response) : - mResponsePtr(response) - { - } - ~LLSimpleRPCClient() {} - void echo(const LLSD& parameter) - { - LLSimpleRPCResponse* resp; - resp = new LLSimpleRPCResponse(mResponsePtr); - static const std::string URI_NONE; - static const std::string METHOD_ECHO("echo"); - call(URI_NONE, METHOD_ECHO, parameter, resp, EPBQ_CALLBACK); - } - public: - LLSD* mResponsePtr; - }; - - class LLSimpleRPCServer : public LLSDRPCServer - { - public: - LLSimpleRPCServer() - { - mMethods["echo"] = new mem_fn_t( - this, - &LLSimpleRPCServer::rpc_Echo); - } - ~LLSimpleRPCServer() {} - protected: - typedef LLSDRPCMethodCall mem_fn_t; - ESDRPCSStatus rpc_Echo( - const LLSD& parameter, - const LLChannelDescriptors& channels, - LLBufferArray* data) - { - buildResponse(channels, data, parameter); - return ESDRPCS_DONE; - } - }; - - apr_pool_t* mPool; - LLPumpIO* mPump; - LLPumpIO::chain_t mChain; - LLSimpleRPCClient* mClient; - LLSD mResponse; - - rpc_server_data() : - mPool(NULL), - mPump(NULL), - mClient(NULL) - { - apr_pool_create(&mPool, NULL); - mPump = new LLPumpIO(mPool); - mClient = new LLSimpleRPCClient(&mResponse); - mChain.push_back(LLIOPipe::ptr_t(mClient)); - mChain.push_back(LLIOPipe::ptr_t(new LLFilterSD2XMLRPCRequest)); - mChain.push_back(LLIOPipe::ptr_t(new LLFilterXMLRPCRequest2LLSD)); - mChain.push_back(LLIOPipe::ptr_t(new LLSimpleRPCServer)); - mChain.push_back(LLIOPipe::ptr_t(new LLFilterSD2XMLRPCResponse)); - mChain.push_back(LLIOPipe::ptr_t(new LLFilterXMLRPCResponse2LLSD)); - mChain.push_back(LLIOPipe::ptr_t(mClient)); - } - ~rpc_server_data() - { - mChain.clear(); - delete mPump; - mPump = NULL; - apr_pool_destroy(mPool); - mPool = NULL; - } - void pump_loop(const LLSD& request) - { - LLTimer timer; - timer.setTimerExpirySec(1.0f); - mClient->echo(request); - mPump->addChain(mChain, DEFAULT_CHAIN_EXPIRY_SECS); - while(mResponse.isUndefined() && !timer.hasExpired()) - { - mPump->pump(); - mPump->callback(); - } - } - }; - typedef test_group rpc_server_test; - typedef rpc_server_test::object rpc_server_object; - tut::rpc_server_test rpc("rpc_server"); - - template<> template<> - void rpc_server_object::test<1>() - { - LLSD request; - request = 1; - pump_loop(request); - //llinfos << "request: " << *request << llendl; - //llinfos << "response: " << *mResponse << llendl; - ensure_equals("integer request response", mResponse.asInteger(), 1); - } - - template<> template<> - void rpc_server_object::test<2>() - { -//#if LL_WINDOWS && _MSC_VER >= 1400 -// skip_fail("Fails on VS2005 due to broken LLSDSerialize::fromNotation() parser."); -//#endif - std::string uri("sl-am:66.150.244.180:12035/location?start=region&px=70.9247&py=254.378&pz=38.7304&lx=-0.043753&ly=-0.999042&lz=0"); - std::stringstream stream; - stream << "{'task_id':ucc706f2d-0b68-68f8-11a4-f1043ff35ca0}\n{\n\tname\tObject|\n\tpermissions 0\n}"; - std::vector expected_binary; - expected_binary.resize(stream.str().size()); - memcpy(&expected_binary[0], stream.str().c_str(), stream.str().size()); /* Flawfinder: ignore */ - stream.str(""); - stream << "[{'uri':'" << uri << "'}, {'version':i1}, " - << "{'agent_id':'3c115e51-04f4-523c-9fa6-98aff1034730', 'session_id':'2c585cec-038c-40b0-b42e-a25ebab4d132', 'circuit_code':i1075, 'start':'region', 'limited_to_estate':i1 'first_name':'Phoenix', 'last_name':'Linden', 'group_title':'', 'group_id':u00000000-0000-0000-0000-000000000000, 'position':[r70.9247,r254.378,r38.7304], 'look_at':[r-0.043753,r-0.999042,r0], 'granters':[ua2e76fcd-9360-4f6d-a924-000000000003], 'texture_data':['5e481e8a-58a6-fc34-6e61-c7a36095c07f', 'c39675f5-ca90-a304-bb31-42cdb803a132', '5c989edf-88d1-b2ac-b00b-5ed4bab8e368', '6522e74d-1660-4e7f-b601-6f48c1659a77', '7ca39b4c-bd19-4699-aff7-f93fd03d3e7b', '41c58177-5eb6-5aeb-029d-bc4093f3c130', '97b75473-8b93-9b25-2a11-035b9ae93195', '1c2d8d9b-90eb-89d4-dea8-c1ed83990614', '69ec543f-e27b-c07c-9094-a8be6300f274', 'c9f8b80f-c629-4633-04ee-c566ce9fea4b', '989cddba-7ab6-01ed-67aa-74accd2a2a65', '45e319b2-6a8c-fa5c-895b-1a7149b88aef', '5748decc-f629-461c-9a36-a35a221fe21f', 'c228d1cf-4b5d-4ba8-84f4-899a0796aa97', 'c228d1cf-4b5d-4ba8-84f4-899a0796aa97', '685fbe10-ab40-f065-0aec-726cc6dfd7a1', '406f98fd-9c89-1d52-5f39-e67d508c5ee5', '685fbe10-ab40-f065-0aec-726cc6dfd7a1', 'c228d1cf-4b5d-4ba8-84f4-899a0796aa97', 'c228d1cf-4b5d-4ba8-84f4-899a0796aa97'], " - << "'attachment_data':[" - << "{'attachment_point':i2, 'item_id':'d6852c11-a74e-309a-0462-50533f1ef9b3', 'asset_id':'c69b29b1-8944-58ae-a7c5-2ca7b23e22fb'}," - << "{'attachment_point':i10, 'item_id':'ff852c22-a74e-309a-0462-50533f1ef900', 'asset_data':b(" << expected_binary.size() << ")\""; - stream.write((const char*)&expected_binary[0], expected_binary.size()); - stream << "\"}" - << "]" - << "}]"; - - LLSD request; - S32 count = LLSDSerialize::fromNotation( - request, - stream, - stream.str().size()); - ensure("parsed something", (count > 0)); - - pump_loop(request); - ensure_equals("return type", mResponse.type(), LLSD::TypeArray); - ensure_equals("return size", mResponse.size(), 3); - - ensure_equals( - "uri parameter type", - mResponse[0].type(), - LLSD::TypeMap); - ensure_equals( - "uri type", - mResponse[0]["uri"].type(), - LLSD::TypeString); - ensure_equals("uri value", mResponse[0]["uri"].asString(), uri); - - ensure_equals( - "version parameter type", - mResponse[1].type(), - LLSD::TypeMap); - ensure_equals( - "version type", - mResponse[1]["version"].type(), - LLSD::TypeInteger); - ensure_equals( - "version value", - mResponse[1]["version"].asInteger(), - 1); - - ensure_equals("agent params type", mResponse[2].type(), LLSD::TypeMap); - LLSD attachment_data = mResponse[2]["attachment_data"]; - ensure("attachment data exists", attachment_data.isDefined()); - ensure_equals( - "attachment type", - attachment_data.type(), - LLSD::TypeArray); - ensure_equals( - "attachment type 0", - attachment_data[0].type(), - LLSD::TypeMap); - ensure_equals( - "attachment type 1", - attachment_data[1].type(), - LLSD::TypeMap); - ensure_equals("attachment size 1", attachment_data[1].size(), 3); - ensure_equals( - "asset data type", - attachment_data[1]["asset_data"].type(), - LLSD::TypeBinary); - std::vector actual_binary; - actual_binary = attachment_data[1]["asset_data"].asBinary(); - ensure_equals( - "binary data size", - actual_binary.size(), - expected_binary.size()); - ensure( - "binary data", - (0 == memcmp( - &actual_binary[0], - &expected_binary[0], - expected_binary.size()))); - } - - template<> template<> - void rpc_server_object::test<3>() - { -//#if LL_WINDOWS && _MSC_VER >= 1400 -// skip_fail("Fails on VS2005 due to broken LLSDSerialize::fromNotation() parser."); -//#endif - std::string uri("sl-am:66.150.244.180:12035/location?start=region&px=70.9247&py=254.378&pz=38.7304&lx=-0.043753&ly=-0.999042&lz=0"); - - LLBufferArray buffer; - LLChannelDescriptors buffer_channels = buffer.nextChannel(); - LLBufferStream stream(buffer_channels, &buffer); - stream << "[{'uri':'" << uri << "'}, {'version':i1}, " - << "{'agent_id':'3c115e51-04f4-523c-9fa6-98aff1034730', 'session_id':'2c585cec-038c-40b0-b42e-a25ebab4d132', 'circuit_code':i1075, 'start':'region', 'limited_to_estate':i1 'first_name':'Phoenix', 'last_name':'Linden', 'group_title':'', 'group_id':u00000000-0000-0000-0000-000000000000, 'position':[r70.9247,r254.378,r38.7304], 'look_at':[r-0.043753,r-0.999042,r0], 'granters':[ua2e76fcd-9360-4f6d-a924-000000000003], 'texture_data':['5e481e8a-58a6-fc34-6e61-c7a36095c07f', 'c39675f5-ca90-a304-bb31-42cdb803a132', '5c989edf-88d1-b2ac-b00b-5ed4bab8e368', '6522e74d-1660-4e7f-b601-6f48c1659a77', '7ca39b4c-bd19-4699-aff7-f93fd03d3e7b', '41c58177-5eb6-5aeb-029d-bc4093f3c130', '97b75473-8b93-9b25-2a11-035b9ae93195', '1c2d8d9b-90eb-89d4-dea8-c1ed83990614', '69ec543f-e27b-c07c-9094-a8be6300f274', 'c9f8b80f-c629-4633-04ee-c566ce9fea4b', '989cddba-7ab6-01ed-67aa-74accd2a2a65', '45e319b2-6a8c-fa5c-895b-1a7149b88aef', '5748decc-f629-461c-9a36-a35a221fe21f', 'c228d1cf-4b5d-4ba8-84f4-899a0796aa97', 'c228d1cf-4b5d-4ba8-84f4-899a0796aa97', '685fbe10-ab40-f065-0aec-726cc6dfd7a1', '406f98fd-9c89-1d52-5f39-e67d508c5ee5', '685fbe10-ab40-f065-0aec-726cc6dfd7a1', 'c228d1cf-4b5d-4ba8-84f4-899a0796aa97', 'c228d1cf-4b5d-4ba8-84f4-899a0796aa97'], " - << "'attachment_data':[" - << "{'attachment_point':i2, 'item_id':'d6852c11-a74e-309a-0462-50533f1ef9b3', 'asset_id':'c69b29b1-8944-58ae-a7c5-2ca7b23e22fb'},"; - - std::stringstream tmp_str; - tmp_str << "{'task_id':ucc706f2d-0b68-68f8-11a4-f1043ff35ca0}\n{\n\tname\tObject|\n\tpermissions 0\n}"; - std::vector expected_binary; - expected_binary.resize(tmp_str.str().size()); - memcpy( /* Flawfinder: ignore */ - &expected_binary[0], - tmp_str.str().c_str(), - tmp_str.str().size()); - - LLBufferArray attachment_buffer; - LLChannelDescriptors attach_channels = attachment_buffer.nextChannel(); - LLBufferStream attach_stream(attach_channels, &attachment_buffer); - attach_stream.write((const char*)&expected_binary[0], expected_binary.size()); - attach_stream.flush(); - S32 len = attachment_buffer.countAfter(attach_channels.out(), NULL); - stream << "{'attachment_point':i10, 'item_id':'ff852c22-a74e-309a-0462-50533f1ef900', 'asset_data':b(" << len << ")\""; - stream.flush(); - buffer.takeContents(attachment_buffer); - stream << "\"}]}]"; - stream.flush(); - - LLChannelDescriptors read_channel = buffer.nextChannel(); - LLBufferStream read_stream(read_channel, &buffer); - LLSD request; - S32 count = LLSDSerialize::fromNotation( - request, - read_stream, - buffer.count(read_channel.in())); - ensure("parsed something", (count > 0)); - ensure("deserialized", request.isDefined()); - - // do the rpc round trip - pump_loop(request); - - ensure_equals("return type", mResponse.type(), LLSD::TypeArray); - ensure_equals("return size", mResponse.size(), 3); - - LLSD child = mResponse[0]; - ensure("uri map exists", child.isDefined()); - ensure_equals("uri parameter type", child.type(), LLSD::TypeMap); - ensure("uri string exists", child.has("uri")); - ensure_equals("uri type", child["uri"].type(), LLSD::TypeString); - ensure_equals("uri value", child["uri"].asString(), uri); - - child = mResponse[1]; - ensure("version map exists", child.isDefined()); - ensure_equals("version param type", child.type(), LLSD::TypeMap); - ensure_equals( - "version type", - child["version"].type(), - LLSD::TypeInteger); - ensure_equals("version value", child["version"].asInteger(), 1); - - child = mResponse[2]; - ensure("agent params map exists", child.isDefined()); - ensure_equals("agent params type", child.type(), LLSD::TypeMap); - child = child["attachment_data"]; - ensure("attachment data exists", child.isDefined()); - ensure_equals("attachment type", child.type(), LLSD::TypeArray); - LLSD attachment = child[0]; - ensure_equals("attachment type 0", attachment.type(), LLSD::TypeMap); - attachment = child[1]; - ensure_equals("attachment type 1", attachment.type(), LLSD::TypeMap); - ensure_equals("attachment size 1", attachment.size(), 3); - ensure_equals( - "asset data type", - attachment["asset_data"].type(), - LLSD::TypeBinary); - std::vector actual_binary = attachment["asset_data"].asBinary(); - ensure_equals( - "binary data size", - actual_binary.size(), - expected_binary.size()); - ensure( - "binary data", - (0 == memcmp( - &actual_binary[0], - &expected_binary[0], - expected_binary.size()))); - } - - template<> template<> - void rpc_server_object::test<4>() - { - std::string message("parcel '' is naughty."); - std::stringstream str; - str << "{'message':'" << LLSDNotationFormatter::escapeString(message) - << "'}"; - LLSD request; - S32 count = LLSDSerialize::fromNotation( - request, - str, - str.str().size()); - ensure_equals("parse count", count, 2); - ensure_equals("request type", request.type(), LLSD::TypeMap); - pump_loop(request); - ensure("valid response", mResponse.isDefined()); - ensure_equals("response type", mResponse.type(), LLSD::TypeMap); - std::string actual = mResponse["message"].asString(); - ensure_equals("message contents", actual, message); - } - - template<> template<> - void rpc_server_object::test<5>() - { - // test some of the problem cases with llsdrpc over xmlrpc - - // for example: - // * arrays are auto-converted to parameter lists, thus, this - // becomes one parameter. - // * undef goes over the wire as false (this might not be a good idea) - // * uuids are converted to string. - std::string val = "[{'failures':!,'successfuls':[u3c115e51-04f4-523c-9fa6-98aff1034730]}]"; - std::istringstream istr; - istr.str(val); - LLSD sd; - LLSDSerialize::fromNotation(sd, istr, val.size()); - pump_loop(sd); - ensure("valid response", mResponse.isDefined()); - ensure_equals("parsed type", mResponse.type(), LLSD::TypeMap); - ensure_equals("parsed size", mResponse.size(), 2); - LLSD failures = mResponse["failures"]; - ensure_equals("no failures.", failures.asBoolean(), false); - LLSD success = mResponse["successfuls"]; - ensure_equals("success type", success.type(), LLSD::TypeArray); - ensure_equals("success size", success.size(), 1); - ensure_equals( - "success instance type", - success[0].type(), - LLSD::TypeString); - } - -/* - template<> template<> - void rpc_server_object::test<5>() - { - std::string expected("\xf3");//\xffsomething"); - LLSD* request = LLSD::createString(expected); - pump_loop(request); - std::string actual; - mResponse->getString(actual); - if(actual != expected) - { - //llwarns << "iteration " << i << llendl; - std::ostringstream e_str; - std::string::iterator iter = expected.begin(); - std::string::iterator end = expected.end(); - for(; iter != end; ++iter) - { - e_str << (S32)((U8)(*iter)) << " "; - } - e_str << std::endl; - llsd_serialize_string(e_str, expected); - llwarns << "expected size: " << expected.size() << llendl; - llwarns << "expected: " << e_str.str() << llendl; - - std::ostringstream a_str; - iter = actual.begin(); - end = actual.end(); - for(; iter != end; ++iter) - { - a_str << (S32)((U8)(*iter)) << " "; - } - a_str << std::endl; - llsd_serialize_string(a_str, actual); - llwarns << "actual size: " << actual.size() << llendl; - llwarns << "actual: " << a_str.str() << llendl; - } - ensure_equals("binary string request response", actual, expected); - delete request; - } - - template<> template<> - void rpc_server_object::test<5>() - { - } -*/ -} - - -/* -'asset_data':b(12100)"{'task_id':ucc706f2d-0b68-68f8-11a4-f1043ff35ca0}\n{\n\tname\tObject|\n\tpermissions 0\n\t{\n\t\tbase_mask\t7fffffff\n\t\towner_mask\t7fffffff\n\t\tgroup_mask\t00000000\n\t\teveryone_mask\t00000000\n\t\tnext_owner_mask\t7fffffff\n\t\tcreator_id\t13fd9595-a47b-4d64-a5fb-6da645f038e0\n\t\towner_id\t3c115e51-04f4-523c-9fa6-98aff1034730\n\t\tlast_owner_id\t3c115e51-04f4-523c-9fa6-98aff1034730\n\t\tgroup_id\t00000000-0000-0000-0000-000000000000\n\t}\n\tlocal_id\t217444921\n\ttotal_crc\t323\n\ttype\t2\n\ttask_valid\t2\n\ttravel_access\t13\n\tdisplayopts\t2\n\tdisplaytype\tv\n\tpos\t-0.368634403\t0.00781063363\t-0.569040775\n\toldpos\t150.117996\t25.8658009\t8.19664001\n\trotation\t-0.06293071806430816650390625\t-0.6995697021484375\t-0.7002241611480712890625\t0.1277817934751510620117188\n\tchildpos\t-0.00499999989\t-0.0359999985\t0.307999998\n\tchildrot\t-0.515492737293243408203125\t-0.46601200103759765625\t0.529055416584014892578125\t0.4870323240756988525390625\n\tscale\t0.074629\t0.289956\t0.01\n\tsit_offset\t0\t0\t0\n\tcamera_eye_offset\t0\t0\t0\n\tcamera_at_offset\t0\t0\t0\n\tsit_quat\t0\t0\t0\t1\n\tsit_hint\t0\n\tstate\t160\n\tmaterial\t3\n\tsoundid\t00000000-0000-0000-0000-000000000000\n\tsoundgain\t0\n\tsoundradius\t0\n\tsoundflags\t0\n\ttextcolor\t0 0 0 1\n\tselected\t0\n\tselector\t00000000-0000-0000-0000-000000000000\n\tusephysics\t0\n\trotate_x\t1\n\trotate_y\t1\n\trotate_z\t1\n\tphantom\t0\n\tremote_script_access_pin\t0\n\tvolume_detect\t0\n\tblock_grabs\t0\n\tdie_at_edge\t0\n\treturn_at_edge\t0\n\ttemporary\t0\n\tsandbox\t0\n\tsandboxhome\t0\t0\t0\n\tshape 0\n\t{\n\t\tpath 0\n\t\t{\n\t\t\tcurve\t16\n\t\t\tbegin\t0\n\t\t\tend\t1\n\t\t\tscale_x\t1\n\t\t\tscale_y\t1\n\t\t\tshear_x\t0\n\t\t\tshear_y\t0\n\t\t\ttwist\t0\n\t\t\ttwist_begin\t0\n\t\t\tradius_offset\t0\n\t\t\ttaper_x\t0\n\t\t\ttaper_y\t0\n\t\t\trevolutions\t1\n\t\t\tskew\t0\n\t\t}\n\t\tprofile 0\n\t\t{\n\t\t\tcurve\t1\n\t\t\tbegin\t0\n\t\t\tend\t1\n\t\t\thollow\t0\n\t\t}\n\t}\n\tfaces\t6\n\t{\n\t\timageid\tddde1ffc-678b-3cda-1748-513086bdf01b\n\t\tcolors\t0.937255 0.796078 0.494118 1\n\t\tscales\t1\n\t\tscalet\t1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t0\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\t{\n\t\timageid\tf54a0c32-3cd1-d49a-5b4f-7b792bebc204\n\t\tcolors\t0.937255 0.796078 0.494118 1\n\t\tscales\t1\n\t\tscalet\t1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t0\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\t{\n\t\timageid\tf54a0c32-3cd1-d49a-5b4f-7b792bebc204\n\t\tcolors\t0.937255 0.796078 0.494118 1\n\t\tscales\t1\n\t\tscalet\t1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t0\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\t{\n\t\timageid\tf54a0c32-3cd1-d49a-5b4f-7b792bebc204\n\t\tcolors\t0.937255 0.796078 0.494118 1\n\t\tscales\t1\n\t\tscalet\t1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t0\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\t{\n\t\timageid\tf54a0c32-3cd1-d49a-5b4f-7b792bebc204\n\t\tcolors\t0.937255 0.796078 0.494118 1\n\t\tscales\t1\n\t\tscalet\t1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t0\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\t{\n\t\timageid\tddde1ffc-678b-3cda-1748-513086bdf01b\n\t\tcolors\t0.937255 0.796078 0.494118 1\n\t\tscales\t1\n\t\tscalet\t-1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t0\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\tps_next_crc\t1\n\tgpw_bias\t1\n\tip\t0\n\tcomplete\tTRUE\n\tdelay\t50000\n\tnextstart\t0\n\tbirthtime\t1061088050622956\n\treztime\t1094866329019785\n\tparceltime\t1133568981980596\n\ttax_rate\t1.00084\n\tscratchpad\t0\n\t{\n\t\n\t}\n\tsale_info\t0\n\t{\n\t\tsale_type\tnot\n\t\tsale_price\t10\n\t}\n\tcorrect_family_id\t00000000-0000-0000-0000-000000000000\n\thas_rezzed\t0\n\tpre_link_base_mask\t7fffffff\n\tlinked \tchild\n\tdefault_pay_price\t-2\t1\t5\t10\t20\n}\n{'task_id':u61fa7364-e151-0597-774c-523312dae31b}\n{\n\tname\tObject|\n\tpermissions 0\n\t{\n\t\tbase_mask\t7fffffff\n\t\towner_mask\t7fffffff\n\t\tgroup_mask\t00000000\n\t\teveryone_mask\t00000000\n\t\tnext_owner_mask\t7fffffff\n\t\tcreator_id\t13fd9595-a47b-4d64-a5fb-6da645f038e0\n\t\towner_id\t3c115e51-04f4-523c-9fa6-98aff1034730\n\t\tlast_owner_id\t3c115e51-04f4-523c-9fa6-98aff1034730\n\t\tgroup_id\t00000000-0000-0000-0000-000000000000\n\t}\n\tlocal_id\t217444922\n\ttotal_crc\t324\n\ttype\t2\n\ttask_valid\t2\n\ttravel_access\t13\n\tdisplayopts\t2\n\tdisplaytype\tv\n\tpos\t-0.367110789\t0.00780026987\t-0.566269755\n\toldpos\t150.115005\t25.8479004\t8.18669987\n\trotation\t0.47332942485809326171875\t-0.380102097988128662109375\t-0.5734078884124755859375\t0.550168216228485107421875\n\tchildpos\t-0.00499999989\t-0.0370000005\t0.305000007\n\tchildrot\t-0.736649334430694580078125\t-0.03042060509324073791503906\t-0.02784589119255542755126953\t0.67501628398895263671875\n\tscale\t0.074629\t0.289956\t0.01\n\tsit_offset\t0\t0\t0\n\tcamera_eye_offset\t0\t0\t0\n\tcamera_at_offset\t0\t0\t0\n\tsit_quat\t0\t0\t0\t1\n\tsit_hint\t0\n\tstate\t160\n\tmaterial\t3\n\tsoundid\t00000000-0000-0000-0000-000000000000\n\tsoundgain\t0\n\tsoundradius\t0\n\tsoundflags\t0\n\ttextcolor\t0 0 0 1\n\tselected\t0\n\tselector\t00000000-0000-0000-0000-000000000000\n\tusephysics\t0\n\trotate_x\t1\n\trotate_y\t1\n\trotate_z\t1\n\tphantom\t0\n\tremote_script_access_pin\t0\n\tvolume_detect\t0\n\tblock_grabs\t0\n\tdie_at_edge\t0\n\treturn_at_edge\t0\n\ttemporary\t0\n\tsandbox\t0\n\tsandboxhome\t0\t0\t0\n\tshape 0\n\t{\n\t\tpath 0\n\t\t{\n\t\t\tcurve\t16\n\t\t\tbegin\t0\n\t\t\tend\t1\n\t\t\tscale_x\t1\n\t\t\tscale_y\t1\n\t\t\tshear_x\t0\n\t\t\tshear_y\t0\n\t\t\ttwist\t0\n\t\t\ttwist_begin\t0\n\t\t\tradius_offset\t0\n\t\t\ttaper_x\t0\n\t\t\ttaper_y\t0\n\t\t\trevolutions\t1\n\t\t\tskew\t0\n\t\t}\n\t\tprofile 0\n\t\t{\n\t\t\tcurve\t1\n\t\t\tbegin\t0\n\t\t\tend\t1\n\t\t\thollow\t0\n\t\t}\n\t}\n\tfaces\t6\n\t{\n\t\timageid\tddde1ffc-678b-3cda-1748-513086bdf01b\n\t\tcolors\t0.937255 0.796078 0.494118 1\n\t\tscales\t1\n\t\tscalet\t1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t0\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\t{\n\t\timageid\tf54a0c32-3cd1-d49a-5b4f-7b792bebc204\n\t\tcolors\t0.937255 0.796078 0.494118 1\n\t\tscales\t1\n\t\tscalet\t1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t0\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\t{\n\t\timageid\tf54a0c32-3cd1-d49a-5b4f-7b792bebc204\n\t\tcolors\t0.937255 0.796078 0.494118 1\n\t\tscales\t1\n\t\tscalet\t1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t0\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\t{\n\t\timageid\tf54a0c32-3cd1-d49a-5b4f-7b792bebc204\n\t\tcolors\t0.937255 0.796078 0.494118 1\n\t\tscales\t1\n\t\tscalet\t1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t0\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\t{\n\t\timageid\tf54a0c32-3cd1-d49a-5b4f-7b792bebc204\n\t\tcolors\t0.937255 0.796078 0.494118 1\n\t\tscales\t1\n\t\tscalet\t1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t0\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\t{\n\t\timageid\tddde1ffc-678b-3cda-1748-513086bdf01b\n\t\tcolors\t0.937255 0.796078 0.494118 1\n\t\tscales\t1\n\t\tscalet\t-1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t0\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\tps_next_crc\t1\n\tgpw_bias\t1\n\tip\t0\n\tcomplete\tTRUE\n\tdelay\t50000\n\tnextstart\t0\n\tbirthtime\t1061087839248891\n\treztime\t1094866329020800\n\tparceltime\t1133568981981983\n\ttax_rate\t1.00084\n\tscratchpad\t0\n\t{\n\t\n\t}\n\tsale_info\t0\n\t{\n\t\tsale_type\tnot\n\t\tsale_price\t10\n\t}\n\tcorrect_family_id\t00000000-0000-0000-0000-000000000000\n\thas_rezzed\t0\n\tpre_link_base_mask\t7fffffff\n\tlinked \tchild\n\tdefault_pay_price\t-2\t1\t5\t10\t20\n}\n{'task_id':ub8d68643-7dd8-57af-0d24-8790032aed0c}\n{\n\tname\tObject|\n\tpermissions 0\n\t{\n\t\tbase_mask\t7fffffff\n\t\towner_mask\t7fffffff\n\t\tgroup_mask\t00000000\n\t\teveryone_mask\t00000000\n\t\tnext_owner_mask\t7fffffff\n\t\tcreator_id\t13fd9595-a47b-4d64-a5fb-6da645f038e0\n\t\towner_id\t3c115e51-04f4-523c-9fa6-98aff1034730\n\t\tlast_owner_id\t3c115e51-04f4-523c-9fa6-98aff1034730\n\t\tgroup_id\t00000000-0000-0000-0000-000000000000\n\t}\n\tlocal_id\t217444923\n\ttotal_crc\t235\n\ttype\t2\n\ttask_valid\t2\n\ttravel_access\t13\n\tdisplayopts\t2\n\tdisplaytype\tv\n\tpos\t-0.120029509\t-0.00284469454\t-0.0302077383\n\toldpos\t150.710999\t25.8584995\t8.19172001\n\trotation\t0.145459949970245361328125\t-0.1646589934825897216796875\t0.659558117389678955078125\t-0.718826770782470703125\n\tchildpos\t0\t-0.182999998\t-0.26699999\n\tchildrot\t0.991444766521453857421875\t3.271923924330621957778931e-05\t-0.0002416197530692443251609802\t0.1305266767740249633789062\n\tscale\t0.0382982\t0.205957\t0.368276\n\tsit_offset\t0\t0\t0\n\tcamera_eye_offset\t0\t0\t0\n\tcamera_at_offset\t0\t0\t0\n\tsit_quat\t0\t0\t0\t1\n\tsit_hint\t0\n\tstate\t160\n\tmaterial\t3\n\tsoundid\t00000000-0000-0000-0000-000000000000\n\tsoundgain\t0\n\tsoundradius\t0\n\tsoundflags\t0\n\ttextcolor\t0 0 0 1\n\tselected\t0\n\tselector\t00000000-0000-0000-0000-000000000000\n\tusephysics\t0\n\trotate_x\t1\n\trotate_y\t1\n\trotate_z\t1\n\tphantom\t0\n\tremote_script_access_pin\t0\n\tvolume_detect\t0\n\tblock_grabs\t0\n\tdie_at_edge\t0\n\treturn_at_edge\t0\n\ttemporary\t0\n\tsandbox\t0\n\tsandboxhome\t0\t0\t0\n\tshape 0\n\t{\n\t\tpath 0\n\t\t{\n\t\t\tcurve\t32\n\t\t\tbegin\t0.3\n\t\t\tend\t0.65\n\t\t\tscale_x\t1\n\t\t\tscale_y\t0.05\n\t\t\tshear_x\t0\n\t\t\tshear_y\t0\n\t\t\ttwist\t0\n\t\t\ttwist_begin\t0\n\t\t\tradius_offset\t0\n\t\t\ttaper_x\t0\n\t\t\ttaper_y\t0\n\t\t\trevolutions\t1\n\t\t\tskew\t0\n\t\t}\n\t\tprofile 0\n\t\t{\n\t\t\tcurve\t0\n\t\t\tbegin\t0\n\t\t\tend\t1\n\t\t\thollow\t0\n\t\t}\n\t}\n\tfaces\t3\n\t{\n\t\timageid\te7150bed-3e3e-c698-eb15-d17b178148af\n\t\tcolors\t0.843137 0.156863 0.156863 1\n\t\tscales\t15\n\t\tscalet\t1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t-1.57084\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\t{\n\t\timageid\te7150bed-3e3e-c698-eb15-d17b178148af\n\t\tcolors\t0.843137 0.156863 0.156863 1\n\t\tscales\t15\n\t\tscalet\t1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t-1.57084\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\t{\n\t\timageid\te7150bed-3e3e-c698-eb15-d17b178148af\n\t\tcolors\t0.843137 0.156863 0.156863 1\n\t\tscales\t15\n\t\tscalet\t1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t-1.57084\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\tps_next_crc\t1\n\tgpw_bias\t1\n\tip\t0\n\tcomplete\tTRUE\n\tdelay\t50000\n\tnextstart\t0\n\tbirthtime\t1061087534454174\n\treztime\t1094866329021741\n\tparceltime\t1133568981982889\n\ttax_rate\t1.00326\n\tscratchpad\t0\n\t{\n\t\n\t}\n\tsale_info\t0\n\t{\n\t\tsale_type\tnot\n\t\tsale_price\t10\n\t}\n\tcorrect_family_id\t00000000-0000-0000-0000-000000000000\n\thas_rezzed\t0\n\tpre_link_base_mask\t7fffffff\n\tlinked \tchild\n\tdefault_pay_price\t-2\t1\t5\t10\t20\n}\n{'task_id':ue4b19200-9d33-962f-c8c5-6f25be3a3fd0}\n{\n\tname\tApotheosis_Immolaine_tail|\n\tpermissions 0\n\t{\n\t\tbase_mask\t7fffffff\n\t\towner_mask\t7fffffff\n\t\tgroup_mask\t00000000\n\t\teveryone_mask\t00000000\n\t\tnext_owner_mask\t7fffffff\n\t\tcreator_id\t13fd9595-a47b-4d64-a5fb-6da645f038e0\n\t\towner_id\t3c115e51-04f4-523c-9fa6-98aff1034730\n\t\tlast_owner_id\t3c115e51-04f4-523c-9fa6-98aff1034730\n\t\tgroup_id\t00000000-0000-0000-0000-000000000000\n\t}\n\tlocal_id\t217444924\n\ttotal_crc\t675\n\ttype\t1\n\ttask_valid\t2\n\ttravel_access\t13\n\tdisplayopts\t2\n\tdisplaytype\tv\n\tpos\t-0.34780401\t-0.00968400016\t-0.260098994\n\toldpos\t0\t0\t0\n\trotation\t0.73164522647857666015625\t-0.67541944980621337890625\t-0.07733880728483200073242188\t0.05022468417882919311523438\n\tvelocity\t0\t0\t0\n\tangvel\t0\t0\t0\n\tscale\t0.0382982\t0.32228\t0.383834\n\tsit_offset\t0\t0\t0\n\tcamera_eye_offset\t0\t0\t0\n\tcamera_at_offset\t0\t0\t0\n\tsit_quat\t0\t0\t0\t1\n\tsit_hint\t0\n\tstate\t160\n\tmaterial\t3\n\tsoundid\t00000000-0000-0000-0000-000000000000\n\tsoundgain\t0\n\tsoundradius\t0\n\tsoundflags\t0\n\ttextcolor\t0 0 0 1\n\tselected\t0\n\tselector\t00000000-0000-0000-0000-000000000000\n\tusephysics\t0\n\trotate_x\t1\n\trotate_y\t1\n\trotate_z\t1\n\tphantom\t0\n\tremote_script_access_pin\t0\n\tvolume_detect\t0\n\tblock_grabs\t0\n\tdie_at_edge\t0\n\treturn_at_edge\t0\n\ttemporary\t0\n\tsandbox\t0\n\tsandboxhome\t0\t0\t0\n\tshape 0\n\t{\n\t\tpath 0\n\t\t{\n\t\t\tcurve\t32\n\t\t\tbegin\t0.3\n\t\t\tend\t0.65\n\t\t\tscale_x\t1\n\t\t\tscale_y\t0.05\n\t\t\tshear_x\t0\n\t\t\tshear_y\t0\n\t\t\ttwist\t0\n\t\t\ttwist_begin\t0\n\t\t\tradius_offset\t0\n\t\t\ttaper_x\t0\n\t\t\ttaper_y\t0\n\t\t\trevolutions\t1\n\t\t\tskew\t0\n\t\t}\n\t\tprofile 0\n\t\t{\n\t\t\tcurve\t0\n\t\t\tbegin\t0\n\t\t\tend\t1\n\t\t\thollow\t0\n\t\t}\n\t}\n\tfaces\t3\n\t{\n\t\timageid\te7150bed-3e3e-c698-eb15-d17b178148af\n\t\tcolors\t0.843137 0.156863 0.156863 1\n\t\tscales\t15\n\t\tscalet\t1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t-1.57084\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\t{\n\t\timageid\te7150bed-3e3e-c698-eb15-d17b178148af\n\t\tcolors\t0.843137 0.156863 0.156863 1\n\t\tscales\t15\n\t\tscalet\t1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t-1.57084\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\t{\n\t\timageid\te7150bed-3e3e-c698-eb15-d17b178148af\n\t\tcolors\t0.843137 0.156863 0.156863 1\n\t\tscales\t15\n\t\tscalet\t1\n\t\toffsets\t0\n\t\toffsett\t0\n\t\timagerot\t-1.57084\n\t\tbump\t0\n\t\tfullbright\t0\n\t\tmedia_flags\t0\n\t}\n\tps_next_crc\t1\n\tgpw_bias\t1\n\tip\t0\n\tcomplete\tTRUE\n\tdelay\t50000\n\tnextstart\t0\n\tbirthtime\t1061087463950186\n\treztime\t1094866329022555\n\tparceltime\t1133568981984359\n\tdescription\t(No Description)|\n\ttax_rate\t1.01736\n\tnamevalue\tAttachPt U32 RW S 10\n\tnamevalue\tAttachmentOrientation VEC3 RW DS -3.110088, -0.182018, 1.493795\n\tnamevalue\tAttachmentOffset VEC3 RW DS -0.347804, -0.009684, -0.260099\n\tnamevalue\tAttachItemID STRING RW SV 20f36c3a-b44b-9bc7-87f3-018bfdfc8cda\n\tscratchpad\t0\n\t{\n\t\n\t}\n\tsale_info\t0\n\t{\n\t\tsale_type\tnot\n\t\tsale_price\t10\n\t}\n\torig_asset_id\t8747acbc-d391-1e59-69f1-41d06830e6c0\n\torig_item_id\t20f36c3a-b44b-9bc7-87f3-018bfdfc8cda\n\tfrom_task_id\t3c115e51-04f4-523c-9fa6-98aff1034730\n\tcorrect_family_id\t00000000-0000-0000-0000-000000000000\n\thas_rezzed\t0\n\tpre_link_base_mask\t7fffffff\n\tlinked \tlinked\n\tdefault_pay_price\t-2\t1\t5\t10\t20\n}\n" -*/