diff options
Diffstat (limited to 'indra/llcorehttp')
| -rw-r--r-- | indra/llcorehttp/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | indra/llcorehttp/_httpservice.cpp | 1162 | ||||
| -rw-r--r-- | indra/llcorehttp/bufferarray.cpp | 736 | 
3 files changed, 949 insertions, 950 deletions
| diff --git a/indra/llcorehttp/CMakeLists.txt b/indra/llcorehttp/CMakeLists.txt index 87796abd3c..5650c4c8ba 100644 --- a/indra/llcorehttp/CMakeLists.txt +++ b/indra/llcorehttp/CMakeLists.txt @@ -3,7 +3,6 @@  project(llcorehttp)  include(00-Common) -include(GoogleMock)  include(CURL)  include(OpenSSL)  include(NGHTTP2) diff --git a/indra/llcorehttp/_httpservice.cpp b/indra/llcorehttp/_httpservice.cpp index d543512ec4..10a8c87550 100644 --- a/indra/llcorehttp/_httpservice.cpp +++ b/indra/llcorehttp/_httpservice.cpp @@ -1,581 +1,581 @@ -/** - * @file _httpservice.cpp - * @brief Internal definitions of the Http service thread - * - * $LicenseInfo:firstyear=2012&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2012-2014, 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 "_httpservice.h" - -#include <boost/bind.hpp> -#include <boost/function.hpp> - -#include "_httpoperation.h" -#include "_httprequestqueue.h" -#include "_httppolicy.h" -#include "_httplibcurl.h" -#include "_thread.h" -#include "_httpinternal.h" - -#include "lltimer.h" -#include "llthread.h" -#include "llexception.h" -#include "llmemory.h" - -namespace -{ - -static const char * const LOG_CORE("CoreHttp"); - -} // end anonymous namespace - - -namespace LLCore -{ - -const HttpService::OptionDescriptor HttpService::sOptionDesc[] = -{ //    isLong     isDynamic  isGlobal    isClass -    {   true,       true,       true,       true,       false   },      // PO_CONNECTION_LIMIT -    {   true,       true,       false,      true,       false   },      // PO_PER_HOST_CONNECTION_LIMIT -    {   false,      false,      true,       false,      false   },      // PO_CA_PATH -    {   false,      false,      true,       false,      false   },      // PO_CA_FILE -    {   false,      true,       true,       false,      false   },      // PO_HTTP_PROXY -    {   true,       true,       true,       false,      false   },      // PO_LLPROXY -    {   true,       true,       true,       false,      false   },      // PO_TRACE -    {   true,       true,       false,      true,       false   },      // PO_ENABLE_PIPELINING -    {   true,       true,       false,      true,       false   },      // PO_THROTTLE_RATE -    {   false,      false,      true,       false,      true    }       // PO_SSL_VERIFY_CALLBACK -}; -HttpService * HttpService::sInstance(NULL); -volatile HttpService::EState HttpService::sState(NOT_INITIALIZED); - -HttpService::HttpService() -    : mRequestQueue(NULL), -      mExitRequested(0U), -      mThread(NULL), -      mPolicy(NULL), -      mTransport(NULL), -      mLastPolicy(0) -{} - - -HttpService::~HttpService() -{ -    LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; -    mExitRequested = 1U; -    if (RUNNING == sState) -    { -        // Trying to kill the service object with a running thread -        // is a bit tricky. -        if (mRequestQueue) -        { -            if (mRequestQueue->stopQueue()) -            { -                // Give mRequestQueue a chance to finish -                ms_sleep(10); -            } -        } - -        if (mThread) -        { -            if (! mThread->timedJoin(250)) -            { -                // Failed to join, expect problems ahead so do a hard termination. -                LL_WARNS(LOG_CORE) << "Destroying HttpService with running thread.  Expect problems." << LL_NEWLINE -                                    << "State: " << S32(sState) -                                    << " Last policy: " << U32(mLastPolicy) -                                    << LL_ENDL; - -                mThread->cancel(); -            } -        } -    } - -    if (mRequestQueue) -    { -        mRequestQueue->release(); -        mRequestQueue = NULL; -    } - -    delete mTransport; -    mTransport = NULL; - -    delete mPolicy; -    mPolicy = NULL; - -    if (mThread) -    { -        mThread->release(); -        mThread = NULL; -    } -} - - -void HttpService::init(HttpRequestQueue * queue) -{ -    LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; -    llassert_always(! sInstance); -    llassert_always(NOT_INITIALIZED == sState); -    sInstance = new HttpService(); - -    queue->addRef(); -    sInstance->mRequestQueue = queue; -    sInstance->mPolicy = new HttpPolicy(sInstance); -    sInstance->mTransport = new HttpLibcurl(sInstance); -    sState = INITIALIZED; -} - - -void HttpService::term() -{ -    LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; -    if (sInstance) -    { -        if (RUNNING == sState && sInstance->mThread) -        { -            // Unclean termination.  Thread appears to be running.  We'll -            // try to give the worker thread a chance to cancel using the -            // exit flag... -            sInstance->mExitRequested = 1U; -            sInstance->mRequestQueue->stopQueue(); - -            // And a little sleep -            for (int i(0); i < 10 && RUNNING == sState; ++i) -            { -                ms_sleep(100); -            } -        } - -        delete sInstance; -        sInstance = NULL; -    } -    sState = NOT_INITIALIZED; -} - - -HttpRequest::policy_t HttpService::createPolicyClass() -{ -    mLastPolicy = mPolicy->createPolicyClass(); -    return mLastPolicy; -} - - -bool HttpService::isStopped() -{ -    // What is really wanted here is something like: -    // -    //     HttpService * service = instanceOf(); -    //     return STOPPED == sState && (! service || ! service->mThread || ! service->mThread->joinable()); -    // -    // But boost::thread is not giving me a consistent story on joinability -    // of a thread after it returns.  Debug and non-debug builds are showing -    // different behavior on Linux/Etch so we do a weaker test that may -    // not be globally correct (i.e. thread *is* stopping, may not have -    // stopped but will very soon): - -    return STOPPED == sState; -} - - -/// Threading:  callable by consumer thread *once*. -void HttpService::startThread() -{ -    LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; -    llassert_always(! mThread || STOPPED == sState); -    llassert_always(INITIALIZED == sState || STOPPED == sState); - -    if (mThread) -    { -        mThread->release(); -    } - -    // Push current policy definitions, enable policy & transport components -    mPolicy->start(); -    mTransport->start(mLastPolicy + 1); - -    mThread = new LLCoreInt::HttpThread(boost::bind(&HttpService::threadRun, this, _1)); -    sState = RUNNING; -} - - -/// Threading:  callable by worker thread. -void HttpService::stopRequested() -{ -    mExitRequested = 1U; -} - - -/// Try to find the given request handle on any of the request -/// queues and cancel the operation. -/// -/// @return         True if the request was canceled. -/// -/// Threading:  callable by worker thread. -bool HttpService::cancel(HttpHandle handle) -{ -    LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; -    bool canceled(false); - -    // Request can't be on request queue so skip that. - -    // Check the policy component's queues first -    canceled = mPolicy->cancel(handle); - -    if (! canceled) -    { -        // If that didn't work, check transport's. -        canceled = mTransport->cancel(handle); -    } - -    return canceled; -} - - -/// Threading:  callable by worker thread. -void HttpService::shutdown() -{ -    LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; -    // Disallow future enqueue of requests -    mRequestQueue->stopQueue(); - -    // Cancel requests already on the request queue -    HttpRequestQueue::OpContainer ops; -    mRequestQueue->fetchAll(false, ops); - -    for (HttpRequestQueue::OpContainer::iterator it = ops.begin(); -        it != ops.end(); ++it) -    { -        (*it)->cancel(); -    } -    ops.clear(); - -    // Shutdown transport canceling requests, freeing resources -    mTransport->shutdown(); - -    // And now policy -    mPolicy->shutdown(); -} - - -// Working thread loop-forever method.  Gives time to -// each of the request queue, policy layer and transport -// layer pieces and then either sleeps for a small time -// or waits for a request to come in.  Repeats until -// requested to stop. -void HttpService::threadRun(LLCoreInt::HttpThread * thread) -{ -    LL_PROFILER_SET_THREAD_NAME("HttpService"); - -    boost::this_thread::disable_interruption di; - -    LLThread::registerThreadID(); - -    ELoopSpeed loop(REQUEST_SLEEP); -    while (! mExitRequested) -    { -        LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; -        try -        { -            loop = processRequestQueue(loop); - -            // Process ready queue issuing new requests as needed -            ELoopSpeed new_loop = mPolicy->processReadyQueue(); -            loop = (std::min)(loop, new_loop); - -            // Give libcurl some cycles -            new_loop = mTransport->processTransport(); -            loop = (std::min)(loop, new_loop); - -            // Determine whether to spin, sleep briefly or sleep for next request -            if (REQUEST_SLEEP != loop) -            { -                ms_sleep(HTTP_SERVICE_LOOP_SLEEP_NORMAL_MS); -            } -        } -        catch (const LLContinueError&) -        { -            LOG_UNHANDLED_EXCEPTION(""); -        } -        catch (std::bad_alloc&) -        { -            LLMemory::logMemoryInfo(TRUE); - -            //output possible call stacks to log file. -            LLError::LLUserWarningMsg::showOutOfMemory(); -            LLError::LLCallStacks::print(); - -            LL_ERRS() << "Bad memory allocation in HttpService::threadRun()!" << LL_ENDL; -        } -        catch (...) -        { -            CRASH_ON_UNHANDLED_EXCEPTION(""); -        } -    } - -    shutdown(); -    sState = STOPPED; -} - - -HttpService::ELoopSpeed HttpService::processRequestQueue(ELoopSpeed loop) -{ -    LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; -    HttpRequestQueue::OpContainer ops; -    const bool wait_for_req(REQUEST_SLEEP == loop); - -    mRequestQueue->fetchAll(wait_for_req, ops); -    while (! ops.empty()) -    { -        HttpOperation::ptr_t op(ops.front()); -        ops.erase(ops.begin()); - -        // Process operation -        if (! mExitRequested) -        { -            // Setup for subsequent tracing -            long tracing(HTTP_TRACE_OFF); -            mPolicy->getGlobalOptions().get(HttpRequest::PO_TRACE, &tracing); -            op->mTracing = (std::max)(op->mTracing, int(tracing)); - -            if (op->mTracing > HTTP_TRACE_OFF) -            { -                LL_INFOS(LOG_CORE) << "TRACE, FromRequestQueue, Handle:  " -                                   << op->getHandle() -                                   << LL_ENDL; -            } - -            // Stage -            op->stageFromRequest(this); -        } - -        // Done with operation -        op.reset(); -    } - -    // Queue emptied, allow polling loop to sleep -    return REQUEST_SLEEP; -} - - -HttpStatus HttpService::getPolicyOption(HttpRequest::EPolicyOption opt, HttpRequest::policy_t pclass, -                                        long * ret_value) -{ -    LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; -    if (opt < HttpRequest::PO_CONNECTION_LIMIT                                          // option must be in range -        || opt >= HttpRequest::PO_LAST                                                  // ditto -        || (! sOptionDesc[opt].mIsLong)                                                 // datatype is long -        || (pclass != HttpRequest::GLOBAL_POLICY_ID && pclass > mLastPolicy)            // pclass in valid range -        || (pclass == HttpRequest::GLOBAL_POLICY_ID && ! sOptionDesc[opt].mIsGlobal)    // global setting permitted -        || (pclass != HttpRequest::GLOBAL_POLICY_ID && ! sOptionDesc[opt].mIsClass))    // class setting permitted -                                                                                        // can always get, no dynamic check -    { -        return HttpStatus(HttpStatus::LLCORE, LLCore::HE_INVALID_ARG); -    } - -    HttpStatus status; -    if (pclass == HttpRequest::GLOBAL_POLICY_ID) -    { -        HttpPolicyGlobal & opts(mPolicy->getGlobalOptions()); - -        status = opts.get(opt, ret_value); -    } -    else -    { -        HttpPolicyClass & opts(mPolicy->getClassOptions(pclass)); - -        status = opts.get(opt, ret_value); -    } - -    return status; -} - - -HttpStatus HttpService::getPolicyOption(HttpRequest::EPolicyOption opt, HttpRequest::policy_t pclass, -                                        std::string * ret_value) -{ -    LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; -    HttpStatus status(HttpStatus::LLCORE, LLCore::HE_INVALID_ARG); - -    if (opt < HttpRequest::PO_CONNECTION_LIMIT                                          // option must be in range -        || opt >= HttpRequest::PO_LAST                                                  // ditto -        || (sOptionDesc[opt].mIsLong)                                                   // datatype is string -        || (pclass != HttpRequest::GLOBAL_POLICY_ID && pclass > mLastPolicy)            // pclass in valid range -        || (pclass == HttpRequest::GLOBAL_POLICY_ID && ! sOptionDesc[opt].mIsGlobal)    // global setting permitted -        || (pclass != HttpRequest::GLOBAL_POLICY_ID && ! sOptionDesc[opt].mIsClass))    // class setting permitted -                                                                                        // can always get, no dynamic check -    { -        return status; -    } - -    // Only global has string values -    if (pclass == HttpRequest::GLOBAL_POLICY_ID) -    { -        HttpPolicyGlobal & opts(mPolicy->getGlobalOptions()); - -        status = opts.get(opt, ret_value); -    } - -    return status; -} - -HttpStatus HttpService::getPolicyOption(HttpRequest::EPolicyOption opt, HttpRequest::policy_t pclass, -    HttpRequest::policyCallback_t * ret_value) -{ -    LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; -    HttpStatus status(HttpStatus::LLCORE, LLCore::HE_INVALID_ARG); - -    if (opt < HttpRequest::PO_CONNECTION_LIMIT                                          // option must be in range -        || opt >= HttpRequest::PO_LAST                                                  // ditto -        || (sOptionDesc[opt].mIsLong)                                                   // datatype is string -        || (pclass != HttpRequest::GLOBAL_POLICY_ID && pclass > mLastPolicy)            // pclass in valid range -        || (pclass == HttpRequest::GLOBAL_POLICY_ID && !sOptionDesc[opt].mIsGlobal) // global setting permitted -        || (pclass != HttpRequest::GLOBAL_POLICY_ID && !sOptionDesc[opt].mIsClass)) // class setting permitted -        // can always get, no dynamic check -    { -        return status; -    } - -    // Only global has callback values -    if (pclass == HttpRequest::GLOBAL_POLICY_ID) -    { -        HttpPolicyGlobal & opts(mPolicy->getGlobalOptions()); - -        status = opts.get(opt, ret_value); -    } - -    return status; -} - - - -HttpStatus HttpService::setPolicyOption(HttpRequest::EPolicyOption opt, HttpRequest::policy_t pclass, -                                        long value, long * ret_value) -{ -    LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; -    HttpStatus status(HttpStatus::LLCORE, LLCore::HE_INVALID_ARG); - -    if (opt < HttpRequest::PO_CONNECTION_LIMIT                                          // option must be in range -        || opt >= HttpRequest::PO_LAST                                                  // ditto -        || (! sOptionDesc[opt].mIsLong)                                                 // datatype is long -        || (pclass != HttpRequest::GLOBAL_POLICY_ID && pclass > mLastPolicy)            // pclass in valid range -        || (pclass == HttpRequest::GLOBAL_POLICY_ID && ! sOptionDesc[opt].mIsGlobal)    // global setting permitted -        || (pclass != HttpRequest::GLOBAL_POLICY_ID && ! sOptionDesc[opt].mIsClass)     // class setting permitted -        || (RUNNING == sState && ! sOptionDesc[opt].mIsDynamic))                        // dynamic setting permitted -    { -        return status; -    } - -    if (pclass == HttpRequest::GLOBAL_POLICY_ID) -    { -        HttpPolicyGlobal & opts(mPolicy->getGlobalOptions()); - -        status = opts.set(opt, value); -        if (status && ret_value) -        { -            status = opts.get(opt, ret_value); -        } -    } -    else -    { -        HttpPolicyClass & opts(mPolicy->getClassOptions(pclass)); - -        status = opts.set(opt, value); -        if (status) -        { -            mTransport->policyUpdated(pclass); -            if (ret_value) -            { -                status = opts.get(opt, ret_value); -            } -        } -    } - -    return status; -} - - -HttpStatus HttpService::setPolicyOption(HttpRequest::EPolicyOption opt, HttpRequest::policy_t pclass, -                                        const std::string & value, std::string * ret_value) -{ -    LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; -    HttpStatus status(HttpStatus::LLCORE, LLCore::HE_INVALID_ARG); - -    if (opt < HttpRequest::PO_CONNECTION_LIMIT                                          // option must be in range -        || opt >= HttpRequest::PO_LAST                                                  // ditto -        || (sOptionDesc[opt].mIsLong)                                                   // datatype is string -        || (pclass != HttpRequest::GLOBAL_POLICY_ID && pclass > mLastPolicy)            // pclass in valid range -        || (pclass == HttpRequest::GLOBAL_POLICY_ID && ! sOptionDesc[opt].mIsGlobal)    // global setting permitted -        || (pclass != HttpRequest::GLOBAL_POLICY_ID && ! sOptionDesc[opt].mIsClass)     // class setting permitted -        || (RUNNING == sState && ! sOptionDesc[opt].mIsDynamic))                        // dynamic setting permitted -    { -        return status; -    } - -    // String values are always global (at this time). -    if (pclass == HttpRequest::GLOBAL_POLICY_ID) -    { -        HttpPolicyGlobal & opts(mPolicy->getGlobalOptions()); - -        status = opts.set(opt, value); -        if (status && ret_value) -        { -            status = opts.get(opt, ret_value); -        } -    } - -    return status; -} - -HttpStatus HttpService::setPolicyOption(HttpRequest::EPolicyOption opt, HttpRequest::policy_t pclass, -    HttpRequest::policyCallback_t value, HttpRequest::policyCallback_t * ret_value) -{ -    LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; -    HttpStatus status(HttpStatus::LLCORE, LLCore::HE_INVALID_ARG); - -    if (opt < HttpRequest::PO_CONNECTION_LIMIT                                          // option must be in range -        || opt >= HttpRequest::PO_LAST                                                  // ditto -        || (sOptionDesc[opt].mIsLong)                                                   // datatype is string -        || (pclass != HttpRequest::GLOBAL_POLICY_ID && pclass > mLastPolicy)            // pclass in valid range -        || (pclass == HttpRequest::GLOBAL_POLICY_ID && !sOptionDesc[opt].mIsGlobal) // global setting permitted -        || (pclass != HttpRequest::GLOBAL_POLICY_ID && !sOptionDesc[opt].mIsClass)      // class setting permitted -        || (RUNNING == sState && !sOptionDesc[opt].mIsDynamic))                     // dynamic setting permitted -    { -        return status; -    } - -    // Callbacks values are always global (at this time). -    if (pclass == HttpRequest::GLOBAL_POLICY_ID) -    { -        HttpPolicyGlobal & opts(mPolicy->getGlobalOptions()); - -        status = opts.set(opt, value); -        if (status && ret_value) -        { -            status = opts.get(opt, ret_value); -        } -    } - -    return status; -} - - -}  // end namespace LLCore +/**
 + * @file _httpservice.cpp
 + * @brief Internal definitions of the Http service thread
 + *
 + * $LicenseInfo:firstyear=2012&license=viewerlgpl$
 + * Second Life Viewer Source Code
 + * Copyright (C) 2012-2014, 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 "_httpservice.h"
 +
 +#include <boost/bind.hpp>
 +#include <boost/function.hpp>
 +
 +#include "_httpoperation.h"
 +#include "_httprequestqueue.h"
 +#include "_httppolicy.h"
 +#include "_httplibcurl.h"
 +#include "_thread.h"
 +#include "_httpinternal.h"
 +
 +#include "lltimer.h"
 +#include "llthread.h"
 +#include "llexception.h"
 +#include "llmemory.h"
 +
 +namespace
 +{
 +
 +static const char * const LOG_CORE("CoreHttp");
 +
 +} // end anonymous namespace
 +
 +
 +namespace LLCore
 +{
 +
 +const HttpService::OptionDescriptor HttpService::sOptionDesc[] =
 +{ //    isLong     isDynamic  isGlobal    isClass
 +    {   true,       true,       true,       true,       false   },      // PO_CONNECTION_LIMIT
 +    {   true,       true,       false,      true,       false   },      // PO_PER_HOST_CONNECTION_LIMIT
 +    {   false,      false,      true,       false,      false   },      // PO_CA_PATH
 +    {   false,      false,      true,       false,      false   },      // PO_CA_FILE
 +    {   false,      true,       true,       false,      false   },      // PO_HTTP_PROXY
 +    {   true,       true,       true,       false,      false   },      // PO_LLPROXY
 +    {   true,       true,       true,       false,      false   },      // PO_TRACE
 +    {   true,       true,       false,      true,       false   },      // PO_ENABLE_PIPELINING
 +    {   true,       true,       false,      true,       false   },      // PO_THROTTLE_RATE
 +    {   false,      false,      true,       false,      true    }       // PO_SSL_VERIFY_CALLBACK
 +};
 +HttpService * HttpService::sInstance(NULL);
 +volatile HttpService::EState HttpService::sState(NOT_INITIALIZED);
 +
 +HttpService::HttpService()
 +    : mRequestQueue(NULL),
 +      mExitRequested(0U),
 +      mThread(NULL),
 +      mPolicy(NULL),
 +      mTransport(NULL),
 +      mLastPolicy(0)
 +{}
 +
 +
 +HttpService::~HttpService()
 +{
 +    LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
 +    mExitRequested = 1U;
 +    if (RUNNING == sState)
 +    {
 +        // Trying to kill the service object with a running thread
 +        // is a bit tricky.
 +        if (mRequestQueue)
 +        {
 +            if (mRequestQueue->stopQueue())
 +            {
 +                // Give mRequestQueue a chance to finish
 +                ms_sleep(10);
 +            }
 +        }
 +
 +        if (mThread)
 +        {
 +            if (! mThread->timedJoin(250))
 +            {
 +                // Failed to join, expect problems ahead so do a hard termination.
 +                LL_WARNS(LOG_CORE) << "Destroying HttpService with running thread.  Expect problems." << LL_NEWLINE
 +                                    << "State: " << S32(sState)
 +                                    << " Last policy: " << U32(mLastPolicy)
 +                                    << LL_ENDL;
 +
 +                mThread->cancel();
 +            }
 +        }
 +    }
 +
 +    if (mRequestQueue)
 +    {
 +        mRequestQueue->release();
 +        mRequestQueue = NULL;
 +    }
 +
 +    delete mTransport;
 +    mTransport = NULL;
 +
 +    delete mPolicy;
 +    mPolicy = NULL;
 +
 +    if (mThread)
 +    {
 +        mThread->release();
 +        mThread = NULL;
 +    }
 +}
 +
 +
 +void HttpService::init(HttpRequestQueue * queue)
 +{
 +    LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
 +    llassert_always(! sInstance);
 +    llassert_always(NOT_INITIALIZED == sState);
 +    sInstance = new HttpService();
 +
 +    queue->addRef();
 +    sInstance->mRequestQueue = queue;
 +    sInstance->mPolicy = new HttpPolicy(sInstance);
 +    sInstance->mTransport = new HttpLibcurl(sInstance);
 +    sState = INITIALIZED;
 +}
 +
 +
 +void HttpService::term()
 +{
 +    LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
 +    if (sInstance)
 +    {
 +        if (RUNNING == sState && sInstance->mThread)
 +        {
 +            // Unclean termination.  Thread appears to be running.  We'll
 +            // try to give the worker thread a chance to cancel using the
 +            // exit flag...
 +            sInstance->mExitRequested = 1U;
 +            sInstance->mRequestQueue->stopQueue();
 +
 +            // And a little sleep
 +            for (int i(0); i < 10 && RUNNING == sState; ++i)
 +            {
 +                ms_sleep(100);
 +            }
 +        }
 +
 +        delete sInstance;
 +        sInstance = NULL;
 +    }
 +    sState = NOT_INITIALIZED;
 +}
 +
 +
 +HttpRequest::policy_t HttpService::createPolicyClass()
 +{
 +    mLastPolicy = mPolicy->createPolicyClass();
 +    return mLastPolicy;
 +}
 +
 +
 +bool HttpService::isStopped()
 +{
 +    // What is really wanted here is something like:
 +    //
 +    //     HttpService * service = instanceOf();
 +    //     return STOPPED == sState && (! service || ! service->mThread || ! service->mThread->joinable());
 +    //
 +    // But boost::thread is not giving me a consistent story on joinability
 +    // of a thread after it returns.  Debug and non-debug builds are showing
 +    // different behavior on Linux/Etch so we do a weaker test that may
 +    // not be globally correct (i.e. thread *is* stopping, may not have
 +    // stopped but will very soon):
 +
 +    return STOPPED == sState;
 +}
 +
 +
 +/// Threading:  callable by consumer thread *once*.
 +void HttpService::startThread()
 +{
 +    LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
 +    llassert_always(! mThread || STOPPED == sState);
 +    llassert_always(INITIALIZED == sState || STOPPED == sState);
 +
 +    if (mThread)
 +    {
 +        mThread->release();
 +    }
 +
 +    // Push current policy definitions, enable policy & transport components
 +    mPolicy->start();
 +    mTransport->start(mLastPolicy + 1);
 +
 +    mThread = new LLCoreInt::HttpThread(boost::bind(&HttpService::threadRun, this, _1));
 +    sState = RUNNING;
 +}
 +
 +
 +/// Threading:  callable by worker thread.
 +void HttpService::stopRequested()
 +{
 +    mExitRequested = 1U;
 +}
 +
 +
 +/// Try to find the given request handle on any of the request
 +/// queues and cancel the operation.
 +///
 +/// @return         True if the request was canceled.
 +///
 +/// Threading:  callable by worker thread.
 +bool HttpService::cancel(HttpHandle handle)
 +{
 +    LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
 +    bool canceled(false);
 +
 +    // Request can't be on request queue so skip that.
 +
 +    // Check the policy component's queues first
 +    canceled = mPolicy->cancel(handle);
 +
 +    if (! canceled)
 +    {
 +        // If that didn't work, check transport's.
 +        canceled = mTransport->cancel(handle);
 +    }
 +
 +    return canceled;
 +}
 +
 +
 +/// Threading:  callable by worker thread.
 +void HttpService::shutdown()
 +{
 +    LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
 +    // Disallow future enqueue of requests
 +    mRequestQueue->stopQueue();
 +
 +    // Cancel requests already on the request queue
 +    HttpRequestQueue::OpContainer ops;
 +    mRequestQueue->fetchAll(false, ops);
 +
 +    for (HttpRequestQueue::OpContainer::iterator it = ops.begin();
 +        it != ops.end(); ++it)
 +    {
 +        (*it)->cancel();
 +    }
 +    ops.clear();
 +
 +    // Shutdown transport canceling requests, freeing resources
 +    mTransport->shutdown();
 +
 +    // And now policy
 +    mPolicy->shutdown();
 +}
 +
 +
 +// Working thread loop-forever method.  Gives time to
 +// each of the request queue, policy layer and transport
 +// layer pieces and then either sleeps for a small time
 +// or waits for a request to come in.  Repeats until
 +// requested to stop.
 +void HttpService::threadRun(LLCoreInt::HttpThread * thread)
 +{
 +    LL_PROFILER_SET_THREAD_NAME("HttpService");
 +
 +    boost::this_thread::disable_interruption di;
 +
 +    LLThread::registerThreadID();
 +
 +    ELoopSpeed loop(REQUEST_SLEEP);
 +    while (! mExitRequested)
 +    {
 +        LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
 +        try
 +        {
 +            loop = processRequestQueue(loop);
 +
 +            // Process ready queue issuing new requests as needed
 +            ELoopSpeed new_loop = mPolicy->processReadyQueue();
 +            loop = (std::min)(loop, new_loop);
 +
 +            // Give libcurl some cycles
 +            new_loop = mTransport->processTransport();
 +            loop = (std::min)(loop, new_loop);
 +
 +            // Determine whether to spin, sleep briefly or sleep for next request
 +            if (REQUEST_SLEEP != loop)
 +            {
 +                ms_sleep(HTTP_SERVICE_LOOP_SLEEP_NORMAL_MS);
 +            }
 +        }
 +        catch (const LLContinueError&)
 +        {
 +            LOG_UNHANDLED_EXCEPTION("");
 +        }
 +        catch (std::bad_alloc&)
 +        {
 +            LLMemory::logMemoryInfo(true);
 +
 +            //output possible call stacks to log file.
 +            LLError::LLUserWarningMsg::showOutOfMemory();
 +            LLError::LLCallStacks::print();
 +
 +            LL_ERRS() << "Bad memory allocation in HttpService::threadRun()!" << LL_ENDL;
 +        }
 +        catch (...)
 +        {
 +            CRASH_ON_UNHANDLED_EXCEPTION("");
 +        }
 +    }
 +
 +    shutdown();
 +    sState = STOPPED;
 +}
 +
 +
 +HttpService::ELoopSpeed HttpService::processRequestQueue(ELoopSpeed loop)
 +{
 +    LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
 +    HttpRequestQueue::OpContainer ops;
 +    const bool wait_for_req(REQUEST_SLEEP == loop);
 +
 +    mRequestQueue->fetchAll(wait_for_req, ops);
 +    while (! ops.empty())
 +    {
 +        HttpOperation::ptr_t op(ops.front());
 +        ops.erase(ops.begin());
 +
 +        // Process operation
 +        if (! mExitRequested)
 +        {
 +            // Setup for subsequent tracing
 +            long tracing(HTTP_TRACE_OFF);
 +            mPolicy->getGlobalOptions().get(HttpRequest::PO_TRACE, &tracing);
 +            op->mTracing = (std::max)(op->mTracing, int(tracing));
 +
 +            if (op->mTracing > HTTP_TRACE_OFF)
 +            {
 +                LL_INFOS(LOG_CORE) << "TRACE, FromRequestQueue, Handle:  "
 +                                   << op->getHandle()
 +                                   << LL_ENDL;
 +            }
 +
 +            // Stage
 +            op->stageFromRequest(this);
 +        }
 +
 +        // Done with operation
 +        op.reset();
 +    }
 +
 +    // Queue emptied, allow polling loop to sleep
 +    return REQUEST_SLEEP;
 +}
 +
 +
 +HttpStatus HttpService::getPolicyOption(HttpRequest::EPolicyOption opt, HttpRequest::policy_t pclass,
 +                                        long * ret_value)
 +{
 +    LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
 +    if (opt < HttpRequest::PO_CONNECTION_LIMIT                                          // option must be in range
 +        || opt >= HttpRequest::PO_LAST                                                  // ditto
 +        || (! sOptionDesc[opt].mIsLong)                                                 // datatype is long
 +        || (pclass != HttpRequest::GLOBAL_POLICY_ID && pclass > mLastPolicy)            // pclass in valid range
 +        || (pclass == HttpRequest::GLOBAL_POLICY_ID && ! sOptionDesc[opt].mIsGlobal)    // global setting permitted
 +        || (pclass != HttpRequest::GLOBAL_POLICY_ID && ! sOptionDesc[opt].mIsClass))    // class setting permitted
 +                                                                                        // can always get, no dynamic check
 +    {
 +        return HttpStatus(HttpStatus::LLCORE, LLCore::HE_INVALID_ARG);
 +    }
 +
 +    HttpStatus status;
 +    if (pclass == HttpRequest::GLOBAL_POLICY_ID)
 +    {
 +        HttpPolicyGlobal & opts(mPolicy->getGlobalOptions());
 +
 +        status = opts.get(opt, ret_value);
 +    }
 +    else
 +    {
 +        HttpPolicyClass & opts(mPolicy->getClassOptions(pclass));
 +
 +        status = opts.get(opt, ret_value);
 +    }
 +
 +    return status;
 +}
 +
 +
 +HttpStatus HttpService::getPolicyOption(HttpRequest::EPolicyOption opt, HttpRequest::policy_t pclass,
 +                                        std::string * ret_value)
 +{
 +    LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
 +    HttpStatus status(HttpStatus::LLCORE, LLCore::HE_INVALID_ARG);
 +
 +    if (opt < HttpRequest::PO_CONNECTION_LIMIT                                          // option must be in range
 +        || opt >= HttpRequest::PO_LAST                                                  // ditto
 +        || (sOptionDesc[opt].mIsLong)                                                   // datatype is string
 +        || (pclass != HttpRequest::GLOBAL_POLICY_ID && pclass > mLastPolicy)            // pclass in valid range
 +        || (pclass == HttpRequest::GLOBAL_POLICY_ID && ! sOptionDesc[opt].mIsGlobal)    // global setting permitted
 +        || (pclass != HttpRequest::GLOBAL_POLICY_ID && ! sOptionDesc[opt].mIsClass))    // class setting permitted
 +                                                                                        // can always get, no dynamic check
 +    {
 +        return status;
 +    }
 +
 +    // Only global has string values
 +    if (pclass == HttpRequest::GLOBAL_POLICY_ID)
 +    {
 +        HttpPolicyGlobal & opts(mPolicy->getGlobalOptions());
 +
 +        status = opts.get(opt, ret_value);
 +    }
 +
 +    return status;
 +}
 +
 +HttpStatus HttpService::getPolicyOption(HttpRequest::EPolicyOption opt, HttpRequest::policy_t pclass,
 +    HttpRequest::policyCallback_t * ret_value)
 +{
 +    LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
 +    HttpStatus status(HttpStatus::LLCORE, LLCore::HE_INVALID_ARG);
 +
 +    if (opt < HttpRequest::PO_CONNECTION_LIMIT                                          // option must be in range
 +        || opt >= HttpRequest::PO_LAST                                                  // ditto
 +        || (sOptionDesc[opt].mIsLong)                                                   // datatype is string
 +        || (pclass != HttpRequest::GLOBAL_POLICY_ID && pclass > mLastPolicy)            // pclass in valid range
 +        || (pclass == HttpRequest::GLOBAL_POLICY_ID && !sOptionDesc[opt].mIsGlobal) // global setting permitted
 +        || (pclass != HttpRequest::GLOBAL_POLICY_ID && !sOptionDesc[opt].mIsClass)) // class setting permitted
 +        // can always get, no dynamic check
 +    {
 +        return status;
 +    }
 +
 +    // Only global has callback values
 +    if (pclass == HttpRequest::GLOBAL_POLICY_ID)
 +    {
 +        HttpPolicyGlobal & opts(mPolicy->getGlobalOptions());
 +
 +        status = opts.get(opt, ret_value);
 +    }
 +
 +    return status;
 +}
 +
 +
 +
 +HttpStatus HttpService::setPolicyOption(HttpRequest::EPolicyOption opt, HttpRequest::policy_t pclass,
 +                                        long value, long * ret_value)
 +{
 +    LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
 +    HttpStatus status(HttpStatus::LLCORE, LLCore::HE_INVALID_ARG);
 +
 +    if (opt < HttpRequest::PO_CONNECTION_LIMIT                                          // option must be in range
 +        || opt >= HttpRequest::PO_LAST                                                  // ditto
 +        || (! sOptionDesc[opt].mIsLong)                                                 // datatype is long
 +        || (pclass != HttpRequest::GLOBAL_POLICY_ID && pclass > mLastPolicy)            // pclass in valid range
 +        || (pclass == HttpRequest::GLOBAL_POLICY_ID && ! sOptionDesc[opt].mIsGlobal)    // global setting permitted
 +        || (pclass != HttpRequest::GLOBAL_POLICY_ID && ! sOptionDesc[opt].mIsClass)     // class setting permitted
 +        || (RUNNING == sState && ! sOptionDesc[opt].mIsDynamic))                        // dynamic setting permitted
 +    {
 +        return status;
 +    }
 +
 +    if (pclass == HttpRequest::GLOBAL_POLICY_ID)
 +    {
 +        HttpPolicyGlobal & opts(mPolicy->getGlobalOptions());
 +
 +        status = opts.set(opt, value);
 +        if (status && ret_value)
 +        {
 +            status = opts.get(opt, ret_value);
 +        }
 +    }
 +    else
 +    {
 +        HttpPolicyClass & opts(mPolicy->getClassOptions(pclass));
 +
 +        status = opts.set(opt, value);
 +        if (status)
 +        {
 +            mTransport->policyUpdated(pclass);
 +            if (ret_value)
 +            {
 +                status = opts.get(opt, ret_value);
 +            }
 +        }
 +    }
 +
 +    return status;
 +}
 +
 +
 +HttpStatus HttpService::setPolicyOption(HttpRequest::EPolicyOption opt, HttpRequest::policy_t pclass,
 +                                        const std::string & value, std::string * ret_value)
 +{
 +    LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
 +    HttpStatus status(HttpStatus::LLCORE, LLCore::HE_INVALID_ARG);
 +
 +    if (opt < HttpRequest::PO_CONNECTION_LIMIT                                          // option must be in range
 +        || opt >= HttpRequest::PO_LAST                                                  // ditto
 +        || (sOptionDesc[opt].mIsLong)                                                   // datatype is string
 +        || (pclass != HttpRequest::GLOBAL_POLICY_ID && pclass > mLastPolicy)            // pclass in valid range
 +        || (pclass == HttpRequest::GLOBAL_POLICY_ID && ! sOptionDesc[opt].mIsGlobal)    // global setting permitted
 +        || (pclass != HttpRequest::GLOBAL_POLICY_ID && ! sOptionDesc[opt].mIsClass)     // class setting permitted
 +        || (RUNNING == sState && ! sOptionDesc[opt].mIsDynamic))                        // dynamic setting permitted
 +    {
 +        return status;
 +    }
 +
 +    // String values are always global (at this time).
 +    if (pclass == HttpRequest::GLOBAL_POLICY_ID)
 +    {
 +        HttpPolicyGlobal & opts(mPolicy->getGlobalOptions());
 +
 +        status = opts.set(opt, value);
 +        if (status && ret_value)
 +        {
 +            status = opts.get(opt, ret_value);
 +        }
 +    }
 +
 +    return status;
 +}
 +
 +HttpStatus HttpService::setPolicyOption(HttpRequest::EPolicyOption opt, HttpRequest::policy_t pclass,
 +    HttpRequest::policyCallback_t value, HttpRequest::policyCallback_t * ret_value)
 +{
 +    LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK;
 +    HttpStatus status(HttpStatus::LLCORE, LLCore::HE_INVALID_ARG);
 +
 +    if (opt < HttpRequest::PO_CONNECTION_LIMIT                                          // option must be in range
 +        || opt >= HttpRequest::PO_LAST                                                  // ditto
 +        || (sOptionDesc[opt].mIsLong)                                                   // datatype is string
 +        || (pclass != HttpRequest::GLOBAL_POLICY_ID && pclass > mLastPolicy)            // pclass in valid range
 +        || (pclass == HttpRequest::GLOBAL_POLICY_ID && !sOptionDesc[opt].mIsGlobal) // global setting permitted
 +        || (pclass != HttpRequest::GLOBAL_POLICY_ID && !sOptionDesc[opt].mIsClass)      // class setting permitted
 +        || (RUNNING == sState && !sOptionDesc[opt].mIsDynamic))                     // dynamic setting permitted
 +    {
 +        return status;
 +    }
 +
 +    // Callbacks values are always global (at this time).
 +    if (pclass == HttpRequest::GLOBAL_POLICY_ID)
 +    {
 +        HttpPolicyGlobal & opts(mPolicy->getGlobalOptions());
 +
 +        status = opts.set(opt, value);
 +        if (status && ret_value)
 +        {
 +            status = opts.get(opt, ret_value);
 +        }
 +    }
 +
 +    return status;
 +}
 +
 +
 +}  // end namespace LLCore
 diff --git a/indra/llcorehttp/bufferarray.cpp b/indra/llcorehttp/bufferarray.cpp index 50a8d461a7..c35f44c2c9 100644 --- a/indra/llcorehttp/bufferarray.cpp +++ b/indra/llcorehttp/bufferarray.cpp @@ -1,368 +1,368 @@ -/** - * @file bufferarray.cpp - * @brief Implements the BufferArray scatter/gather buffer - * - * $LicenseInfo:firstyear=2012&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2012, 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 "bufferarray.h" -#include "llexception.h" -#include "llmemory.h" - - -// BufferArray is a list of chunks, each a BufferArray::Block, of contiguous -// data presented as a single array.  Chunks are at least BufferArray::BLOCK_ALLOC_SIZE -// in length and can be larger.  Any chunk may be partially filled or even -// empty. -// -// The BufferArray itself is sharable as a RefCounted entity.  As shared -// reads don't work with the concept of a current position/seek value, -// none is kept with the object.  Instead, the read and write operations -// all take position arguments.  Single write/shared read isn't supported -// directly and any such attempts have to be serialized outside of this -// implementation. - -namespace LLCore -{ - - -// ================================== -// BufferArray::Block Declaration -// ================================== - -class BufferArray::Block -{ -public: -    ~Block(); - -    void operator delete(void *); -    void operator delete(void *, size_t len); - -protected: -    Block(size_t len); - -    Block(const Block &);                       // Not defined -    void operator=(const Block &);              // Not defined - -    // Allocate the block with the additional space for the -    // buffered data at the end of the object. -    void * operator new(size_t len, size_t addl_len); - -public: -    // Only public entry to get a block. -    static Block * alloc(size_t len); - -public: -    size_t mUsed; -    size_t mAlloced; - -    // *NOTE:  Must be last member of the object.  We'll -    // overallocate as requested via operator new and index -    // into the array at will. -    char mData[1]; -}; - - -// ================================== -// BufferArray Definitions -// ================================== - - -#if ! LL_WINDOWS -const size_t BufferArray::BLOCK_ALLOC_SIZE; -#endif  // ! LL_WINDOWS - -BufferArray::BufferArray() -    : LLCoreInt::RefCounted(true), -      mLen(0) -{} - - -BufferArray::~BufferArray() -{ -    for (container_t::iterator it(mBlocks.begin()); -         it != mBlocks.end(); -         ++it) -    { -        delete *it; -        *it = NULL; -    } -    mBlocks.clear(); -} - - -size_t BufferArray::append(const void * src, size_t len) -{ -    const size_t ret(len); -    const char * c_src(static_cast<const char *>(src)); - -    // First, try to copy into the last block -    if (len && ! mBlocks.empty()) -    { -        Block & last(*mBlocks.back()); -        if (last.mUsed < last.mAlloced) -        { -            // Some will fit... -            const size_t copy_len((std::min)(len, (last.mAlloced - last.mUsed))); - -            memcpy(&last.mData[last.mUsed], c_src, copy_len); -            last.mUsed += copy_len; -            llassert_always(last.mUsed <= last.mAlloced); -            mLen += copy_len; -            c_src += copy_len; -            len -= copy_len; -        } -    } - -    // Then get new blocks as needed -    while (len) -    { -        const size_t copy_len((std::min)(len, BLOCK_ALLOC_SIZE)); - -        if (mBlocks.size() >= mBlocks.capacity()) -        { -            mBlocks.reserve(mBlocks.size() + 5); -        } -        Block * block; -        try -        { -            block = Block::alloc(BLOCK_ALLOC_SIZE); -        } -        catch (std::bad_alloc&) -        { -            LLMemory::logMemoryInfo(TRUE); - -            //output possible call stacks to log file. -            LLError::LLCallStacks::print(); - -            LL_WARNS() << "Bad memory allocation in thrown by Block::alloc in read!" << LL_ENDL; -            break; -        } -        memcpy(block->mData, c_src, copy_len); -        block->mUsed = copy_len; -        llassert_always(block->mUsed <= block->mAlloced); -        mBlocks.push_back(block); -        mLen += copy_len; -        c_src += copy_len; -        len -= copy_len; -    } -    return ret - len; -} - - -void * BufferArray::appendBufferAlloc(size_t len) -{ -    // If someone asks for zero-length, we give them a valid pointer. -    if (mBlocks.size() >= mBlocks.capacity()) -    { -        mBlocks.reserve(mBlocks.size() + 5); -    } -    Block * block = Block::alloc((std::max)(BLOCK_ALLOC_SIZE, len)); -    block->mUsed = len; -    mBlocks.push_back(block); -    mLen += len; -    return block->mData; -} - - -size_t BufferArray::read(size_t pos, void * dst, size_t len) -{ -    char * c_dst(static_cast<char *>(dst)); - -    if (pos >= mLen) -        return 0; -    size_t len_limit(mLen - pos); -    len = (std::min)(len, len_limit); -    if (0 == len) -        return 0; - -    size_t result(0), offset(0); -    const auto block_limit(mBlocks.size()); -    int block_start(findBlock(pos, &offset)); -    if (block_start < 0) -        return 0; - -    do -    { -        Block & block(*mBlocks[block_start]); -        size_t block_limit(block.mUsed - offset); -        size_t block_len((std::min)(block_limit, len)); - -        memcpy(c_dst, &block.mData[offset], block_len); -        result += block_len; -        len -= block_len; -        c_dst += block_len; -        offset = 0; -        ++block_start; -    } -    while (len && block_start < block_limit); - -    return result; -} - - -size_t BufferArray::write(size_t pos, const void * src, size_t len) -{ -    const char * c_src(static_cast<const char *>(src)); - -    if (pos > mLen || 0 == len) -        return 0; - -    size_t result(0), offset(0); -    const auto block_limit(mBlocks.size()); -    int block_start(findBlock(pos, &offset)); - -    if (block_start >= 0) -    { -        // Some or all of the write will be on top of -        // existing data. -        do -        { -            Block & block(*mBlocks[block_start]); -            size_t block_limit(block.mUsed - offset); -            size_t block_len((std::min)(block_limit, len)); - -            memcpy(&block.mData[offset], c_src, block_len); -            result += block_len; -            c_src += block_len; -            len -= block_len; -            offset = 0; -            ++block_start; -        } -        while (len && block_start < block_limit); -    } - -    // Something left, see if it will fit in the free -    // space of the last block. -    if (len && ! mBlocks.empty()) -    { -        Block & last(*mBlocks.back()); -        if (last.mUsed < last.mAlloced) -        { -            // Some will fit... -            const size_t copy_len((std::min)(len, (last.mAlloced - last.mUsed))); - -            memcpy(&last.mData[last.mUsed], c_src, copy_len); -            last.mUsed += copy_len; -            result += copy_len; -            llassert_always(last.mUsed <= last.mAlloced); -            mLen += copy_len; -            c_src += copy_len; -            len -= copy_len; -        } -    } - -    if (len) -    { -        // Some or all of the remaining write data will -        // be an append. -        result += append(c_src, len); -    } - -    return result; -} - - -int BufferArray::findBlock(size_t pos, size_t * ret_offset) -{ -    *ret_offset = 0; -    if (pos >= mLen) -        return -1;      // Doesn't exist - -    const int block_limit(narrow<size_t>(mBlocks.size())); -    for (int i(0); i < block_limit; ++i) -    { -        if (pos < mBlocks[i]->mUsed) -        { -            *ret_offset = pos; -            return i; -        } -        pos -= mBlocks[i]->mUsed; -    } - -    // Shouldn't get here but... -    return -1; -} - - -bool BufferArray::getBlockStartEnd(int block, const char ** start, const char ** end) -{ -    if (block < 0 || block >= mBlocks.size()) -    { -        return false; -    } - -    const Block & b(*mBlocks[block]); -    *start = &b.mData[0]; -    *end = &b.mData[b.mUsed]; -    return true; -} - - -// ================================== -// BufferArray::Block Definitions -// ================================== - - -BufferArray::Block::Block(size_t len) -    : mUsed(0), -      mAlloced(len) -{ -    memset(mData, 0, len); -} - - -BufferArray::Block::~Block() -{ -    mUsed = 0; -    mAlloced = 0; -} - - -void * BufferArray::Block::operator new(size_t len, size_t addl_len) -{ -    void * mem = new char[len + addl_len + sizeof(void *)]; -    return mem; -} - - -void BufferArray::Block::operator delete(void * mem) -{ -    char * cmem = static_cast<char *>(mem); -    delete [] cmem; -} - - -void BufferArray::Block::operator delete(void * mem, size_t) -{ -    operator delete(mem); -} - - -BufferArray::Block * BufferArray::Block::alloc(size_t len) -{ -    Block * block = new (len) Block(len); -    return block; -} - - -}  // end namespace LLCore +/**
 + * @file bufferarray.cpp
 + * @brief Implements the BufferArray scatter/gather buffer
 + *
 + * $LicenseInfo:firstyear=2012&license=viewerlgpl$
 + * Second Life Viewer Source Code
 + * Copyright (C) 2012, 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 "bufferarray.h"
 +#include "llexception.h"
 +#include "llmemory.h"
 +
 +
 +// BufferArray is a list of chunks, each a BufferArray::Block, of contiguous
 +// data presented as a single array.  Chunks are at least BufferArray::BLOCK_ALLOC_SIZE
 +// in length and can be larger.  Any chunk may be partially filled or even
 +// empty.
 +//
 +// The BufferArray itself is sharable as a RefCounted entity.  As shared
 +// reads don't work with the concept of a current position/seek value,
 +// none is kept with the object.  Instead, the read and write operations
 +// all take position arguments.  Single write/shared read isn't supported
 +// directly and any such attempts have to be serialized outside of this
 +// implementation.
 +
 +namespace LLCore
 +{
 +
 +
 +// ==================================
 +// BufferArray::Block Declaration
 +// ==================================
 +
 +class BufferArray::Block
 +{
 +public:
 +    ~Block();
 +
 +    void operator delete(void *);
 +    void operator delete(void *, size_t len);
 +
 +protected:
 +    Block(size_t len);
 +
 +    Block(const Block &);                       // Not defined
 +    void operator=(const Block &);              // Not defined
 +
 +    // Allocate the block with the additional space for the
 +    // buffered data at the end of the object.
 +    void * operator new(size_t len, size_t addl_len);
 +
 +public:
 +    // Only public entry to get a block.
 +    static Block * alloc(size_t len);
 +
 +public:
 +    size_t mUsed;
 +    size_t mAlloced;
 +
 +    // *NOTE:  Must be last member of the object.  We'll
 +    // overallocate as requested via operator new and index
 +    // into the array at will.
 +    char mData[1];
 +};
 +
 +
 +// ==================================
 +// BufferArray Definitions
 +// ==================================
 +
 +
 +#if ! LL_WINDOWS
 +const size_t BufferArray::BLOCK_ALLOC_SIZE;
 +#endif  // ! LL_WINDOWS
 +
 +BufferArray::BufferArray()
 +    : LLCoreInt::RefCounted(true),
 +      mLen(0)
 +{}
 +
 +
 +BufferArray::~BufferArray()
 +{
 +    for (container_t::iterator it(mBlocks.begin());
 +         it != mBlocks.end();
 +         ++it)
 +    {
 +        delete *it;
 +        *it = NULL;
 +    }
 +    mBlocks.clear();
 +}
 +
 +
 +size_t BufferArray::append(const void * src, size_t len)
 +{
 +    const size_t ret(len);
 +    const char * c_src(static_cast<const char *>(src));
 +
 +    // First, try to copy into the last block
 +    if (len && ! mBlocks.empty())
 +    {
 +        Block & last(*mBlocks.back());
 +        if (last.mUsed < last.mAlloced)
 +        {
 +            // Some will fit...
 +            const size_t copy_len((std::min)(len, (last.mAlloced - last.mUsed)));
 +
 +            memcpy(&last.mData[last.mUsed], c_src, copy_len);
 +            last.mUsed += copy_len;
 +            llassert_always(last.mUsed <= last.mAlloced);
 +            mLen += copy_len;
 +            c_src += copy_len;
 +            len -= copy_len;
 +        }
 +    }
 +
 +    // Then get new blocks as needed
 +    while (len)
 +    {
 +        const size_t copy_len((std::min)(len, BLOCK_ALLOC_SIZE));
 +
 +        if (mBlocks.size() >= mBlocks.capacity())
 +        {
 +            mBlocks.reserve(mBlocks.size() + 5);
 +        }
 +        Block * block;
 +        try
 +        {
 +            block = Block::alloc(BLOCK_ALLOC_SIZE);
 +        }
 +        catch (std::bad_alloc&)
 +        {
 +            LLMemory::logMemoryInfo(true);
 +
 +            //output possible call stacks to log file.
 +            LLError::LLCallStacks::print();
 +
 +            LL_WARNS() << "Bad memory allocation in thrown by Block::alloc in read!" << LL_ENDL;
 +            break;
 +        }
 +        memcpy(block->mData, c_src, copy_len);
 +        block->mUsed = copy_len;
 +        llassert_always(block->mUsed <= block->mAlloced);
 +        mBlocks.push_back(block);
 +        mLen += copy_len;
 +        c_src += copy_len;
 +        len -= copy_len;
 +    }
 +    return ret - len;
 +}
 +
 +
 +void * BufferArray::appendBufferAlloc(size_t len)
 +{
 +    // If someone asks for zero-length, we give them a valid pointer.
 +    if (mBlocks.size() >= mBlocks.capacity())
 +    {
 +        mBlocks.reserve(mBlocks.size() + 5);
 +    }
 +    Block * block = Block::alloc((std::max)(BLOCK_ALLOC_SIZE, len));
 +    block->mUsed = len;
 +    mBlocks.push_back(block);
 +    mLen += len;
 +    return block->mData;
 +}
 +
 +
 +size_t BufferArray::read(size_t pos, void * dst, size_t len)
 +{
 +    char * c_dst(static_cast<char *>(dst));
 +
 +    if (pos >= mLen)
 +        return 0;
 +    size_t len_limit(mLen - pos);
 +    len = (std::min)(len, len_limit);
 +    if (0 == len)
 +        return 0;
 +
 +    size_t result(0), offset(0);
 +    const auto block_limit(mBlocks.size());
 +    int block_start(findBlock(pos, &offset));
 +    if (block_start < 0)
 +        return 0;
 +
 +    do
 +    {
 +        Block & block(*mBlocks[block_start]);
 +        size_t block_limit(block.mUsed - offset);
 +        size_t block_len((std::min)(block_limit, len));
 +
 +        memcpy(c_dst, &block.mData[offset], block_len);
 +        result += block_len;
 +        len -= block_len;
 +        c_dst += block_len;
 +        offset = 0;
 +        ++block_start;
 +    }
 +    while (len && block_start < block_limit);
 +
 +    return result;
 +}
 +
 +
 +size_t BufferArray::write(size_t pos, const void * src, size_t len)
 +{
 +    const char * c_src(static_cast<const char *>(src));
 +
 +    if (pos > mLen || 0 == len)
 +        return 0;
 +
 +    size_t result(0), offset(0);
 +    const auto block_limit(mBlocks.size());
 +    int block_start(findBlock(pos, &offset));
 +
 +    if (block_start >= 0)
 +    {
 +        // Some or all of the write will be on top of
 +        // existing data.
 +        do
 +        {
 +            Block & block(*mBlocks[block_start]);
 +            size_t block_limit(block.mUsed - offset);
 +            size_t block_len((std::min)(block_limit, len));
 +
 +            memcpy(&block.mData[offset], c_src, block_len);
 +            result += block_len;
 +            c_src += block_len;
 +            len -= block_len;
 +            offset = 0;
 +            ++block_start;
 +        }
 +        while (len && block_start < block_limit);
 +    }
 +
 +    // Something left, see if it will fit in the free
 +    // space of the last block.
 +    if (len && ! mBlocks.empty())
 +    {
 +        Block & last(*mBlocks.back());
 +        if (last.mUsed < last.mAlloced)
 +        {
 +            // Some will fit...
 +            const size_t copy_len((std::min)(len, (last.mAlloced - last.mUsed)));
 +
 +            memcpy(&last.mData[last.mUsed], c_src, copy_len);
 +            last.mUsed += copy_len;
 +            result += copy_len;
 +            llassert_always(last.mUsed <= last.mAlloced);
 +            mLen += copy_len;
 +            c_src += copy_len;
 +            len -= copy_len;
 +        }
 +    }
 +
 +    if (len)
 +    {
 +        // Some or all of the remaining write data will
 +        // be an append.
 +        result += append(c_src, len);
 +    }
 +
 +    return result;
 +}
 +
 +
 +int BufferArray::findBlock(size_t pos, size_t * ret_offset)
 +{
 +    *ret_offset = 0;
 +    if (pos >= mLen)
 +        return -1;      // Doesn't exist
 +
 +    const int block_limit(narrow<size_t>(mBlocks.size()));
 +    for (int i(0); i < block_limit; ++i)
 +    {
 +        if (pos < mBlocks[i]->mUsed)
 +        {
 +            *ret_offset = pos;
 +            return i;
 +        }
 +        pos -= mBlocks[i]->mUsed;
 +    }
 +
 +    // Shouldn't get here but...
 +    return -1;
 +}
 +
 +
 +bool BufferArray::getBlockStartEnd(int block, const char ** start, const char ** end)
 +{
 +    if (block < 0 || block >= mBlocks.size())
 +    {
 +        return false;
 +    }
 +
 +    const Block & b(*mBlocks[block]);
 +    *start = &b.mData[0];
 +    *end = &b.mData[b.mUsed];
 +    return true;
 +}
 +
 +
 +// ==================================
 +// BufferArray::Block Definitions
 +// ==================================
 +
 +
 +BufferArray::Block::Block(size_t len)
 +    : mUsed(0),
 +      mAlloced(len)
 +{
 +    memset(mData, 0, len);
 +}
 +
 +
 +BufferArray::Block::~Block()
 +{
 +    mUsed = 0;
 +    mAlloced = 0;
 +}
 +
 +
 +void * BufferArray::Block::operator new(size_t len, size_t addl_len)
 +{
 +    void * mem = new char[len + addl_len + sizeof(void *)];
 +    return mem;
 +}
 +
 +
 +void BufferArray::Block::operator delete(void * mem)
 +{
 +    char * cmem = static_cast<char *>(mem);
 +    delete [] cmem;
 +}
 +
 +
 +void BufferArray::Block::operator delete(void * mem, size_t)
 +{
 +    operator delete(mem);
 +}
 +
 +
 +BufferArray::Block * BufferArray::Block::alloc(size_t len)
 +{
 +    Block * block = new (len) Block(len);
 +    return block;
 +}
 +
 +
 +}  // end namespace LLCore
 | 
