diff options
Diffstat (limited to 'indra')
-rwxr-xr-x | indra/llcommon/CMakeLists.txt | 3 | ||||
-rw-r--r-- | indra/llcommon/lldeadmantimer.cpp | 156 | ||||
-rw-r--r-- | indra/llcommon/lldeadmantimer.h | 194 | ||||
-rwxr-xr-x | indra/llcommon/lltimer.h | 7 | ||||
-rw-r--r-- | indra/llcommon/tests/lldeadmantimer_test.cpp | 344 | ||||
-rwxr-xr-x | indra/llcorehttp/_httpinternal.h | 6 | ||||
-rwxr-xr-x | indra/llcorehttp/httprequest.h | 15 | ||||
-rwxr-xr-x | indra/newview/llappcorehttp.cpp | 48 | ||||
-rwxr-xr-x | indra/newview/llappcorehttp.h | 16 | ||||
-rwxr-xr-x | indra/newview/llmeshrepository.cpp | 958 | ||||
-rwxr-xr-x | indra/newview/llmeshrepository.h | 28 |
11 files changed, 1744 insertions, 31 deletions
diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 3a4a8facc2..af7a9aa3a6 100755 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -43,6 +43,7 @@ set(llcommon_SOURCE_FILES llcriticaldamp.cpp llcursortypes.cpp lldate.cpp + lldeadmantimer.cpp lldependencies.cpp lldictionary.cpp llerror.cpp @@ -145,6 +146,7 @@ set(llcommon_HEADER_FILES lldarray.h lldarrayptr.h lldate.h + lldeadmantimer.h lldefs.h lldependencies.h lldeleteutils.h @@ -322,6 +324,7 @@ if (LL_TESTS) LL_ADD_INTEGRATION_TEST(bitpack "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llbase64 "" "${test_libs}") LL_ADD_INTEGRATION_TEST(lldate "" "${test_libs}") + LL_ADD_INTEGRATION_TEST(lldeadmantimer "" "${test_libs}") LL_ADD_INTEGRATION_TEST(lldependencies "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llerror "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llframetimer "" "${test_libs}") diff --git a/indra/llcommon/lldeadmantimer.cpp b/indra/llcommon/lldeadmantimer.cpp new file mode 100644 index 0000000000..2a356d857a --- /dev/null +++ b/indra/llcommon/lldeadmantimer.cpp @@ -0,0 +1,156 @@ +/** +* @file lldeadmantimer.cpp +* @brief Simple deadman-switch timer. +* @author monty@lindenlab.com +* +* $LicenseInfo:firstyear=2013&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2013, 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 "lldeadmantimer.h" + + +// *TODO: Currently, this uses lltimer functions for its time +// aspects and this leaks into the apis in the U64s/F64s. Would +// like to perhaps switch this over to TSC register-based timers +// sometime and drop the overhead some more. + + +// Flag states and their meaning: +// mActive mDone Meaning +// false false Nothing running, no result available +// true false Timer running, no result available +// false true Timer finished, result can be read once +// true true Not allowed +// +LLDeadmanTimer::LLDeadmanTimer(F64 horizon) + : mHorizon(time_type(llmax(horizon, F64(0.0)) * gClockFrequency)), + mActive(false), // If true, a timer is running. + mDone(false), // If true, timer has completed and can be read (once) + mStarted(U64L(0)), + mExpires(U64L(0)), + mStopped(U64L(0)), + mCount(U64L(0)) +{} + + +// static +LLDeadmanTimer::time_type LLDeadmanTimer::getNow() +{ + return LLTimer::getCurrentClockCount(); +} + + +void LLDeadmanTimer::start(time_type now) +{ + // *TODO: If active, let's complete an existing timer and save + // the result to the side. I think this will be useful later. + // For now, wipe out anything in progress, start fresh. + + if (! now) + { + now = LLTimer::getCurrentClockCount(); + } + mActive = true; + mDone = false; + mStarted = now; + mExpires = now + mHorizon; + mStopped = now; + mCount = U64L(0); +} + + +void LLDeadmanTimer::stop(time_type now) +{ + if (! mActive) + { + return; + } + + if (! now) + { + now = getNow(); + } + mStopped = now; + mActive = false; + mDone = true; +} + + +bool LLDeadmanTimer::isExpired(time_type now, F64 & started, F64 & stopped, U64 & count) +{ + if (mActive && ! mDone) + { + if (! now) + { + now = getNow(); + } + + if (now >= mExpires) + { + // mStopped from ringBell() is the value we want + mActive = false; + mDone = true; + } + } + + if (! mDone) + { + return false; + } + + started = mStarted * gClockFrequencyInv; + stopped = mStopped * gClockFrequencyInv; + count = mCount; + mDone = false; + + return true; +} + + +void LLDeadmanTimer::ringBell(time_type now, unsigned int count) +{ + if (! mActive) + { + return; + } + + if (! now) + { + now = getNow(); + } + + if (now >= mExpires) + { + mActive = false; + mDone = true; + } + else + { + mStopped = now; + mExpires = now + mHorizon; + mCount += count; + } + + return; +} + diff --git a/indra/llcommon/lldeadmantimer.h b/indra/llcommon/lldeadmantimer.h new file mode 100644 index 0000000000..8643b8cad8 --- /dev/null +++ b/indra/llcommon/lldeadmantimer.h @@ -0,0 +1,194 @@ +/** +* @file lldeadmantimer.h +* @brief Interface to a simple event timer with a deadman's switch +* @author monty@lindenlab.com +* +* $LicenseInfo:firstyear=2013&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2013, 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_DEADMANTIMER_H +#define LL_DEADMANTIMER_H + + +#include "linden_common.h" + +#include "lltimer.h" + + +/// @file lldeadmantimer.h +/// +/// There are interesting user-experienced events in the viewer that +/// would seem to have well-defined start and stop points but which +/// actually lack such milestones in the code. Such events (like +/// time to load meshes after logging in, initial inventory load, +/// display name fetch) can be defined somewhat after-the-fact by +/// noticing when we no longer perform operations towards their +/// completion. This class is intended to help in such applications. +/// +/// What it implements is a deadman's switch (also known as a +/// keepalive switch and a doorbell switch). The basic operation is +/// as follows: +/// +/// * LLDeadmanTimer is instantiated with a horizon value in seconds, +/// one for each event of interest. +/// * When an event starts, @see start() is invoked to begin a +/// timing operation. +/// * As operations are performed in service of the event (issuing +/// HTTP requests, receiving responses), @see ringBell() is invoked +/// to inform the timer that the operation is still active. +/// * If the operation is canceled or otherwise terminated, @see +/// stop() can be called to end the timing operation. +/// * Concurrent with the ringBell() calls, the program makes +/// periodic (shorter than the horizon but not too short) calls +/// to @see isExpired() to see if the event has expired due to +/// either a stop() call or lack of activity (defined as a ringBell() +/// call in the previous 'horizon' seconds). If it has expired, +/// the caller also receives start, stop and count values for the +/// event which the application can then report in whatever manner +/// it sees fit. +/// * The timer becomes passive after an isExpired() call that returns +/// true. It can then be restarted with a new start() call. +/// +/// Threading: Instances are not thread-safe. They also use +/// timing code from lltimer.h which is also unsafe. +/// +/// Allocation: Not refcounted, may be stack or heap allocated. +/// + +class LL_COMMON_API LLDeadmanTimer +{ +public: + /// Public types + + /// Low-level time type chosen for compatibility with + /// LLTimer::getCurrentClockCount() which is the basis + /// of time operations in this class. This is likely + /// to change in a future version in a move to TSC-based + /// timing. + typedef U64 time_type; + +public: + /// Construct and initialize an LLDeadmanTimer + /// + /// @param horizon Time, in seconds, after the last @see ringBell() + /// call at which point the timer will consider itself + /// expired. + /// + LLDeadmanTimer(F64 horizon); + + ~LLDeadmanTimer() + {} + +private: + LLDeadmanTimer(const LLDeadmanTimer &); // Not defined + void operator=(const LLDeadmanTimer &); // Not defined + +public: + /// Get the current time. Zero-basis for this time + /// representation is not defined and is different on + /// different platforms. Do not attempt to compute + /// negative times relative to the first value returned, + /// there may not be enough 'front porch' on the range + /// to prevent wraparound. + /// + /// Note: Implementation is expected to change in a + /// future release as well. + /// + static time_type getNow(); + + /// Begin timing. If the timer is already active, it is reset + /// and timing begins now. + /// + /// @param now Current time as returned by @see + /// LLTimer::getCurrentClockCount(). If zero, + /// method will lookup current time. + /// + void start(time_type now); + + /// End timing. Actively declare the end of the event independent + /// of the deadman's switch operation. @see isExpired() will return + /// true and appropriate values will be returned. + /// + /// @param now Current time as returned by @see + /// LLTimer::getCurrentClockCount(). If zero, + /// method will lookup current time. + /// + void stop(time_type now); + + /// Declare that something interesting happened. This has two + /// effects on an unexpired-timer. 1) The expiration time + /// is extended for 'horizon' seconds after the 'now' value. + /// 2) An internal counter associated with the event is incremented + /// by the @ref count parameter. This count is returned via the + /// @see isExpired() method. + /// + /// @param now Current time as returned by @see + /// LLTimer::getCurrentClockCount(). If zero, + /// method will lookup current time. + /// + /// @param count Count of events to be associated with + /// this bell ringing. + /// + void ringBell(time_type now, unsigned int count); + + /// Checks on the status of the timer Declare that something interesting happened. This has two + /// effects on an unexpired-timer. 1) The expiration time + /// is extended for 'horizon' seconds after the 'now' value. + /// 2) An internal counter associated with the event is incremented. + /// This count is returned via the @see isExpired() method. + /// + /// @param now Current time as returned by @see + /// LLTimer::getCurrentClockCount(). If zero, + /// method will lookup current time. + /// + /// @param started If expired, the starting time of the event is + /// returned to the caller via this reference. + /// + /// @param stopped If expired, the ending time of the event is + /// returned to the caller via this reference. + /// Ending time will be that provided in the + /// stop() method or the last ringBell() call + /// leading to expiration, whichever (stop() call + /// or notice of expiration) happened first. + /// + /// @param count If expired, the number of ringBell() calls + /// made prior to expiration. + /// + /// @return true if the timer has expired, false otherwise. + /// If true, it also returns the started, + /// stopped and count values otherwise these are + /// left unchanged. + /// + bool isExpired(time_type now, F64 & started, F64 & stopped, U64 & count); + +protected: + time_type mHorizon; + bool mActive; + bool mDone; + time_type mStarted; + time_type mExpires; + time_type mStopped; + time_type mCount; +}; + + +#endif // LL_DEADMANTIMER_H diff --git a/indra/llcommon/lltimer.h b/indra/llcommon/lltimer.h index 513de0605d..e73741217c 100755 --- a/indra/llcommon/lltimer.h +++ b/indra/llcommon/lltimer.h @@ -146,6 +146,13 @@ static inline time_t time_max() } } +// These are really statics but they've been global for awhile +// and they're material to other timing classes. If you are +// not implementing a timer class, do not use these directly. +extern LL_COMMON_API F64 gClockFrequency; +extern LL_COMMON_API F64 gClockFrequencyInv; +extern LL_COMMON_API F64 gClocksToMicroseconds; + // Correction factor used by time_corrected() above. extern LL_COMMON_API S32 gUTCOffset; diff --git a/indra/llcommon/tests/lldeadmantimer_test.cpp b/indra/llcommon/tests/lldeadmantimer_test.cpp new file mode 100644 index 0000000000..63cab29e04 --- /dev/null +++ b/indra/llcommon/tests/lldeadmantimer_test.cpp @@ -0,0 +1,344 @@ +/** + * @file lldeadmantimer_test.cpp + * @brief Tests for the LLDeadmanTimer class. + * + * $LicenseInfo:firstyear=2013&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2013, 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 "../lldeadmantimer.h" +#include "../llsd.h" + +#include "../test/lltut.h" + +// Convert between floating point time deltas and U64 time deltas. +// Reflects an implementation detail inside lldeadmantimer.cpp + +static LLDeadmanTimer::time_type float_time_to_u64(F64 delta) +{ + return LLDeadmanTimer::time_type(delta * gClockFrequency); +} + +static F64 u64_time_to_float(LLDeadmanTimer::time_type delta) +{ + return delta * gClockFrequencyInv; +} + + +namespace tut +{ + +struct deadmantimer_test +{ + deadmantimer_test() + { + // LLTimer internals updating + update_clock_frequencies(); + } +}; + +typedef test_group<deadmantimer_test> deadmantimer_group_t; +typedef deadmantimer_group_t::object deadmantimer_object_t; +tut::deadmantimer_group_t deadmantimer_instance("LLDeadmanTimer"); + +// Basic construction test and isExpired() call +template<> template<> +void deadmantimer_object_t::test<1>() +{ + F64 started(42.0), stopped(97.0); + U64 count(U64L(8)); + LLDeadmanTimer timer(10.0); + + ensure_equals("isExpired() returns false after ctor()", timer.isExpired(0, started, stopped, count), false); + ensure_approximately_equals("t1 - isExpired() does not modify started", started, F64(42.0), 2); + ensure_approximately_equals("t1 - isExpired() does not modify stopped", stopped, F64(97.0), 2); + ensure_equals("t1 - isExpired() does not modify count", count, U64L(8)); +} + + +// Construct with zero horizon - not useful generally but will be useful in testing +template<> template<> +void deadmantimer_object_t::test<2>() +{ + F64 started(42.0), stopped(97.0); + U64 count(U64L(8)); + LLDeadmanTimer timer(0.0); // Zero is pre-expired + + ensure_equals("isExpired() still returns false with 0.0 time ctor()", + timer.isExpired(0, started, stopped, count), false); +} + + +// "pre-expired" timer - starting a timer with a 0.0 horizon will result in +// expiration on first test. +template<> template<> +void deadmantimer_object_t::test<3>() +{ + F64 started(42.0), stopped(97.0); + U64 count(U64L(8)); + LLDeadmanTimer timer(0.0); + + timer.start(0); + ensure_equals("isExpired() returns true with 0.0 horizon time", + timer.isExpired(0, started, stopped, count), true); + ensure_approximately_equals("expired timer with no bell ringing has stopped == started", started, stopped, 8); +} + + +// "pre-expired" timer - bell rings are ignored as we're already expired. +template<> template<> +void deadmantimer_object_t::test<4>() +{ + F64 started(42.0), stopped(97.0); + U64 count(U64L(8)); + LLDeadmanTimer timer(0.0); + + timer.start(0); + timer.ringBell(LLDeadmanTimer::getNow() + float_time_to_u64(1000.0), 1); + ensure_equals("isExpired() returns true with 0.0 horizon time after bell ring", + timer.isExpired(0, started, stopped, count), true); + ensure_approximately_equals("ringBell has no impact on expired timer leaving stopped == started", started, stopped, 8); +} + + +// start(0) test - unexpired timer reports unexpired +template<> template<> +void deadmantimer_object_t::test<5>() +{ + F64 started(42.0), stopped(97.0); + U64 count(U64L(8)); + LLDeadmanTimer timer(10.0); + + timer.start(0); + ensure_equals("isExpired() returns false after starting with 10.0 horizon time", + timer.isExpired(0, started, stopped, count), false); + ensure_approximately_equals("t5 - isExpired() does not modify started", started, F64(42.0), 2); + ensure_approximately_equals("t5 - isExpired() does not modify stopped", stopped, F64(97.0), 2); + ensure_equals("t5 - isExpired() does not modify count", count, U64L(8)); +} + + +// start() test - start in the past but not beyond 1 horizon +template<> template<> +void deadmantimer_object_t::test<6>() +{ + F64 started(42.0), stopped(97.0); + U64 count(U64L(8)); + LLDeadmanTimer timer(10.0); + + // Would like to do subtraction on current time but can't because + // the implementation on Windows is zero-based. We wrap around + // the backside resulting in a large U64 number. + + LLDeadmanTimer::time_type the_past(LLDeadmanTimer::getNow()); + LLDeadmanTimer::time_type now(the_past + float_time_to_u64(5.0)); + timer.start(the_past); + ensure_equals("isExpired() returns false with 10.0 horizon time starting 5.0 in past", + timer.isExpired(now, started, stopped, count), false); + ensure_approximately_equals("t6 - isExpired() does not modify started", started, F64(42.0), 2); + ensure_approximately_equals("t6 - isExpired() does not modify stopped", stopped, F64(97.0), 2); + ensure_equals("t6 - isExpired() does not modify count", count, U64L(8)); +} + + +// start() test - start in the past but well beyond 1 horizon +template<> template<> +void deadmantimer_object_t::test<7>() +{ + F64 started(42.0), stopped(97.0); + U64 count(U64L(8)); + LLDeadmanTimer timer(10.0); + + // Would like to do subtraction on current time but can't because + // the implementation on Windows is zero-based. We wrap around + // the backside resulting in a large U64 number. + + LLDeadmanTimer::time_type the_past(LLDeadmanTimer::getNow()); + LLDeadmanTimer::time_type now(the_past + float_time_to_u64(20.0)); + timer.start(the_past); + ensure_equals("isExpired() returns true with 10.0 horizon time starting 20.0 in past", + timer.isExpired(now,started, stopped, count), true); + ensure_approximately_equals("starting before horizon still gives equal started / stopped", started, stopped, 8); +} + + +// isExpired() test - results are read-once. Probes after first true are false. +template<> template<> +void deadmantimer_object_t::test<8>() +{ + F64 started(42.0), stopped(97.0); + U64 count(U64L(8)); + LLDeadmanTimer timer(10.0); + + // Would like to do subtraction on current time but can't because + // the implementation on Windows is zero-based. We wrap around + // the backside resulting in a large U64 number. + + LLDeadmanTimer::time_type the_past(LLDeadmanTimer::getNow()); + LLDeadmanTimer::time_type now(the_past + float_time_to_u64(20.0)); + timer.start(the_past); + ensure_equals("t8 - isExpired() returns true with 10.0 horizon time starting 20.0 in past", + timer.isExpired(now, started, stopped, count), true); + + started = 42.0; + stopped = 97.0; + count = U64L(8); + ensure_equals("t8 - second isExpired() returns false after true", + timer.isExpired(now, started, stopped, count), false); + ensure_approximately_equals("t8 - 2nd isExpired() does not modify started", started, F64(42.0), 2); + ensure_approximately_equals("t8 - 2nd isExpired() does not modify stopped", stopped, F64(97.0), 2); + ensure_equals("t8 - 2nd isExpired() does not modify count", count, U64L(8)); +} + + +// ringBell() test - see that we can keep a timer from expiring +template<> template<> +void deadmantimer_object_t::test<9>() +{ + F64 started(42.0), stopped(97.0); + U64 count(U64L(8)); + LLDeadmanTimer timer(5.0); + + LLDeadmanTimer::time_type now(LLDeadmanTimer::getNow()); + F64 real_start(u64_time_to_float(now)); + timer.start(0); + + now += float_time_to_u64(1.0); + timer.ringBell(now, 1); + now += float_time_to_u64(1.0); + timer.ringBell(now, 1); + now += float_time_to_u64(1.0); + timer.ringBell(now, 1); + now += float_time_to_u64(1.0); + timer.ringBell(now, 1); + now += float_time_to_u64(1.0); + timer.ringBell(now, 1); + now += float_time_to_u64(1.0); + timer.ringBell(now, 1); + now += float_time_to_u64(1.0); + timer.ringBell(now, 1); + now += float_time_to_u64(1.0); + timer.ringBell(now, 1); + now += float_time_to_u64(1.0); + timer.ringBell(now, 1); + now += float_time_to_u64(1.0); + timer.ringBell(now, 1); + ensure_equals("t9 - 5.0 horizon timer has not timed out after 10 1-second bell rings", + timer.isExpired(now, started, stopped, count), false); + F64 last_good_ring(u64_time_to_float(now)); + + // Jump forward and expire + now += float_time_to_u64(10.0); + ensure_equals("t9 - 5.0 horizon timer expires on 10-second jump", + timer.isExpired(now, started, stopped, count), true); + ensure_approximately_equals("t9 - started matches start() time", started, real_start, 4); + ensure_approximately_equals("t9 - stopped matches last ringBell() time", stopped, last_good_ring, 4); + ensure_equals("t9 - 10 good ringBell()s", count, U64L(10)); + ensure_equals("t9 - single read only", timer.isExpired(now, started, stopped, count), false); +} + + +// restart after expiration test - verify that restarts behave well +template<> template<> +void deadmantimer_object_t::test<10>() +{ + F64 started(42.0), stopped(97.0); + U64 count(U64L(8)); + LLDeadmanTimer timer(5.0); + + LLDeadmanTimer::time_type now(LLDeadmanTimer::getNow()); + F64 real_start(u64_time_to_float(now)); + timer.start(0); + + now += float_time_to_u64(1.0); + timer.ringBell(now, 1); + now += float_time_to_u64(1.0); + timer.ringBell(now, 1); + now += float_time_to_u64(1.0); + timer.ringBell(now, 1); + now += float_time_to_u64(1.0); + timer.ringBell(now, 1); + now += float_time_to_u64(1.0); + timer.ringBell(now, 1); + now += float_time_to_u64(1.0); + timer.ringBell(now, 1); + now += float_time_to_u64(1.0); + timer.ringBell(now, 1); + now += float_time_to_u64(1.0); + timer.ringBell(now, 1); + now += float_time_to_u64(1.0); + timer.ringBell(now, 1); + now += float_time_to_u64(1.0); + timer.ringBell(now, 1); + ensure_equals("t10 - 5.0 horizon timer has not timed out after 10 1-second bell rings", + timer.isExpired(now, started, stopped, count), false); + F64 last_good_ring(u64_time_to_float(now)); + + // Jump forward and expire + now += float_time_to_u64(10.0); + ensure_equals("t10 - 5.0 horizon timer expires on 10-second jump", + timer.isExpired(now, started, stopped, count), true); + ensure_approximately_equals("t10 - started matches start() time", started, real_start, 4); + ensure_approximately_equals("t10 - stopped matches last ringBell() time", stopped, last_good_ring, 4); + ensure_equals("t10 - 10 good ringBell()s", count, U64L(10)); + ensure_equals("t10 - single read only", timer.isExpired(now, started, stopped, count), false); + + // Jump forward and restart + now += float_time_to_u64(1.0); + real_start = u64_time_to_float(now); + timer.start(now); + + // Run a modified bell ring sequence + now += float_time_to_u64(1.0); + timer.ringBell(now, 1); + now += float_time_to_u64(1.0); + timer.ringBell(now, 1); + now += float_time_to_u64(1.0); + timer.ringBell(now, 1); + now += float_time_to_u64(1.0); + timer.ringBell(now, 1); + now += float_time_to_u64(1.0); + timer.ringBell(now, 1); + now += float_time_to_u64(1.0); + timer.ringBell(now, 1); + now += float_time_to_u64(1.0); + timer.ringBell(now, 1); + now += float_time_to_u64(1.0); + timer.ringBell(now, 1); + ensure_equals("t10 - 5.0 horizon timer has not timed out after 8 1-second bell rings", + timer.isExpired(now, started, stopped, count), false); + last_good_ring = u64_time_to_float(now); + + // Jump forward and expire + now += float_time_to_u64(10.0); + ensure_equals("t10 - 5.0 horizon timer expires on 8-second jump", + timer.isExpired(now, started, stopped, count), true); + ensure_approximately_equals("t10 - 2nd started matches start() time", started, real_start, 4); + ensure_approximately_equals("t10 - 2nd stopped matches last ringBell() time", stopped, last_good_ring, 4); + ensure_equals("t10 - 8 good ringBell()s", count, U64L(8)); + ensure_equals("t10 - single read only - 2nd start", + timer.isExpired(now, started, stopped, count), false); +} + + +} // end namespace tut diff --git a/indra/llcorehttp/_httpinternal.h b/indra/llcorehttp/_httpinternal.h index 14f744a9f1..30b0905c12 100755 --- a/indra/llcorehttp/_httpinternal.h +++ b/indra/llcorehttp/_httpinternal.h @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2012&license=viewerlgpl$ * Second Life Viewer Source Code - * Copyright (C) 2012, Linden Research, Inc. + * Copyright (C) 2012-2013, 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 @@ -97,8 +97,8 @@ namespace LLCore { // Maxium number of policy classes that can be defined. -// *TODO: Currently limited to the default class, extend. -const int HTTP_POLICY_CLASS_LIMIT = 1; +// *TODO: Currently limited to the default class + 1, extend. +const int HTTP_POLICY_CLASS_LIMIT = 2; // Debug/informational tracing. Used both // as a global option and in per-request traces. diff --git a/indra/llcorehttp/httprequest.h b/indra/llcorehttp/httprequest.h index ab2f302d34..5000f47d0d 100755 --- a/indra/llcorehttp/httprequest.h +++ b/indra/llcorehttp/httprequest.h @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2012&license=viewerlgpl$ * Second Life Viewer Source Code - * Copyright (C) 2012, Linden Research, Inc. + * Copyright (C) 2012-2013, 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 @@ -158,10 +158,17 @@ public: /// Create a new policy class into which requests can be made. /// + /// All class creation must occur before threads are started and + /// transport begins. Policy classes are limited to a small value. + /// Currently that limit is the default class + 1. + /// /// @return If positive, the policy_id used to reference - /// the class in other methods. If 0, an error - /// occurred and @see getStatus() may provide more - /// detail on the reason. + /// the class in other methods. If 0, requests + /// for classes have exceeded internal limits + /// or caller has tried to create a class after + /// threads have been started. Caller must fallback + /// and recover. + /// static policy_t createPolicyClass(); enum EClassPolicy diff --git a/indra/newview/llappcorehttp.cpp b/indra/newview/llappcorehttp.cpp index 0d7d41304d..4386e3283b 100755 --- a/indra/newview/llappcorehttp.cpp +++ b/indra/newview/llappcorehttp.cpp @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2012&license=viewerlgpl$ * Second Life Viewer Source Code - * Copyright (C) 2012, Linden Research, Inc. + * Copyright (C) 2012-2013, 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 @@ -38,7 +38,9 @@ LLAppCoreHttp::LLAppCoreHttp() mStopHandle(LLCORE_HTTP_HANDLE_INVALID), mStopRequested(0.0), mStopped(false), - mPolicyDefault(-1) + mPolicyDefault(-1), + mPolicyTexture(-1), + mPolicyMesh(-1) {} @@ -95,6 +97,9 @@ void LLAppCoreHttp::init() // Setup default policy and constrain if directed to mPolicyDefault = LLCore::HttpRequest::DEFAULT_POLICY_ID; + + // Texture policy will use default for now. + mPolicyTexture = mPolicyDefault; static const std::string texture_concur("TextureFetchConcurrency"); if (gSavedSettings.controlExists(texture_concur)) { @@ -103,7 +108,7 @@ void LLAppCoreHttp::init() if (concur > 0) { LLCore::HttpStatus status; - status = LLCore::HttpRequest::setPolicyClassOption(mPolicyDefault, + status = LLCore::HttpRequest::setPolicyClassOption(mPolicyTexture, LLCore::HttpRequest::CP_CONNECTION_LIMIT, concur); if (! status) @@ -120,6 +125,43 @@ void LLAppCoreHttp::init() } } } + + // Create the mesh class + mPolicyMesh = LLCore::HttpRequest::createPolicyClass(); + if (! mPolicyMesh) + { + LL_WARNS("Init") << "Failed to create HTTP policy class for Mesh. Using default policy." + << LL_ENDL; + mPolicyMesh = mPolicyDefault; + } + else + { + static const std::string mesh_concur("MeshMaxConcurrentRequests"); + if (gSavedSettings.controlExists(mesh_concur)) + { + U32 setting(llmin(gSavedSettings.getU32(mesh_concur), U32(32))); + + if (setting > 0) + { + LLCore::HttpStatus status; + status = LLCore::HttpRequest::setPolicyClassOption(mPolicyMesh, + LLCore::HttpRequest::CP_CONNECTION_LIMIT, + setting); + if (! status) + { + LL_WARNS("Init") << "Unable to set mesh fetch concurrency. Reason: " + << status.toString() + << LL_ENDL; + } + else + { + LL_INFOS("Init") << "Application settings overriding default mesh fetch concurrency. New value: " + << setting + << LL_ENDL; + } + } + } + } // Kick the thread status = LLCore::HttpRequest::startThread(); diff --git a/indra/newview/llappcorehttp.h b/indra/newview/llappcorehttp.h index 241d73ad52..d90af9e5ca 100755 --- a/indra/newview/llappcorehttp.h +++ b/indra/newview/llappcorehttp.h @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2012&license=viewerlgpl$ * Second Life Viewer Source Code - * Copyright (C) 2012, Linden Research, Inc. + * Copyright (C) 2012-2013, 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 @@ -70,6 +70,18 @@ public: { return mPolicyDefault; } + + // Get the texture fetch policy class. + int getPolicyTexture() const + { + return mPolicyTexture; + } + + // Get the mesh fetch policy class. + int getPolicyMesh() const + { + return mPolicyMesh; + } private: static const F64 MAX_THREAD_WAIT_TIME; @@ -80,6 +92,8 @@ private: F64 mStopRequested; bool mStopped; int mPolicyDefault; + int mPolicyTexture; + int mPolicyMesh; }; diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index b47fe9d4b1..b52c86dd4e 100755 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2005&license=viewerlgpl$ * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. + * Copyright (C) 2010-2013, 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 @@ -37,6 +37,7 @@ #include "llcallbacklist.h" #include "llcurl.h" #include "lldatapacker.h" +#include "lldeadmantimer.h" #include "llfloatermodelpreview.h" #include "llfloaterperms.h" #include "lleconomy.h" @@ -51,6 +52,7 @@ #include "llviewercontrol.h" #include "llviewerinventory.h" #include "llviewermenufile.h" +#include "llviewermessage.h" #include "llviewerobjectlist.h" #include "llviewerregion.h" #include "llviewertexturelist.h" @@ -64,6 +66,7 @@ #include "llfoldertype.h" #include "llviewerparcelmgr.h" #include "lluploadfloaterobservers.h" +#include "bufferarray.h" #include "boost/lexical_cast.hpp" @@ -75,6 +78,7 @@ LLMeshRepository gMeshRepo; +const S32 MESH_HEADER_SIZE = 4096; const U32 MAX_MESH_REQUESTS_PER_SECOND = 100; // Maximum mesh version to support. Three least significant digits are reserved for the minor version, @@ -94,8 +98,9 @@ U32 LLMeshRepository::sLODPending = 0; U32 LLMeshRepository::sCacheBytesRead = 0; U32 LLMeshRepository::sCacheBytesWritten = 0; U32 LLMeshRepository::sPeakKbps = 0; - +LLDeadmanTimer LLMeshRepository::sQuiescentTimer(15.0); + const U32 MAX_TEXTURE_UPLOAD_RETRIES = 5; static S32 dump_num = 0; @@ -107,7 +112,7 @@ std::string make_dump_name(std::string prefix, S32 num) void dump_llsd_to_file(const LLSD& content, std::string filename); LLSD llsd_from_file(std::string filename); -std::string header_lod[] = +const std::string header_lod[] = { "lowest_lod", "low_lod", @@ -115,6 +120,13 @@ std::string header_lod[] = "high_lod" }; +// Static data and functions to measure mesh load +// time metrics for a new region scene. +static bool metrics_inited(false); +static boost::signals2::connection metrics_teleport_connection; +static unsigned int metrics_teleport_start_count(0); +static void metrics_teleport_started(); +static bool is_retryable(LLCore::HttpStatus status); //get the number of bytes resident in memory for given volume U32 get_volume_memory_size(const LLVolume* volume) @@ -200,6 +212,160 @@ S32 LLMeshRepoThread::sActiveHeaderRequests = 0; S32 LLMeshRepoThread::sActiveLODRequests = 0; U32 LLMeshRepoThread::sMaxConcurrentRequests = 1; +class LLMeshHandlerBase : public LLCore::HttpHandler +{ +public: + LLMeshHandlerBase() + : LLCore::HttpHandler(), + mMeshParams(), + mProcessed(false) + {} + virtual ~LLMeshHandlerBase(); + +protected: + LLMeshHandlerBase(const LLMeshHandlerBase &); // Not defined + void operator=(const LLMeshHandlerBase &); // Not defined + +public: + virtual void onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response); + virtual void processData(LLCore::BufferArray * body, U8 * data, S32 data_size) = 0; + virtual void processFailure(LLCore::HttpStatus status) = 0; + +public: + LLVolumeParams mMeshParams; + bool mProcessed; + LLCore::HttpHandle mHttpHandle; +}; + + +class LLMeshHeaderHandler : public LLMeshHandlerBase +{ +public: + LLMeshHeaderHandler(const LLVolumeParams & mesh_params) + : LLMeshHandlerBase() + { + mMeshParams = mesh_params; + LLMeshRepoThread::incActiveHeaderRequests(); + } + virtual ~LLMeshHeaderHandler(); + +protected: + LLMeshHeaderHandler(const LLMeshHeaderHandler &); // Not defined + void operator=(const LLMeshHeaderHandler &); // Not defined + +public: + virtual void processData(LLCore::BufferArray * body, U8 * data, S32 data_size); + virtual void processFailure(LLCore::HttpStatus status); +}; + + +class LLMeshLODHandler : public LLMeshHandlerBase +{ +public: + LLMeshLODHandler(const LLVolumeParams & mesh_params, S32 lod, U32 offset, U32 requested_bytes) + : LLMeshHandlerBase(), + mLOD(lod), + mRequestedBytes(requested_bytes), + mOffset(offset) + { + mMeshParams = mesh_params; + LLMeshRepoThread::incActiveLODRequests(); + } + virtual ~LLMeshLODHandler(); + +protected: + LLMeshLODHandler(const LLMeshLODHandler &); // Not defined + void operator=(const LLMeshLODHandler &); // Not defined + +public: + virtual void processData(LLCore::BufferArray * body, U8 * data, S32 data_size); + virtual void processFailure(LLCore::HttpStatus status); + +public: + S32 mLOD; + U32 mRequestedBytes; + U32 mOffset; +}; + + +class LLMeshSkinInfoHandler : public LLMeshHandlerBase +{ +public: + LLMeshSkinInfoHandler(const LLUUID& id, U32 offset, U32 size) + : LLMeshHandlerBase(), + mMeshID(id), + mRequestedBytes(size), + mOffset(offset) + {} + virtual ~LLMeshSkinInfoHandler(); + +protected: + LLMeshSkinInfoHandler(const LLMeshSkinInfoHandler &); // Not defined + void operator=(const LLMeshSkinInfoHandler &); // Not defined + +public: + virtual void processData(LLCore::BufferArray * body, U8 * data, S32 data_size); + virtual void processFailure(LLCore::HttpStatus status); + +public: + LLUUID mMeshID; + U32 mRequestedBytes; + U32 mOffset; +}; + + +class LLMeshDecompositionHandler : public LLMeshHandlerBase +{ +public: + LLMeshDecompositionHandler(const LLUUID& id, U32 offset, U32 size) + : LLMeshHandlerBase(), + mMeshID(id), + mRequestedBytes(size), + mOffset(offset) + {} + virtual ~LLMeshDecompositionHandler(); + +protected: + LLMeshDecompositionHandler(const LLMeshDecompositionHandler &); // Not defined + void operator=(const LLMeshDecompositionHandler &); // Not defined + +public: + virtual void processData(LLCore::BufferArray * body, U8 * data, S32 data_size); + virtual void processFailure(LLCore::HttpStatus status); + +public: + LLUUID mMeshID; + U32 mRequestedBytes; + U32 mOffset; +}; + + +class LLMeshPhysicsShapeHandler : public LLMeshHandlerBase +{ +public: + LLMeshPhysicsShapeHandler(const LLUUID& id, U32 offset, U32 size) + : LLMeshHandlerBase(), + mMeshID(id), + mRequestedBytes(size), + mOffset(offset) + {} + virtual ~LLMeshPhysicsShapeHandler(); + +protected: + LLMeshPhysicsShapeHandler(const LLMeshPhysicsShapeHandler &); // Not defined + void operator=(const LLMeshPhysicsShapeHandler &); // Not defined + +public: + virtual void processData(LLCore::BufferArray * body, U8 * data, S32 data_size); + virtual void processFailure(LLCore::HttpStatus status); + +public: + LLUUID mMeshID; + U32 mRequestedBytes; + U32 mOffset; +}; + + class LLMeshHeaderResponder : public LLCurl::Responder { public: @@ -529,16 +695,45 @@ public: }; LLMeshRepoThread::LLMeshRepoThread() -: LLThread("mesh repo") +: LLThread("mesh repo"), + mCurlRequest(NULL), + mWaiting(false), + mHttpRequest(NULL), + mHttpOptions(NULL), + mHttpHeaders(NULL), + mHttpPolicyClass(LLCore::HttpRequest::DEFAULT_POLICY_ID) { - mWaiting = false; mMutex = new LLMutex(NULL); mHeaderMutex = new LLMutex(NULL); mSignal = new LLCondition(NULL); + mHttpRequest = new LLCore::HttpRequest; + mHttpOptions = new LLCore::HttpOptions; + mHttpHeaders = new LLCore::HttpHeaders; + mHttpHeaders->mHeaders.push_back("Accept: application/vnd.ll.mesh"); + mHttpPolicyClass = LLAppViewer::instance()->getAppCoreHttp().getPolicyMesh(); } LLMeshRepoThread::~LLMeshRepoThread() { + for (http_request_set::iterator iter(mHttpRequestSet.begin()); + iter != mHttpRequestSet.end(); + ++iter) + { + delete *iter; + } + mHttpRequestSet.clear(); + if (mHttpHeaders) + { + mHttpHeaders->release(); + mHttpHeaders = NULL; + } + if (mHttpOptions) + { + mHttpOptions->release(); + mHttpOptions = NULL; + } + delete mHttpRequest; + mHttpRequest = NULL; delete mMutex; mMutex = NULL; delete mHeaderMutex; @@ -562,7 +757,7 @@ void LLMeshRepoThread::run() mSignal->wait(); mWaiting = false; - if (!LLApp::isQuitting()) + if (! LLApp::isQuitting() && ! mHttpRequestSet.empty()) { static U32 count = 0; @@ -651,6 +846,7 @@ void LLMeshRepoThread::run() } mCurlRequest->process(); + mHttpRequest->update(0L); } } @@ -1036,13 +1232,15 @@ bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params, U32& c S32 size = file.getSize(); if (size > 0) - { //NOTE -- if the header size is ever more than 4KB, this will break - U8 buffer[4096]; - S32 bytes = llmin(size, 4096); + { + // *NOTE: if the header size is ever more than 4KB, this will break + U8 buffer[MESH_HEADER_SIZE]; + S32 bytes = llmin(size, MESH_HEADER_SIZE); LLMeshRepository::sCacheBytesRead += bytes; file.read(buffer, bytes); if (headerReceived(mesh_params, buffer, bytes)) - { //did not do an HTTP request, return false + { + // Found mesh in VFS cache return true; } } @@ -1059,7 +1257,31 @@ 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 0 + retval = mCurlRequest->getByteRange(http_url, headers, 0, MESH_HEADER_SIZE, new LLMeshHeaderResponder(mesh_params)); +#else + LLMeshHeaderHandler * handler = new LLMeshHeaderHandler(mesh_params); + LLCore::HttpHandle handle = mHttpRequest->requestGetByteRange(mHttpPolicyClass, + 0, // *TODO: Get better priority value + http_url, + 0, + MESH_HEADER_SIZE, + mHttpOptions, + mHttpHeaders, + handler); + if (LLCORE_HTTP_HANDLE_INVALID == handle) + { + // *TODO: Better error message + llwarns << "HTTP GET request failed for mesh " << mID << llendl; + delete handler; + retval = false; + } + else + { + handler->mHttpHandle = handle; + mHttpRequestSet.insert(handler); + } +#endif if(retval) { LLMeshRepository::sHTTPRequestCount++; @@ -1200,7 +1422,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); // make sure only one thread access mPendingLOD at the same time. @@ -2153,6 +2375,336 @@ void LLMeshPhysicsShapeResponder::completedRaw(U32 status, const std::string& re delete [] data; } + +LLMeshHandlerBase::~LLMeshHandlerBase() +{} + + +void LLMeshHandlerBase::onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response) +{ + mProcessed = true; + + LLCore::HttpStatus status(response->getStatus()); + if (! status) + { + processFailure(status); + } + else + { + // From texture fetch code and applies here: + // + // A warning about partial (HTTP 206) data. Some grid services + // do *not* return a 'Content-Range' header in the response to + // Range requests with a 206 status. We're forced to assume + // we get what we asked for in these cases until we can fix + // the services. + static const LLCore::HttpStatus par_status(HTTP_PARTIAL_CONTENT); + + LLCore::BufferArray * body(response->getBody()); + S32 data_size(body ? body->size() : 0); + U8 * data(NULL); + + if (data_size > 0) + { + // *TODO: Try to get rid of data copying and add interfaces + // that support BufferArray directly. + data = new U8[data_size]; + body->read(0, (char *) data, data_size); + LLMeshRepository::sBytesReceived += llmin(data_size, MESH_HEADER_SIZE); + } + + processData(body, data, data_size); + + delete [] data; + } + + // Release handler + gMeshRepo.mThread->mHttpRequestSet.erase(this); + + delete this; // Must be last statement +} + + +LLMeshHeaderHandler::~LLMeshHeaderHandler() +{ + if (!LLApp::isQuitting()) + { + if (! mProcessed) + { + // something went wrong, retry + llwarns << "Timeout or service unavailable, retrying." << llendl; + LLMeshRepository::sHTTPRetryCount++; + LLMeshRepoThread::HeaderRequest req(mMeshParams); + LLMutexLock lock(gMeshRepo.mThread->mMutex); + gMeshRepo.mThread->mHeaderReqQ.push(req); + } + LLMeshRepoThread::decActiveHeaderRequests(); + } +} + + +void LLMeshHeaderHandler::processFailure(LLCore::HttpStatus status) +{ + if (is_retryable(status)) + { + llwarns << "Timeout or service unavailable, retrying." << llendl; + LLMeshRepository::sHTTPRetryCount++; + LLMeshRepoThread::HeaderRequest req(mMeshParams); + LLMutexLock lock(gMeshRepo.mThread->mMutex); + gMeshRepo.mThread->mHeaderReqQ.push(req); + } + else + { + // *TODO: better error message + llwarns << "Unhandled status." << llendl; + } +} + + +void LLMeshHeaderHandler::processData(LLCore::BufferArray * body, U8 * data, S32 data_size) +{ + bool success = gMeshRepo.mThread->headerReceived(mMeshParams, data, data_size); + llassert(success); + if (! success) + { + // *TODO: Get real reason for parse failure here + llwarns << "Unable to parse mesh header: " << llendl; + } + else if (data && data_size > 0) + { + // header was successfully retrieved from sim, cache in vfs + LLUUID mesh_id = mMeshParams.getSculptID(); + LLSD header = gMeshRepo.mThread->mMeshHeader[mesh_id]; + + S32 version = header["version"].asInteger(); + + if (version <= MAX_MESH_VERSION) + { + std::stringstream str; + + S32 lod_bytes = 0; + + for (U32 i = 0; i < LLModel::LOD_PHYSICS; ++i) + { + // figure out how many bytes we'll need to reserve in the file + std::string lod_name = header_lod[i]; + lod_bytes = llmax(lod_bytes, header[lod_name]["offset"].asInteger()+header[lod_name]["size"].asInteger()); + } + + // just in case skin info or decomposition is at the end of the file (which it shouldn't be) + lod_bytes = llmax(lod_bytes, header["skin"]["offset"].asInteger() + header["skin"]["size"].asInteger()); + lod_bytes = llmax(lod_bytes, header["physics_convex"]["offset"].asInteger() + header["physics_convex"]["size"].asInteger()); + + S32 header_bytes = (S32) gMeshRepo.mThread->mMeshHeaderSize[mesh_id]; + S32 bytes = lod_bytes + header_bytes; + + + // It's possible for the remote asset to have more data than is needed for the local cache + // only allocate as much space in the VFS as is needed for the local cache + data_size = llmin(data_size, bytes); + + LLVFile file(gVFS, mesh_id, LLAssetType::AT_MESH, LLVFile::WRITE); + if (file.getMaxSize() >= bytes || file.setMaxSize(bytes)) + { + LLMeshRepository::sCacheBytesWritten += data_size; + + file.write(data, data_size); + + // zero out the rest of the file + U8 block[MESH_HEADER_SIZE]; + memset(block, 0, MESH_HEADER_SIZE); + + while (bytes-file.tell() > MESH_HEADER_SIZE) + { + file.write(block, MESH_HEADER_SIZE); + } + + S32 remaining = bytes-file.tell(); + + if (remaining > 0) + { + file.write(block, remaining); + } + } + } + } +} + + +LLMeshLODHandler::~LLMeshLODHandler() +{ + if (! LLApp::isQuitting()) + { + if (! mProcessed) + { + llwarns << "Killed without being processed, retrying." << llendl; + LLMeshRepository::sHTTPRetryCount++; + gMeshRepo.mThread->lockAndLoadMeshLOD(mMeshParams, mLOD); + } + LLMeshRepoThread::decActiveLODRequests(); + } +} + +void LLMeshLODHandler::processFailure(LLCore::HttpStatus status) +{ + if (is_retryable(status)) + { + llwarns << "Timeout or service unavailable, retrying." << llendl; + LLMeshRepository::sHTTPRetryCount++; + // *FIXME: Is this safe? Does this need locking? + gMeshRepo.mThread->loadMeshLOD(mMeshParams, mLOD); + } + else + { + // *TODO: better error message + llwarns << "Unhandled status." << llendl; + } +} + +void LLMeshLODHandler::processData(LLCore::BufferArray * body, U8 * data, S32 data_size) +{ + if (gMeshRepo.mThread->lodReceived(mMeshParams, mLOD, data, data_size)) + { + // good fetch from sim, write to VFS for caching + LLVFile file(gVFS, mMeshParams.getSculptID(), LLAssetType::AT_MESH, LLVFile::WRITE); + + S32 offset = mOffset; + S32 size = mRequestedBytes; + + if (file.getSize() >= offset+size) + { + file.seek(offset); + file.write(data, size); + LLMeshRepository::sCacheBytesWritten += size; + } + } +} + + +LLMeshSkinInfoHandler::~LLMeshSkinInfoHandler() +{ + llassert(mProcessed); +} + +void LLMeshSkinInfoHandler::processFailure(LLCore::HttpStatus status) +{ + if (is_retryable(status)) + { + llwarns << "Timeout or service unavailable, retrying." << llendl; + LLMeshRepository::sHTTPRetryCount++; + // *FIXME: Is this safe? Does this need locking? + gMeshRepo.mThread->loadMeshSkinInfo(mMeshID); + } + else + { + // *TODO: better error message + llwarns << "Unhandled status." << llendl; + } +} + +void LLMeshSkinInfoHandler::processData(LLCore::BufferArray * body, U8 * data, S32 data_size) +{ + if (gMeshRepo.mThread->skinInfoReceived(mMeshID, data, data_size)) + { + // good fetch from sim, write to VFS for caching + LLVFile file(gVFS, mMeshID, LLAssetType::AT_MESH, LLVFile::WRITE); + + S32 offset = mOffset; + S32 size = mRequestedBytes; + + if (file.getSize() >= offset+size) + { + LLMeshRepository::sCacheBytesWritten += size; + file.seek(offset); + file.write(data, size); + } + } +} + + +LLMeshDecompositionHandler::~LLMeshDecompositionHandler() +{ + llassert(mProcessed); +} + +void LLMeshDecompositionHandler::processFailure(LLCore::HttpStatus status) +{ + if (is_retryable(status)) + { + llwarns << "Timeout or service unavailable, retrying." << llendl; + LLMeshRepository::sHTTPRetryCount++; + // *FIXME: Is this safe? Does this need locking? + gMeshRepo.mThread->loadMeshDecomposition(mMeshID); + } + else + { + // *TODO: better error message + llwarns << "Unhandled status." << llendl; + } +} + +void LLMeshDecompositionHandler::processData(LLCore::BufferArray * body, U8 * data, S32 data_size) +{ + if (gMeshRepo.mThread->decompositionReceived(mMeshID, data, data_size)) + { + // good fetch from sim, write to VFS for caching + LLVFile file(gVFS, mMeshID, LLAssetType::AT_MESH, LLVFile::WRITE); + + S32 offset = mOffset; + S32 size = mRequestedBytes; + + if (file.getSize() >= offset+size) + { + LLMeshRepository::sCacheBytesWritten += size; + file.seek(offset); + file.write(data, size); + } + } +} + + +LLMeshPhysicsShapeHandler::~LLMeshPhysicsShapeHandler() +{ + llassert(mProcessed); +} + +void LLMeshPhysicsShapeHandler::processFailure(LLCore::HttpStatus status) +{ + if (is_retryable(status)) + { + llwarns << "Timeout or service unavailable, retrying." << llendl; + LLMeshRepository::sHTTPRetryCount++; + // *FIXME: Is this safe? Does this need locking? + gMeshRepo.mThread->loadMeshPhysicsShape(mMeshID); + } + else + { + // *TODO: better error message + llwarns << "Unhandled status." << llendl; + } +} + + +void LLMeshPhysicsShapeHandler::processData(LLCore::BufferArray * body, U8 * data, S32 data_size) +{ + if (gMeshRepo.mThread->physicsShapeReceived(mMeshID, data, data_size)) + { + // good fetch from sim, write to VFS for caching + LLVFile file(gVFS, mMeshID, LLAssetType::AT_MESH, LLVFile::WRITE); + + S32 offset = mOffset; + S32 size = mRequestedBytes; + + if (file.getSize() >= offset+size) + { + LLMeshRepository::sCacheBytesWritten += size; + file.seek(offset); + file.write(data, size); + } + } +} + + void LLMeshHeaderResponder::completedRaw(U32 status, const std::string& reason, const LLChannelDescriptors& channels, const LLIOPipe::buffer_ptr_t& buffer) @@ -2206,7 +2758,7 @@ void LLMeshHeaderResponder::completedRaw(U32 status, const std::string& reason, buffer->readAfter(channels.in(), NULL, data, data_size); } - LLMeshRepository::sBytesReceived += llmin(data_size, 4096); + LLMeshRepository::sBytesReceived += llmin(data_size, MESH_HEADER_SIZE); bool success = gMeshRepo.mThread->headerReceived(mMeshParams, data, data_size); @@ -2258,12 +2810,12 @@ void LLMeshHeaderResponder::completedRaw(U32 status, const std::string& reason, file.write((const U8*) data, data_size); //zero out the rest of the file - U8 block[4096]; - memset(block, 0, 4096); + U8 block[MESH_HEADER_SIZE]; + memset(block, 0, MESH_HEADER_SIZE); - while (bytes-file.tell() > 4096) + while (bytes-file.tell() > MESH_HEADER_SIZE) { - file.write(block, 4096); + file.write(block, MESH_HEADER_SIZE); } S32 remaining = bytes-file.tell(); @@ -2306,12 +2858,26 @@ void LLMeshRepository::init() mThread = new LLMeshRepoThread(); mThread->start(); + + if (! metrics_inited) + { + // Get teleport started signals to restart timings. + metrics_teleport_connection = LLViewerMessage::getInstance()-> + setTeleportStartedCallback(metrics_teleport_started); + metrics_inited = true; + } } void LLMeshRepository::shutdown() { llinfos << "Shutting down mesh repository." << llendl; + if (metrics_inited) + { + metrics_teleport_connection.disconnect(); + metrics_inited = false; + } + for (U32 i = 0; i < mUploads.size(); ++i) { llinfos << "Discard the pending mesh uploads " << llendl; @@ -2357,6 +2923,9 @@ void LLMeshRepository::shutdown() //called in the main thread. S32 LLMeshRepository::update() { + // Conditionally log a mesh metrics event + metricsUpdate(); + if(mUploadWaitList.empty()) { return 0 ; @@ -2376,6 +2945,9 @@ S32 LLMeshRepository::update() S32 LLMeshRepository::loadMesh(LLVOVolume* vobj, const LLVolumeParams& mesh_params, S32 detail, S32 last_lod) { + // Manage time-to-load metrics for mesh download operations. + metricsProgress(1); + if (detail < 0 || detail > 4) { return detail; @@ -2678,6 +3250,9 @@ void LLMeshRepository::notifyDecompositionReceived(LLModel::Decomposition* decom void LLMeshRepository::notifyMeshLoaded(const LLVolumeParams& mesh_params, LLVolume* volume) { //called from main thread + // Manage time-to-load metrics for mesh download operations. + metricsProgress(0); + S32 detail = LLVolumeLODGroup::getVolumeDetailFromScale(volume->getDetail()); //get list of objects waiting to be notified this mesh is loaded @@ -2721,6 +3296,9 @@ void LLMeshRepository::notifyMeshLoaded(const LLVolumeParams& mesh_params, LLVol void LLMeshRepository::notifyMeshUnavailable(const LLVolumeParams& mesh_params, S32 lod) { //called from main thread + // Manage time-to-load metrics for mesh download operations. + metricsProgress(0); + //get list of objects waiting to be notified this mesh is loaded mesh_load_map::iterator obj_iter = mLoadingMeshes[lod].find(mesh_params); @@ -3697,3 +4275,349 @@ bool LLMeshRepository::meshRezEnabled() } return false; } + +// Threading: main thread only +// static +void LLMeshRepository::metricsStart() +{ + sQuiescentTimer.start(0); +} + +// Threading: main thread only +// static +void LLMeshRepository::metricsStop() +{ + sQuiescentTimer.stop(0); +} + +// Threading: main thread only +// static +void LLMeshRepository::metricsProgress(unsigned int this_count) +{ + static bool first_start(true); + + if (first_start) + { + ++metrics_teleport_start_count; + metricsStart(); + first_start = false; + } + sQuiescentTimer.ringBell(0, this_count); +} + +// Threading: main thread only +// static +void LLMeshRepository::metricsUpdate() +{ + F64 started, stopped; + U64 total_count; + + if (sQuiescentTimer.isExpired(0, started, stopped, total_count)) + { + LLSD metrics; + + metrics["reason"] = "Mesh Download Quiescent"; + metrics["scope"] = "Login"; + metrics["start"] = started; + metrics["stop"] = stopped; + metrics["downloads"] = LLSD::Integer(total_count); + metrics["teleports"] = LLSD::Integer(metrics_teleport_start_count); + llinfos << "EventMarker " << metrics << llendl; + } +} + +// Will use a request to start a teleport as a signal to +// restart a timing sequence. We don't get one of these +// for login so initial start is done above. +// +// Threading: main thread only +// static +void metrics_teleport_started() +{ + LLMeshRepository::metricsStart(); + ++metrics_teleport_start_count; +} + + +// This comes from an edit in viewer-cat. Unify this once that's +// available everywhere. +bool is_retryable(LLCore::HttpStatus status) +{ + static const LLCore::HttpStatus cant_connect(LLCore::HttpStatus::EXT_CURL_EASY, CURLE_COULDNT_CONNECT); + static const LLCore::HttpStatus cant_res_proxy(LLCore::HttpStatus::EXT_CURL_EASY, CURLE_COULDNT_RESOLVE_PROXY); + static const LLCore::HttpStatus cant_res_host(LLCore::HttpStatus::EXT_CURL_EASY, CURLE_COULDNT_RESOLVE_HOST); + static const LLCore::HttpStatus send_error(LLCore::HttpStatus::EXT_CURL_EASY, CURLE_SEND_ERROR); + static const LLCore::HttpStatus recv_error(LLCore::HttpStatus::EXT_CURL_EASY, CURLE_RECV_ERROR); + static const LLCore::HttpStatus upload_failed(LLCore::HttpStatus::EXT_CURL_EASY, CURLE_UPLOAD_FAILED); + static const LLCore::HttpStatus op_timedout(LLCore::HttpStatus::EXT_CURL_EASY, CURLE_OPERATION_TIMEDOUT); + static const LLCore::HttpStatus post_error(LLCore::HttpStatus::EXT_CURL_EASY, CURLE_HTTP_POST_ERROR); + static const LLCore::HttpStatus partial_file(LLCore::HttpStatus::EXT_CURL_EASY, CURLE_PARTIAL_FILE); + static const LLCore::HttpStatus inv_cont_range(LLCore::HttpStatus::LLCORE, LLCore::HE_INV_CONTENT_RANGE_HDR); + + return ((! status) && + ((status.isHttpStatus() && status.mType >= 499 && status.mType <= 599) || // Include special 499 in retryables + status == cant_connect || // Connection reset/endpoint problems + status == cant_res_proxy || // DNS problems + status == cant_res_host || // DNS problems + status == send_error || // General socket problems + status == recv_error || // General socket problems + status == upload_failed || // Transport problem + status == op_timedout || // Timer expired + status == post_error || // Transport problem + status == partial_file || // Data inconsistency in response + status == inv_cont_range)); // Short data read disagrees with content-range +} + + + + +// =========== +// +// HTTP fragments I'll be needing +// +// +#if 0 + mHttpPolicyClass(LLCore::HttpRequest::DEFAULT_POLICY_ID), + mHttpRequest = new LLCore::HttpRequest; + mHttpOptions = new LLCore::HttpOptions; + mHttpHeaders = new LLCore::HttpHeaders; + mHttpHeaders->mHeaders.push_back("Accept: image/x-j2c"); + mHttpMetricsHeaders = new LLCore::HttpHeaders; + mHttpMetricsHeaders->mHeaders.push_back("Content-Type: application/llsd+xml"); + mHttpPolicyClass = LLAppViewer::instance()->getAppCoreHttp().getPolicyDefault(); + + + LLCore::HttpHandle mHttpHandle; // Handle of any active request + LLCore::BufferArray * mHttpBufferArray; // Refcounted pointer to response data + int mHttpPolicyClass; + bool mHttpActive; // Active request to http library + unsigned int mHttpReplySize; // Actual received data size + unsigned int mHttpReplyOffset; // Actual received data offset + bool mHttpHasResource; // Counts against Fetcher's mHttpSemaphore + + + mHttpHandle = LLCORE_HTTP_HANDLE_INVALID; + if (!mUrl.empty()) + { + mRequestedTimer.reset(); + mLoaded = FALSE; + mGetStatus = LLCore::HttpStatus(); + mGetReason.clear(); + LL_DEBUGS("Texture") << "HTTP GET: " << mID << " Offset: " << mRequestedOffset + << " Bytes: " << mRequestedSize + << " Bandwidth(kbps): " << mFetcher->getTextureBandwidth() << "/" << mFetcher->mMaxBandwidth + << LL_ENDL; + + // Will call callbackHttpGet when curl request completes + mHttpHandle = mFetcher->mHttpRequest->requestGetByteRange(mHttpPolicyClass, + mWorkPriority, + mUrl, + mRequestedOffset, + mRequestedSize, + mFetcher->mHttpOptions, + mFetcher->mHttpHeaders, + this); + } + if (LLCORE_HTTP_HANDLE_INVALID == mHttpHandle) + { + llwarns << "HTTP GET request failed for " << mID << llendl; + resetFormattedData(); + releaseHttpSemaphore(); + return true; // failed + } + + mHttpActive = true; + mFetcher->addToHTTPQueue(mID); + recordTextureStart(true); + setPriority(LLWorkerThread::PRIORITY_LOW | mWorkPriority); + mState = WAIT_HTTP_REQ; + + // fall through + } + + + +// Threads: Ttf +// virtual +void LLTextureFetchWorker::onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response) +{ + static LLCachedControl<bool> log_to_viewer_log(gSavedSettings, "LogTextureDownloadsToViewerLog"); + static LLCachedControl<bool> log_to_sim(gSavedSettings, "LogTextureDownloadsToSimulator"); + static LLCachedControl<bool> log_texture_traffic(gSavedSettings, "LogTextureNetworkTraffic") ; + + LLMutexLock lock(&mWorkMutex); // +Mw + + mHttpActive = false; + + if (log_to_viewer_log || log_to_sim) + { + U64 timeNow = LLTimer::getTotalTime(); + mFetcher->mTextureInfo.setRequestStartTime(mID, mMetricsStartTime); + mFetcher->mTextureInfo.setRequestType(mID, LLTextureInfoDetails::REQUEST_TYPE_HTTP); + mFetcher->mTextureInfo.setRequestSize(mID, mRequestedSize); + mFetcher->mTextureInfo.setRequestOffset(mID, mRequestedOffset); + mFetcher->mTextureInfo.setRequestCompleteTimeAndLog(mID, timeNow); + } + + bool success = true; + bool partial = false; + LLCore::HttpStatus status(response->getStatus()); + + lldebugs << "HTTP COMPLETE: " << mID + << " status: " << status.toHex() + << " '" << status.toString() << "'" + << llendl; +// unsigned int offset(0), length(0), full_length(0); +// response->getRange(&offset, &length, &full_length); +// llwarns << "HTTP COMPLETE: " << mID << " handle: " << handle +// << " status: " << status.toULong() << " '" << status.toString() << "'" +// << " req offset: " << mRequestedOffset << " req length: " << mRequestedSize +// << " offset: " << offset << " length: " << length +// << llendl; + + if (! status) + { + success = false; + std::string reason(status.toString()); + setGetStatus(status, reason); + llwarns << "CURL GET FAILED, status: " << status.toHex() + << " reason: " << reason << llendl; + } + else + { + // A warning about partial (HTTP 206) data. Some grid services + // do *not* return a 'Content-Range' header in the response to + // Range requests with a 206 status. We're forced to assume + // we get what we asked for in these cases until we can fix + // the services. + static const LLCore::HttpStatus par_status(HTTP_PARTIAL_CONTENT); + + partial = (par_status == status); + } + + S32 data_size = callbackHttpGet(response, partial, success); + + if (log_texture_traffic && data_size > 0) + { + LLViewerTexture* tex = LLViewerTextureManager::findTexture(mID); + if (tex) + { + gTotalTextureBytesPerBoostLevel[tex->getBoostLevel()] += data_size ; + } + } + + mFetcher->removeFromHTTPQueue(mID, data_size); + + recordTextureDone(true); +} // -Mw + + +// Threads: Ttf +// Locks: Mw +S32 LLTextureFetchWorker::callbackHttpGet(LLCore::HttpResponse * response, + bool partial, bool success) +{ + S32 data_size = 0 ; + + if (mState != WAIT_HTTP_REQ) + { + llwarns << "callbackHttpGet for unrequested fetch worker: " << mID + << " req=" << mSentRequest << " state= " << mState << llendl; + return data_size; + } + if (mLoaded) + { + llwarns << "Duplicate callback for " << mID.asString() << llendl; + return data_size ; // ignore duplicate callback + } + if (success) + { + // get length of stream: + LLCore::BufferArray * body(response->getBody()); + data_size = body ? body->size() : 0; + + LL_DEBUGS("Texture") << "HTTP RECEIVED: " << mID.asString() << " Bytes: " << data_size << LL_ENDL; + if (data_size > 0) + { + LLViewerStatsRecorder::instance().textureFetch(data_size); + // *TODO: set the formatted image data here directly to avoid the copy + + // Hold on to body for later copy + llassert_always(NULL == mHttpBufferArray); + body->addRef(); + mHttpBufferArray = body; + + if (partial) + { + unsigned int offset(0), length(0), full_length(0); + response->getRange(&offset, &length, &full_length); + if (! offset && ! length) + { + // This is the case where we receive a 206 status but + // there wasn't a useful Content-Range header in the response. + // This could be because it was badly formatted but is more + // likely due to capabilities services which scrub headers + // from responses. Assume we got what we asked for... + mHttpReplySize = data_size; + mHttpReplyOffset = mRequestedOffset; + } + else + { + mHttpReplySize = length; + mHttpReplyOffset = offset; + } + } + + if (! partial) + { + // Response indicates this is the entire asset regardless + // of our asking for a byte range. Mark it so and drop + // any partial data we might have so that the current + // response body becomes the entire dataset. + if (data_size <= mRequestedOffset) + { + LL_WARNS("Texture") << "Fetched entire texture " << mID + << " when it was expected to be marked complete. mImageSize: " + << mFileSize << " datasize: " << mFormattedImage->getDataSize() + << LL_ENDL; + } + mHaveAllData = TRUE; + llassert_always(mDecodeHandle == 0); + mFormattedImage = NULL; // discard any previous data we had + } + else if (data_size < mRequestedSize) + { + mHaveAllData = TRUE; + } + else if (data_size > mRequestedSize) + { + // *TODO: This shouldn't be happening any more (REALLY don't expect this anymore) + llwarns << "data_size = " << data_size << " > requested: " << mRequestedSize << llendl; + mHaveAllData = TRUE; + llassert_always(mDecodeHandle == 0); + mFormattedImage = NULL; // discard any previous data we had + } + } + else + { + // We requested data but received none (and no error), + // so presumably we have all of it + mHaveAllData = TRUE; + } + mRequestedSize = data_size; + } + else + { + mRequestedSize = -1; // error + } + + mLoaded = TRUE; + setPriority(LLWorkerThread::PRIORITY_HIGH | mWorkPriority); + + LLViewerStatsRecorder::instance().log(0.2f); + return data_size ; +} + +#endif +// ============ + diff --git a/indra/newview/llmeshrepository.h b/indra/newview/llmeshrepository.h index 8602271f84..8ffe2d68cb 100755 --- a/indra/newview/llmeshrepository.h +++ b/indra/newview/llmeshrepository.h @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2001&license=viewerlgpl$ * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. + * Copyright (C) 2010-2013, 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 @@ -32,6 +32,12 @@ #include "lluuid.h" #include "llviewertexture.h" #include "llvolume.h" +#include "lldeadmantimer.h" +#include "httpcommon.h" +#include "httprequest.h" +#include "httpoptions.h" +#include "httpheaders.h" +#include "httphandler.h" #define LLCONVEXDECOMPINTER_STATIC 1 @@ -315,6 +321,15 @@ public: typedef std::map<LLVolumeParams, std::vector<S32> > pending_lod_map; pending_lod_map mPendingLOD; + // llcorehttp library interface objects. + LLCore::HttpRequest * mHttpRequest; + LLCore::HttpOptions * mHttpOptions; + LLCore::HttpHeaders * mHttpHeaders; + LLCore::HttpRequest::policy_t mHttpPolicyClass; + + typedef std::set<LLCore::HttpHandler *> http_request_set; + http_request_set mHttpRequestSet; // Outstanding HTTP requests + static std::string constructUrl(LLUUID mesh_id); LLMeshRepoThread(); @@ -455,14 +470,15 @@ public: static U32 sCacheBytesRead; static U32 sCacheBytesWritten; static U32 sPeakKbps; - + static LLDeadmanTimer sQuiescentTimer; // time-to-complete-mesh-downloads after significant events + static F32 getStreamingCost(LLSD& header, F32 radius, S32* bytes = NULL, S32* visible_bytes = NULL, S32 detail = -1, F32 *unscaled_value = NULL); LLMeshRepository(); void init(); void shutdown(); - S32 update() ; + S32 update(); //mesh management functions S32 loadMesh(LLVOVolume* volume, const LLVolumeParams& mesh_params, S32 detail = 0, S32 last_lod = -1); @@ -495,6 +511,12 @@ public: S32 getMeshSize(const LLUUID& mesh_id, S32 lod); + // Quiescent timer management, main thread only. + static void metricsStart(); + static void metricsStop(); + static void metricsProgress(unsigned int count); + static void metricsUpdate(); + typedef std::map<LLVolumeParams, std::set<LLUUID> > mesh_load_map; mesh_load_map mLoadingMeshes[4]; |