From 188221a90c7a054d390ddaa534d391f6370ac6bc Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Tue, 9 Apr 2013 23:44:59 +0000 Subject: SH-4088 Deadman timer switch started in llcommon. Unit test started. Will be used for mesh, inventory, etc., operation markers. --- indra/llcommon/CMakeLists.txt | 3 + indra/llcommon/lldeadmantimer.cpp | 141 ++++++++++++++++++++++ indra/llcommon/lldeadmantimer.h | 171 +++++++++++++++++++++++++++ indra/llcommon/lltimer.h | 7 ++ indra/llcommon/tests/lldeadmantimer_test.cpp | 63 ++++++++++ 5 files changed, 385 insertions(+) create mode 100644 indra/llcommon/lldeadmantimer.cpp create mode 100644 indra/llcommon/lldeadmantimer.h create mode 100644 indra/llcommon/tests/lldeadmantimer_test.cpp (limited to 'indra') diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 5cce8ff2c4..7ed4137065 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -42,6 +42,7 @@ set(llcommon_SOURCE_FILES llcriticaldamp.cpp llcursortypes.cpp lldate.cpp + lldeadmantimer.cpp lldependencies.cpp lldictionary.cpp llerror.cpp @@ -144,6 +145,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..2f48d13c2d --- /dev/null +++ b/indra/llcommon/lldeadmantimer.cpp @@ -0,0 +1,141 @@ +/** +* @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" + + +LLDeadmanTimer::LLDeadmanTimer(F64 horizon) + : mHorizon(U64(horizon * 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)) +{} + + +void LLDeadmanTimer::start(U64 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(U64 now) +{ + if (! mActive) + { + return; + } + + if (! now) + { + now = LLTimer::getCurrentClockCount(); + } + mStopped = now; + mActive = false; + mDone = true; +} + + +bool LLDeadmanTimer::isExpired(F64 & started, F64 & stopped, U64 & count, U64 now) +{ + if (! mActive) + { + return false; + } + + if (! mDone) + { + if (! now) + { + now = LLTimer::getCurrentClockCount(); + } + + 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(U64 now) +{ + if (! mActive) + { + return; + } + + if (! now) + { + now = LLTimer::getCurrentClockCount(); + } + + if (now > mExpires) + { + mActive = false; + mDone = true; + } + else + { + mStopped = now; + mExpires = now + mHorizon; + ++mCount; + } + + return; +} + diff --git a/indra/llcommon/lldeadmantimer.h b/indra/llcommon/lldeadmantimer.h new file mode 100644 index 0000000000..84023723ab --- /dev/null +++ b/indra/llcommon/lldeadmantimer.h @@ -0,0 +1,171 @@ +/** +* @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: + /// 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: + /// 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(U64 now = U64L(0)); + + + /// 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(U64 now = U64L(0)); + + + /// 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. + /// + void ringBell(U64 now = U64L(0)); + + + /// 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 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. + /// + /// @param now Current time as returned by @see + /// LLTimer::getCurrentClockCount(). If zero, + /// method will lookup current time. + /// + /// @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(F64 & started, F64 & stopped, U64 & count, U64 now = U64L(0)); + +protected: + U64 mHorizon; + bool mActive; + bool mDone; + U64 mStarted; + U64 mExpires; + U64 mStopped; + U64 mCount; +}; + + +#endif // LL_DEADMANTIMER_H diff --git a/indra/llcommon/lltimer.h b/indra/llcommon/lltimer.h index 513de0605d..e73741217c 100644 --- 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..52a27b9c0a --- /dev/null +++ b/indra/llcommon/tests/lldeadmantimer_test.cpp @@ -0,0 +1,63 @@ +/** + * @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" + +namespace tut +{ + +struct deadmantimer_test +{ + deadmantimer_test() + { + // LLTimer internals updating + update_clock_frequencies(); + } +}; + +typedef test_group deadmantimer_group_t; +typedef deadmantimer_group_t::object deadmantimer_object_t; +tut::deadmantimer_group_t deadmantimer_instance("LLDeadmanTimer"); + +template<> template<> +void deadmantimer_object_t::test<1>() +{ + F64 started(42.0), stopped(97.0); + U64 count(U64L(8)); + LLDeadmanTimer timer(1.0); + + ensure_equals("isExpired() returns false after ctor()", timer.isExpired(started, stopped, count), false); + ensure_approximately_equals("isExpired() does not modify started", started, F64(42.0), 2); + ensure_approximately_equals("isExpired() does not modify stopped", stopped, F64(97.0), 2); + ensure_equals("isExpired() does not modified count", count, U64L(8)); +} + +} // end namespace tut -- cgit v1.2.3 From aabda8f3073928980b26530eac9b05eeb85a2207 Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Wed, 10 Apr 2013 16:30:26 +0000 Subject: SH-4089 Unit test work for timer. Knocked off some tests for the deadman's timer. Found some bugs, dig some cleanup and documented a few things. Definitely want to get rid of the U64/F64 interfaces at sometime but this is a good start. --- indra/llcommon/lldeadmantimer.cpp | 26 ++- indra/llcommon/tests/lldeadmantimer_test.cpp | 259 ++++++++++++++++++++++++++- 2 files changed, 272 insertions(+), 13 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/lldeadmantimer.cpp b/indra/llcommon/lldeadmantimer.cpp index 2f48d13c2d..3d3f738c06 100644 --- a/indra/llcommon/lldeadmantimer.cpp +++ b/indra/llcommon/lldeadmantimer.cpp @@ -29,8 +29,21 @@ #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(U64(horizon * gClockFrequency)), + : mHorizon(U64(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)), @@ -78,19 +91,14 @@ void LLDeadmanTimer::stop(U64 now) bool LLDeadmanTimer::isExpired(F64 & started, F64 & stopped, U64 & count, U64 now) { - if (! mActive) - { - return false; - } - - if (! mDone) + if (mActive && ! mDone) { if (! now) { now = LLTimer::getCurrentClockCount(); } - if (now > mExpires) + if (now >= mExpires) { // mStopped from ringBell() is the value we want mActive = false; @@ -124,7 +132,7 @@ void LLDeadmanTimer::ringBell(U64 now) now = LLTimer::getCurrentClockCount(); } - if (now > mExpires) + if (now >= mExpires) { mActive = false; mDone = true; diff --git a/indra/llcommon/tests/lldeadmantimer_test.cpp b/indra/llcommon/tests/lldeadmantimer_test.cpp index 52a27b9c0a..571d43825f 100644 --- a/indra/llcommon/tests/lldeadmantimer_test.cpp +++ b/indra/llcommon/tests/lldeadmantimer_test.cpp @@ -31,6 +31,20 @@ #include "../test/lltut.h" +// Convert between floating point time deltas and U64 time deltas. +// Reflects an implementation detail inside lldeadmantimer.cpp + +static U64 float_time_to_u64(F64 delta) +{ + return U64(delta * gClockFrequency); +} + +static F64 u64_time_to_float(U64 delta) +{ + return delta * gClockFrequencyInv; +} + + namespace tut { @@ -47,17 +61,254 @@ typedef test_group 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(1.0); + LLDeadmanTimer timer(10.0); ensure_equals("isExpired() returns false after ctor()", timer.isExpired(started, stopped, count), false); - ensure_approximately_equals("isExpired() does not modify started", started, F64(42.0), 2); - ensure_approximately_equals("isExpired() does not modify stopped", stopped, F64(97.0), 2); - ensure_equals("isExpired() does not modified count", count, U64L(8)); + 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 negative 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(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(); + ensure_equals("isExpired() returns true with 0.0 horizon time", timer.isExpired(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(); + timer.ringBell(LLTimer::getCurrentClockCount() + float_time_to_u64(1000.0)); + ensure_equals("isExpired() returns true with 0.0 horizon time after bell ring", timer.isExpired(started, stopped, count), true); + ensure_approximately_equals("ringBell has no impact on expired timer leaving stopped == started", started, stopped, 8); +} + + +// start() 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(); + ensure_equals("isExpired() returns false after starting with 10.0 horizon time", timer.isExpired(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); + + U64 the_past(LLTimer::getCurrentClockCount() - 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(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); + + U64 the_past(LLTimer::getCurrentClockCount() - 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(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); + + U64 the_past(LLTimer::getCurrentClockCount() - 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(started, stopped, count), true); + + started = 42.0; + stopped = 97.0; + count = U64L(8); + ensure_equals("t8 - second isExpired() returns false after true", timer.isExpired(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); + + U64 now(LLTimer::getCurrentClockCount()); + F64 real_start(u64_time_to_float(now)); + timer.start(); + + now += float_time_to_u64(1.0); + timer.ringBell(now); + now += float_time_to_u64(1.0); + timer.ringBell(now); + now += float_time_to_u64(1.0); + timer.ringBell(now); + now += float_time_to_u64(1.0); + timer.ringBell(now); + now += float_time_to_u64(1.0); + timer.ringBell(now); + now += float_time_to_u64(1.0); + timer.ringBell(now); + now += float_time_to_u64(1.0); + timer.ringBell(now); + now += float_time_to_u64(1.0); + timer.ringBell(now); + now += float_time_to_u64(1.0); + timer.ringBell(now); + now += float_time_to_u64(1.0); + timer.ringBell(now); + ensure_equals("t9 - 5.0 horizon timer has not timed out after 10 1-second bell rings", timer.isExpired(started, stopped, count, now), 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(started, stopped, count, now), 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(started, stopped, count, now), 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); + + U64 now(LLTimer::getCurrentClockCount()); + F64 real_start(u64_time_to_float(now)); + timer.start(); + + now += float_time_to_u64(1.0); + timer.ringBell(now); + now += float_time_to_u64(1.0); + timer.ringBell(now); + now += float_time_to_u64(1.0); + timer.ringBell(now); + now += float_time_to_u64(1.0); + timer.ringBell(now); + now += float_time_to_u64(1.0); + timer.ringBell(now); + now += float_time_to_u64(1.0); + timer.ringBell(now); + now += float_time_to_u64(1.0); + timer.ringBell(now); + now += float_time_to_u64(1.0); + timer.ringBell(now); + now += float_time_to_u64(1.0); + timer.ringBell(now); + now += float_time_to_u64(1.0); + timer.ringBell(now); + ensure_equals("t10 - 5.0 horizon timer has not timed out after 10 1-second bell rings", timer.isExpired(started, stopped, count, now), 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(started, stopped, count, now), 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(started, stopped, count, now), 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); + now += float_time_to_u64(1.0); + timer.ringBell(now); + now += float_time_to_u64(1.0); + timer.ringBell(now); + now += float_time_to_u64(1.0); + timer.ringBell(now); + now += float_time_to_u64(1.0); + timer.ringBell(now); + now += float_time_to_u64(1.0); + timer.ringBell(now); + now += float_time_to_u64(1.0); + timer.ringBell(now); + now += float_time_to_u64(1.0); + timer.ringBell(now); + ensure_equals("t10 - 5.0 horizon timer has not timed out after 8 1-second bell rings", timer.isExpired(started, stopped, count, now), 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(started, stopped, count, now), 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(started, stopped, count, now), false); +} + + } // end namespace tut -- cgit v1.2.3 From 6536fe24049248a84447f1f06570f1be1dd24ef9 Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Wed, 10 Apr 2013 17:50:42 +0000 Subject: SH-4089 Unit test work. lltimer basis on Windows is zero which doesn't allow negative offsetting for unit tests. Had to keep things positive. Could do windows-specific test cases but I'm hoping to dump lltimer from the implementation later. --- indra/llcommon/tests/lldeadmantimer_test.cpp | 31 +++++++++++++++++++++------- 1 file changed, 23 insertions(+), 8 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/tests/lldeadmantimer_test.cpp b/indra/llcommon/tests/lldeadmantimer_test.cpp index 571d43825f..40e354115b 100644 --- a/indra/llcommon/tests/lldeadmantimer_test.cpp +++ b/indra/llcommon/tests/lldeadmantimer_test.cpp @@ -76,7 +76,7 @@ void deadmantimer_object_t::test<1>() } -// Construct with negative horizon - not useful generally but will be useful in testing +// Construct with zero horizon - not useful generally but will be useful in testing template<> template<> void deadmantimer_object_t::test<2>() { @@ -142,9 +142,14 @@ void deadmantimer_object_t::test<6>() U64 count(U64L(8)); LLDeadmanTimer timer(10.0); - U64 the_past(LLTimer::getCurrentClockCount() - float_time_to_u64(5.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. + + U64 the_past(LLTimer::getCurrentClockCount()); + U64 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(started, stopped, count), false); + ensure_equals("isExpired() returns false with 10.0 horizon time starting 5.0 in past", timer.isExpired(started, stopped, count, now), 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)); @@ -159,9 +164,14 @@ void deadmantimer_object_t::test<7>() U64 count(U64L(8)); LLDeadmanTimer timer(10.0); - U64 the_past(LLTimer::getCurrentClockCount() - float_time_to_u64(20.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. + + U64 the_past(LLTimer::getCurrentClockCount()); + U64 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(started, stopped, count), true); + ensure_equals("isExpired() returns true with 10.0 horizon time starting 20.0 in past", timer.isExpired(started, stopped, count, now), true); ensure_approximately_equals("starting before horizon still gives equal started / stopped", started, stopped, 8); } @@ -174,14 +184,19 @@ void deadmantimer_object_t::test<8>() U64 count(U64L(8)); LLDeadmanTimer timer(10.0); - U64 the_past(LLTimer::getCurrentClockCount() - float_time_to_u64(20.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. + + U64 the_past(LLTimer::getCurrentClockCount()); + U64 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(started, stopped, count), true); + ensure_equals("t8 - isExpired() returns true with 10.0 horizon time starting 20.0 in past", timer.isExpired(started, stopped, count, now), true); started = 42.0; stopped = 97.0; count = U64L(8); - ensure_equals("t8 - second isExpired() returns false after true", timer.isExpired(started, stopped, count), false); + ensure_equals("t8 - second isExpired() returns false after true", timer.isExpired(started, stopped, count, now), 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)); -- cgit v1.2.3 From 1310630ac60ba292f52761e795eaa55610818b9b Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Fri, 12 Apr 2013 20:11:17 +0000 Subject: SH-4090 [WIP] Basic deadman timer integration started on Linux. Moving to windows to do real work. --- indra/newview/llmeshrepository.cpp | 45 ++++++++++++++++++++++++++++++++++++-- indra/newview/llmeshrepository.h | 11 ++++++++-- 2 files changed, 52 insertions(+), 4 deletions(-) (limited to 'indra') diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index 1223615079..11c5780a30 100755 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -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" @@ -94,8 +95,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; @@ -115,7 +117,6 @@ std::string header_lod[] = "high_lod" }; - //get the number of bytes resident in memory for given volume U32 get_volume_memory_size(const LLVolume* volume) { @@ -2678,6 +2679,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. + metricsCheck(); + S32 detail = LLVolumeLODGroup::getVolumeDetailFromScale(volume->getDetail()); //get list of objects waiting to be notified this mesh is loaded @@ -3699,3 +3703,40 @@ bool LLMeshRepository::meshRezEnabled() } return false; } + +void LLMeshRepository::metricsStart() +{ + sQuiescentTimer.start(); +} + +void LLMeshRepository::metricsStop() +{ + sQuiescentTimer.stop(); +} + +void LLMeshRepository::metricsCheck() +{ + static bool first_start(true); + F64 started, stopped; + U64 count; + + if (first_start) + { + // Let the first request start the timing cycle for login. + metricsStart(); + first_start = false; + } + sQuiescentTimer.ringBell(); + if (sQuiescentTimer.isExpired(started, stopped, count)) + { + LLSD metrics; + + metrics["reason"] = "Mesh download quiescent"; + metrics["scope"] = "Login"; + metrics["start"] = started; + metrics["stop"] = stopped; + metrics["downloads"] = LLSD::Integer(count); + llinfos << "MetricsMarker" << metrics << llendl; + } +} + diff --git a/indra/newview/llmeshrepository.h b/indra/newview/llmeshrepository.h index 8602271f84..f08feedf81 100644 --- a/indra/newview/llmeshrepository.h +++ b/indra/newview/llmeshrepository.h @@ -32,6 +32,7 @@ #include "lluuid.h" #include "llviewertexture.h" #include "llvolume.h" +#include "lldeadmantimer.h" #define LLCONVEXDECOMPINTER_STATIC 1 @@ -455,14 +456,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 +497,11 @@ public: S32 getMeshSize(const LLUUID& mesh_id, S32 lod); + // Quiescent timer management, main thread only. + static void metricsStart(); + static void metricsStop(); + static void metricsCheck(); + typedef std::map > mesh_load_map; mesh_load_map mLoadingMeshes[4]; -- cgit v1.2.3 From 2df0a2494691ebfdd305e6a5ee280dd758a1b337 Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Tue, 7 May 2013 16:18:31 +0000 Subject: SH-4139 Convert http downloaders and responders to llcorehttp patterns Initial work completed on linux, moving over to windows to do debug and refinement. This includes 5/6 handlers based on existing responders and use of llcorehttp for the mesh header fetch. --- indra/llcorehttp/_httpinternal.h | 6 +- indra/llcorehttp/httprequest.h | 15 +- indra/newview/llappcorehttp.cpp | 48 ++- indra/newview/llappcorehttp.h | 16 +- indra/newview/llmeshrepository.cpp | 854 ++++++++++++++++++++++++++++++++++++- indra/newview/llmeshrepository.h | 16 +- 6 files changed, 929 insertions(+), 26 deletions(-) (limited to 'indra') diff --git a/indra/llcorehttp/_httpinternal.h b/indra/llcorehttp/_httpinternal.h index 14f744a9f1..30b0905c12 100644 --- 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 100644 --- 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 100644 --- 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 100644 --- 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 0f33128057..21b7b120f6 100755 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -66,6 +66,7 @@ #include "llfoldertype.h" #include "llviewerparcelmgr.h" #include "lluploadfloaterobservers.h" +#include "bufferarray.h" #include "boost/lexical_cast.hpp" @@ -77,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, @@ -124,6 +126,7 @@ 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) @@ -209,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: @@ -538,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; @@ -571,7 +757,7 @@ void LLMeshRepoThread::run() mSignal->wait(); mWaiting = false; - if (!LLApp::isQuitting()) + if (! LLApp::isQuitting() && ! mHttpRequestSet.empty()) { static U32 count = 0; @@ -660,6 +846,7 @@ void LLMeshRepoThread::run() } mCurlRequest->process(); + mHttpRequest->update(0L); } } @@ -1045,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; } } @@ -1068,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++; @@ -1209,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. @@ -2162,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) @@ -2215,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); @@ -2267,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(); @@ -3797,3 +4340,286 @@ void metrics_teleport_started() ++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 log_to_viewer_log(gSavedSettings, "LogTextureDownloadsToViewerLog"); + static LLCachedControl log_to_sim(gSavedSettings, "LogTextureDownloadsToSimulator"); + static LLCachedControl 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 3cdc66e1f0..8ffe2d68cb 100644 --- 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 @@ -33,6 +33,11 @@ #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 @@ -316,6 +321,15 @@ public: typedef std::map > 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 http_request_set; + http_request_set mHttpRequestSet; // Outstanding HTTP requests + static std::string constructUrl(LLUUID mesh_id); LLMeshRepoThread(); -- cgit v1.2.3 From aa855b18ecde2786c758ac27d2ddb049d0929d55 Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Tue, 7 May 2013 17:24:12 -0400 Subject: SH-4139 Convert http downloaders and responders to llcorehttp patterns First version running with all five downloaders converted. Not certain all are functional yet and the whole thing is slow but it is running. --- indra/newview/llmeshrepository.cpp | 247 ++++++++++++++++++------------------- 1 file changed, 123 insertions(+), 124 deletions(-) (limited to 'indra') diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index 21b7b120f6..419613091a 100755 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -753,11 +753,19 @@ void LLMeshRepoThread::run() while (!LLApp::isQuitting()) { - mWaiting = true; - mSignal->wait(); - mWaiting = false; - - if (! LLApp::isQuitting() && ! mHttpRequestSet.empty()) + if (! mHttpRequestSet.empty()) + { + mHttpRequest->update(0L); + ms_sleep(100); + } + else + { + mWaiting = true; + mSignal->wait(); + mWaiting = false; + } + + if (! LLApp::isQuitting()) { static U32 count = 0; @@ -846,7 +854,6 @@ void LLMeshRepoThread::run() } mCurlRequest->process(); - mHttpRequest->update(0L); } } @@ -1007,10 +1014,35 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id) std::string http_url = constructUrl(mesh_id); if (!http_url.empty()) - { + { +#if 0 ret = mCurlRequest->getByteRange(http_url, headers, offset, size, new LLMeshSkinInfoResponder(mesh_id, offset, size)); - if(ret) +#else + LLMeshSkinInfoHandler * handler = new LLMeshSkinInfoHandler(mesh_id, offset, size); + // LL_WARNS("Mesh") << "MESH: Issuing Skin Info Request" << LL_ENDL; + LLCore::HttpHandle handle = mHttpRequest->requestGetByteRange(mHttpPolicyClass, + 0, // *TODO: Get better priority value + http_url, + offset, + 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; + ret = false; + } + else + { + handler->mHttpHandle = handle; + mHttpRequestSet.insert(handler); + } +#endif + if (ret) { LLMeshRepository::sHTTPRequestCount++; } @@ -1089,10 +1121,35 @@ bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id) std::string http_url = constructUrl(mesh_id); if (!http_url.empty()) - { + { +#if 0 ret = mCurlRequest->getByteRange(http_url, headers, offset, size, new LLMeshDecompositionResponder(mesh_id, offset, size)); - if(ret) +#else + LLMeshDecompositionHandler * handler = new LLMeshDecompositionHandler(mesh_id, offset, size); + // LL_WARNS("Mesh") << "MESH: Issuing Decomp Request" << LL_ENDL; + LLCore::HttpHandle handle = mHttpRequest->requestGetByteRange(mHttpPolicyClass, + 0, // *TODO: Get better priority value + http_url, + offset, + size, + mHttpOptions, + mHttpHeaders, + handler); + if (LLCORE_HTTP_HANDLE_INVALID == handle) + { + // *TODO: Better error message + llwarns << "HTTP GET request failed for decomposition mesh " << mID << llendl; + delete handler; + ret = false; + } + else + { + handler->mHttpHandle = handle; + mHttpRequestSet.insert(handler); + } +#endif + if (ret) { LLMeshRepository::sHTTPRequestCount++; } @@ -1170,11 +1227,35 @@ bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id) std::string http_url = constructUrl(mesh_id); if (!http_url.empty()) - { + { +#if 0 ret = mCurlRequest->getByteRange(http_url, headers, offset, size, new LLMeshPhysicsShapeResponder(mesh_id, offset, size)); - - if(ret) +#else + LLMeshPhysicsShapeHandler * handler = new LLMeshPhysicsShapeHandler(mesh_id, offset, size); + // LL_WARNS("Mesh") << "MESH: Issuing Physics Shape Request" << LL_ENDL; + LLCore::HttpHandle handle = mHttpRequest->requestGetByteRange(mHttpPolicyClass, + 0, // *TODO: Get better priority value + http_url, + offset, + size, + mHttpOptions, + mHttpHeaders, + handler); + if (LLCORE_HTTP_HANDLE_INVALID == handle) + { + // *TODO: Better error message + llwarns << "HTTP GET request failed for physics shape mesh " << mID << llendl; + delete handler; + ret = false; + } + else + { + handler->mHttpHandle = handle; + mHttpRequestSet.insert(handler); + } +#endif + if (ret) { LLMeshRepository::sHTTPRequestCount++; } @@ -1261,6 +1342,7 @@ bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params, U32& c retval = mCurlRequest->getByteRange(http_url, headers, 0, MESH_HEADER_SIZE, new LLMeshHeaderResponder(mesh_params)); #else LLMeshHeaderHandler * handler = new LLMeshHeaderHandler(mesh_params); + // LL_WARNS("Mesh") << "MESH: Issuing Request" << LL_ENDL; LLCore::HttpHandle handle = mHttpRequest->requestGetByteRange(mHttpPolicyClass, 0, // *TODO: Get better priority value http_url, @@ -1352,10 +1434,34 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, std::string http_url = constructUrl(mesh_id); if (!http_url.empty()) - { + { +#if 0 retval = mCurlRequest->getByteRange(constructUrl(mesh_id), headers, offset, size, new LLMeshLODResponder(mesh_params, lod, offset, size)); - +#else + LLMeshLODHandler * handler = new LLMeshLODHandler(mesh_params, lod, offset, size); + // LL_WARNS("Mesh") << "MESH: Issuing LOD Request" << LL_ENDL; + LLCore::HttpHandle handle = mHttpRequest->requestGetByteRange(mHttpPolicyClass, + 0, // *TODO: Get better priority value + http_url, + offset, + size, + mHttpOptions, + mHttpHeaders, + handler); + if (LLCORE_HTTP_HANDLE_INVALID == handle) + { + // *TODO: Better error message + llwarns << "HTTP GET request failed for LOD mesh " << mID << llendl; + delete handler; + retval = false; + } + else + { + handler->mHttpHandle = handle; + mHttpRequestSet.insert(handler); + } +#endif if(retval) { LLMeshRepository::sHTTPRequestCount++; @@ -2445,6 +2551,7 @@ LLMeshHeaderHandler::~LLMeshHeaderHandler() void LLMeshHeaderHandler::processFailure(LLCore::HttpStatus status) { + LL_WARNS("Mesh") << "MESH: Processing Failure" << LL_ENDL; if (is_retryable(status)) { llwarns << "Timeout or service unavailable, retrying." << llendl; @@ -2463,6 +2570,7 @@ void LLMeshHeaderHandler::processFailure(LLCore::HttpStatus status) void LLMeshHeaderHandler::processData(LLCore::BufferArray * body, U8 * data, S32 data_size) { + // LL_WARNS("Mesh") << "MESH: Processing Data" << LL_ENDL; bool success = gMeshRepo.mThread->headerReceived(mMeshParams, data, data_size); llassert(success); if (! success) @@ -4379,103 +4487,6 @@ bool is_retryable(LLCore::HttpStatus status) // // #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 log_to_viewer_log(gSavedSettings, "LogTextureDownloadsToViewerLog"); - static LLCachedControl log_to_sim(gSavedSettings, "LogTextureDownloadsToSimulator"); - static LLCachedControl 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) { @@ -4499,19 +4510,7 @@ void LLTextureFetchWorker::onCompleted(LLCore::HttpHandle handle, LLCore::HttpRe 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 -- cgit v1.2.3 From e3db003cbff0faa44d29e35139601b9778acfbca Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Wed, 8 May 2013 13:48:14 -0400 Subject: SH-4139 Convert http downloaders and responders to llcorehttp patterns Conversion was mostly trivial. Did some refactoring in the conversion of Responders to Handlers which eliminated 5X code replication. More will be done especially as this is extended to deal with the various possible combinations of 200/206/416 status for ranged gets. There are a lot of thread races in the existing code, that is going to need some real attention. And the scheduling/liveness logic in the thread management bounces around from thread to thread wasting a lot of time and using expensive synchronization. Much can be done here. But the result is that the 8 connections in the Mesh corehttp class now perform as did the 32 connections of the original. And that 32 actually looks like it could bleed to over 64. So, progress... --- indra/newview/llmeshrepository.cpp | 54 +++++--------------------------------- 1 file changed, 6 insertions(+), 48 deletions(-) (limited to 'indra') diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index 2d4692be5b..7a7cc72711 100755 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -756,25 +756,21 @@ void LLMeshRepoThread::run() if (! mHttpRequestSet.empty()) { mHttpRequest->update(0L); - ms_sleep(100); - } - else - { - mWaiting = true; - mSignal->wait(); - mWaiting = false; } + + mWaiting = true; + mSignal->wait(); + mWaiting = false; if (! LLApp::isQuitting()) { static U32 count = 0; - static F32 last_hundred = gFrameTimeSeconds; if (gFrameTimeSeconds - last_hundred > 1.f) { //a second has gone by, clear count last_hundred = gFrameTimeSeconds; - count = 0; + count = 0; } // NOTE: throttling intentionally favors LOD requests over header requests @@ -1015,10 +1011,6 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id) std::string http_url = constructUrl(mesh_id); if (!http_url.empty()) { -#if 0 - ret = mCurlRequest->getByteRange(http_url, headers, offset, size, - new LLMeshSkinInfoResponder(mesh_id, offset, size)); -#else LLMeshSkinInfoHandler * handler = new LLMeshSkinInfoHandler(mesh_id, offset, size); // LL_WARNS("Mesh") << "MESH: Issuing Skin Info Request" << LL_ENDL; LLCore::HttpHandle handle = mHttpRequest->requestGetByteRange(mHttpPolicyClass, @@ -1040,10 +1032,6 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id) { handler->mHttpHandle = handle; mHttpRequestSet.insert(handler); - } -#endif - if (ret) - { LLMeshRepository::sHTTPRequestCount++; } } @@ -1122,10 +1110,6 @@ bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id) std::string http_url = constructUrl(mesh_id); if (!http_url.empty()) { -#if 0 - ret = mCurlRequest->getByteRange(http_url, headers, offset, size, - new LLMeshDecompositionResponder(mesh_id, offset, size)); -#else LLMeshDecompositionHandler * handler = new LLMeshDecompositionHandler(mesh_id, offset, size); // LL_WARNS("Mesh") << "MESH: Issuing Decomp Request" << LL_ENDL; LLCore::HttpHandle handle = mHttpRequest->requestGetByteRange(mHttpPolicyClass, @@ -1147,10 +1131,6 @@ bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id) { handler->mHttpHandle = handle; mHttpRequestSet.insert(handler); - } -#endif - if (ret) - { LLMeshRepository::sHTTPRequestCount++; } } @@ -1228,10 +1208,6 @@ bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id) std::string http_url = constructUrl(mesh_id); if (!http_url.empty()) { -#if 0 - ret = mCurlRequest->getByteRange(http_url, headers, offset, size, - new LLMeshPhysicsShapeResponder(mesh_id, offset, size)); -#else LLMeshPhysicsShapeHandler * handler = new LLMeshPhysicsShapeHandler(mesh_id, offset, size); // LL_WARNS("Mesh") << "MESH: Issuing Physics Shape Request" << LL_ENDL; LLCore::HttpHandle handle = mHttpRequest->requestGetByteRange(mHttpPolicyClass, @@ -1253,10 +1229,6 @@ bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id) { handler->mHttpHandle = handle; mHttpRequestSet.insert(handler); - } -#endif - if (ret) - { LLMeshRepository::sHTTPRequestCount++; } } @@ -1338,9 +1310,7 @@ 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 -#if 0 - retval = mCurlRequest->getByteRange(http_url, headers, 0, MESH_HEADER_SIZE, new LLMeshHeaderResponder(mesh_params)); -#else + LLMeshHeaderHandler * handler = new LLMeshHeaderHandler(mesh_params); // LL_WARNS("Mesh") << "MESH: Issuing Request" << LL_ENDL; LLCore::HttpHandle handle = mHttpRequest->requestGetByteRange(mHttpPolicyClass, @@ -1362,10 +1332,6 @@ bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params, U32& c { handler->mHttpHandle = handle; mHttpRequestSet.insert(handler); - } -#endif - if(retval) - { LLMeshRepository::sHTTPRequestCount++; } count++; @@ -1435,10 +1401,6 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, std::string http_url = constructUrl(mesh_id); if (!http_url.empty()) { -#if 0 - retval = mCurlRequest->getByteRange(constructUrl(mesh_id), headers, offset, size, - new LLMeshLODResponder(mesh_params, lod, offset, size)); -#else LLMeshLODHandler * handler = new LLMeshLODHandler(mesh_params, lod, offset, size); // LL_WARNS("Mesh") << "MESH: Issuing LOD Request" << LL_ENDL; LLCore::HttpHandle handle = mHttpRequest->requestGetByteRange(mHttpPolicyClass, @@ -1460,10 +1422,6 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, { handler->mHttpHandle = handle; mHttpRequestSet.insert(handler); - } -#endif - if(retval) - { LLMeshRepository::sHTTPRequestCount++; } count++; -- cgit v1.2.3 From f55f4bf1b25d14bcd3d956e6c170ece3880ad5f5 Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Wed, 8 May 2013 18:37:08 -0400 Subject: SH-4163 Run an initial series of 'B' tests on the mesh downloader code Bleh, had some old initialization code in place that meant I was using 32 connections. (Always verify with 'netstat'...) Logic is now to use 1/4 of MeshMaxConncurrentRequests to head in the direction of 8 at a time. Full count is used to implement a high-water level keeping llcorehttp in work. --- indra/newview/llappcorehttp.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/newview/llappcorehttp.cpp b/indra/newview/llappcorehttp.cpp index 4386e3283b..142344e277 100644 --- a/indra/newview/llappcorehttp.cpp +++ b/indra/newview/llappcorehttp.cpp @@ -139,7 +139,8 @@ void LLAppCoreHttp::init() static const std::string mesh_concur("MeshMaxConcurrentRequests"); if (gSavedSettings.controlExists(mesh_concur)) { - U32 setting(llmin(gSavedSettings.getU32(mesh_concur), U32(32))); + U32 setting(llmin(gSavedSettings.getU32(mesh_concur), 256U) / 4U); + setting = llmax(setting, 2U); if (setting > 0) { -- cgit v1.2.3 From 211d1dfb770aa029d77cd231815a5848640b54a6 Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Mon, 3 Jun 2013 13:54:15 -0400 Subject: SH-4184 Conversion to llcorehttp. Remove unneeded responder classes (moved to Handlers). --- indra/newview/llmeshrepository.cpp | 559 +------------------------------------ 1 file changed, 2 insertions(+), 557 deletions(-) (limited to 'indra') diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index 7a7cc72711..f1def284df 100755 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -218,7 +218,8 @@ public: LLMeshHandlerBase() : LLCore::HttpHandler(), mMeshParams(), - mProcessed(false) + mProcessed(false), + mHttpHandle(LLCORE_HTTP_HANDLE_INVALID) {} virtual ~LLMeshHandlerBase(); @@ -365,154 +366,6 @@ public: U32 mOffset; }; - -class LLMeshHeaderResponder : public LLCurl::Responder -{ -public: - LLVolumeParams mMeshParams; - bool mProcessed; - - LLMeshHeaderResponder(const LLVolumeParams& mesh_params) - : mMeshParams(mesh_params) - { - LLMeshRepoThread::incActiveHeaderRequests(); - mProcessed = false; - } - - ~LLMeshHeaderResponder() - { - 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(); - } - } - - virtual void completedRaw(U32 status, const std::string& reason, - const LLChannelDescriptors& channels, - const LLIOPipe::buffer_ptr_t& buffer); - -}; - -class LLMeshLODResponder : public LLCurl::Responder -{ -public: - LLVolumeParams mMeshParams; - S32 mLOD; - U32 mRequestedBytes; - U32 mOffset; - bool mProcessed; - - LLMeshLODResponder(const LLVolumeParams& mesh_params, S32 lod, U32 offset, U32 requested_bytes) - : mMeshParams(mesh_params), mLOD(lod), mOffset(offset), mRequestedBytes(requested_bytes) - { - LLMeshRepoThread::incActiveLODRequests(); - mProcessed = false; - } - - ~LLMeshLODResponder() - { - if (!LLApp::isQuitting()) - { - if (!mProcessed) - { - llwarns << "Killed without being processed, retrying." << llendl; - LLMeshRepository::sHTTPRetryCount++; - gMeshRepo.mThread->lockAndLoadMeshLOD(mMeshParams, mLOD); - } - LLMeshRepoThread::decActiveLODRequests(); - } - } - - virtual void completedRaw(U32 status, const std::string& reason, - const LLChannelDescriptors& channels, - const LLIOPipe::buffer_ptr_t& buffer); - -}; - -class LLMeshSkinInfoResponder : public LLCurl::Responder -{ -public: - LLUUID mMeshID; - U32 mRequestedBytes; - U32 mOffset; - bool mProcessed; - - LLMeshSkinInfoResponder(const LLUUID& id, U32 offset, U32 size) - : mMeshID(id), mRequestedBytes(size), mOffset(offset) - { - mProcessed = false; - } - - ~LLMeshSkinInfoResponder() - { - llassert(mProcessed); - } - - virtual void completedRaw(U32 status, const std::string& reason, - const LLChannelDescriptors& channels, - const LLIOPipe::buffer_ptr_t& buffer); - -}; - -class LLMeshDecompositionResponder : public LLCurl::Responder -{ -public: - LLUUID mMeshID; - U32 mRequestedBytes; - U32 mOffset; - bool mProcessed; - - LLMeshDecompositionResponder(const LLUUID& id, U32 offset, U32 size) - : mMeshID(id), mRequestedBytes(size), mOffset(offset) - { - mProcessed = false; - } - - ~LLMeshDecompositionResponder() - { - llassert(mProcessed); - } - - virtual void completedRaw(U32 status, const std::string& reason, - const LLChannelDescriptors& channels, - const LLIOPipe::buffer_ptr_t& buffer); - -}; - -class LLMeshPhysicsShapeResponder : public LLCurl::Responder -{ -public: - LLUUID mMeshID; - U32 mRequestedBytes; - U32 mOffset; - bool mProcessed; - - LLMeshPhysicsShapeResponder(const LLUUID& id, U32 offset, U32 size) - : mMeshID(id), mRequestedBytes(size), mOffset(offset) - { - mProcessed = false; - } - - ~LLMeshPhysicsShapeResponder() - { - llassert(mProcessed); - } - - virtual void completedRaw(U32 status, const std::string& reason, - const LLChannelDescriptors& channels, - const LLIOPipe::buffer_ptr_t& buffer); - -}; - void log_upload_error(S32 status, const LLSD& content, std::string stage, std::string model_name) { // Add notification popup. @@ -1005,9 +858,6 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id) } //reading from VFS failed for whatever reason, fetch from sim - std::vector headers; - headers.push_back("Accept: application/octet-stream"); - std::string http_url = constructUrl(mesh_id); if (!http_url.empty()) { @@ -1104,9 +954,6 @@ bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id) } //reading from VFS failed for whatever reason, fetch from sim - std::vector headers; - headers.push_back("Accept: application/octet-stream"); - std::string http_url = constructUrl(mesh_id); if (!http_url.empty()) { @@ -1202,9 +1049,6 @@ bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id) } //reading from VFS failed for whatever reason, fetch from sim - std::vector headers; - headers.push_back("Accept: application/octet-stream"); - std::string http_url = constructUrl(mesh_id); if (!http_url.empty()) { @@ -1301,9 +1145,6 @@ bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params, U32& c //either cache entry doesn't exist or is corrupt, request header from simulator bool retval = true ; - std::vector headers; - headers.push_back("Accept: application/octet-stream"); - std::string http_url = constructUrl(mesh_params.getSculptID()); if (!http_url.empty()) { @@ -1395,9 +1236,6 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, } //reading from VFS failed for whatever reason, fetch from sim - std::vector headers; - headers.push_back("Accept: application/octet-stream"); - std::string http_url = constructUrl(mesh_id); if (!http_url.empty()) { @@ -2184,270 +2022,12 @@ void LLMeshRepository::cacheOutgoingMesh(LLMeshUploadData& data, LLSD& header) } -void LLMeshLODResponder::completedRaw(U32 status, const std::string& reason, - const LLChannelDescriptors& channels, - const LLIOPipe::buffer_ptr_t& buffer) -{ - mProcessed = true; - - // thread could have already be destroyed during logout - if( !gMeshRepo.mThread ) - { - return; - } - - S32 data_size = buffer->countAfter(channels.in(), NULL); - - if (status < 200 || status > 400) - { - llwarns << status << ": " << reason << llendl; - } - - if (data_size < mRequestedBytes) - { - if (status == 499 || status == 503) - { //timeout or service unavailable, try again - llwarns << "Timeout or service unavailable, retrying." << llendl; - LLMeshRepository::sHTTPRetryCount++; - gMeshRepo.mThread->loadMeshLOD(mMeshParams, mLOD); - } - else - { - llassert(status == 499 || status == 503); //intentionally trigger a breakpoint - llwarns << "Unhandled status " << status << llendl; - } - return; - } - - LLMeshRepository::sBytesReceived += mRequestedBytes; - - U8* data = NULL; - - if (data_size > 0) - { - data = new U8[data_size]; - buffer->readAfter(channels.in(), NULL, data, 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; - } - } - - delete [] data; -} - -void LLMeshSkinInfoResponder::completedRaw(U32 status, const std::string& reason, - const LLChannelDescriptors& channels, - const LLIOPipe::buffer_ptr_t& buffer) -{ - mProcessed = true; - - // thread could have already be destroyed during logout - if( !gMeshRepo.mThread ) - { - return; - } - - S32 data_size = buffer->countAfter(channels.in(), NULL); - - if (status < 200 || status > 400) - { - llwarns << status << ": " << reason << llendl; - } - - if (data_size < mRequestedBytes) - { - if (status == 499 || status == 503) - { //timeout or service unavailable, try again - llwarns << "Timeout or service unavailable, retrying." << llendl; - LLMeshRepository::sHTTPRetryCount++; - gMeshRepo.mThread->loadMeshSkinInfo(mMeshID); - } - else - { - llassert(status == 499 || status == 503); //intentionally trigger a breakpoint - llwarns << "Unhandled status " << status << llendl; - } - return; - } - - LLMeshRepository::sBytesReceived += mRequestedBytes; - - U8* data = NULL; - - if (data_size > 0) - { - data = new U8[data_size]; - buffer->readAfter(channels.in(), NULL, data, 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); - } - } - - delete [] data; -} - -void LLMeshDecompositionResponder::completedRaw(U32 status, const std::string& reason, - const LLChannelDescriptors& channels, - const LLIOPipe::buffer_ptr_t& buffer) -{ - mProcessed = true; - - if( !gMeshRepo.mThread ) - { - return; - } - - S32 data_size = buffer->countAfter(channels.in(), NULL); - - if (status < 200 || status > 400) - { - llwarns << status << ": " << reason << llendl; - } - - if (data_size < mRequestedBytes) - { - if (status == 499 || status == 503) - { //timeout or service unavailable, try again - llwarns << "Timeout or service unavailable, retrying." << llendl; - LLMeshRepository::sHTTPRetryCount++; - gMeshRepo.mThread->loadMeshDecomposition(mMeshID); - } - else - { - llassert(status == 499 || status == 503); //intentionally trigger a breakpoint - llwarns << "Unhandled status " << status << llendl; - } - return; - } - - LLMeshRepository::sBytesReceived += mRequestedBytes; - - U8* data = NULL; - - if (data_size > 0) - { - data = new U8[data_size]; - buffer->readAfter(channels.in(), NULL, data, 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); - } - } - - delete [] data; -} - -void LLMeshPhysicsShapeResponder::completedRaw(U32 status, const std::string& reason, - const LLChannelDescriptors& channels, - const LLIOPipe::buffer_ptr_t& buffer) -{ - mProcessed = true; - - // thread could have already be destroyed during logout - if( !gMeshRepo.mThread ) - { - return; - } - - S32 data_size = buffer->countAfter(channels.in(), NULL); - - if (status < 200 || status > 400) - { - llwarns << status << ": " << reason << llendl; - } - - if (data_size < mRequestedBytes) - { - if (status == 499 || status == 503) - { //timeout or service unavailable, try again - llwarns << "Timeout or service unavailable, retrying." << llendl; - LLMeshRepository::sHTTPRetryCount++; - gMeshRepo.mThread->loadMeshPhysicsShape(mMeshID); - } - else - { - llassert(status == 499 || status == 503); //intentionally trigger a breakpoint - llwarns << "Unhandled status " << status << llendl; - } - return; - } - - LLMeshRepository::sBytesReceived += mRequestedBytes; - - U8* data = NULL; - - if (data_size > 0) - { - data = new U8[data_size]; - buffer->readAfter(channels.in(), NULL, data, 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); - } - } - - delete [] data; -} - - LLMeshHandlerBase::~LLMeshHandlerBase() {} - void LLMeshHandlerBase::onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response) { mProcessed = true; - LLCore::HttpStatus status(response->getStatus()); if (! status) { @@ -2506,7 +2086,6 @@ LLMeshHeaderHandler::~LLMeshHeaderHandler() } } - void LLMeshHeaderHandler::processFailure(LLCore::HttpStatus status) { LL_WARNS("Mesh") << "MESH: Processing Failure" << LL_ENDL; @@ -2525,7 +2104,6 @@ void LLMeshHeaderHandler::processFailure(LLCore::HttpStatus status) } } - void LLMeshHeaderHandler::processData(LLCore::BufferArray * body, U8 * data, S32 data_size) { // LL_WARNS("Mesh") << "MESH: Processing Data" << LL_ENDL; @@ -2596,7 +2174,6 @@ void LLMeshHeaderHandler::processData(LLCore::BufferArray * body, U8 * data, S32 } } - LLMeshLODHandler::~LLMeshLODHandler() { if (! LLApp::isQuitting()) @@ -2646,7 +2223,6 @@ void LLMeshLODHandler::processData(LLCore::BufferArray * body, U8 * data, S32 da } } - LLMeshSkinInfoHandler::~LLMeshSkinInfoHandler() { llassert(mProcessed); @@ -2687,7 +2263,6 @@ void LLMeshSkinInfoHandler::processData(LLCore::BufferArray * body, U8 * data, S } } - LLMeshDecompositionHandler::~LLMeshDecompositionHandler() { llassert(mProcessed); @@ -2728,7 +2303,6 @@ void LLMeshDecompositionHandler::processData(LLCore::BufferArray * body, U8 * da } } - LLMeshPhysicsShapeHandler::~LLMeshPhysicsShapeHandler() { llassert(mProcessed); @@ -2750,7 +2324,6 @@ void LLMeshPhysicsShapeHandler::processFailure(LLCore::HttpStatus status) } } - void LLMeshPhysicsShapeHandler::processData(LLCore::BufferArray * body, U8 * data, S32 data_size) { if (gMeshRepo.mThread->physicsShapeReceived(mMeshID, data, data_size)) @@ -2770,134 +2343,6 @@ void LLMeshPhysicsShapeHandler::processData(LLCore::BufferArray * body, U8 * dat } } - -void LLMeshHeaderResponder::completedRaw(U32 status, const std::string& reason, - const LLChannelDescriptors& channels, - const LLIOPipe::buffer_ptr_t& buffer) -{ - mProcessed = true; - - // thread could have already be destroyed during logout - if( !gMeshRepo.mThread ) - { - return; - } - - if (status < 200 || status > 400) - { - //llwarns - // << "Header responder failed with status: " - // << status << ": " << reason << llendl; - - // 503 (service unavailable) or 499 (timeout) - // can be due to server load and can be retried - - // TODO*: Add maximum retry logic, exponential backoff - // and (somewhat more optional than the others) retries - // again after some set period of time - - llassert(status == 503 || status == 499); - - if (status == 503 || status == 499) - { //retry - llwarns << "Timeout or service unavailable, retrying." << llendl; - LLMeshRepository::sHTTPRetryCount++; - LLMeshRepoThread::HeaderRequest req(mMeshParams); - LLMutexLock lock(gMeshRepo.mThread->mMutex); - gMeshRepo.mThread->mHeaderReqQ.push(req); - - return; - } - else - { - llwarns << "Unhandled status." << llendl; - } - } - - S32 data_size = buffer->countAfter(channels.in(), NULL); - - U8* data = NULL; - - if (data_size > 0) - { - data = new U8[data_size]; - buffer->readAfter(channels.in(), NULL, data, data_size); - } - - LLMeshRepository::sBytesReceived += llmin(data_size, MESH_HEADER_SIZE); - - bool success = gMeshRepo.mThread->headerReceived(mMeshParams, data, data_size); - - llassert(success); - - if (!success) - { - llwarns - << "Unable to parse mesh header: " - << status << ": " << reason << 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((const U8*) 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); - } - } - } - } - - delete [] data; -} - - LLMeshRepository::LLMeshRepository() : mMeshMutex(NULL), mMeshThreadCount(0), -- cgit v1.2.3 From 7c9d0b58aca072ff932b30f927e2876dfc6858aa Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Fri, 7 Jun 2013 20:14:52 -0400 Subject: Mostly cleanup. A chunk of comment code nobody needs. Dereference after delete, erase() on end() iterator, a few more like that. Killed a dead variable. --- indra/newview/llmeshrepository.cpp | 163 ++----------------------------------- indra/newview/llmeshrepository.h | 1 - 2 files changed, 9 insertions(+), 155 deletions(-) (limited to 'indra') diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index 8f6860aec7..702e940983 100755 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -97,7 +97,6 @@ U32 LLMeshRepository::sLODPending = 0; U32 LLMeshRepository::sCacheBytesRead = 0; U32 LLMeshRepository::sCacheBytesWritten = 0; -U32 LLMeshRepository::sPeakKbps = 0; LLDeadmanTimer LLMeshRepository::sQuiescentTimer(15.0, true); // true -> gather cpu metrics @@ -641,6 +640,7 @@ void LLMeshRepoThread::run() { mMutex->lock(); mLODReqQ.push(req) ; + ++LLMeshRepository::sLODProcessing; mMutex->unlock(); } } @@ -882,7 +882,7 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id) { handler->mHttpHandle = handle; mHttpRequestSet.insert(handler); - LLMeshRepository::sHTTPRequestCount++; + ++LLMeshRepository::sHTTPRequestCount; } } } @@ -978,7 +978,7 @@ bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id) { handler->mHttpHandle = handle; mHttpRequestSet.insert(handler); - LLMeshRepository::sHTTPRequestCount++; + ++LLMeshRepository::sHTTPRequestCount; } } } @@ -1073,7 +1073,7 @@ bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id) { handler->mHttpHandle = handle; mHttpRequestSet.insert(handler); - LLMeshRepository::sHTTPRequestCount++; + ++LLMeshRepository::sHTTPRequestCount; } } } @@ -1173,7 +1173,7 @@ bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params, U32& c { handler->mHttpHandle = handle; mHttpRequestSet.insert(handler); - LLMeshRepository::sHTTPRequestCount++; + ++LLMeshRepository::sHTTPRequestCount; } count++; } @@ -1260,7 +1260,7 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, { handler->mHttpHandle = handle; mHttpRequestSet.insert(handler); - LLMeshRepository::sHTTPRequestCount++; + ++LLMeshRepository::sHTTPRequestCount; } count++; } @@ -2738,9 +2738,8 @@ void LLMeshRepository::notifySkinInfoReceived(LLMeshSkinInfo& info) vobj->notifyMeshLoaded(); } } + mLoadingSkins.erase(info.mMeshID); } - - mLoadingSkins.erase(info.mMeshID); } void LLMeshRepository::notifyDecompositionReceived(LLModel::Decomposition* decomp) @@ -2749,14 +2748,14 @@ void LLMeshRepository::notifyDecompositionReceived(LLModel::Decomposition* decom if (iter == mDecompositionMap.end()) { //just insert decomp into map mDecompositionMap[decomp->mMeshID] = decomp; + mLoadingDecompositions.erase(decomp->mMeshID); } else { //merge decomp with existing entry iter->second->merge(decomp); + mLoadingDecompositions.erase(decomp->mMeshID); delete decomp; } - - mLoadingDecompositions.erase(decomp->mMeshID); } void LLMeshRepository::notifyMeshLoaded(const LLVolumeParams& mesh_params, LLVolume* volume) @@ -3881,147 +3880,3 @@ bool is_retryable(LLCore::HttpStatus status) status == inv_cont_range)); // Short data read disagrees with content-range } - - - -// =========== -// -// HTTP fragments I'll be needing -// -// -#if 0 - - 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); - - - - -// 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 8ffe2d68cb..62f81ce9e2 100755 --- a/indra/newview/llmeshrepository.h +++ b/indra/newview/llmeshrepository.h @@ -469,7 +469,6 @@ public: static U32 sLODProcessing; 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); -- cgit v1.2.3 From 626752beab7c12a355ab707d70aba6f4fe096c10 Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Wed, 19 Jun 2013 13:55:54 -0400 Subject: SH-4252 Add second policy class for large mesh asset downloads Added second mesh class as well as an asset upload class. Refactored initialization to use less code and more data to cleanly get http started. Modified mesh to use the new http class for large requests (>2MB for now). Added additional timeout setting to llcorehttp to distinguish connection timeout from transport timeout and are now using transport timeout values for large asset downloads that may need more time. --- indra/llcorehttp/_httpinternal.h | 3 +- indra/llcorehttp/_httpoprequest.cpp | 15 ++- indra/llcorehttp/_httpoprequest.h | 3 +- indra/llcorehttp/_httppolicy.cpp | 6 ++ indra/llcorehttp/httpoptions.cpp | 9 +- indra/llcorehttp/httpoptions.h | 9 +- indra/llcorehttp/httpresponse.cpp | 6 +- indra/llcorehttp/httpresponse.h | 17 +++- indra/newview/llappcorehttp.cpp | 156 ++++++++++++++++------------ indra/newview/llappcorehttp.h | 36 +++---- indra/newview/llmeshrepository.cpp | 197 +++++++++++++++++++++--------------- indra/newview/llmeshrepository.h | 21 +++- indra/newview/lltexturefetch.cpp | 4 +- 13 files changed, 301 insertions(+), 181 deletions(-) (limited to 'indra') diff --git a/indra/llcorehttp/_httpinternal.h b/indra/llcorehttp/_httpinternal.h index 30b0905c12..d60996756f 100755 --- a/indra/llcorehttp/_httpinternal.h +++ b/indra/llcorehttp/_httpinternal.h @@ -98,7 +98,7 @@ namespace LLCore // Maxium number of policy classes that can be defined. // *TODO: Currently limited to the default class + 1, extend. -const int HTTP_POLICY_CLASS_LIMIT = 2; +const int HTTP_POLICY_CLASS_LIMIT = 4; // Debug/informational tracing. Used both // as a global option and in per-request traces. @@ -129,6 +129,7 @@ const int HTTP_REDIRECTS_DEFAULT = 10; // Retries and time-on-queue are not included and aren't // accounted for. const long HTTP_REQUEST_TIMEOUT_DEFAULT = 30L; +const long HTTP_REQUEST_XFER_TIMEOUT_DEFAULT = 0L; const long HTTP_REQUEST_TIMEOUT_MIN = 0L; const long HTTP_REQUEST_TIMEOUT_MAX = 3600L; diff --git a/indra/llcorehttp/_httpoprequest.cpp b/indra/llcorehttp/_httpoprequest.cpp index 51a8eaf998..d403b2d249 100755 --- a/indra/llcorehttp/_httpoprequest.cpp +++ b/indra/llcorehttp/_httpoprequest.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 @@ -110,6 +110,7 @@ HttpOpRequest::HttpOpRequest() mReplyFullLength(0), mReplyHeaders(NULL), mPolicyRetries(0), + mPolicy503Retries(0), mPolicyRetryAt(HttpTime(0)), mPolicyRetryLimit(HTTP_RETRY_COUNT_DEFAULT) { @@ -224,6 +225,7 @@ void HttpOpRequest::visitNotifier(HttpRequest * request) response->setRange(mReplyOffset, mReplyLength, mReplyFullLength); } response->setContentType(mReplyConType); + response->setRetries(mPolicyRetries, mPolicy503Retries); mUserHandler->onCompleted(static_cast(this), response); @@ -524,12 +526,19 @@ HttpStatus HttpOpRequest::prepareRequest(HttpService * service) // Request options long timeout(HTTP_REQUEST_TIMEOUT_DEFAULT); + long xfer_timeout(HTTP_REQUEST_XFER_TIMEOUT_DEFAULT); if (mReqOptions) - { + { timeout = mReqOptions->getTimeout(); timeout = llclamp(timeout, HTTP_REQUEST_TIMEOUT_MIN, HTTP_REQUEST_TIMEOUT_MAX); + xfer_timeout = mReqOptions->getTransferTimeout(); + xfer_timeout = llclamp(xfer_timeout, HTTP_REQUEST_TIMEOUT_MIN, HTTP_REQUEST_TIMEOUT_MAX); + } + if (xfer_timeout == 0L) + { + xfer_timeout = timeout; } - curl_easy_setopt(mCurlHandle, CURLOPT_TIMEOUT, timeout); + curl_easy_setopt(mCurlHandle, CURLOPT_TIMEOUT, xfer_timeout); curl_easy_setopt(mCurlHandle, CURLOPT_CONNECTTIMEOUT, timeout); // Request headers diff --git a/indra/llcorehttp/_httpoprequest.h b/indra/llcorehttp/_httpoprequest.h index 7b65d17783..831e5bebf7 100755 --- a/indra/llcorehttp/_httpoprequest.h +++ b/indra/llcorehttp/_httpoprequest.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 @@ -187,6 +187,7 @@ public: // Policy data int mPolicyRetries; + int mPolicy503Retries; HttpTime mPolicyRetryAt; int mPolicyRetryLimit; }; // end class HttpOpRequest diff --git a/indra/llcorehttp/_httppolicy.cpp b/indra/llcorehttp/_httppolicy.cpp index 76c1e22431..54c9c6bb1b 100755 --- a/indra/llcorehttp/_httppolicy.cpp +++ b/indra/llcorehttp/_httppolicy.cpp @@ -140,6 +140,7 @@ void HttpPolicy::addOp(HttpOpRequest * op) const int policy_class(op->mReqPolicy); op->mPolicyRetries = 0; + op->mPolicy503Retries = 0; mState[policy_class].mReadyQueue.push(op); } @@ -155,6 +156,7 @@ void HttpPolicy::retryOp(HttpOpRequest * op) 5000000 // ... to every 5.0 S. }; static const int delta_max(int(LL_ARRAY_SIZE(retry_deltas)) - 1); + static const HttpStatus error_503(503); const HttpTime now(totalTime()); const int policy_class(op->mReqPolicy); @@ -162,6 +164,10 @@ void HttpPolicy::retryOp(HttpOpRequest * op) const HttpTime delta(retry_deltas[llclamp(op->mPolicyRetries, 0, delta_max)]); op->mPolicyRetryAt = now + delta; ++op->mPolicyRetries; + if (error_503 == op->mStatus) + { + ++op->mPolicy503Retries; + } LL_WARNS("CoreHttp") << "HTTP request " << static_cast(op) << " retry " << op->mPolicyRetries << " scheduled for +" << (delta / HttpTime(1000)) diff --git a/indra/llcorehttp/httpoptions.cpp b/indra/llcorehttp/httpoptions.cpp index 1699d19f8d..4dcd862ca4 100755 --- a/indra/llcorehttp/httpoptions.cpp +++ b/indra/llcorehttp/httpoptions.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,6 +38,7 @@ HttpOptions::HttpOptions() mWantHeaders(false), mTracing(HTTP_TRACE_OFF), mTimeout(HTTP_REQUEST_TIMEOUT_DEFAULT), + mTransferTimeout(HTTP_REQUEST_XFER_TIMEOUT_DEFAULT), mRetries(HTTP_RETRY_COUNT_DEFAULT) {} @@ -64,6 +65,12 @@ void HttpOptions::setTimeout(unsigned int timeout) } +void HttpOptions::setTransferTimeout(unsigned int timeout) +{ + mTransferTimeout = timeout; +} + + void HttpOptions::setRetries(unsigned int retries) { mRetries = retries; diff --git a/indra/llcorehttp/httpoptions.h b/indra/llcorehttp/httpoptions.h index 97e46a8cd3..623d71d3e6 100755 --- a/indra/llcorehttp/httpoptions.h +++ b/indra/llcorehttp/httpoptions.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 @@ -86,6 +86,12 @@ public: return mTimeout; } + void setTransferTimeout(unsigned int timeout); + unsigned int getTransferTimeout() const + { + return mTransferTimeout; + } + void setRetries(unsigned int retries); unsigned int getRetries() const { @@ -96,6 +102,7 @@ protected: bool mWantHeaders; int mTracing; unsigned int mTimeout; + unsigned int mTransferTimeout; unsigned int mRetries; }; // end class HttpOptions diff --git a/indra/llcorehttp/httpresponse.cpp b/indra/llcorehttp/httpresponse.cpp index a552e48a1b..c974395b0a 100755 --- a/indra/llcorehttp/httpresponse.cpp +++ b/indra/llcorehttp/httpresponse.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 @@ -39,7 +39,9 @@ HttpResponse::HttpResponse() mReplyLength(0U), mReplyFullLength(0U), mBufferArray(NULL), - mHeaders(NULL) + mHeaders(NULL), + mRetries(0U), + m503Retries(0U) {} diff --git a/indra/llcorehttp/httpresponse.h b/indra/llcorehttp/httpresponse.h index 4a481db6ac..a7f296e03f 100755 --- a/indra/llcorehttp/httpresponse.h +++ b/indra/llcorehttp/httpresponse.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 @@ -144,6 +144,19 @@ public: mContentType = con_type; } + /// Get and set retry attempt information on the request. + void getRetries(unsigned int * retries, unsigned int * retries_503) const + { + *retries = mRetries; + *retries_503 = m503Retries; + } + + void setRetries(unsigned int retries, unsigned int retries_503) + { + mRetries = retries; + m503Retries = retries_503; + } + protected: // Response data here HttpStatus mStatus; @@ -153,6 +166,8 @@ protected: BufferArray * mBufferArray; HttpHeaders * mHeaders; std::string mContentType; + unsigned int mRetries; + unsigned int m503Retries; }; diff --git a/indra/newview/llappcorehttp.cpp b/indra/newview/llappcorehttp.cpp index 142344e277..b601b31d21 100755 --- a/indra/newview/llappcorehttp.cpp +++ b/indra/newview/llappcorehttp.cpp @@ -37,11 +37,13 @@ LLAppCoreHttp::LLAppCoreHttp() : mRequest(NULL), mStopHandle(LLCORE_HTTP_HANDLE_INVALID), mStopRequested(0.0), - mStopped(false), - mPolicyDefault(-1), - mPolicyTexture(-1), - mPolicyMesh(-1) -{} + mStopped(false) +{ + for (int i(0); i < LL_ARRAY_SIZE(mPolicies); ++i) + { + mPolicies[i] = LLCore::HttpRequest::DEFAULT_POLICY_ID; + } +} LLAppCoreHttp::~LLAppCoreHttp() @@ -53,11 +55,43 @@ LLAppCoreHttp::~LLAppCoreHttp() void LLAppCoreHttp::init() { + static const struct + { + EAppPolicy mPolicy; + U32 mDefault; + U32 mMin; + U32 mMax; + U32 mDivisor; + std::string mKey; + const char * mUsage; + } init_data[] = // Default and dynamic values for classes + { + { + AP_TEXTURE, 8, 1, 12, 1, + "TextureFetchConcurrency", + "texture fetch" + }, + { + AP_MESH, 8, 1, 32, 4, + "MeshMaxConcurrentRequests", + "mesh fetch" + }, + { + AP_LARGE_MESH, 2, 1, 8, 1, + "", + "large mesh fetch" + }, + { + AP_UPLOADS, 2, 1, 8, 1, + "", + "asset upload" + } + }; + LLCore::HttpStatus status = LLCore::HttpRequest::createService(); if (! status) { - LL_ERRS("Init") << "Failed to initialize HTTP services. Reason: " - << status.toString() + LL_ERRS("Init") << "Failed to initialize HTTP services. Reason: " << status.toString() << LL_ENDL; } @@ -66,8 +100,7 @@ void LLAppCoreHttp::init() gDirUtilp->getCAFile()); if (! status) { - LL_ERRS("Init") << "Failed to set CA File for HTTP services. Reason: " - << status.toString() + LL_ERRS("Init") << "Failed to set CA File for HTTP services. Reason: " << status.toString() << LL_ENDL; } @@ -77,8 +110,7 @@ void LLAppCoreHttp::init() status = LLCore::HttpRequest::setPolicyGlobalOption(LLCore::HttpRequest::GP_LLPROXY, 1); if (! status) { - LL_ERRS("Init") << "Failed to set HTTP proxy for HTTP services. Reason: " - << status.toString() + LL_ERRS("Init") << "Failed to set HTTP proxy for HTTP services. Reason: " << status.toString() << LL_ENDL; } @@ -96,80 +128,72 @@ void LLAppCoreHttp::init() } // Setup default policy and constrain if directed to - mPolicyDefault = LLCore::HttpRequest::DEFAULT_POLICY_ID; + mPolicies[AP_DEFAULT] = 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)) + // Setup additional policies based on table and some special rules + // *TODO: Make these configurations dynamic later + for (int i(0); i < LL_ARRAY_SIZE(init_data); ++i) { - U32 concur(llmin(gSavedSettings.getU32(texture_concur), U32(12))); + const EAppPolicy policy(init_data[i].mPolicy); - if (concur > 0) + // Create a policy class but use default for texture for now. + // This also has the side-effect of initializing the default + // class to desired values. + if (AP_TEXTURE == policy) { - LLCore::HttpStatus status; - status = LLCore::HttpRequest::setPolicyClassOption(mPolicyTexture, - LLCore::HttpRequest::CP_CONNECTION_LIMIT, - concur); - if (! status) - { - LL_WARNS("Init") << "Unable to set texture fetch concurrency. Reason: " - << status.toString() - << LL_ENDL; - } - else + mPolicies[policy] = mPolicies[AP_DEFAULT]; + } + else + { + mPolicies[policy] = LLCore::HttpRequest::createPolicyClass(); + if (! mPolicies[policy]) { - LL_INFOS("Init") << "Application settings overriding default texture fetch concurrency. New value: " - << concur + // Use default policy (but don't accidentally modify default) + LL_WARNS("Init") << "Failed to create HTTP policy class for " << init_data[i].mUsage + << ". Using default policy." << LL_ENDL; + mPolicies[policy] = mPolicies[AP_DEFAULT]; + continue; } } - } - // 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)) + // Get target connection concurrency value + U32 setting(init_data[i].mDefault); + if (! init_data[i].mKey.empty() && gSavedSettings.controlExists(init_data[i].mKey)) { - U32 setting(llmin(gSavedSettings.getU32(mesh_concur), 256U) / 4U); - setting = llmax(setting, 2U); - - if (setting > 0) + U32 new_setting(gSavedSettings.getU32(init_data[i].mKey)); + if (new_setting) { - 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; - } + // Treat zero settings as an ask for default + setting = new_setting / init_data[i].mDivisor; + setting = llclamp(setting, init_data[i].mMin, init_data[i].mMax); } } + + // Set it and report + LLCore::HttpStatus status; + status = LLCore::HttpRequest::setPolicyClassOption(mPolicies[policy], + LLCore::HttpRequest::CP_CONNECTION_LIMIT, + setting); + if (! status) + { + LL_WARNS("Init") << "Unable to set " << init_data[i].mUsage + << " concurrency. Reason: " << status.toString() + << LL_ENDL; + } + else if (setting != init_data[i].mDefault) + { + LL_INFOS("Init") << "Application settings overriding default " << init_data[i].mUsage + << " concurrency. New value: " << setting + << LL_ENDL; + } } // Kick the thread status = LLCore::HttpRequest::startThread(); if (! status) { - LL_ERRS("Init") << "Failed to start HTTP servicing thread. Reason: " - << status.toString() + LL_ERRS("Init") << "Failed to start HTTP servicing thread. Reason: " << status.toString() << LL_ENDL; } diff --git a/indra/newview/llappcorehttp.h b/indra/newview/llappcorehttp.h index d90af9e5ca..532e1f5cb0 100755 --- a/indra/newview/llappcorehttp.h +++ b/indra/newview/llappcorehttp.h @@ -40,6 +40,19 @@ // as a singleton and static construction is fine. class LLAppCoreHttp : public LLCore::HttpHandler { +public: + typedef LLCore::HttpRequest::policy_t policy_t; + + enum EAppPolicy + { + AP_DEFAULT, + AP_TEXTURE, + AP_MESH, + AP_LARGE_MESH, + AP_UPLOADS, + AP_COUNT // Must be last + }; + public: LLAppCoreHttp(); ~LLAppCoreHttp(); @@ -65,22 +78,11 @@ public: // Notification when the stop request is complete. virtual void onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response); - // Retrieve the policy class for default operations. - int getPolicyDefault() const - { - return mPolicyDefault; - } - - // Get the texture fetch policy class. - int getPolicyTexture() const - { - return mPolicyTexture; - } - - // Get the mesh fetch policy class. - int getPolicyMesh() const + // Retrieve a policy class identifier for desired + // application function. + policy_t getPolicy(EAppPolicy policy) const { - return mPolicyMesh; + return mPolicies[policy]; } private: @@ -91,9 +93,7 @@ private: LLCore::HttpHandle mStopHandle; F64 mStopRequested; bool mStopped; - int mPolicyDefault; - int mPolicyTexture; - int mPolicyMesh; + policy_t mPolicies[AP_COUNT]; }; diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index 702e940983..f0ec97a34d 100755 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -80,6 +80,10 @@ LLMeshRepository gMeshRepo; const S32 MESH_HEADER_SIZE = 4096; const U32 MAX_MESH_REQUESTS_PER_SECOND = 100; +const S32 REQUEST_HIGH_WATER_MIN = 32; +const S32 REQUEST_LOW_WATER_MIN = 16; +const U32 LARGE_MESH_FETCH_THRESHOLD = 1U << 21; // Size at which requests goes to narrow/slow queue +const long LARGE_MESH_XFER_TIMEOUT = 240L; // Seconds to complete xfer // Maximum mesh version to support. Three least significant digits are reserved for the minor version, // with major version changes indicating a format change that is not backwards compatible and should not @@ -210,6 +214,8 @@ void get_vertex_buffer_from_mesh(LLCDMeshData& mesh, LLModel::PhysicsMesh& res, S32 LLMeshRepoThread::sActiveHeaderRequests = 0; S32 LLMeshRepoThread::sActiveLODRequests = 0; U32 LLMeshRepoThread::sMaxConcurrentRequests = 1; +S32 LLMeshRepoThread::sRequestLowWater = REQUEST_LOW_WATER_MIN; +S32 LLMeshRepoThread::sRequestHighWater = REQUEST_HIGH_WATER_MIN; class LLMeshHandlerBase : public LLCore::HttpHandler { @@ -548,25 +554,37 @@ public: LLMeshRepoThread::LLMeshRepoThread() : LLThread("mesh repo"), - mCurlRequest(NULL), mWaiting(false), mHttpRequest(NULL), mHttpOptions(NULL), + mHttpLargeOptions(NULL), mHttpHeaders(NULL), - mHttpPolicyClass(LLCore::HttpRequest::DEFAULT_POLICY_ID) + mHttpPolicyClass(LLCore::HttpRequest::DEFAULT_POLICY_ID), + mHttpLargePolicyClass(LLCore::HttpRequest::DEFAULT_POLICY_ID), + mHttpPriority(0), + mHttpGetCount(0U), + mHttpLargeGetCount(0U) { mMutex = new LLMutex(NULL); mHeaderMutex = new LLMutex(NULL); mSignal = new LLCondition(NULL); mHttpRequest = new LLCore::HttpRequest; mHttpOptions = new LLCore::HttpOptions; + mHttpLargeOptions = new LLCore::HttpOptions; + mHttpLargeOptions->setTransferTimeout(LARGE_MESH_XFER_TIMEOUT); mHttpHeaders = new LLCore::HttpHeaders; mHttpHeaders->mHeaders.push_back("Accept: application/vnd.ll.mesh"); - mHttpPolicyClass = LLAppViewer::instance()->getAppCoreHttp().getPolicyMesh(); + mHttpPolicyClass = LLAppViewer::instance()->getAppCoreHttp().getPolicy(LLAppCoreHttp::AP_MESH); + mHttpPolicyClass = LLAppViewer::instance()->getAppCoreHttp().getPolicy(LLAppCoreHttp::AP_LARGE_MESH); } + LLMeshRepoThread::~LLMeshRepoThread() { + LL_INFOS("Mesh") << "Small GETs issued: " + << mHttpGetCount << ", Large GETs issued: " + << mHttpLargeGetCount << LL_ENDL; + for (http_request_set::iterator iter(mHttpRequestSet.begin()); iter != mHttpRequestSet.end(); ++iter) @@ -584,6 +602,11 @@ LLMeshRepoThread::~LLMeshRepoThread() mHttpOptions->release(); mHttpOptions = NULL; } + if (mHttpLargeOptions) + { + mHttpLargeOptions->release(); + mHttpLargeOptions = NULL; + } delete mHttpRequest; mHttpRequest = NULL; delete mMutex; @@ -596,7 +619,6 @@ LLMeshRepoThread::~LLMeshRepoThread() void LLMeshRepoThread::run() { - mCurlRequest = new LLCurlRequest(); LLCDResult res = LLConvexDecomposition::initThread(); if (res != LLCD_OK) { @@ -627,7 +649,7 @@ void LLMeshRepoThread::run() // NOTE: throttling intentionally favors LOD requests over header requests - while (!mLODReqQ.empty() && count < MAX_MESH_REQUESTS_PER_SECOND && sActiveLODRequests < sMaxConcurrentRequests) + while (!mLODReqQ.empty() && count < MAX_MESH_REQUESTS_PER_SECOND) { if (mMutex) { @@ -646,7 +668,7 @@ void LLMeshRepoThread::run() } } - while (!mHeaderReqQ.empty() && count < MAX_MESH_REQUESTS_PER_SECOND && sActiveHeaderRequests < sMaxConcurrentRequests) + while (!mHeaderReqQ.empty() && count < MAX_MESH_REQUESTS_PER_SECOND) { if (mMutex) { @@ -701,8 +723,6 @@ void LLMeshRepoThread::run() } mPhysicsShapeRequests = incomplete; } - - mCurlRequest->process(); } } @@ -716,9 +736,6 @@ void LLMeshRepoThread::run() { llwarns << "convex decomposition unable to be quit" << llendl; } - - delete mCurlRequest; - mCurlRequest = NULL; } void LLMeshRepoThread::loadMeshSkinInfo(const LLUUID& mesh_id) @@ -800,6 +817,42 @@ std::string LLMeshRepoThread::constructUrl(LLUUID mesh_id) return http_url; } +// May only be called by repo thread +LLCore::HttpHandle LLMeshRepoThread::getByteRange(const std::string & url, + size_t offset, + size_t len, + LLCore::HttpHandler * handler) +{ + LLCore::HttpHandle handle(LLCORE_HTTP_HANDLE_INVALID); + + if (len < LARGE_MESH_FETCH_THRESHOLD) + { + handle = mHttpRequest->requestGetByteRange(mHttpPolicyClass, + mHttpPriority, + url, + offset, + len, + mHttpOptions, + mHttpHeaders, + handler); + ++mHttpGetCount; + } + else + { + handle = mHttpRequest->requestGetByteRange(mHttpLargePolicyClass, + mHttpPriority, + url, + offset, + len, + mHttpLargeOptions, + mHttpHeaders, + handler); + ++mHttpLargeGetCount; + } + return handle; +} + + bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id) { //protected by mMutex @@ -863,14 +916,7 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id) { LLMeshSkinInfoHandler * handler = new LLMeshSkinInfoHandler(mesh_id, offset, size); // LL_WARNS("Mesh") << "MESH: Issuing Skin Info Request" << LL_ENDL; - LLCore::HttpHandle handle = mHttpRequest->requestGetByteRange(mHttpPolicyClass, - 0, // *TODO: Get better priority value - http_url, - offset, - size, - mHttpOptions, - mHttpHeaders, - handler); + LLCore::HttpHandle handle = getByteRange(http_url, offset, size, handler); if (LLCORE_HTTP_HANDLE_INVALID == handle) { // *TODO: Better error message @@ -959,14 +1005,7 @@ bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id) { LLMeshDecompositionHandler * handler = new LLMeshDecompositionHandler(mesh_id, offset, size); // LL_WARNS("Mesh") << "MESH: Issuing Decomp Request" << LL_ENDL; - LLCore::HttpHandle handle = mHttpRequest->requestGetByteRange(mHttpPolicyClass, - 0, // *TODO: Get better priority value - http_url, - offset, - size, - mHttpOptions, - mHttpHeaders, - handler); + LLCore::HttpHandle handle = getByteRange(http_url, offset, size, handler); if (LLCORE_HTTP_HANDLE_INVALID == handle) { // *TODO: Better error message @@ -1054,14 +1093,7 @@ bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id) { LLMeshPhysicsShapeHandler * handler = new LLMeshPhysicsShapeHandler(mesh_id, offset, size); // LL_WARNS("Mesh") << "MESH: Issuing Physics Shape Request" << LL_ENDL; - LLCore::HttpHandle handle = mHttpRequest->requestGetByteRange(mHttpPolicyClass, - 0, // *TODO: Get better priority value - http_url, - offset, - size, - mHttpOptions, - mHttpHeaders, - handler); + LLCore::HttpHandle handle = getByteRange(http_url, offset, size, handler); if (LLCORE_HTTP_HANDLE_INVALID == handle) { // *TODO: Better error message @@ -1154,14 +1186,7 @@ bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params, U32& c LLMeshHeaderHandler * handler = new LLMeshHeaderHandler(mesh_params); // LL_WARNS("Mesh") << "MESH: Issuing Request" << LL_ENDL; - LLCore::HttpHandle handle = mHttpRequest->requestGetByteRange(mHttpPolicyClass, - 0, // *TODO: Get better priority value - http_url, - 0, - MESH_HEADER_SIZE, - mHttpOptions, - mHttpHeaders, - handler); + LLCore::HttpHandle handle = getByteRange(http_url, 0, MESH_HEADER_SIZE, handler); if (LLCORE_HTTP_HANDLE_INVALID == handle) { // *TODO: Better error message @@ -1241,14 +1266,7 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, { LLMeshLODHandler * handler = new LLMeshLODHandler(mesh_params, lod, offset, size); // LL_WARNS("Mesh") << "MESH: Issuing LOD Request" << LL_ENDL; - LLCore::HttpHandle handle = mHttpRequest->requestGetByteRange(mHttpPolicyClass, - 0, // *TODO: Get better priority value - http_url, - offset, - size, - mHttpOptions, - mHttpHeaders, - handler); + LLCore::HttpHandle handle = getByteRange(http_url, offset, size, handler); if (LLCORE_HTTP_HANDLE_INVALID == handle) { // *TODO: Better error message @@ -2537,9 +2555,14 @@ S32 LLMeshRepository::loadMesh(LLVOVolume* vobj, const LLVolumeParams& mesh_para void LLMeshRepository::notifyLoadedMeshes() { //called from main thread - - LLMeshRepoThread::sMaxConcurrentRequests = gSavedSettings.getU32("MeshMaxConcurrentRequests"); - + // *FIXME: Scaling down the setting by a factor of 4 for now to reflect + // target goal. May want to rename the setting before release. + LLMeshRepoThread::sMaxConcurrentRequests = gSavedSettings.getU32("MeshMaxConcurrentRequests") / 4; + LLMeshRepoThread::sRequestHighWater = llmax(50 * S32(LLMeshRepoThread::sMaxConcurrentRequests), + REQUEST_HIGH_WATER_MIN); + LLMeshRepoThread::sRequestLowWater = llmax(LLMeshRepoThread::sRequestLowWater / 2, + REQUEST_LOW_WATER_MIN); + //clean up completed upload threads for (std::vector::iterator iter = mUploads.begin(); iter != mUploads.end(); ) { @@ -2617,7 +2640,7 @@ void LLMeshRepository::notifyLoadedMeshes() //call completed callbacks on finished decompositions mDecompThread->notifyCompleted(); - if (!mThread->mWaiting) + if (!mThread->mWaiting && mPendingRequests.empty()) { //curl thread is churning, wait for it to go idle return; } @@ -2644,47 +2667,55 @@ void LLMeshRepository::notifyLoadedMeshes() mUploadErrorQ.pop(); } - S32 push_count = LLMeshRepoThread::sMaxConcurrentRequests-(LLMeshRepoThread::sActiveHeaderRequests+LLMeshRepoThread::sActiveLODRequests); - - if (push_count > 0) + S32 active_count = LLMeshRepoThread::sActiveHeaderRequests + LLMeshRepoThread::sActiveLODRequests; + if (active_count < LLMeshRepoThread::sRequestLowWater) { - //calculate "score" for pending requests - - //create score map - std::map score_map; + S32 push_count = LLMeshRepoThread::sRequestHighWater - active_count; - for (U32 i = 0; i < 4; ++i) + if (mPendingRequests.size() > push_count) { - for (mesh_load_map::iterator iter = mLoadingMeshes[i].begin(); iter != mLoadingMeshes[i].end(); ++iter) + // More requests than the high-water limit allows so + // sort and forward the most important. + + //calculate "score" for pending requests + + //create score map + std::map score_map; + + for (U32 i = 0; i < 4; ++i) { - F32 max_score = 0.f; - for (std::set::iterator obj_iter = iter->second.begin(); obj_iter != iter->second.end(); ++obj_iter) + for (mesh_load_map::iterator iter = mLoadingMeshes[i].begin(); iter != mLoadingMeshes[i].end(); ++iter) { - LLViewerObject* object = gObjectList.findObject(*obj_iter); - - if (object) + F32 max_score = 0.f; + for (std::set::iterator obj_iter = iter->second.begin(); obj_iter != iter->second.end(); ++obj_iter) { - LLDrawable* drawable = object->mDrawable; - if (drawable) + LLViewerObject* object = gObjectList.findObject(*obj_iter); + + if (object) { - F32 cur_score = drawable->getRadius()/llmax(drawable->mDistanceWRTCamera, 1.f); - max_score = llmax(max_score, cur_score); + LLDrawable* drawable = object->mDrawable; + if (drawable) + { + F32 cur_score = drawable->getRadius()/llmax(drawable->mDistanceWRTCamera, 1.f); + max_score = llmax(max_score, cur_score); + } } } - } - score_map[iter->first.getSculptID()] = max_score; + score_map[iter->first.getSculptID()] = max_score; + } } - } - //set "score" for pending requests - for (std::vector::iterator iter = mPendingRequests.begin(); iter != mPendingRequests.end(); ++iter) - { - iter->mScore = score_map[iter->mMeshParams.getSculptID()]; - } + //set "score" for pending requests + for (std::vector::iterator iter = mPendingRequests.begin(); iter != mPendingRequests.end(); ++iter) + { + iter->mScore = score_map[iter->mMeshParams.getSculptID()]; + } - //sort by "score" - std::sort(mPendingRequests.begin(), mPendingRequests.end(), LLMeshRepoThread::CompareScoreGreater()); + //sort by "score" + std::partial_sort(mPendingRequests.begin(), mPendingRequests.begin() + push_count, + mPendingRequests.end(), LLMeshRepoThread::CompareScoreGreater()); + } while (!mPendingRequests.empty() && push_count > 0) { diff --git a/indra/newview/llmeshrepository.h b/indra/newview/llmeshrepository.h index 62f81ce9e2..0dca29e7d4 100755 --- a/indra/newview/llmeshrepository.h +++ b/indra/newview/llmeshrepository.h @@ -224,13 +224,14 @@ public: static S32 sActiveHeaderRequests; static S32 sActiveLODRequests; static U32 sMaxConcurrentRequests; + static S32 sRequestLowWater; + static S32 sRequestHighWater; - LLCurlRequest* mCurlRequest; LLMutex* mMutex; LLMutex* mHeaderMutex; LLCondition* mSignal; - bool mWaiting; + volatile bool mWaiting; //map of known mesh headers typedef std::map mesh_header_map; @@ -324,8 +325,11 @@ public: // llcorehttp library interface objects. LLCore::HttpRequest * mHttpRequest; LLCore::HttpOptions * mHttpOptions; + LLCore::HttpOptions * mHttpLargeOptions; LLCore::HttpHeaders * mHttpHeaders; LLCore::HttpRequest::policy_t mHttpPolicyClass; + LLCore::HttpRequest::policy_t mHttpLargePolicyClass; + LLCore::HttpRequest::priority_t mHttpPriority; typedef std::set http_request_set; http_request_set mHttpRequestSet; // Outstanding HTTP requests @@ -373,6 +377,19 @@ public: static void incActiveHeaderRequests(); static void decActiveHeaderRequests(); +private: + // Issue a GET request to a URL with 'Range' header using + // the correct policy class and other attributes. If an invalid + // handle is returned, the request failed and caller must retry + // or dispose of handler. + // + // Threads: Repo thread only + LLCore::HttpHandle getByteRange(const std::string & url, size_t offset, size_t len, + LLCore::HttpHandler * handler); + +private: + U32 mHttpGetCount; + U32 mHttpLargeGetCount; }; class LLMeshUploadThread : public LLThread diff --git a/indra/newview/lltexturefetch.cpp b/indra/newview/lltexturefetch.cpp index be5fde9e2b..d934ef9dc4 100755 --- a/indra/newview/lltexturefetch.cpp +++ b/indra/newview/lltexturefetch.cpp @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2000&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 @@ -2410,7 +2410,7 @@ LLTextureFetch::LLTextureFetch(LLTextureCache* cache, LLImageDecodeThread* image 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(); + mHttpPolicyClass = LLAppViewer::instance()->getAppCoreHttp().getPolicy(LLAppCoreHttp::AP_TEXTURE); } LLTextureFetch::~LLTextureFetch() -- cgit v1.2.3 From 4eef1c8a2e2a8abcc463b9df38b66f025da57589 Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Mon, 15 Apr 2013 16:55:35 +0000 Subject: SH-4106 Significantly upgrade the HttpHeaders interface for SSB. Header container moves from a vector of raw lines to a vector of string pairs representing name/value pairs in headers. For incoming headers, we normalize the name to lowercase and trim it. Values are only left-trimmed. Outgoing headers are left as-is. Simple find() method for the common case, forward and reverse iterators for those few who need to do it themselves. The HTTP status line (e.g. 'HTTP/1.1 200 Ok') is no longer treated as a header to be returned to caller. Unit tests, as usual, were a bear but they absolutely ensured outgoing HTTP header conformance after the change. Grunt work paid off. LLTextureFetch was also given a second options structure for texture fetches. Same as the original but with header return to caller requested. Baked textures should use this, the other 20,000 texture fetch requests should continue to use the original. --- indra/llcorehttp/_httplibcurl.cpp | 17 +- indra/llcorehttp/_httpoprequest.cpp | 12 +- indra/llcorehttp/examples/http_texture_load.cpp | 4 +- indra/llcorehttp/httpheaders.cpp | 141 +++++- indra/llcorehttp/httpheaders.h | 112 ++++- indra/llcorehttp/tests/test_httpheaders.hpp | 345 ++++++++++++++- indra/llcorehttp/tests/test_httprequest.hpp | 563 +++++++++++++++++++----- indra/newview/lltexturefetch.cpp | 15 +- indra/newview/lltexturefetch.h | 3 +- 9 files changed, 1054 insertions(+), 158 deletions(-) (limited to 'indra') diff --git a/indra/llcorehttp/_httplibcurl.cpp b/indra/llcorehttp/_httplibcurl.cpp index 6fe0bfc7d1..d187697e7b 100755 --- a/indra/llcorehttp/_httplibcurl.cpp +++ b/indra/llcorehttp/_httplibcurl.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 @@ -359,12 +359,17 @@ int HttpLibcurl::getActiveCountInClass(int policy_class) const struct curl_slist * append_headers_to_slist(const HttpHeaders * headers, struct curl_slist * slist) { - for (HttpHeaders::container_t::const_iterator it(headers->mHeaders.begin()); - - headers->mHeaders.end() != it; - ++it) + const HttpHeaders::const_iterator end(headers->end()); + for (HttpHeaders::const_iterator it(headers->begin()); end != it; ++it) { - slist = curl_slist_append(slist, (*it).c_str()); + static const char sep[] = ": "; + std::string header; + header.reserve((*it).first.size() + (*it).second.size() + sizeof(sep)); + header.append((*it).first); + header.append(sep); + header.append((*it).second); + + slist = curl_slist_append(slist, header.c_str()); } return slist; } diff --git a/indra/llcorehttp/_httpoprequest.cpp b/indra/llcorehttp/_httpoprequest.cpp index d403b2d249..a4c0a12fdc 100755 --- a/indra/llcorehttp/_httpoprequest.cpp +++ b/indra/llcorehttp/_httpoprequest.cpp @@ -618,7 +618,8 @@ size_t HttpOpRequest::headerCallback(void * data, size_t size, size_t nmemb, voi const size_t hdr_size(size * nmemb); const char * hdr_data(static_cast(data)); // Not null terminated - + bool is_header(true); + if (hdr_size >= status_line_len && ! strncmp(status_line, hdr_data, status_line_len)) { // One of possibly several status lines. Reset what we know and start over @@ -629,8 +630,9 @@ size_t HttpOpRequest::headerCallback(void * data, size_t size, size_t nmemb, voi op->mStatus = HttpStatus(); if (op->mReplyHeaders) { - op->mReplyHeaders->mHeaders.clear(); + op->mReplyHeaders->clear(); } + is_header = false; } // Nothing in here wants a final CR/LF combination. Remove @@ -645,18 +647,18 @@ size_t HttpOpRequest::headerCallback(void * data, size_t size, size_t nmemb, voi } // Save header if caller wants them in the response - if (op->mProcFlags & PF_SAVE_HEADERS) + if (is_header && op->mProcFlags & PF_SAVE_HEADERS) { // Save headers in response if (! op->mReplyHeaders) { op->mReplyHeaders = new HttpHeaders; } - op->mReplyHeaders->mHeaders.push_back(std::string(hdr_data, wanted_hdr_size)); + op->mReplyHeaders->appendNormal(hdr_data, wanted_hdr_size); } // Detect and parse 'Content-Range' headers - if (op->mProcFlags & PF_SCAN_RANGE_HEADER) + if (is_header && op->mProcFlags & PF_SCAN_RANGE_HEADER) { char hdr_buffer[128]; // Enough for a reasonable header size_t frag_size((std::min)(wanted_hdr_size, sizeof(hdr_buffer) - 1)); diff --git a/indra/llcorehttp/examples/http_texture_load.cpp b/indra/llcorehttp/examples/http_texture_load.cpp index 40ad4f047d..909dc5b0cb 100755 --- a/indra/llcorehttp/examples/http_texture_load.cpp +++ b/indra/llcorehttp/examples/http_texture_load.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 @@ -328,7 +328,7 @@ WorkingSet::WorkingSet() mTextures.reserve(30000); mHeaders = new LLCore::HttpHeaders; - mHeaders->mHeaders.push_back("Accept: image/x-j2c"); + mHeaders->append("Accept", "image/x-j2c"); } diff --git a/indra/llcorehttp/httpheaders.cpp b/indra/llcorehttp/httpheaders.cpp index 2832696271..23ebea361c 100755 --- a/indra/llcorehttp/httpheaders.cpp +++ b/indra/llcorehttp/httpheaders.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 @@ -26,6 +26,8 @@ #include "httpheaders.h" +#include "llstring.h" + namespace LLCore { @@ -40,5 +42,142 @@ HttpHeaders::~HttpHeaders() {} +void +HttpHeaders::clear() +{ + mHeaders.clear(); +} + + +void HttpHeaders::append(const std::string & name, const std::string & value) +{ + mHeaders.push_back(value_type(name, value)); +} + + +void HttpHeaders::append(const char * name, const char * value) +{ + mHeaders.push_back(value_type(name, value)); +} + + +void HttpHeaders::appendNormal(const char * header, size_t size) +{ + std::string name; + std::string value; + + int col_pos(0); + for (; col_pos < size; ++col_pos) + { + if (':' == header[col_pos]) + break; + } + + if (col_pos < size) + { + // Looks like a header, split it and normalize. + // Name is everything before the colon, may be zero-length. + name.assign(header, col_pos); + + // Value is everything after the colon, may also be zero-length. + const size_t val_len(size - col_pos - 1); + if (val_len) + { + value.assign(header + col_pos + 1, val_len); + } + + // Clean the strings + LLStringUtil::toLower(name); + LLStringUtil::trim(name); + LLStringUtil::trimHead(value); + } + else + { + // Uncertain what this is, we'll pack it as + // a name without a value. Won't clean as we don't + // know what it is... + name.assign(header, size); + } + + mHeaders.push_back(value_type(name, value)); +} + + +// Find from end to simulate a tradition of using single-valued +// std::map for this in the past. +const std::string * HttpHeaders::find(const char * name) const +{ + const_reverse_iterator iend(rend()); + for (const_reverse_iterator iter(rbegin()); iend != iter; ++iter) + { + if ((*iter).first == name) + { + return &(*iter).second; + } + } + return NULL; +} + + +// Standard Iterators +HttpHeaders::iterator HttpHeaders::begin() +{ + return mHeaders.begin(); +} + + +HttpHeaders::const_iterator HttpHeaders::begin() const +{ + return mHeaders.begin(); +} + + +HttpHeaders::iterator HttpHeaders::end() +{ + return mHeaders.end(); +} + + +HttpHeaders::const_iterator HttpHeaders::end() const +{ + return mHeaders.end(); +} + + +// Standard Reverse Iterators +HttpHeaders::reverse_iterator HttpHeaders::rbegin() +{ + return mHeaders.rbegin(); +} + + +HttpHeaders::const_reverse_iterator HttpHeaders::rbegin() const +{ + return mHeaders.rbegin(); +} + + +HttpHeaders::reverse_iterator HttpHeaders::rend() +{ + return mHeaders.rend(); +} + + +HttpHeaders::const_reverse_iterator HttpHeaders::rend() const +{ + return mHeaders.rend(); +} + + +// Return the raw container to the caller. +// +// To be used FOR UNIT TESTS ONLY. +// +HttpHeaders::container_t & HttpHeaders::getContainerTESTONLY() +{ + return mHeaders; +} + + } // end namespace LLCore diff --git a/indra/llcorehttp/httpheaders.h b/indra/llcorehttp/httpheaders.h index 3449daa3a1..f70cd898f3 100755 --- a/indra/llcorehttp/httpheaders.h +++ b/indra/llcorehttp/httpheaders.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 @@ -43,13 +43,26 @@ namespace LLCore /// caller has asked that headers be returned (not the default /// option). /// -/// @note -/// This is a minimally-functional placeholder at the moment -/// to fill out the class hierarchy. The final class will be -/// something else, probably more pair-oriented. It's also -/// an area where shared values are desirable so refcounting is -/// already specced and a copy-on-write scheme imagined. -/// Expect changes here. +/// Class is mostly a thin wrapper around a vector of pairs +/// of strings. Methods provided are few and intended to +/// reflect actual use patterns. These include: +/// - Clearing the list +/// - Appending a name/value pair to the vector +/// - Processing a raw byte string into a normalized name/value +/// pair and appending the result. +/// - Simple case-sensitive find-last-by-name search +/// - Forward and reverse iterators over all pairs +/// +/// Container is ordered and multi-valued. Headers are +/// written in the order in which they are appended and +/// are stored in the order in which they're received from +/// the wire. The same header may appear two or more times +/// in any container. Searches using the simple find() +/// interface will find only the last occurrence (somewhat +/// simulates the use of std::map). Fuller searches require +/// the use of an iterator. Headers received from the wire +/// are only returned from the last request when redirections +/// are involved. /// /// Threading: Not intrinsically thread-safe. It *is* expected /// that callers will build these objects and then share them @@ -63,6 +76,16 @@ namespace LLCore class HttpHeaders : public LLCoreInt::RefCounted { +public: + typedef std::pair header_t; + typedef std::vector container_t; + typedef container_t::iterator iterator; + typedef container_t::const_iterator const_iterator; + typedef container_t::reverse_iterator reverse_iterator; + typedef container_t::const_reverse_iterator const_reverse_iterator; + typedef container_t::value_type value_type; + typedef container_t::size_type size_type; + public: /// @post In addition to the instance, caller has a refcount /// to the instance. A call to @see release() will destroy @@ -76,7 +99,78 @@ protected: void operator=(const HttpHeaders &); // Not defined public: - typedef std::vector container_t; + // Empty the list of headers. + void clear(); + + // Append a name/value pair supplied as either std::strings + // or NUL-terminated char * to the header list. No normalization + // is performed on the strings. No conformance test is + // performed (names may contain spaces, colons, etc.). + // + void append(const std::string & name, const std::string & value); + void append(const char * name, const char * value); + + // Extract a name/value pair from a raw byte array using + // the first colon character as a separator. Input string + // does not need to be NUL-terminated. Resulting name/value + // pair is appended to the header list. + // + // Normalization is performed on the name/value pair as + // follows: + // - name is lower-cased according to mostly ASCII rules + // - name is left- and right-trimmed of spaces and tabs + // - value is left-trimmed of spaces and tabs + // - either or both of name and value may be zero-length + // + // By convention, headers read from the wire will be normalized + // in this fashion prior to delivery to any HttpHandler code. + // Headers to be written to the wire are left as appended to + // the list. + void appendNormal(const char * header, size_t size); + + // Perform a simple, case-sensitive search of the header list + // returning a pointer to the value of the last matching header + // in the header list. If none is found, a NULL pointer is returned. + // + // Any pointer returned references objects in the container itself + // and will have the same lifetime as this class. If you want + // the value beyond the lifetime of this instance, make a copy. + // + // @arg name C-style string giving the name of a header + // to search. The comparison is case-sensitive + // though list entries may have been normalized + // to lower-case. + // + // @return NULL if the header wasn't found otherwise + // a pointer to a std::string in the container. + // Pointer is valid only for the lifetime of + // the container or until container is modifed. + // + const std::string * find(const char * name) const; + + // Count of headers currently in the list. + size_type size() const + { + return mHeaders.size(); + } + + // Standard std::vector-based forward iterators. + iterator begin(); + const_iterator begin() const; + iterator end(); + const_iterator end() const; + + // Standard std::vector-based reverse iterators. + reverse_iterator rbegin(); + const_reverse_iterator rbegin() const; + reverse_iterator rend(); + const_reverse_iterator rend() const; + +public: + // For unit tests only - not a public API + container_t & getContainerTESTONLY(); + +protected: container_t mHeaders; }; // end class HttpHeaders diff --git a/indra/llcorehttp/tests/test_httpheaders.hpp b/indra/llcorehttp/tests/test_httpheaders.hpp index ce0d19b058..668c36dc66 100755 --- a/indra/llcorehttp/tests/test_httpheaders.hpp +++ b/indra/llcorehttp/tests/test_httpheaders.hpp @@ -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 @@ -36,7 +36,6 @@ using namespace LLCoreInt; - namespace tut { @@ -63,7 +62,7 @@ void HttpHeadersTestObjectType::test<1>() HttpHeaders * headers = new HttpHeaders(); ensure("One ref on construction of HttpHeaders", headers->getRefCount() == 1); ensure("Memory being used", mMemTotal < GetMemTotal()); - ensure("Nothing in headers", 0 == headers->mHeaders.size()); + ensure("Nothing in headers", 0 == headers->size()); // release the implicit reference, causing the object to be released headers->release(); @@ -85,14 +84,340 @@ void HttpHeadersTestObjectType::test<2>() { // Append a few strings - std::string str1("Pragma:"); - headers->mHeaders.push_back(str1); - std::string str2("Accept: application/json"); - headers->mHeaders.push_back(str2); + std::string str1n("Pragma"); + std::string str1v(""); + headers->append(str1n, str1v); + std::string str2n("Accept"); + std::string str2v("application/json"); + headers->append(str2n, str2v); + + ensure("Headers retained", 2 == headers->size()); + HttpHeaders::container_t & c(headers->getContainerTESTONLY()); + + ensure("First name is first name", c[0].first == str1n); + ensure("First value is first value", c[0].second == str1v); + ensure("Second name is second name", c[1].first == str2n); + ensure("Second value is second value", c[1].second == str2v); + } + + // release the implicit reference, causing the object to be released + headers->release(); + + // make sure we didn't leak any memory + ensure(mMemTotal == GetMemTotal()); +} + +template <> template <> +void HttpHeadersTestObjectType::test<3>() +{ + set_test_name("HttpHeaders basic find"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + + // create a new ref counted object with an implicit reference + HttpHeaders * headers = new HttpHeaders(); + + { + // Append a few strings + std::string str1n("Uno"); + std::string str1v("1"); + headers->append(str1n, str1v); + std::string str2n("doS"); + std::string str2v("2-2-2-2"); + headers->append(str2n, str2v); + std::string str3n("TRES"); + std::string str3v("trois gymnopedie"); + headers->append(str3n, str3v); + + ensure("Headers retained", 3 == headers->size()); + + const std::string * result(NULL); + + // Find a header + result = headers->find("TRES"); + ensure("Found the last item", result != NULL); + ensure("Last item is a nice", result != NULL && str3v == *result); + + // appends above are raw and find is case sensitive + result = headers->find("TReS"); + ensure("Last item not found due to case", result == NULL); + + result = headers->find("TRE"); + ensure("Last item not found due to prefixing (1)", result == NULL); + + result = headers->find("TRESS"); + ensure("Last item not found due to prefixing (2)", result == NULL); + } + + // release the implicit reference, causing the object to be released + headers->release(); + + // make sure we didn't leak any memory + ensure(mMemTotal == GetMemTotal()); +} + +template <> template <> +void HttpHeadersTestObjectType::test<4>() +{ + set_test_name("HttpHeaders normalized header entry"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + + // create a new ref counted object with an implicit reference + HttpHeaders * headers = new HttpHeaders(); + + { + static char line1[] = " AcCePT : image/yourfacehere"; + static char line1v[] = "image/yourfacehere"; + headers->appendNormal(line1, sizeof(line1) - 1); + + ensure("First append worked in some fashion", 1 == headers->size()); + + const std::string * result(NULL); + + // Find a header + result = headers->find("accept"); + ensure("Found 'accept'", result != NULL); + ensure("accept value has face", result != NULL && *result == line1v); + + // Left-clean on value + static char line2[] = " next : \t\tlinejunk \t"; + headers->appendNormal(line2, sizeof(line2) - 1); + ensure("Second append worked", 2 == headers->size()); + result = headers->find("next"); + ensure("Found 'next'", result != NULL); + ensure("next value is left-clean", result != NULL && + *result == "linejunk \t"); + + // First value unmolested + result = headers->find("accept"); + ensure("Found 'accept' again", result != NULL); + ensure("accept value has face", result != NULL && *result == line1v); + + // Colons in value are okay + static char line3[] = "FancY-PANTs::plop:-neuf-=vleem="; + static char line3v[] = ":plop:-neuf-=vleem="; + headers->appendNormal(line3, sizeof(line3) - 1); + ensure("Third append worked", 3 == headers->size()); + result = headers->find("fancy-pants"); + ensure("Found 'fancy-pants'", result != NULL); + ensure("fancy-pants value has colons", result != NULL && *result == line3v); + + // Zero-length value + static char line4[] = "all-talk-no-walk:"; + headers->appendNormal(line4, sizeof(line4) - 1); + ensure("Fourth append worked", 4 == headers->size()); + result = headers->find("all-talk-no-walk"); + ensure("Found 'all-talk'", result != NULL); + ensure("al-talk value is zero-length", result != NULL && result->size() == 0); + + // Zero-length name + static char line5[] = ":all-talk-no-walk"; + static char line5v[] = "all-talk-no-walk"; + headers->appendNormal(line5, sizeof(line5) - 1); + ensure("Fifth append worked", 5 == headers->size()); + result = headers->find(""); + ensure("Found no-name", result != NULL); + ensure("no-name value is something", result != NULL && *result == line5v); + + // Lone colon is still something + headers->clear(); + static char line6[] = " :"; + headers->appendNormal(line6, sizeof(line6) - 1); + ensure("Sixth append worked", 1 == headers->size()); + result = headers->find(""); + ensure("Found 2nd no-name", result != NULL); + ensure("2nd no-name value is nothing", result != NULL && result->size() == 0); + + // Line without colons is taken as-is and unstripped in name + static char line7[] = " \toskdgioasdghaosdghoowg28342908tg8902hg0hwedfhqew890v7qh0wdebv78q0wdevbhq>?M>BNM?NZ? \t"; + headers->appendNormal(line7, sizeof(line7) - 1); + ensure("Seventh append worked", 2 == headers->size()); + result = headers->find(line7); + ensure("Found whatsit line", result != NULL); + ensure("Whatsit line has no value", result != NULL && result->size() == 0); + + // Normaling interface heeds the byte count, doesn't look for NUL-terminator + static char line8[] = "binary:ignorestuffontheendofthis"; + headers->appendNormal(line8, 13); + ensure("Eighth append worked", 3 == headers->size()); + result = headers->find("binary"); + ensure("Found 'binary'", result != NULL); + ensure("binary value was limited to 'ignore'", result != NULL && + *result == "ignore"); + + } - ensure("Headers retained", 2 == headers->mHeaders.size()); - ensure("First is first", headers->mHeaders[0] == str1); - ensure("Second is second", headers->mHeaders[1] == str2); + // release the implicit reference, causing the object to be released + headers->release(); + + // make sure we didn't leak any memory + ensure(mMemTotal == GetMemTotal()); +} + +// Verify forward iterator finds everything as expected +template <> template <> +void HttpHeadersTestObjectType::test<5>() +{ + set_test_name("HttpHeaders iterator tests"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + + // create a new ref counted object with an implicit reference + HttpHeaders * headers = new HttpHeaders(); + + HttpHeaders::iterator end(headers->end()), begin(headers->begin()); + ensure("Empty container has equal begin/end const iterators", end == begin); + HttpHeaders::const_iterator cend(headers->end()), cbegin(headers->begin()); + ensure("Empty container has equal rbegin/rend const iterators", cend == cbegin); + + ensure("Empty container has equal begin/end iterators", headers->end() == headers->begin()); + + { + static char line1[] = " AcCePT : image/yourfacehere"; + static char line1v[] = "image/yourfacehere"; + headers->appendNormal(line1, sizeof(line1) - 1); + + static char line2[] = " next : \t\tlinejunk \t"; + static char line2v[] = "linejunk \t"; + headers->appendNormal(line2, sizeof(line2) - 1); + + static char line3[] = "FancY-PANTs::plop:-neuf-=vleem="; + static char line3v[] = ":plop:-neuf-=vleem="; + headers->appendNormal(line3, sizeof(line3) - 1); + + static char line4[] = "all-talk-no-walk:"; + static char line4v[] = ""; + headers->appendNormal(line4, sizeof(line4) - 1); + + static char line5[] = ":all-talk-no-walk"; + static char line5v[] = "all-talk-no-walk"; + headers->appendNormal(line5, sizeof(line5) - 1); + + static char line6[] = " :"; + static char line6v[] = ""; + headers->appendNormal(line6, sizeof(line6) - 1); + + ensure("All entries accounted for", 6 == headers->size()); + + static char * values[] = { + line1v, + line2v, + line3v, + line4v, + line5v, + line6v + }; + + int i(0); + HttpHeaders::const_iterator cend(headers->end()); + for (HttpHeaders::const_iterator it(headers->begin()); + cend != it; + ++it, ++i) + { + std::ostringstream str; + str << "Const Iterator value # " << i << " was " << values[i]; + ensure(str.str(), (*it).second == values[i]); + } + + // Rewind, do non-consts + i = 0; + HttpHeaders::iterator end(headers->end()); + for (HttpHeaders::iterator it(headers->begin()); + end != it; + ++it, ++i) + { + std::ostringstream str; + str << "Const Iterator value # " << i << " was " << values[i]; + ensure(str.str(), (*it).second == values[i]); + } + } + + // release the implicit reference, causing the object to be released + headers->release(); + + // make sure we didn't leak any memory + ensure(mMemTotal == GetMemTotal()); +} + +// Reverse iterators find everything as expected +template <> template <> +void HttpHeadersTestObjectType::test<6>() +{ + set_test_name("HttpHeaders reverse iterator tests"); + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + + // create a new ref counted object with an implicit reference + HttpHeaders * headers = new HttpHeaders(); + + HttpHeaders::reverse_iterator rend(headers->rend()), rbegin(headers->rbegin()); + ensure("Empty container has equal rbegin/rend const iterators", rend == rbegin); + HttpHeaders::const_reverse_iterator crend(headers->rend()), crbegin(headers->rbegin()); + ensure("Empty container has equal rbegin/rend const iterators", crend == crbegin); + + { + static char line1[] = " AcCePT : image/yourfacehere"; + static char line1v[] = "image/yourfacehere"; + headers->appendNormal(line1, sizeof(line1) - 1); + + static char line2[] = " next : \t\tlinejunk \t"; + static char line2v[] = "linejunk \t"; + headers->appendNormal(line2, sizeof(line2) - 1); + + static char line3[] = "FancY-PANTs::plop:-neuf-=vleem="; + static char line3v[] = ":plop:-neuf-=vleem="; + headers->appendNormal(line3, sizeof(line3) - 1); + + static char line4[] = "all-talk-no-walk:"; + static char line4v[] = ""; + headers->appendNormal(line4, sizeof(line4) - 1); + + static char line5[] = ":all-talk-no-walk"; + static char line5v[] = "all-talk-no-walk"; + headers->appendNormal(line5, sizeof(line5) - 1); + + static char line6[] = " :"; + static char line6v[] = ""; + headers->appendNormal(line6, sizeof(line6) - 1); + + ensure("All entries accounted for", 6 == headers->size()); + + static char * values[] = { + line6v, + line5v, + line4v, + line3v, + line2v, + line1v + }; + + int i(0); + HttpHeaders::const_reverse_iterator cend(headers->rend()); + for (HttpHeaders::const_reverse_iterator it(headers->rbegin()); + cend != it; + ++it, ++i) + { + std::ostringstream str; + str << "Const Iterator value # " << i << " was " << values[i]; + ensure(str.str(), (*it).second == values[i]); + } + + // Rewind, do non-consts + i = 0; + HttpHeaders::reverse_iterator end(headers->rend()); + for (HttpHeaders::reverse_iterator it(headers->rbegin()); + end != it; + ++it, ++i) + { + std::ostringstream str; + str << "Iterator value # " << i << " was " << values[i]; + ensure(str.str(), (*it).second == values[i]); + } } // release the implicit reference, causing the object to be released diff --git a/indra/llcorehttp/tests/test_httprequest.hpp b/indra/llcorehttp/tests/test_httprequest.hpp index e5488cf941..27d65f171e 100755 --- a/indra/llcorehttp/tests/test_httprequest.hpp +++ b/indra/llcorehttp/tests/test_httprequest.hpp @@ -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 @@ -60,6 +60,8 @@ void usleep(unsigned long usec); namespace tut { +typedef std::vector > regex_container_t; + struct HttpRequestTestData { // the test objects inherit from this so the member functions and variables @@ -109,11 +111,17 @@ public: for (int i(0); i < mHeadersRequired.size(); ++i) { bool found = false; - for (HttpHeaders::container_t::const_iterator iter(header->mHeaders.begin()); - header->mHeaders.end() != iter; + for (HttpHeaders::const_iterator iter(header->begin()); + header->end() != iter; ++iter) { - if (boost::regex_match(*iter, mHeadersRequired[i])) + // std::cerr << "Header: " << (*iter).first + // << ": " << (*iter).second << std::endl; + + if (boost::regex_match((*iter).first, + mHeadersRequired[i].first) && + boost::regex_match((*iter).second, + mHeadersRequired[i].second)) { found = true; break; @@ -129,11 +137,14 @@ public: { for (int i(0); i < mHeadersDisallowed.size(); ++i) { - for (HttpHeaders::container_t::const_iterator iter(header->mHeaders.begin()); - header->mHeaders.end() != iter; + for (HttpHeaders::const_iterator iter(header->begin()); + header->end() != iter; ++iter) { - if (boost::regex_match(*iter, mHeadersDisallowed[i])) + if (boost::regex_match((*iter).first, + mHeadersDisallowed[i].first) && + boost::regex_match((*iter).second, + mHeadersDisallowed[i].second)) { std::ostringstream str; str << "Disallowed header # " << i << " not found in response"; @@ -159,8 +170,8 @@ public: std::string mName; HttpHandle mExpectHandle; std::string mCheckContentType; - std::vector mHeadersRequired; - std::vector mHeadersDisallowed; + regex_container_t mHeadersRequired; + regex_container_t mHeadersDisallowed; }; typedef test_group HttpRequestTestGroupType; @@ -1335,7 +1346,9 @@ void HttpRequestTestObjectType::test<13>() // Issue a GET that succeeds mStatus = HttpStatus(200); - handler.mHeadersRequired.push_back(boost::regex("\\W*X-LL-Special:.*", boost::regex::icase)); + handler.mHeadersRequired.push_back( + regex_container_t::value_type(boost::regex("X-LL-Special", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); HttpHandle handle = req->requestGetByteRange(HttpRequest::DEFAULT_POLICY_ID, 0U, url_base, @@ -1702,18 +1715,54 @@ void HttpRequestTestObjectType::test<16>() // Issue a GET that *can* connect mStatus = HttpStatus(200); - handler.mHeadersRequired.push_back(boost::regex("X-Reflect-connection:\\s*keep-alive", boost::regex::icase)); - handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept:\\s*\\*/\\*", boost::regex::icase)); - handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept-encoding:\\s*((gzip|deflate),\\s*)+(gzip|deflate)", boost::regex::icase)); // close enough - handler.mHeadersRequired.push_back(boost::regex("X-Reflect-keep-alive:\\s*\\d+", boost::regex::icase)); - handler.mHeadersRequired.push_back(boost::regex("X-Reflect-host:\\s*.*", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-cache-control:.*", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-pragma:.*", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-range:.*", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-transfer-encoding:.*", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-referer:.*", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-content-type:.*", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-content-encoding:.*", boost::regex::icase)); + handler.mHeadersRequired.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-connection", boost::regex::icase), + boost::regex("keep-alive", boost::regex::icase))); + handler.mHeadersRequired.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-accept", boost::regex::icase), + boost::regex("\\*/\\*", boost::regex::icase))); + handler.mHeadersRequired.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-accept-encoding", boost::regex::icase), + boost::regex("((gzip|deflate),\\s*)+(gzip|deflate)", boost::regex::icase))); // close enough + handler.mHeadersRequired.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-keep-alive", boost::regex::icase), + boost::regex("\\d+", boost::regex::icase))); + handler.mHeadersRequired.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-host", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-cache-control", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-pragma", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-range", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-transfer-encoding", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-referer", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-content-type", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-content-encoding", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); HttpHandle handle = req->requestGet(HttpRequest::DEFAULT_POLICY_ID, 0U, url_base + "reflect/", @@ -1735,23 +1784,60 @@ void HttpRequestTestObjectType::test<16>() // Do a texture-style fetch headers = new HttpHeaders; - headers->mHeaders.push_back("Accept: image/x-j2c"); + headers->append("Accept", "image/x-j2c"); mStatus = HttpStatus(200); handler.mHeadersRequired.clear(); handler.mHeadersDisallowed.clear(); - handler.mHeadersRequired.push_back(boost::regex("X-Reflect-connection:\\s*keep-alive", boost::regex::icase)); - handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept:\\s*image/x-j2c", boost::regex::icase)); - handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept-encoding:\\s*((gzip|deflate),\\s*)+(gzip|deflate)", boost::regex::icase)); // close enough - handler.mHeadersRequired.push_back(boost::regex("X-Reflect-keep-alive:\\s*\\d+", boost::regex::icase)); - handler.mHeadersRequired.push_back(boost::regex("X-Reflect-host:\\s*.*", boost::regex::icase)); - handler.mHeadersRequired.push_back(boost::regex("\\W*X-Reflect-range:.*", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-cache-control:.*", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-pragma:.*", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-transfer-encoding:.*", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-referer:.*", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-content-type:.*", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-content-encoding:.*", boost::regex::icase)); + handler.mHeadersRequired.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-connection", boost::regex::icase), + boost::regex("keep-alive", boost::regex::icase))); + handler.mHeadersRequired.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-accept", boost::regex::icase), + boost::regex("image/x-j2c", boost::regex::icase))); + handler.mHeadersRequired.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-accept-encoding", boost::regex::icase), + boost::regex("((gzip|deflate),\\s*)+(gzip|deflate)", boost::regex::icase))); // close enough + handler.mHeadersRequired.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-keep-alive", boost::regex::icase), + boost::regex("\\d+", boost::regex::icase))); + handler.mHeadersRequired.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-host", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); + handler.mHeadersRequired.push_back( + regex_container_t::value_type( + boost::regex("\\W*X-Reflect-range", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); + + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-cache-control", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-pragma", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-transfer-encoding", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-referer", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-content-type", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-content-encoding", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); handle = req->requestGetByteRange(HttpRequest::DEFAULT_POLICY_ID, 0U, url_base + "reflect/", @@ -1892,20 +1978,63 @@ void HttpRequestTestObjectType::test<17>() // Issue a default POST mStatus = HttpStatus(200); - handler.mHeadersRequired.push_back(boost::regex("X-Reflect-connection:\\s*keep-alive", boost::regex::icase)); - handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept:\\s*\\*/\\*", boost::regex::icase)); - handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept-encoding:\\s*((gzip|deflate),\\s*)+(gzip|deflate)", boost::regex::icase)); // close enough - handler.mHeadersRequired.push_back(boost::regex("X-Reflect-keep-alive:\\s*\\d+", boost::regex::icase)); - handler.mHeadersRequired.push_back(boost::regex("X-Reflect-host:\\s*.*", boost::regex::icase)); - handler.mHeadersRequired.push_back(boost::regex("X-Reflect-content-length:\\s*\\d+", boost::regex::icase)); - handler.mHeadersRequired.push_back(boost::regex("X-Reflect-content-type:\\s*application/x-www-form-urlencoded", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-cache-control:.*", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-pragma:.*", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-range:.*", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-referer:.*", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-content-encoding:.*", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-expect:.*", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-transfer-encoding:\\s*.*chunked.*", boost::regex::icase)); + handler.mHeadersRequired.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-connection", boost::regex::icase), + boost::regex("keep-alive", boost::regex::icase))); + handler.mHeadersRequired.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-accept", boost::regex::icase), + boost::regex("\\*/\\*", boost::regex::icase))); + handler.mHeadersRequired.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-accept-encoding", boost::regex::icase), + boost::regex("((gzip|deflate),\\s*)+(gzip|deflate)", boost::regex::icase))); // close enough + handler.mHeadersRequired.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-keep-alive", boost::regex::icase), + boost::regex("\\d+", boost::regex::icase))); + handler.mHeadersRequired.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-host", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); + handler.mHeadersRequired.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-content-length", boost::regex::icase), + boost::regex("\\d+", boost::regex::icase))); + handler.mHeadersRequired.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-content-type", boost::regex::icase), + boost::regex("application/x-www-form-urlencoded", boost::regex::icase))); + + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-cache-control", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-pragma", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-range", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-referer", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-content-encoding", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-expect", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-transfer_encoding", boost::regex::icase), + boost::regex(".*chunked.*", boost::regex::icase))); HttpHandle handle = req->requestPost(HttpRequest::DEFAULT_POLICY_ID, 0U, url_base + "reflect/", @@ -2052,20 +2181,64 @@ void HttpRequestTestObjectType::test<18>() // Issue a default PUT mStatus = HttpStatus(200); - handler.mHeadersRequired.push_back(boost::regex("X-Reflect-connection:\\s*keep-alive", boost::regex::icase)); - handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept:\\s*\\*/\\*", boost::regex::icase)); - handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept-encoding:\\s*((gzip|deflate),\\s*)+(gzip|deflate)", boost::regex::icase)); // close enough - handler.mHeadersRequired.push_back(boost::regex("X-Reflect-keep-alive:\\s*\\d+", boost::regex::icase)); - handler.mHeadersRequired.push_back(boost::regex("X-Reflect-host:\\s*.*", boost::regex::icase)); - handler.mHeadersRequired.push_back(boost::regex("X-Reflect-content-length:\\s*\\d+", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-cache-control:.*", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-pragma:.*", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-range:.*", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-referer:.*", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-content-encoding:.*", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-expect:.*", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-transfer-encoding:\\s*.*chunked.*", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("X-Reflect-content-type:.*", boost::regex::icase)); + handler.mHeadersRequired.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-connection", boost::regex::icase), + boost::regex("keep-alive", boost::regex::icase))); + handler.mHeadersRequired.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-accept", boost::regex::icase), + boost::regex("\\*/\\*", boost::regex::icase))); + handler.mHeadersRequired.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-accept-encoding", boost::regex::icase), + boost::regex("((gzip|deflate),\\s*)+(gzip|deflate)", boost::regex::icase))); // close enough + handler.mHeadersRequired.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-keep-alive", boost::regex::icase), + boost::regex("\\d+", boost::regex::icase))); + handler.mHeadersRequired.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-host", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); + handler.mHeadersRequired.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-content-length", boost::regex::icase), + boost::regex("\\d+", boost::regex::icase))); + + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-cache-control", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-pragma", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-range", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-referer", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-content-encoding", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-expect", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-transfer-encoding", boost::regex::icase), + boost::regex(".*chunked.*", boost::regex::icase))); + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-content-type", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); + HttpHandle handle = req->requestPut(HttpRequest::DEFAULT_POLICY_ID, 0U, url_base + "reflect/", @@ -2206,27 +2379,73 @@ void HttpRequestTestObjectType::test<19>() // headers headers = new HttpHeaders; - headers->mHeaders.push_back("Keep-Alive: 120"); - headers->mHeaders.push_back("Accept-encoding: deflate"); - headers->mHeaders.push_back("Accept: text/plain"); + headers->append("Keep-Alive", "120"); + headers->append("Accept-encoding", "deflate"); + headers->append("Accept", "text/plain"); // Issue a GET with modified headers mStatus = HttpStatus(200); - handler.mHeadersRequired.push_back(boost::regex("X-Reflect-connection:\\s*keep-alive", boost::regex::icase)); - handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept:\\s*text/plain", boost::regex::icase)); - handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept-encoding:\\s*deflate", boost::regex::icase)); // close enough - handler.mHeadersRequired.push_back(boost::regex("X-Reflect-keep-alive:\\s*120", boost::regex::icase)); - handler.mHeadersRequired.push_back(boost::regex("X-Reflect-host:\\s*.*", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("X-Reflect-accept-encoding:\\s*((gzip|deflate),\\s*)+(gzip|deflate)", boost::regex::icase)); // close enough - handler.mHeadersDisallowed.push_back(boost::regex("X-Reflect-keep-alive:\\s*300", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("X-Reflect-accept:\\s*\\*/\\*", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-cache-control:.*", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-pragma:.*", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-range:.*", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-transfer-encoding:.*", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-referer:.*", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-content-type:.*", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-content-encoding:.*", boost::regex::icase)); + handler.mHeadersRequired.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-connection", boost::regex::icase), + boost::regex("keep-alive", boost::regex::icase))); + handler.mHeadersRequired.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-accept", boost::regex::icase), + boost::regex("text/plain", boost::regex::icase))); + handler.mHeadersRequired.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-accept-encoding", boost::regex::icase), + boost::regex("deflate", boost::regex::icase))); // close enough + handler.mHeadersRequired.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-keep-alive", boost::regex::icase), + boost::regex("120", boost::regex::icase))); + handler.mHeadersRequired.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-host", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); + + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-accept-encoding", boost::regex::icase), + boost::regex("((gzip|deflate),\\s*)+(gzip|deflate)", boost::regex::icase))); // close enough + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-keep-alive", boost::regex::icase), + boost::regex("300", boost::regex::icase))); + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-accept", boost::regex::icase), + boost::regex("\\*/\\*", boost::regex::icase))); + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-cache-control", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-pragma", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-range", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-transfer-encoding", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-referer", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-content-type", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-content-encoding", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); HttpHandle handle = req->requestGet(HttpRequest::DEFAULT_POLICY_ID, 0U, url_base + "reflect/", @@ -2359,10 +2578,10 @@ void HttpRequestTestObjectType::test<20>() // headers headers = new HttpHeaders(); - headers->mHeaders.push_back("keep-Alive: 120"); - headers->mHeaders.push_back("Accept: text/html"); - headers->mHeaders.push_back("content-type: application/llsd+xml"); - headers->mHeaders.push_back("cache-control: no-store"); + headers->append("keep-Alive", "120"); + headers->append("Accept", "text/html"); + headers->append("content-type", "application/llsd+xml"); + headers->append("cache-control", "no-store"); // And a buffer array const char * msg("It was the best of times, it was the worst of times."); @@ -2371,23 +2590,76 @@ void HttpRequestTestObjectType::test<20>() // Issue a default POST mStatus = HttpStatus(200); - handler.mHeadersRequired.push_back(boost::regex("X-Reflect-connection:\\s*keep-alive", boost::regex::icase)); - handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept:\\s*text/html", boost::regex::icase)); - handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept-encoding:\\s*((gzip|deflate),\\s*)+(gzip|deflate)", boost::regex::icase)); // close enough - handler.mHeadersRequired.push_back(boost::regex("X-Reflect-keep-alive:\\s*120", boost::regex::icase)); - handler.mHeadersRequired.push_back(boost::regex("X-Reflect-host:\\s*.*", boost::regex::icase)); - handler.mHeadersRequired.push_back(boost::regex("X-Reflect-content-length:\\s*\\d+", boost::regex::icase)); - handler.mHeadersRequired.push_back(boost::regex("X-Reflect-content-type:\\s*application/llsd\\+xml", boost::regex::icase)); - handler.mHeadersRequired.push_back(boost::regex("\\s*X-Reflect-cache-control:\\s*no-store", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("X-Reflect-content-type:\\s*application/x-www-form-urlencoded", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("X-Reflect-accept:\\s*\\*/\\*", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("X-Reflect-keep-alive:\\s*300", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-pragma:.*", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-range:.*", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-referer:.*", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-content-encoding:.*", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-expect:.*", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-transfer-encoding:\\s*.*chunked.*", boost::regex::icase)); + handler.mHeadersRequired.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-connection", boost::regex::icase), + boost::regex("keep-alive", boost::regex::icase))); + handler.mHeadersRequired.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-accept", boost::regex::icase), + boost::regex("text/html", boost::regex::icase))); + handler.mHeadersRequired.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-accept-encoding", boost::regex::icase), + boost::regex("((gzip|deflate),\\s*)+(gzip|deflate)", boost::regex::icase))); // close enough + handler.mHeadersRequired.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-keep-alive", boost::regex::icase), + boost::regex("120", boost::regex::icase))); + handler.mHeadersRequired.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-host", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); + handler.mHeadersRequired.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-content-length", boost::regex::icase), + boost::regex("\\d+", boost::regex::icase))); + handler.mHeadersRequired.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-content-type", boost::regex::icase), + boost::regex("application/llsd\\+xml", boost::regex::icase))); + handler.mHeadersRequired.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-cache-control", boost::regex::icase), + boost::regex("no-store", boost::regex::icase))); + + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-content-type", boost::regex::icase), + boost::regex("application/x-www-form-urlencoded", boost::regex::icase))); + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-accept", boost::regex::icase), + boost::regex("\\*/\\*", boost::regex::icase))); + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-keep-alive", boost::regex::icase), + boost::regex("300", boost::regex::icase))); + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-pragma", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-range", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-referer", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-content-encoding", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-expect", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-transfer-encoding", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); + HttpHandle handle = req->requestPost(HttpRequest::DEFAULT_POLICY_ID, 0U, url_base + "reflect/", @@ -2529,9 +2801,9 @@ void HttpRequestTestObjectType::test<21>() // headers headers = new HttpHeaders; - headers->mHeaders.push_back("content-type: text/plain"); - headers->mHeaders.push_back("content-type: text/html"); - headers->mHeaders.push_back("content-type: application/llsd+xml"); + headers->append("content-type", "text/plain"); + headers->append("content-type", "text/html"); + headers->append("content-type", "application/llsd+xml"); // And a buffer array const char * msg("It was the best of times, it was the worst of times."); @@ -2540,22 +2812,71 @@ void HttpRequestTestObjectType::test<21>() // Issue a default PUT mStatus = HttpStatus(200); - handler.mHeadersRequired.push_back(boost::regex("X-Reflect-connection:\\s*keep-alive", boost::regex::icase)); - handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept:\\s*\\*/\\*", boost::regex::icase)); - handler.mHeadersRequired.push_back(boost::regex("X-Reflect-accept-encoding:\\s*((gzip|deflate),\\s*)+(gzip|deflate)", boost::regex::icase)); // close enough - handler.mHeadersRequired.push_back(boost::regex("X-Reflect-keep-alive:\\s*\\d+", boost::regex::icase)); - handler.mHeadersRequired.push_back(boost::regex("X-Reflect-host:\\s*.*", boost::regex::icase)); - handler.mHeadersRequired.push_back(boost::regex("X-Reflect-content-length:\\s*\\d+", boost::regex::icase)); - handler.mHeadersRequired.push_back(boost::regex("X-Reflect-content-type:\\s*application/llsd\\+xml", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-cache-control:.*", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-pragma:.*", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-range:.*", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-referer:.*", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-content-encoding:.*", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-expect:.*", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("\\s*X-Reflect-transfer-encoding:\\s*.*chunked.*", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("X-Reflect-content-type:\\s*text/plain", boost::regex::icase)); - handler.mHeadersDisallowed.push_back(boost::regex("X-Reflect-content-type:\\s*text/html", boost::regex::icase)); + handler.mHeadersRequired.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-connection", boost::regex::icase), + boost::regex("keep-alive", boost::regex::icase))); + handler.mHeadersRequired.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-accept", boost::regex::icase), + boost::regex("\\*/\\*", boost::regex::icase))); + handler.mHeadersRequired.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-accept-encoding", boost::regex::icase), + boost::regex("((gzip|deflate),\\s*)+(gzip|deflate)", boost::regex::icase))); // close enough + handler.mHeadersRequired.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-keep-alive", boost::regex::icase), + boost::regex("\\d+", boost::regex::icase))); + handler.mHeadersRequired.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-host", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); + handler.mHeadersRequired.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-content-length", boost::regex::icase), + boost::regex("\\d+", boost::regex::icase))); + handler.mHeadersRequired.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-content-type", boost::regex::icase), + boost::regex("application/llsd\\+xml", boost::regex::icase))); + + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-cache-control", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-pragma", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-range", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-referer", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-content-encoding", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-expect", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-transfer-encoding", boost::regex::icase), + boost::regex(".*", boost::regex::icase))); + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-content-type", boost::regex::icase), + boost::regex("text/plain", boost::regex::icase))); + handler.mHeadersDisallowed.push_back( + regex_container_t::value_type( + boost::regex("X-Reflect-content-type", boost::regex::icase), + boost::regex("text/html", boost::regex::icase))); HttpHandle handle = req->requestPut(HttpRequest::DEFAULT_POLICY_ID, 0U, url_base + "reflect/", diff --git a/indra/newview/lltexturefetch.cpp b/indra/newview/lltexturefetch.cpp index d934ef9dc4..a8c8445ee1 100755 --- a/indra/newview/lltexturefetch.cpp +++ b/indra/newview/lltexturefetch.cpp @@ -2376,6 +2376,7 @@ LLTextureFetch::LLTextureFetch(LLTextureCache* cache, LLImageDecodeThread* image mQAMode(qa_mode), mHttpRequest(NULL), mHttpOptions(NULL), + mHttpOptionsWithHeaders(NULL), mHttpHeaders(NULL), mHttpMetricsHeaders(NULL), mHttpPolicyClass(LLCore::HttpRequest::DEFAULT_POLICY_ID), @@ -2406,10 +2407,12 @@ LLTextureFetch::LLTextureFetch(LLTextureCache* cache, LLImageDecodeThread* image mHttpRequest = new LLCore::HttpRequest; mHttpOptions = new LLCore::HttpOptions; + mHttpOptionsWithHeaders = new LLCore::HttpOptions; + mHttpOptionsWithHeaders->setWantHeaders(true); mHttpHeaders = new LLCore::HttpHeaders; - mHttpHeaders->mHeaders.push_back("Accept: image/x-j2c"); + mHttpHeaders->append(HTTP_OUT_HEADER_ACCEPT, HTTP_CONTENT_IMAGE_X_J2C); mHttpMetricsHeaders = new LLCore::HttpHeaders; - mHttpMetricsHeaders->mHeaders.push_back("Content-Type: application/llsd+xml"); + mHttpMetricsHeaders->append(HTTP_OUT_HEADER_CONTENT_TYPE, HTTP_CONTENT_LLSD_XML); mHttpPolicyClass = LLAppViewer::instance()->getAppCoreHttp().getPolicy(LLAppCoreHttp::AP_TEXTURE); } @@ -2430,6 +2433,12 @@ LLTextureFetch::~LLTextureFetch() mHttpOptions = NULL; } + if (mHttpOptionsWithHeaders) + { + mHttpOptionsWithHeaders->release(); + mHttpOptionsWithHeaders = NULL; + } + if (mHttpHeaders) { mHttpHeaders->release(); @@ -4041,7 +4050,7 @@ void LLTextureFetchDebugger::init() if (! mHttpHeaders) { mHttpHeaders = new LLCore::HttpHeaders; - mHttpHeaders->mHeaders.push_back("Accept: image/x-j2c"); + mHttpHeaders->append(HTTP_OUT_HEADER_ACCEPT, HTTP_CONTENT_IMAGE_X_J2C); } } diff --git a/indra/newview/lltexturefetch.h b/indra/newview/lltexturefetch.h index 902a3d7a25..3c79a5a24d 100755 --- a/indra/newview/lltexturefetch.h +++ b/indra/newview/lltexturefetch.h @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2000&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 @@ -351,6 +351,7 @@ private: // LLCurl interfaces used in the past. LLCore::HttpRequest * mHttpRequest; // Ttf LLCore::HttpOptions * mHttpOptions; // Ttf + LLCore::HttpOptions * mHttpOptionsWithHeaders; // Ttf LLCore::HttpHeaders * mHttpHeaders; // Ttf LLCore::HttpHeaders * mHttpMetricsHeaders; // Ttf LLCore::HttpRequest::policy_t mHttpPolicyClass; // T* -- cgit v1.2.3 From e59d59878200d25dc6e94753026d986d2704ab8d Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Wed, 17 Apr 2013 18:30:46 -0400 Subject: SH-4090 Integrating deadman timer with mesh repo downloads. Timer interface violated my design rules and I paid for it with clumsiness and silent errors. Cleaned it up mainly removing the evil default values. Found better integration points in the mesh downloader and it's producing fairly consistent numbers on the MeshTest2 test region (about 5500 downloads, ~90 seconds, +/- 10 seconds). Will review with davep and do an early timer stop on teleport which invalidates a timing sequence. --- indra/llcommon/lldeadmantimer.cpp | 25 +++-- indra/llcommon/lldeadmantimer.h | 59 +++++++---- indra/llcommon/tests/lldeadmantimer_test.cpp | 143 +++++++++++++++------------ indra/newview/llmeshrepository.cpp | 48 ++++++--- indra/newview/llmeshrepository.h | 3 +- 5 files changed, 174 insertions(+), 104 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/lldeadmantimer.cpp b/indra/llcommon/lldeadmantimer.cpp index 3d3f738c06..2a356d857a 100644 --- a/indra/llcommon/lldeadmantimer.cpp +++ b/indra/llcommon/lldeadmantimer.cpp @@ -43,7 +43,7 @@ // true true Not allowed // LLDeadmanTimer::LLDeadmanTimer(F64 horizon) - : mHorizon(U64(llmax(horizon, F64(0.0)) * gClockFrequency)), + : 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)), @@ -53,7 +53,14 @@ LLDeadmanTimer::LLDeadmanTimer(F64 horizon) {} -void LLDeadmanTimer::start(U64 now) +// 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. @@ -72,7 +79,7 @@ void LLDeadmanTimer::start(U64 now) } -void LLDeadmanTimer::stop(U64 now) +void LLDeadmanTimer::stop(time_type now) { if (! mActive) { @@ -81,7 +88,7 @@ void LLDeadmanTimer::stop(U64 now) if (! now) { - now = LLTimer::getCurrentClockCount(); + now = getNow(); } mStopped = now; mActive = false; @@ -89,13 +96,13 @@ void LLDeadmanTimer::stop(U64 now) } -bool LLDeadmanTimer::isExpired(F64 & started, F64 & stopped, U64 & count, U64 now) +bool LLDeadmanTimer::isExpired(time_type now, F64 & started, F64 & stopped, U64 & count) { if (mActive && ! mDone) { if (! now) { - now = LLTimer::getCurrentClockCount(); + now = getNow(); } if (now >= mExpires) @@ -120,7 +127,7 @@ bool LLDeadmanTimer::isExpired(F64 & started, F64 & stopped, U64 & count, U64 no } -void LLDeadmanTimer::ringBell(U64 now) +void LLDeadmanTimer::ringBell(time_type now, unsigned int count) { if (! mActive) { @@ -129,7 +136,7 @@ void LLDeadmanTimer::ringBell(U64 now) if (! now) { - now = LLTimer::getCurrentClockCount(); + now = getNow(); } if (now >= mExpires) @@ -141,7 +148,7 @@ void LLDeadmanTimer::ringBell(U64 now) { mStopped = now; mExpires = now + mHorizon; - ++mCount; + mCount += count; } return; diff --git a/indra/llcommon/lldeadmantimer.h b/indra/llcommon/lldeadmantimer.h index 84023723ab..8643b8cad8 100644 --- a/indra/llcommon/lldeadmantimer.h +++ b/indra/llcommon/lldeadmantimer.h @@ -76,6 +76,16 @@ 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 /// @@ -93,6 +103,18 @@ private: 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. /// @@ -100,8 +122,7 @@ public: /// LLTimer::getCurrentClockCount(). If zero, /// method will lookup current time. /// - void start(U64 now = U64L(0)); - + 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 @@ -111,28 +132,34 @@ public: /// LLTimer::getCurrentClockCount(). If zero, /// method will lookup current time. /// - void stop(U64 now = U64L(0)); - + 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. - /// This count is returned via the @see isExpired() method. + /// 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. /// - void ringBell(U64 now = U64L(0)); + /// @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. /// @@ -146,25 +173,21 @@ public: /// @param count If expired, the number of ringBell() calls /// made prior to expiration. /// - /// @param now Current time as returned by @see - /// LLTimer::getCurrentClockCount(). If zero, - /// method will lookup current time. - /// /// @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(F64 & started, F64 & stopped, U64 & count, U64 now = U64L(0)); + bool isExpired(time_type now, F64 & started, F64 & stopped, U64 & count); protected: - U64 mHorizon; + time_type mHorizon; bool mActive; bool mDone; - U64 mStarted; - U64 mExpires; - U64 mStopped; - U64 mCount; + time_type mStarted; + time_type mExpires; + time_type mStopped; + time_type mCount; }; diff --git a/indra/llcommon/tests/lldeadmantimer_test.cpp b/indra/llcommon/tests/lldeadmantimer_test.cpp index 40e354115b..63cab29e04 100644 --- a/indra/llcommon/tests/lldeadmantimer_test.cpp +++ b/indra/llcommon/tests/lldeadmantimer_test.cpp @@ -34,12 +34,12 @@ // Convert between floating point time deltas and U64 time deltas. // Reflects an implementation detail inside lldeadmantimer.cpp -static U64 float_time_to_u64(F64 delta) +static LLDeadmanTimer::time_type float_time_to_u64(F64 delta) { - return U64(delta * gClockFrequency); + return LLDeadmanTimer::time_type(delta * gClockFrequency); } -static F64 u64_time_to_float(U64 delta) +static F64 u64_time_to_float(LLDeadmanTimer::time_type delta) { return delta * gClockFrequencyInv; } @@ -69,7 +69,7 @@ void deadmantimer_object_t::test<1>() U64 count(U64L(8)); LLDeadmanTimer timer(10.0); - ensure_equals("isExpired() returns false after ctor()", timer.isExpired(started, stopped, count), false); + 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)); @@ -84,7 +84,8 @@ void deadmantimer_object_t::test<2>() 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(started, stopped, count), false); + ensure_equals("isExpired() still returns false with 0.0 time ctor()", + timer.isExpired(0, started, stopped, count), false); } @@ -97,8 +98,9 @@ void deadmantimer_object_t::test<3>() U64 count(U64L(8)); LLDeadmanTimer timer(0.0); - timer.start(); - ensure_equals("isExpired() returns true with 0.0 horizon time", timer.isExpired(started, stopped, count), true); + 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); } @@ -111,14 +113,15 @@ void deadmantimer_object_t::test<4>() U64 count(U64L(8)); LLDeadmanTimer timer(0.0); - timer.start(); - timer.ringBell(LLTimer::getCurrentClockCount() + float_time_to_u64(1000.0)); - ensure_equals("isExpired() returns true with 0.0 horizon time after bell ring", timer.isExpired(started, stopped, count), true); + 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() test - unexpired timer reports unexpired +// start(0) test - unexpired timer reports unexpired template<> template<> void deadmantimer_object_t::test<5>() { @@ -126,8 +129,9 @@ void deadmantimer_object_t::test<5>() U64 count(U64L(8)); LLDeadmanTimer timer(10.0); - timer.start(); - ensure_equals("isExpired() returns false after starting with 10.0 horizon time", timer.isExpired(started, stopped, count), false); + 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)); @@ -146,10 +150,11 @@ void deadmantimer_object_t::test<6>() // the implementation on Windows is zero-based. We wrap around // the backside resulting in a large U64 number. - U64 the_past(LLTimer::getCurrentClockCount()); - U64 now(the_past + float_time_to_u64(5.0)); + 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(started, stopped, count, now), false); + 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)); @@ -168,10 +173,11 @@ void deadmantimer_object_t::test<7>() // the implementation on Windows is zero-based. We wrap around // the backside resulting in a large U64 number. - U64 the_past(LLTimer::getCurrentClockCount()); - U64 now(the_past + float_time_to_u64(20.0)); + 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(started, stopped, count, now), true); + 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); } @@ -188,15 +194,17 @@ void deadmantimer_object_t::test<8>() // the implementation on Windows is zero-based. We wrap around // the backside resulting in a large U64 number. - U64 the_past(LLTimer::getCurrentClockCount()); - U64 now(the_past + float_time_to_u64(20.0)); + 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(started, stopped, count, now), true); + 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(started, stopped, count, now), false); + 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)); @@ -211,40 +219,42 @@ void deadmantimer_object_t::test<9>() U64 count(U64L(8)); LLDeadmanTimer timer(5.0); - U64 now(LLTimer::getCurrentClockCount()); + LLDeadmanTimer::time_type now(LLDeadmanTimer::getNow()); F64 real_start(u64_time_to_float(now)); - timer.start(); + timer.start(0); now += float_time_to_u64(1.0); - timer.ringBell(now); + timer.ringBell(now, 1); now += float_time_to_u64(1.0); - timer.ringBell(now); + timer.ringBell(now, 1); now += float_time_to_u64(1.0); - timer.ringBell(now); + timer.ringBell(now, 1); now += float_time_to_u64(1.0); - timer.ringBell(now); + timer.ringBell(now, 1); now += float_time_to_u64(1.0); - timer.ringBell(now); + timer.ringBell(now, 1); now += float_time_to_u64(1.0); - timer.ringBell(now); + timer.ringBell(now, 1); now += float_time_to_u64(1.0); - timer.ringBell(now); + timer.ringBell(now, 1); now += float_time_to_u64(1.0); - timer.ringBell(now); + timer.ringBell(now, 1); now += float_time_to_u64(1.0); - timer.ringBell(now); + timer.ringBell(now, 1); now += float_time_to_u64(1.0); - timer.ringBell(now); - ensure_equals("t9 - 5.0 horizon timer has not timed out after 10 1-second bell rings", timer.isExpired(started, stopped, count, now), false); + 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(started, stopped, count, now), true); + 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(started, stopped, count, now), false); + ensure_equals("t9 - single read only", timer.isExpired(now, started, stopped, count), false); } @@ -256,40 +266,42 @@ void deadmantimer_object_t::test<10>() U64 count(U64L(8)); LLDeadmanTimer timer(5.0); - U64 now(LLTimer::getCurrentClockCount()); + LLDeadmanTimer::time_type now(LLDeadmanTimer::getNow()); F64 real_start(u64_time_to_float(now)); - timer.start(); + timer.start(0); now += float_time_to_u64(1.0); - timer.ringBell(now); + timer.ringBell(now, 1); now += float_time_to_u64(1.0); - timer.ringBell(now); + timer.ringBell(now, 1); now += float_time_to_u64(1.0); - timer.ringBell(now); + timer.ringBell(now, 1); now += float_time_to_u64(1.0); - timer.ringBell(now); + timer.ringBell(now, 1); now += float_time_to_u64(1.0); - timer.ringBell(now); + timer.ringBell(now, 1); now += float_time_to_u64(1.0); - timer.ringBell(now); + timer.ringBell(now, 1); now += float_time_to_u64(1.0); - timer.ringBell(now); + timer.ringBell(now, 1); now += float_time_to_u64(1.0); - timer.ringBell(now); + timer.ringBell(now, 1); now += float_time_to_u64(1.0); - timer.ringBell(now); + timer.ringBell(now, 1); now += float_time_to_u64(1.0); - timer.ringBell(now); - ensure_equals("t10 - 5.0 horizon timer has not timed out after 10 1-second bell rings", timer.isExpired(started, stopped, count, now), false); + 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(started, stopped, count, now), true); + 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(started, stopped, count, now), false); + ensure_equals("t10 - single read only", timer.isExpired(now, started, stopped, count), false); // Jump forward and restart now += float_time_to_u64(1.0); @@ -298,31 +310,34 @@ void deadmantimer_object_t::test<10>() // Run a modified bell ring sequence now += float_time_to_u64(1.0); - timer.ringBell(now); + timer.ringBell(now, 1); now += float_time_to_u64(1.0); - timer.ringBell(now); + timer.ringBell(now, 1); now += float_time_to_u64(1.0); - timer.ringBell(now); + timer.ringBell(now, 1); now += float_time_to_u64(1.0); - timer.ringBell(now); + timer.ringBell(now, 1); now += float_time_to_u64(1.0); - timer.ringBell(now); + timer.ringBell(now, 1); now += float_time_to_u64(1.0); - timer.ringBell(now); + timer.ringBell(now, 1); now += float_time_to_u64(1.0); - timer.ringBell(now); + timer.ringBell(now, 1); now += float_time_to_u64(1.0); - timer.ringBell(now); - ensure_equals("t10 - 5.0 horizon timer has not timed out after 8 1-second bell rings", timer.isExpired(started, stopped, count, now), false); + 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(started, stopped, count, now), true); + 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(started, stopped, count, now), false); + ensure_equals("t10 - single read only - 2nd start", + timer.isExpired(now, started, stopped, count), false); } diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index 11c5780a30..8f97f3e71a 100755 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -2168,6 +2168,9 @@ void LLMeshHeaderResponder::completedRaw(U32 status, const std::string& reason, if (status < 200 || status > 400) { + // Manage time-to-load metrics for mesh download operations. + LLMeshRepository::metricsProgress(0); + //llwarns // << "Header responder failed with status: " // << status << ": " << reason << llendl; @@ -2358,6 +2361,9 @@ void LLMeshRepository::shutdown() //called in the main thread. S32 LLMeshRepository::update() { + // Conditionally log a mesh metrics event + metricsUpdate(); + if(mUploadWaitList.empty()) { return 0 ; @@ -2377,6 +2383,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; @@ -2680,7 +2689,7 @@ 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. - metricsCheck(); + metricsProgress(0); S32 detail = LLVolumeLODGroup::getVolumeDetailFromScale(volume->getDetail()); @@ -2725,6 +2734,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); @@ -3704,39 +3716,51 @@ bool LLMeshRepository::meshRezEnabled() return false; } +// Threading: main thread only +// static void LLMeshRepository::metricsStart() { - sQuiescentTimer.start(); + sQuiescentTimer.start(0); } +// Threading: main thread only +// static void LLMeshRepository::metricsStop() { - sQuiescentTimer.stop(); + sQuiescentTimer.stop(0); } -void LLMeshRepository::metricsCheck() +// Threading: main thread only +// static +void LLMeshRepository::metricsProgress(unsigned int this_count) { static bool first_start(true); - F64 started, stopped; - U64 count; - if (first_start) { // Let the first request start the timing cycle for login. metricsStart(); first_start = false; } - sQuiescentTimer.ringBell(); - if (sQuiescentTimer.isExpired(started, stopped, count)) + 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["reason"] = "Mesh Download Quiescent"; metrics["scope"] = "Login"; metrics["start"] = started; metrics["stop"] = stopped; - metrics["downloads"] = LLSD::Integer(count); - llinfos << "MetricsMarker" << metrics << llendl; + metrics["downloads"] = LLSD::Integer(total_count); + llinfos << "EventMarker " << metrics << llendl; } } diff --git a/indra/newview/llmeshrepository.h b/indra/newview/llmeshrepository.h index f08feedf81..3cdc66e1f0 100644 --- a/indra/newview/llmeshrepository.h +++ b/indra/newview/llmeshrepository.h @@ -500,7 +500,8 @@ public: // Quiescent timer management, main thread only. static void metricsStart(); static void metricsStop(); - static void metricsCheck(); + static void metricsProgress(unsigned int count); + static void metricsUpdate(); typedef std::map > mesh_load_map; mesh_load_map mLoadingMeshes[4]; -- cgit v1.2.3 From 7911f065cde252d3f1eda4a1577e5d0b3eb94e19 Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Thu, 18 Apr 2013 16:23:15 -0400 Subject: SH-4090 Metrics for mesh load time - cleanup after davep review One of the metrics calls was running in an LLCurl-owned thread doing responder invocation. Deleted that invocation and will do with the other safe ones. Added a boost signal on the TeleportStarted message which is now used to restart the metrics timer. I think I'd like to move the metric blob into a free- standing entity later... --- indra/newview/llmeshrepository.cpp | 47 ++++++++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 7 deletions(-) (limited to 'indra') diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index 8f97f3e71a..0f33128057 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 @@ -52,6 +52,7 @@ #include "llviewercontrol.h" #include "llviewerinventory.h" #include "llviewermenufile.h" +#include "llviewermessage.h" #include "llviewerobjectlist.h" #include "llviewerregion.h" #include "llviewertexturelist.h" @@ -109,7 +110,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", @@ -117,6 +118,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(); + //get the number of bytes resident in memory for given volume U32 get_volume_memory_size(const LLVolume* volume) { @@ -2168,9 +2176,6 @@ void LLMeshHeaderResponder::completedRaw(U32 status, const std::string& reason, if (status < 200 || status > 400) { - // Manage time-to-load metrics for mesh download operations. - LLMeshRepository::metricsProgress(0); - //llwarns // << "Header responder failed with status: " // << status << ": " << reason << llendl; @@ -2310,12 +2315,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; @@ -3735,9 +3754,10 @@ void LLMeshRepository::metricsStop() void LLMeshRepository::metricsProgress(unsigned int this_count) { static bool first_start(true); + if (first_start) { - // Let the first request start the timing cycle for login. + ++metrics_teleport_start_count; metricsStart(); first_start = false; } @@ -3760,7 +3780,20 @@ void LLMeshRepository::metricsUpdate() 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; +} + -- cgit v1.2.3 From 7992564e06ed4977c0f4d13fee4f858f6275542a Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Wed, 1 May 2013 23:19:34 +0000 Subject: SH-4153 Port user and system cpu accounting from example program. The http_texture_load example program has some cpu usage gathering tools that should be generally useful and specifically for the deadman switch. Port these into llcommon into new all-static class LLProcInfo. Add unit test, etc. --- indra/llcommon/CMakeLists.txt | 3 + indra/llcommon/llprocinfo.cpp | 94 ++++++++++++++++++++++++++++++++ indra/llcommon/llprocinfo.h | 68 +++++++++++++++++++++++ indra/llcommon/tests/llprocinfo_test.cpp | 91 +++++++++++++++++++++++++++++++ 4 files changed, 256 insertions(+) create mode 100644 indra/llcommon/llprocinfo.cpp create mode 100644 indra/llcommon/llprocinfo.h create mode 100644 indra/llcommon/tests/llprocinfo_test.cpp (limited to 'indra') diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 7ed4137065..86f18a20d6 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -79,6 +79,7 @@ set(llcommon_SOURCE_FILES llptrto.cpp llprocess.cpp llprocessor.cpp + llprocinfo.cpp llqueuedthread.cpp llrand.cpp llrefcount.cpp @@ -207,6 +208,7 @@ set(llcommon_HEADER_FILES llpriqueuemap.h llprocess.h llprocessor.h + llprocinfo.h llptrskiplist.h llptrskipmap.h llptrto.h @@ -331,6 +333,7 @@ if (LL_TESTS) LL_ADD_INTEGRATION_TEST(llinstancetracker "" "${test_libs}") LL_ADD_INTEGRATION_TEST(lllazy "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llprocessor "" "${test_libs}") + LL_ADD_INTEGRATION_TEST(llprocinfo "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llrand "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llsdserialize "" "${test_libs}") LL_ADD_INTEGRATION_TEST(llsingleton "" "${test_libs}") diff --git a/indra/llcommon/llprocinfo.cpp b/indra/llcommon/llprocinfo.cpp new file mode 100644 index 0000000000..c00f979b0b --- /dev/null +++ b/indra/llcommon/llprocinfo.cpp @@ -0,0 +1,94 @@ +/** +* @file llprocinfo.cpp +* @brief Process, cpu and resource usage information APIs. +* @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 "llprocinfo.h" + +#if LL_WINDOWS + +#define PSAPI_VERSION 1 +#include "windows.h" +#include "psapi.h" + +#elif LL_DARWIN + +#include +#include + +#else + +#include +#include + +#endif // LL_WINDOWS/LL_DARWIN + + +// static +void LLProcInfo::getCPUUsage(time_type & user_time, time_type & system_time) +{ +#if LL_WINDOWS + + HANDLE self(GetCurrentProcess()); // Does not have to be closed + FILETIME ft_dummy, ft_system, ft_user; + + GetProcessTimes(self, &ft_dummy, &ft_dummy, &ft_system, &ft_user); + ULARGE_INTEGER uli; + uli.u.LowPart = ft_system.dwLowDateTime; + uli.u.HighPart = ft_system.dwHighDateTime; + system_time = uli.QuadPart / U64L(10); // Convert to uS + uli.u.LowPart = ft_user.dwLowDateTime; + uli.u.HighPart = ft_user.dwHighDateTime; + user_time = uli.QuadPart / U64L(10); + +#elif LL_DARWIN + + struct rusage usage; + + if (getrusage(RUSAGE_SELF, &usage)) + { + user_time = system_time = time_type(0U); + return; + } + user_time = U64(usage.ru_utime.tv_sec) * U64L(1000000) + usage.ru_utime.tv_usec; + system_time = U64(usage.ru_stime.tv_sec) * U64L(1000000) + usage.ru_stime.tv_usec; + +#else // Linux + + struct rusage usage; + + if (getrusage(RUSAGE_SELF, &usage)) + { + user_time = system_time = time_type(0U); + return; + } + user_time = U64(usage.ru_utime.tv_sec) * U64L(1000000) + usage.ru_utime.tv_usec; + system_time = U64(usage.ru_stime.tv_sec) * U64L(1000000) + usage.ru_stime.tv_usec; + +#endif // LL_WINDOWS/LL_DARWIN/Linux +} + + diff --git a/indra/llcommon/llprocinfo.h b/indra/llcommon/llprocinfo.h new file mode 100644 index 0000000000..e78bcf490a --- /dev/null +++ b/indra/llcommon/llprocinfo.h @@ -0,0 +1,68 @@ +/** +* @file llprocinfo.h +* @brief Interface to process/cpu/resource information services. +* @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_PROCINFO_H +#define LL_PROCINFO_H + + +#include "linden_common.h" + +/// @file llprocinfo.h +/// +/// Right now, this is really a namespace disguised as a class. +/// It wraps some types and functions to return information about +/// process resource consumption in a non-OS-specific manner. +/// +/// Threading: No instances so that's thread-safe. Implementations +/// of static functions should be thread-safe, they mostly involve +/// direct syscall invocations. +/// +/// Allocation: Not instantiatable. + +class LL_COMMON_API LLProcInfo +{ +public: + /// Public types + + typedef U64 time_type; /// Relative microseconds + +private: + LLProcInfo(); // Not defined + ~LLProcInfo(); // Not defined + LLProcInfo(const LLProcInfo &); // Not defined + void operator=(const LLProcInfo &); // Not defined + +public: + /// Get accumulated system and user CPU time in + /// microseconds. Syscalls involved in every invocation. + /// + /// Threading: expected to be safe. + static void getCPUUsage(time_type & user_time, time_type & system_time); +}; + + +#endif // LL_PROCINFO_H diff --git a/indra/llcommon/tests/llprocinfo_test.cpp b/indra/llcommon/tests/llprocinfo_test.cpp new file mode 100644 index 0000000000..0a4210dc07 --- /dev/null +++ b/indra/llcommon/tests/llprocinfo_test.cpp @@ -0,0 +1,91 @@ +/** + * @file llprocinfo_test.cpp + * @brief Tests for the LLProcInfo 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 "../llprocinfo.h" + +#include "../test/lltut.h" +#include "../lltimer.h" + + +static const LLProcInfo::time_type bad_user(289375U), bad_system(275U); + + +namespace tut +{ + +struct procinfo_test +{ + procinfo_test() + { + } +}; + +typedef test_group procinfo_group_t; +typedef procinfo_group_t::object procinfo_object_t; +tut::procinfo_group_t procinfo_instance("LLProcInfo"); + + +// Basic invocation works +template<> template<> +void procinfo_object_t::test<1>() +{ + LLProcInfo::time_type user(bad_user), system(bad_system); + + set_test_name("getCPUUsage() basic function"); + + LLProcInfo::getCPUUsage(user, system); + + ensure_not_equals("getCPUUsage() writes to its user argument", user, bad_user); + ensure_not_equals("getCPUUsage() writes to its system argument", system, bad_system); +} + + +// Time increases +template<> template<> +void procinfo_object_t::test<2>() +{ + LLProcInfo::time_type user(bad_user), system(bad_system); + LLProcInfo::time_type user2(bad_user), system2(bad_system); + + set_test_name("getCPUUsage() increases over time"); + + LLProcInfo::getCPUUsage(user, system); + + for (int i(0); i < 100000; ++i) + { + ms_sleep(0); + } + + LLProcInfo::getCPUUsage(user2, system2); + + ensure_equals("getCPUUsage() user value increases over time", user2 > user, true); + ensure_equals("getCPUUsage() system value increases over time", system2 > system, true); +} + + +} // end namespace tut -- cgit v1.2.3 From b833f574bc20d4371b78f9c6b02bafe460a2e359 Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Thu, 2 May 2013 15:43:58 +0000 Subject: SH-4153 Port user and system cpu accounting from example program. Windows resolution isn't good enough for a strictly increasing time test in the unit tests. --- indra/llcommon/tests/llprocinfo_test.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/tests/llprocinfo_test.cpp b/indra/llcommon/tests/llprocinfo_test.cpp index 0a4210dc07..12d5a695ee 100644 --- a/indra/llcommon/tests/llprocinfo_test.cpp +++ b/indra/llcommon/tests/llprocinfo_test.cpp @@ -83,8 +83,8 @@ void procinfo_object_t::test<2>() LLProcInfo::getCPUUsage(user2, system2); - ensure_equals("getCPUUsage() user value increases over time", user2 > user, true); - ensure_equals("getCPUUsage() system value increases over time", system2 > system, true); + ensure_equals("getCPUUsage() user value doesn't decrease over time", user2 >= user, true); + ensure_equals("getCPUUsage() system value doesn't decrease over time", system2 >= system, true); } -- cgit v1.2.3 From 11cca95187f01f594172ca950315dcd8d99dc2c3 Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Thu, 2 May 2013 21:59:53 +0000 Subject: SH-4161 Integrate cpu metrics into LLDeadmanTimer and then metrics viewer Integrated as a ctor-time option to LLDeadmanTimer and have mesh use this mode for the stats I'm gathering. --- indra/llcommon/lldeadmantimer.cpp | 36 +- indra/llcommon/lldeadmantimer.h | 38 +- indra/llcommon/tests/lldeadmantimer_test.cpp | 701 +++++++++++++++++++-------- indra/newview/llmeshrepository.cpp | 2 +- 4 files changed, 562 insertions(+), 215 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/lldeadmantimer.cpp b/indra/llcommon/lldeadmantimer.cpp index 2a356d857a..2ba757d2af 100644 --- a/indra/llcommon/lldeadmantimer.cpp +++ b/indra/llcommon/lldeadmantimer.cpp @@ -42,14 +42,19 @@ // false true Timer finished, result can be read once // true true Not allowed // -LLDeadmanTimer::LLDeadmanTimer(F64 horizon) +LLDeadmanTimer::LLDeadmanTimer(F64 horizon, bool inc_cpu) : 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)) + mCount(U64L(0)), + mIncCPU(inc_cpu), + mUStartCPU(LLProcInfo::time_type(U64L(0))), + mUEndCPU(LLProcInfo::time_type(U64L(0))), + mSStartCPU(LLProcInfo::time_type(U64L(0))), + mSEndCPU(LLProcInfo::time_type(U64L(0))) {} @@ -76,6 +81,10 @@ void LLDeadmanTimer::start(time_type now) mExpires = now + mHorizon; mStopped = now; mCount = U64L(0); + if (mIncCPU) + { + LLProcInfo::getCPUUsage(mUStartCPU, mSStartCPU); + } } @@ -93,9 +102,26 @@ void LLDeadmanTimer::stop(time_type now) mStopped = now; mActive = false; mDone = true; + if (mIncCPU) + { + LLProcInfo::getCPUUsage(mUEndCPU, mSEndCPU); + } } +bool LLDeadmanTimer::isExpired(time_type now, F64 & started, F64 & stopped, U64 & count, + LLProcInfo::time_type & user_cpu, LLProcInfo::time_type & sys_cpu) +{ + const bool status(isExpired(now, started, stopped, count)); + if (status) + { + user_cpu = mUEndCPU - mUStartCPU; + sys_cpu = mSEndCPU - mSStartCPU; + } + return status; +} + + bool LLDeadmanTimer::isExpired(time_type now, F64 & started, F64 & stopped, U64 & count) { if (mActive && ! mDone) @@ -141,14 +167,20 @@ void LLDeadmanTimer::ringBell(time_type now, unsigned int count) if (now >= mExpires) { + // Timer has expired, this event will be dropped mActive = false; mDone = true; } else { + // Timer renewed, keep going mStopped = now; mExpires = now + mHorizon; mCount += count; + if (mIncCPU) + { + LLProcInfo::getCPUUsage(mUEndCPU, mSEndCPU); + } } return; diff --git a/indra/llcommon/lldeadmantimer.h b/indra/llcommon/lldeadmantimer.h index 8643b8cad8..a6022852b9 100644 --- a/indra/llcommon/lldeadmantimer.h +++ b/indra/llcommon/lldeadmantimer.h @@ -32,6 +32,7 @@ #include "linden_common.h" #include "lltimer.h" +#include "llprocinfo.h" /// @file lldeadmantimer.h @@ -93,7 +94,11 @@ public: /// call at which point the timer will consider itself /// expired. /// - LLDeadmanTimer(F64 horizon); + /// @param inc_cpu If true, gather system and user cpu stats while + /// running the timer. This does require more syscalls + /// during updates. If false, cpu usage data isn't + /// collected and will be zero if queried. + LLDeadmanTimer(F64 horizon, bool inc_cpu); ~LLDeadmanTimer() {} @@ -173,21 +178,38 @@ public: /// @param count If expired, the number of ringBell() calls /// made prior to expiration. /// + /// @param user_cpu Amount of CPU spent in user mode by the process + /// during the event. Value in microseconds and will + /// read zero if not enabled by the constructor. + /// + /// @param sys_cpu Amount of CPU spent in system mode by the process. + /// /// @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, + LLProcInfo::time_type & user_cpu, LLProcInfo::time_type & sys_cpu); + + /// Identical to the six-arugment form except is does without the + /// CPU time return if the caller isn't interested in it. 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; + time_type mHorizon; + bool mActive; + bool mDone; + time_type mStarted; + time_type mExpires; + time_type mStopped; + time_type mCount; + + const bool mIncCPU; // Include CPU metrics in timer + LLProcInfo::time_type mUStartCPU; + LLProcInfo::time_type mUEndCPU; + LLProcInfo::time_type mSStartCPU; + LLProcInfo::time_type mSEndCPU; }; diff --git a/indra/llcommon/tests/lldeadmantimer_test.cpp b/indra/llcommon/tests/lldeadmantimer_test.cpp index 63cab29e04..a4ec76a016 100644 --- a/indra/llcommon/tests/lldeadmantimer_test.cpp +++ b/indra/llcommon/tests/lldeadmantimer_test.cpp @@ -28,6 +28,7 @@ #include "../lldeadmantimer.h" #include "../llsd.h" +#include "../lltimer.h" #include "../test/lltut.h" @@ -65,14 +66,32 @@ tut::deadmantimer_group_t deadmantimer_instance("LLDeadmanTimer"); 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)); + { + // Without cpu metrics + F64 started(42.0), stopped(97.0); + U64 count(U64L(8)); + LLDeadmanTimer timer(10.0, false); + + ensure_equals("WOCM isExpired() returns false after ctor()", timer.isExpired(0, started, stopped, count), false); + ensure_approximately_equals("WOCM t1 - isExpired() does not modify started", started, F64(42.0), 2); + ensure_approximately_equals("WOCM t1 - isExpired() does not modify stopped", stopped, F64(97.0), 2); + ensure_equals("WOCM t1 - isExpired() does not modify count", count, U64L(8)); + } + + { + // With cpu metrics + F64 started(42.0), stopped(97.0); + U64 count(U64L(8)); + LLProcInfo::time_type user_cpu(29000), sys_cpu(57000); + LLDeadmanTimer timer(10.0, true); + + ensure_equals("WCM isExpired() returns false after ctor()", timer.isExpired(0, started, stopped, count, user_cpu, sys_cpu), false); + ensure_approximately_equals("WCM t1 - isExpired() does not modify started", started, F64(42.0), 2); + ensure_approximately_equals("WCM t1 - isExpired() does not modify stopped", stopped, F64(97.0), 2); + ensure_equals("WCM t1 - isExpired() does not modify count", count, U64L(8)); + ensure_equals("WCM t1 - isExpired() does not modify user_cpu", user_cpu, LLProcInfo::time_type(29000)); + ensure_equals("WCM t1 - isExpired() does not modify sys_cpu", sys_cpu, LLProcInfo::time_type(57000)); + } } @@ -80,12 +99,26 @@ void deadmantimer_object_t::test<1>() 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); + { + // Without cpu metrics + F64 started(42.0), stopped(97.0); + U64 count(U64L(8)); + LLDeadmanTimer timer(0.0, false); // Zero is pre-expired + + ensure_equals("WOCM isExpired() still returns false with 0.0 time ctor()", + timer.isExpired(0, started, stopped, count), false); + } + + { + // With cpu metrics + F64 started(42.0), stopped(97.0); + U64 count(U64L(8)); + LLProcInfo::time_type user_cpu(29000), sys_cpu(57000); + LLDeadmanTimer timer(0.0, true); // Zero is pre-expired + + ensure_equals("WCM isExpired() still returns false with 0.0 time ctor()", + timer.isExpired(0, started, stopped, count, user_cpu, sys_cpu), false); + } } @@ -94,14 +127,29 @@ void deadmantimer_object_t::test<2>() 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); + { + // Without cpu metrics + F64 started(42.0), stopped(97.0); + U64 count(U64L(8)); + LLDeadmanTimer timer(0.0, false); + + timer.start(0); + ensure_equals("WOCM isExpired() returns true with 0.0 horizon time", + timer.isExpired(0, started, stopped, count), true); + ensure_approximately_equals("WOCM expired timer with no bell ringing has stopped == started", started, stopped, 8); + } + { + // With cpu metrics + F64 started(42.0), stopped(97.0); + U64 count(U64L(8)); + LLProcInfo::time_type user_cpu(29000), sys_cpu(57000); + LLDeadmanTimer timer(0.0, true); + + timer.start(0); + ensure_equals("WCM isExpired() returns true with 0.0 horizon time", + timer.isExpired(0, started, stopped, count, user_cpu, sys_cpu), true); + ensure_approximately_equals("WCM expired timer with no bell ringing has stopped == started", started, stopped, 8); + } } @@ -109,15 +157,31 @@ void deadmantimer_object_t::test<3>() template<> template<> void deadmantimer_object_t::test<4>() { - F64 started(42.0), stopped(97.0); - U64 count(U64L(8)); - LLDeadmanTimer timer(0.0); + { + // Without cpu metrics + F64 started(42.0), stopped(97.0); + U64 count(U64L(8)); + LLDeadmanTimer timer(0.0, false); + + timer.start(0); + timer.ringBell(LLDeadmanTimer::getNow() + float_time_to_u64(1000.0), 1); + ensure_equals("WOCM isExpired() returns true with 0.0 horizon time after bell ring", + timer.isExpired(0, started, stopped, count), true); + ensure_approximately_equals("WOCM ringBell has no impact on expired timer leaving stopped == started", started, stopped, 8); + } + { + // With cpu metrics + F64 started(42.0), stopped(97.0); + U64 count(U64L(8)); + LLProcInfo::time_type user_cpu(29000), sys_cpu(57000); + LLDeadmanTimer timer(0.0, true); - 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); + timer.start(0); + timer.ringBell(LLDeadmanTimer::getNow() + float_time_to_u64(1000.0), 1); + ensure_equals("WCM isExpired() returns true with 0.0 horizon time after bell ring", + timer.isExpired(0, started, stopped, count, user_cpu, sys_cpu), true); + ensure_approximately_equals("WCM ringBell has no impact on expired timer leaving stopped == started", started, stopped, 8); + } } @@ -125,16 +189,35 @@ void deadmantimer_object_t::test<4>() template<> template<> void deadmantimer_object_t::test<5>() { - F64 started(42.0), stopped(97.0); - U64 count(U64L(8)); - LLDeadmanTimer timer(10.0); + { + // Without cpu metrics + F64 started(42.0), stopped(97.0); + U64 count(U64L(8)); + LLDeadmanTimer timer(10.0, false); + + timer.start(0); + ensure_equals("WOCM isExpired() returns false after starting with 10.0 horizon time", + timer.isExpired(0, started, stopped, count), false); + ensure_approximately_equals("WOCM t5 - isExpired() does not modify started", started, F64(42.0), 2); + ensure_approximately_equals("WOCM t5 - isExpired() does not modify stopped", stopped, F64(97.0), 2); + ensure_equals("WOCM t5 - isExpired() does not modify count", count, U64L(8)); + } + { + // With cpu metrics + F64 started(42.0), stopped(97.0); + U64 count(U64L(8)); + LLProcInfo::time_type user_cpu(29000), sys_cpu(57000); + LLDeadmanTimer timer(10.0, true); - 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)); + timer.start(0); + ensure_equals("WCM isExpired() returns false after starting with 10.0 horizon time", + timer.isExpired(0, started, stopped, count, user_cpu, sys_cpu), false); + ensure_approximately_equals("WCM t5 - isExpired() does not modify started", started, F64(42.0), 2); + ensure_approximately_equals("WCM t5 - isExpired() does not modify stopped", stopped, F64(97.0), 2); + ensure_equals("WCM t5 - isExpired() does not modify count", count, U64L(8)); + ensure_equals("WCM t5 - isExpired() does not modify user_cpu", user_cpu, LLProcInfo::time_type(29000)); + ensure_equals("WCM t5 - isExpired() does not modify sys_cpu", sys_cpu, LLProcInfo::time_type(57000)); + } } @@ -142,22 +225,47 @@ void deadmantimer_object_t::test<5>() 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. + { + // Without cpu metrics + F64 started(42.0), stopped(97.0); + U64 count(U64L(8)); + LLDeadmanTimer timer(10.0, false); + + // 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("WOCM t6 - isExpired() returns false with 10.0 horizon time starting 5.0 in past", + timer.isExpired(now, started, stopped, count), false); + ensure_approximately_equals("WOCM t6 - isExpired() does not modify started", started, F64(42.0), 2); + ensure_approximately_equals("WOCM t6 - isExpired() does not modify stopped", stopped, F64(97.0), 2); + ensure_equals("WOCM t6 - isExpired() does not modify count", count, U64L(8)); + } + { + // With cpu metrics + F64 started(42.0), stopped(97.0); + U64 count(U64L(8)); + LLProcInfo::time_type user_cpu(29000), sys_cpu(57000); + LLDeadmanTimer timer(10.0, true); + + // 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)); + 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("WCM t6 - isExpired() returns false with 10.0 horizon time starting 5.0 in past", + timer.isExpired(now, started, stopped, count, user_cpu, sys_cpu), false); + ensure_approximately_equals("WCM t6 - isExpired() does not modify started", started, F64(42.0), 2); + ensure_approximately_equals("WCM t6 - isExpired() does not modify stopped", stopped, F64(97.0), 2); + ensure_equals("t6 - isExpired() does not modify count", count, U64L(8)); + ensure_equals("WCM t6 - isExpired() does not modify user_cpu", user_cpu, LLProcInfo::time_type(29000)); + ensure_equals("WCM t6 - isExpired() does not modify sys_cpu", sys_cpu, LLProcInfo::time_type(57000)); + } } @@ -165,20 +273,41 @@ void deadmantimer_object_t::test<6>() 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. + { + // Without cpu metrics + F64 started(42.0), stopped(97.0); + U64 count(U64L(8)); + LLDeadmanTimer timer(10.0, false); + + // 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("WOCM t7 - isExpired() returns true with 10.0 horizon time starting 20.0 in past", + timer.isExpired(now,started, stopped, count), true); + ensure_approximately_equals("WOCM t7 - starting before horizon still gives equal started / stopped", started, stopped, 8); + } + { + // With cpu metrics + F64 started(42.0), stopped(97.0); + U64 count(U64L(8)); + LLDeadmanTimer timer(10.0, true); + LLProcInfo::time_type user_cpu(29000), sys_cpu(57000); + + // 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); + 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("WCM t7 - isExpired() returns true with 10.0 horizon time starting 20.0 in past", + timer.isExpired(now,started, stopped, count, user_cpu, sys_cpu), true); + ensure_approximately_equals("WOCM t7 - starting before horizon still gives equal started / stopped", started, stopped, 8); + } } @@ -186,28 +315,61 @@ void deadmantimer_object_t::test<7>() 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. + { + // Without cpu metrics + F64 started(42.0), stopped(97.0); + U64 count(U64L(8)); + LLDeadmanTimer timer(10.0, false); + + // 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)); + 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("WOCM 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("WOCM t8 - second isExpired() returns false after true", + timer.isExpired(now, started, stopped, count), false); + ensure_approximately_equals("WOCM t8 - 2nd isExpired() does not modify started", started, F64(42.0), 2); + ensure_approximately_equals("WOCM t8 - 2nd isExpired() does not modify stopped", stopped, F64(97.0), 2); + ensure_equals("WOCM t8 - 2nd isExpired() does not modify count", count, U64L(8)); + } + { + // With cpu metrics + F64 started(42.0), stopped(97.0); + U64 count(U64L(8)); + LLDeadmanTimer timer(10.0, true); + LLProcInfo::time_type user_cpu(29000), sys_cpu(57000); + + // 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("WCM t8 - isExpired() returns true with 10.0 horizon time starting 20.0 in past", + timer.isExpired(now, started, stopped, count, user_cpu, sys_cpu), true); + + started = 42.0; + stopped = 97.0; + count = U64L(8); + user_cpu = 29000; + sys_cpu = 57000; + ensure_equals("WCM t8 - second isExpired() returns false after true", + timer.isExpired(now, started, stopped, count), false); + ensure_approximately_equals("WCM t8 - 2nd isExpired() does not modify started", started, F64(42.0), 2); + ensure_approximately_equals("WCM t8 - 2nd isExpired() does not modify stopped", stopped, F64(97.0), 2); + ensure_equals("WCM t8 - 2nd isExpired() does not modify count", count, U64L(8)); + ensure_equals("WCM t8 - 2nd isExpired() does not modify user_cpu", user_cpu, LLProcInfo::time_type(29000)); + ensure_equals("WCM t8 - 2nd isExpired() does not modify sys_cpu", sys_cpu, LLProcInfo::time_type(57000)); + } } @@ -215,46 +377,93 @@ void deadmantimer_object_t::test<8>() 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); + { + // Without cpu metrics + F64 started(42.0), stopped(97.0); + U64 count(U64L(8)); + LLDeadmanTimer timer(5.0, false); + + 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("WOCM 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("WOCM t9 - 5.0 horizon timer expires on 10-second jump", + timer.isExpired(now, started, stopped, count), true); + ensure_approximately_equals("WOCM t9 - started matches start() time", started, real_start, 4); + ensure_approximately_equals("WOCM t9 - stopped matches last ringBell() time", stopped, last_good_ring, 4); + ensure_equals("WOCM t9 - 10 good ringBell()s", count, U64L(10)); + ensure_equals("WOCM t9 - single read only", timer.isExpired(now, started, stopped, count), false); + } + { + // With cpu metrics + F64 started(42.0), stopped(97.0); + U64 count(U64L(8)); + LLDeadmanTimer timer(5.0, true); + LLProcInfo::time_type user_cpu(29000), sys_cpu(57000); + + 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("WCM t9 - 5.0 horizon timer has not timed out after 10 1-second bell rings", + timer.isExpired(now, started, stopped, count, user_cpu, sys_cpu), false); + F64 last_good_ring(u64_time_to_float(now)); + + // Jump forward and expire + now += float_time_to_u64(10.0); + ensure_equals("WCM t9 - 5.0 horizon timer expires on 10-second jump", + timer.isExpired(now, started, stopped, count, user_cpu, sys_cpu), true); + ensure_approximately_equals("WCM t9 - started matches start() time", started, real_start, 4); + ensure_approximately_equals("WCM t9 - stopped matches last ringBell() time", stopped, last_good_ring, 4); + ensure_equals("WCM t9 - 10 good ringBell()s", count, U64L(10)); + ensure_equals("WCM t9 - single read only", timer.isExpired(now, started, stopped, count, user_cpu, sys_cpu), false); + } } @@ -262,83 +471,167 @@ void deadmantimer_object_t::test<9>() 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); + { + // Without cpu metrics + F64 started(42.0), stopped(97.0); + U64 count(U64L(8)); + LLDeadmanTimer timer(5.0, false); + + 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("WOCM 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("WOCM t10 - 5.0 horizon timer expires on 10-second jump", + timer.isExpired(now, started, stopped, count), true); + ensure_approximately_equals("WOCM t10 - started matches start() time", started, real_start, 4); + ensure_approximately_equals("WOCM t10 - stopped matches last ringBell() time", stopped, last_good_ring, 4); + ensure_equals("WOCM t10 - 10 good ringBell()s", count, U64L(10)); + ensure_equals("WOCM 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("WOCM 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("WOCM t10 - 5.0 horizon timer expires on 8-second jump", + timer.isExpired(now, started, stopped, count), true); + ensure_approximately_equals("WOCM t10 - 2nd started matches start() time", started, real_start, 4); + ensure_approximately_equals("WOCM t10 - 2nd stopped matches last ringBell() time", stopped, last_good_ring, 4); + ensure_equals("WOCM t10 - 8 good ringBell()s", count, U64L(8)); + ensure_equals("WOCM t10 - single read only - 2nd start", + timer.isExpired(now, started, stopped, count), false); + } + { + // With cpu metrics + F64 started(42.0), stopped(97.0); + U64 count(U64L(8)); + LLDeadmanTimer timer(5.0, true); + LLProcInfo::time_type user_cpu(29000), sys_cpu(57000); + + 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("WCM t10 - 5.0 horizon timer has not timed out after 10 1-second bell rings", + timer.isExpired(now, started, stopped, count, user_cpu, sys_cpu), false); + F64 last_good_ring(u64_time_to_float(now)); + + // Jump forward and expire + now += float_time_to_u64(10.0); + ensure_equals("WCM t10 - 5.0 horizon timer expires on 10-second jump", + timer.isExpired(now, started, stopped, count, user_cpu, sys_cpu), true); + ensure_approximately_equals("WCM t10 - started matches start() time", started, real_start, 4); + ensure_approximately_equals("WCM t10 - stopped matches last ringBell() time", stopped, last_good_ring, 4); + ensure_equals("WCM t10 - 10 good ringBell()s", count, U64L(10)); + ensure_equals("WCM t10 - single read only", timer.isExpired(now, started, stopped, count, user_cpu, sys_cpu), 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("WCM t10 - 5.0 horizon timer has not timed out after 8 1-second bell rings", + timer.isExpired(now, started, stopped, count, user_cpu, sys_cpu), false); + last_good_ring = u64_time_to_float(now); + + // Jump forward and expire + now += float_time_to_u64(10.0); + ensure_equals("WCM t10 - 5.0 horizon timer expires on 8-second jump", + timer.isExpired(now, started, stopped, count, user_cpu, sys_cpu), true); + ensure_approximately_equals("WCM t10 - 2nd started matches start() time", started, real_start, 4); + ensure_approximately_equals("WCM t10 - 2nd stopped matches last ringBell() time", stopped, last_good_ring, 4); + ensure_equals("WCM t10 - 8 good ringBell()s", count, U64L(8)); + ensure_equals("WCM t10 - single read only - 2nd start", + timer.isExpired(now, started, stopped, count, user_cpu, sys_cpu), false); + } } + } // end namespace tut diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index 0f33128057..2a863a3103 100755 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -96,7 +96,7 @@ U32 LLMeshRepository::sLODPending = 0; U32 LLMeshRepository::sCacheBytesRead = 0; U32 LLMeshRepository::sCacheBytesWritten = 0; U32 LLMeshRepository::sPeakKbps = 0; -LLDeadmanTimer LLMeshRepository::sQuiescentTimer(15.0); +LLDeadmanTimer LLMeshRepository::sQuiescentTimer(15.0, true); const U32 MAX_TEXTURE_UPLOAD_RETRIES = 5; -- cgit v1.2.3 From 960139aa6f02f90c6102d3c5d5c38b5ebe689f9c Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Thu, 2 May 2013 19:12:59 -0400 Subject: SH-4161 Integrate cpu metrics into LLDeadmanTimer and then metrics viewer Normalize deadman timer's args on U64/F64. Internals remain the same. Modify mesh to collect and output enhanced CPU metrics. --- indra/llcommon/lldeadmantimer.cpp | 6 ++-- indra/llcommon/lldeadmantimer.h | 2 +- indra/llcommon/tests/lldeadmantimer_test.cpp | 47 +++++++++++----------------- indra/newview/llmeshrepository.cpp | 10 +++--- 4 files changed, 29 insertions(+), 36 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/lldeadmantimer.cpp b/indra/llcommon/lldeadmantimer.cpp index 2ba757d2af..7d9097e344 100644 --- a/indra/llcommon/lldeadmantimer.cpp +++ b/indra/llcommon/lldeadmantimer.cpp @@ -110,13 +110,13 @@ void LLDeadmanTimer::stop(time_type now) bool LLDeadmanTimer::isExpired(time_type now, F64 & started, F64 & stopped, U64 & count, - LLProcInfo::time_type & user_cpu, LLProcInfo::time_type & sys_cpu) + U64 & user_cpu, U64 & sys_cpu) { const bool status(isExpired(now, started, stopped, count)); if (status) { - user_cpu = mUEndCPU - mUStartCPU; - sys_cpu = mSEndCPU - mSStartCPU; + user_cpu = U64(mUEndCPU - mUStartCPU); + sys_cpu = U64(mSEndCPU - mSStartCPU); } return status; } diff --git a/indra/llcommon/lldeadmantimer.h b/indra/llcommon/lldeadmantimer.h index a6022852b9..0dde16b717 100644 --- a/indra/llcommon/lldeadmantimer.h +++ b/indra/llcommon/lldeadmantimer.h @@ -190,7 +190,7 @@ public: /// left unchanged. /// bool isExpired(time_type now, F64 & started, F64 & stopped, U64 & count, - LLProcInfo::time_type & user_cpu, LLProcInfo::time_type & sys_cpu); + U64 & user_cpu, U64 & sys_cpu); /// Identical to the six-arugment form except is does without the /// CPU time return if the caller isn't interested in it. diff --git a/indra/llcommon/tests/lldeadmantimer_test.cpp b/indra/llcommon/tests/lldeadmantimer_test.cpp index a4ec76a016..7fd2dde6e0 100644 --- a/indra/llcommon/tests/lldeadmantimer_test.cpp +++ b/indra/llcommon/tests/lldeadmantimer_test.cpp @@ -81,16 +81,15 @@ void deadmantimer_object_t::test<1>() { // With cpu metrics F64 started(42.0), stopped(97.0); - U64 count(U64L(8)); - LLProcInfo::time_type user_cpu(29000), sys_cpu(57000); + U64 count(U64L(8)), user_cpu(29000), sys_cpu(57000); LLDeadmanTimer timer(10.0, true); ensure_equals("WCM isExpired() returns false after ctor()", timer.isExpired(0, started, stopped, count, user_cpu, sys_cpu), false); ensure_approximately_equals("WCM t1 - isExpired() does not modify started", started, F64(42.0), 2); ensure_approximately_equals("WCM t1 - isExpired() does not modify stopped", stopped, F64(97.0), 2); ensure_equals("WCM t1 - isExpired() does not modify count", count, U64L(8)); - ensure_equals("WCM t1 - isExpired() does not modify user_cpu", user_cpu, LLProcInfo::time_type(29000)); - ensure_equals("WCM t1 - isExpired() does not modify sys_cpu", sys_cpu, LLProcInfo::time_type(57000)); + ensure_equals("WCM t1 - isExpired() does not modify user_cpu", user_cpu, U64L(29000)); + ensure_equals("WCM t1 - isExpired() does not modify sys_cpu", sys_cpu, U64L(57000)); } } @@ -112,8 +111,7 @@ void deadmantimer_object_t::test<2>() { // With cpu metrics F64 started(42.0), stopped(97.0); - U64 count(U64L(8)); - LLProcInfo::time_type user_cpu(29000), sys_cpu(57000); + U64 count(U64L(8)), user_cpu(29000), sys_cpu(57000); LLDeadmanTimer timer(0.0, true); // Zero is pre-expired ensure_equals("WCM isExpired() still returns false with 0.0 time ctor()", @@ -141,8 +139,7 @@ void deadmantimer_object_t::test<3>() { // With cpu metrics F64 started(42.0), stopped(97.0); - U64 count(U64L(8)); - LLProcInfo::time_type user_cpu(29000), sys_cpu(57000); + U64 count(U64L(8)), user_cpu(29000), sys_cpu(57000); LLDeadmanTimer timer(0.0, true); timer.start(0); @@ -172,8 +169,7 @@ void deadmantimer_object_t::test<4>() { // With cpu metrics F64 started(42.0), stopped(97.0); - U64 count(U64L(8)); - LLProcInfo::time_type user_cpu(29000), sys_cpu(57000); + U64 count(U64L(8)), user_cpu(29000), sys_cpu(57000); LLDeadmanTimer timer(0.0, true); timer.start(0); @@ -205,8 +201,7 @@ void deadmantimer_object_t::test<5>() { // With cpu metrics F64 started(42.0), stopped(97.0); - U64 count(U64L(8)); - LLProcInfo::time_type user_cpu(29000), sys_cpu(57000); + U64 count(U64L(8)), user_cpu(29000), sys_cpu(57000); LLDeadmanTimer timer(10.0, true); timer.start(0); @@ -215,8 +210,8 @@ void deadmantimer_object_t::test<5>() ensure_approximately_equals("WCM t5 - isExpired() does not modify started", started, F64(42.0), 2); ensure_approximately_equals("WCM t5 - isExpired() does not modify stopped", stopped, F64(97.0), 2); ensure_equals("WCM t5 - isExpired() does not modify count", count, U64L(8)); - ensure_equals("WCM t5 - isExpired() does not modify user_cpu", user_cpu, LLProcInfo::time_type(29000)); - ensure_equals("WCM t5 - isExpired() does not modify sys_cpu", sys_cpu, LLProcInfo::time_type(57000)); + ensure_equals("WCM t5 - isExpired() does not modify user_cpu", user_cpu, U64L(29000)); + ensure_equals("WCM t5 - isExpired() does not modify sys_cpu", sys_cpu, U64L(57000)); } } @@ -247,8 +242,7 @@ void deadmantimer_object_t::test<6>() { // With cpu metrics F64 started(42.0), stopped(97.0); - U64 count(U64L(8)); - LLProcInfo::time_type user_cpu(29000), sys_cpu(57000); + U64 count(U64L(8)), user_cpu(29000), sys_cpu(57000); LLDeadmanTimer timer(10.0, true); // Would like to do subtraction on current time but can't because @@ -263,8 +257,8 @@ void deadmantimer_object_t::test<6>() ensure_approximately_equals("WCM t6 - isExpired() does not modify started", started, F64(42.0), 2); ensure_approximately_equals("WCM t6 - isExpired() does not modify stopped", stopped, F64(97.0), 2); ensure_equals("t6 - isExpired() does not modify count", count, U64L(8)); - ensure_equals("WCM t6 - isExpired() does not modify user_cpu", user_cpu, LLProcInfo::time_type(29000)); - ensure_equals("WCM t6 - isExpired() does not modify sys_cpu", sys_cpu, LLProcInfo::time_type(57000)); + ensure_equals("WCM t6 - isExpired() does not modify user_cpu", user_cpu, U64L(29000)); + ensure_equals("WCM t6 - isExpired() does not modify sys_cpu", sys_cpu, U64L(57000)); } } @@ -293,9 +287,8 @@ void deadmantimer_object_t::test<7>() { // With cpu metrics F64 started(42.0), stopped(97.0); - U64 count(U64L(8)); + U64 count(U64L(8)), user_cpu(29000), sys_cpu(57000); LLDeadmanTimer timer(10.0, true); - LLProcInfo::time_type user_cpu(29000), sys_cpu(57000); // Would like to do subtraction on current time but can't because // the implementation on Windows is zero-based. We wrap around @@ -343,9 +336,8 @@ void deadmantimer_object_t::test<8>() { // With cpu metrics F64 started(42.0), stopped(97.0); - U64 count(U64L(8)); + U64 count(U64L(8)), user_cpu(29000), sys_cpu(57000); LLDeadmanTimer timer(10.0, true); - LLProcInfo::time_type user_cpu(29000), sys_cpu(57000); // Would like to do subtraction on current time but can't because // the implementation on Windows is zero-based. We wrap around @@ -367,8 +359,8 @@ void deadmantimer_object_t::test<8>() ensure_approximately_equals("WCM t8 - 2nd isExpired() does not modify started", started, F64(42.0), 2); ensure_approximately_equals("WCM t8 - 2nd isExpired() does not modify stopped", stopped, F64(97.0), 2); ensure_equals("WCM t8 - 2nd isExpired() does not modify count", count, U64L(8)); - ensure_equals("WCM t8 - 2nd isExpired() does not modify user_cpu", user_cpu, LLProcInfo::time_type(29000)); - ensure_equals("WCM t8 - 2nd isExpired() does not modify sys_cpu", sys_cpu, LLProcInfo::time_type(57000)); + ensure_equals("WCM t8 - 2nd isExpired() does not modify user_cpu", user_cpu, U64L(29000)); + ensure_equals("WCM t8 - 2nd isExpired() does not modify sys_cpu", sys_cpu, U64L(57000)); } } @@ -423,9 +415,8 @@ void deadmantimer_object_t::test<9>() { // With cpu metrics F64 started(42.0), stopped(97.0); - U64 count(U64L(8)); + U64 count(U64L(8)), user_cpu(29000), sys_cpu(57000); LLDeadmanTimer timer(5.0, true); - LLProcInfo::time_type user_cpu(29000), sys_cpu(57000); LLDeadmanTimer::time_type now(LLDeadmanTimer::getNow()); F64 real_start(u64_time_to_float(now)); @@ -553,9 +544,9 @@ void deadmantimer_object_t::test<10>() { // With cpu metrics F64 started(42.0), stopped(97.0); - U64 count(U64L(8)); + U64 count(U64L(8)), user_cpu(29000), sys_cpu(57000); + LLDeadmanTimer timer(5.0, true); - LLProcInfo::time_type user_cpu(29000), sys_cpu(57000); LLDeadmanTimer::time_type now(LLDeadmanTimer::getNow()); F64 real_start(u64_time_to_float(now)); diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index 2a863a3103..4d3937ded1 100755 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -96,7 +96,7 @@ U32 LLMeshRepository::sLODPending = 0; U32 LLMeshRepository::sCacheBytesRead = 0; U32 LLMeshRepository::sCacheBytesWritten = 0; U32 LLMeshRepository::sPeakKbps = 0; -LLDeadmanTimer LLMeshRepository::sQuiescentTimer(15.0, true); +LLDeadmanTimer LLMeshRepository::sQuiescentTimer(15.0, true); // true -> gather cpu metrics const U32 MAX_TEXTURE_UPLOAD_RETRIES = 5; @@ -3769,9 +3769,9 @@ void LLMeshRepository::metricsProgress(unsigned int this_count) void LLMeshRepository::metricsUpdate() { F64 started, stopped; - U64 total_count; - - if (sQuiescentTimer.isExpired(0, started, stopped, total_count)) + U64 total_count(U64L(0)), user_cpu(U64L(0)), sys_cpu(U64L(0)); + + if (sQuiescentTimer.isExpired(0, started, stopped, total_count, user_cpu, sys_cpu)) { LLSD metrics; @@ -3781,6 +3781,8 @@ void LLMeshRepository::metricsUpdate() metrics["stop"] = stopped; metrics["downloads"] = LLSD::Integer(total_count); metrics["teleports"] = LLSD::Integer(metrics_teleport_start_count); + metrics["user_cpu"] = double(user_cpu) / 1.0e6; + metrics["sys_cpu"] = double(sys_cpu) / 1.0e6; llinfos << "EventMarker " << metrics << llendl; } } -- cgit v1.2.3 From d6741a4fc088632c179f767df240953fc4f7474f Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Wed, 19 Jun 2013 19:58:09 +0000 Subject: Fixups for the transplant of the HttpHeader changes from sunshine. --- indra/newview/llmeshrepository.cpp | 2 +- indra/newview/lltexturefetch.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'indra') diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index f0ec97a34d..221a797fc7 100755 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -573,7 +573,7 @@ LLMeshRepoThread::LLMeshRepoThread() mHttpLargeOptions = new LLCore::HttpOptions; mHttpLargeOptions->setTransferTimeout(LARGE_MESH_XFER_TIMEOUT); mHttpHeaders = new LLCore::HttpHeaders; - mHttpHeaders->mHeaders.push_back("Accept: application/vnd.ll.mesh"); + mHttpHeaders->append("Accept", "application/vnd.ll.mesh"); mHttpPolicyClass = LLAppViewer::instance()->getAppCoreHttp().getPolicy(LLAppCoreHttp::AP_MESH); mHttpPolicyClass = LLAppViewer::instance()->getAppCoreHttp().getPolicy(LLAppCoreHttp::AP_LARGE_MESH); } diff --git a/indra/newview/lltexturefetch.cpp b/indra/newview/lltexturefetch.cpp index a8c8445ee1..bf9d803098 100755 --- a/indra/newview/lltexturefetch.cpp +++ b/indra/newview/lltexturefetch.cpp @@ -2410,9 +2410,9 @@ LLTextureFetch::LLTextureFetch(LLTextureCache* cache, LLImageDecodeThread* image mHttpOptionsWithHeaders = new LLCore::HttpOptions; mHttpOptionsWithHeaders->setWantHeaders(true); mHttpHeaders = new LLCore::HttpHeaders; - mHttpHeaders->append(HTTP_OUT_HEADER_ACCEPT, HTTP_CONTENT_IMAGE_X_J2C); + mHttpHeaders->append("Accept", "image/x-j2c"); mHttpMetricsHeaders = new LLCore::HttpHeaders; - mHttpMetricsHeaders->append(HTTP_OUT_HEADER_CONTENT_TYPE, HTTP_CONTENT_LLSD_XML); + mHttpMetricsHeaders->append("Content-Type", "application/llsd+xml"); mHttpPolicyClass = LLAppViewer::instance()->getAppCoreHttp().getPolicy(LLAppCoreHttp::AP_TEXTURE); } @@ -4050,7 +4050,7 @@ void LLTextureFetchDebugger::init() if (! mHttpHeaders) { mHttpHeaders = new LLCore::HttpHeaders; - mHttpHeaders->append(HTTP_OUT_HEADER_ACCEPT, HTTP_CONTENT_IMAGE_X_J2C); + mHttpHeaders->append("Accept", "image/x-j2c"); } } -- cgit v1.2.3 From d6cbcd591aea32357d50b266efe8a95754302cbf Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Thu, 20 Jun 2013 19:18:39 -0400 Subject: SH-4257 Preparation for a new cap grant: GetMesh2 Mesh repo is using three policy classes now: one for large objects, one for GetMesh2 regions, one for GetMesh regions. It's also detecting the presence of the cap and using the correct class. Class initialization cleaned up significantly in llappcorehttp using data-directed code. Pulled in the changes to HttpHeader done for sunshine-internal then did a refactoring pass on the header callback which now uses a unified approach to clean up and deliver header information to all interested parties. Added support for using Retry-After header information on 503 retries. --- indra/llcorehttp/_httpinternal.h | 6 +- indra/llcorehttp/_httpoperation.cpp | 4 +- indra/llcorehttp/_httpoprequest.cpp | 244 ++++++++++++++++++++++++++-------- indra/llcorehttp/_httpoprequest.h | 4 + indra/llcorehttp/_httppolicy.cpp | 12 +- indra/llcorehttp/_httppolicyclass.cpp | 4 +- indra/llcorehttp/_httpservice.cpp | 3 - indra/llcorehttp/httpoptions.cpp | 8 +- indra/llcorehttp/httpoptions.h | 8 +- indra/newview/llappcorehttp.cpp | 10 +- indra/newview/llappcorehttp.h | 3 +- indra/newview/llmeshrepository.cpp | 55 +++++--- indra/newview/llmeshrepository.h | 8 +- 13 files changed, 275 insertions(+), 94 deletions(-) (limited to 'indra') diff --git a/indra/llcorehttp/_httpinternal.h b/indra/llcorehttp/_httpinternal.h index d60996756f..f085ca3b91 100755 --- a/indra/llcorehttp/_httpinternal.h +++ b/indra/llcorehttp/_httpinternal.h @@ -98,7 +98,7 @@ namespace LLCore // Maxium number of policy classes that can be defined. // *TODO: Currently limited to the default class + 1, extend. -const int HTTP_POLICY_CLASS_LIMIT = 4; +const int HTTP_POLICY_CLASS_LIMIT = 8; // Debug/informational tracing. Used both // as a global option and in per-request traces. @@ -138,6 +138,10 @@ const int HTTP_CONNECTION_LIMIT_DEFAULT = 8; const int HTTP_CONNECTION_LIMIT_MIN = 1; const int HTTP_CONNECTION_LIMIT_MAX = 256; +// Miscellaneous defaults +const long HTTP_PIPELINING_DEFAULT = 0L; +const bool HTTP_USE_RETRY_AFTER_DEFAULT = true; + // Tuning parameters // Time worker thread sleeps after a pass through the diff --git a/indra/llcorehttp/_httpoperation.cpp b/indra/llcorehttp/_httpoperation.cpp index 5cf5bc5930..7acd728bbd 100755 --- a/indra/llcorehttp/_httpoperation.cpp +++ b/indra/llcorehttp/_httpoperation.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 @@ -53,7 +53,7 @@ HttpOperation::HttpOperation() mUserHandler(NULL), mReqPolicy(HttpRequest::DEFAULT_POLICY_ID), mReqPriority(0U), - mTracing(0) + mTracing(HTTP_TRACE_OFF) { mMetricCreated = totalTime(); } diff --git a/indra/llcorehttp/_httpoprequest.cpp b/indra/llcorehttp/_httpoprequest.cpp index a4c0a12fdc..8cb7fee701 100755 --- a/indra/llcorehttp/_httpoprequest.cpp +++ b/indra/llcorehttp/_httpoprequest.cpp @@ -64,6 +64,15 @@ int parse_content_range_header(char * buffer, unsigned int * last, unsigned int * length); +// Similar for Retry-After headers. Only parses the delta form +// of the header, HTTP time formats aren't interesting for client +// purposes. +// +// @return 0 if successfully parsed and seconds time delta +// returned in time argument. +// +int parse_retry_after_header(char * buffer, int * time); + // Take data from libcurl's CURLOPT_DEBUGFUNCTION callback and // escape and format it for a tracing line in logging. Absolutely @@ -74,14 +83,12 @@ void escape_libcurl_debug_data(char * buffer, size_t len, bool scrub, std::string & safe_line); -// OS-neutral string comparisons of various types -int os_strncasecmp(const char *s1, const char *s2, size_t n); -int os_strcasecmp(const char *s1, const char *s2); -char * os_strtok_r(char *str, const char *delim, char **saveptr); - - -static const char * const hdr_whitespace(" \t"); -static const char * const hdr_separator(": \t"); +// OS-neutral string comparisons of various types. +int os_strcasecmp(const char * s1, const char * s2); +char * os_strtok_r(char * str, const char * delim, char ** saveptr); +char * os_strtrim(char * str); +char * os_strltrim(char * str); +void os_strlower(char * str); } // end anonymous namespace @@ -104,6 +111,8 @@ HttpOpRequest::HttpOpRequest() mCurlService(NULL), mCurlHeaders(NULL), mCurlBodyPos(0), + mCurlTemp(NULL), + mCurlTempLen(0), mReplyBody(NULL), mReplyOffset(0), mReplyLength(0), @@ -154,6 +163,10 @@ HttpOpRequest::~HttpOpRequest() mCurlHeaders = NULL; } + delete [] mCurlTemp; + mCurlTemp = NULL; + mCurlTempLen = 0; + if (mReplyBody) { mReplyBody->release(); @@ -207,6 +220,11 @@ void HttpOpRequest::stageFromActive(HttpService * service) mCurlHeaders = NULL; } + // Also not needed on the other side + delete [] mCurlTemp; + mCurlTemp = NULL; + mCurlTempLen = 0; + addAsReply(); } @@ -335,6 +353,10 @@ void HttpOpRequest::setupCommon(HttpRequest::policy_t policy_id, { mProcFlags |= PF_SAVE_HEADERS; } + if (options->getUseRetryAfter()) + { + mProcFlags |= PF_USE_RETRY_AFTER; + } mPolicyRetryLimit = options->getRetries(); mPolicyRetryLimit = llclamp(mPolicyRetryLimit, HTTP_RETRY_COUNT_MIN, HTTP_RETRY_COUNT_MAX); mTracing = (std::max)(mTracing, llclamp(options->getTrace(), HTTP_TRACE_MIN, HTTP_TRACE_MAX)); @@ -549,7 +571,7 @@ HttpStatus HttpOpRequest::prepareRequest(HttpService * service) } curl_easy_setopt(mCurlHandle, CURLOPT_HTTPHEADER, mCurlHeaders); - if (mProcFlags & (PF_SCAN_RANGE_HEADER | PF_SAVE_HEADERS)) + if (mProcFlags & (PF_SCAN_RANGE_HEADER | PF_SAVE_HEADERS | PF_USE_RETRY_AFTER)) { curl_easy_setopt(mCurlHandle, CURLOPT_HEADERFUNCTION, headerCallback); curl_easy_setopt(mCurlHandle, CURLOPT_HEADERDATA, this); @@ -610,10 +632,9 @@ size_t HttpOpRequest::headerCallback(void * data, size_t size, size_t nmemb, voi { static const char status_line[] = "HTTP/"; static const size_t status_line_len = sizeof(status_line) - 1; - - static const char con_ran_line[] = "content-range:"; - static const size_t con_ran_line_len = sizeof(con_ran_line) - 1; - + static const char con_ran_line[] = "content-range"; + static const char con_retry_line[] = "retry-after"; + HttpOpRequest * op(static_cast(userdata)); const size_t hdr_size(size * nmemb); @@ -627,6 +648,7 @@ size_t HttpOpRequest::headerCallback(void * data, size_t size, size_t nmemb, voi op->mReplyOffset = 0; op->mReplyLength = 0; op->mReplyFullLength = 0; + op->mReplyRetryAfter = 0; op->mStatus = HttpStatus(); if (op->mReplyHeaders) { @@ -645,6 +667,53 @@ size_t HttpOpRequest::headerCallback(void * data, size_t size, size_t nmemb, voi --wanted_hdr_size; } } + + // Copy and normalize header fragments for the following + // stages. Would like to modify the data in-place but that + // may not be allowed and we need one byte extra for NUL. + // At the end of this we will have: + // + // If ':' present in header: + // 1. name points to text to left of colon which + // will be ascii lower-cased and left and right + // trimmed of whitespace. + // 2. value points to text to right of colon which + // will be left trimmed of whitespace. + // Otherwise: + // 1. name points to header which will be left + // trimmed of whitespace. + // 2. value is NULL + // Any non-NULL pointer may point to a zero-length string. + // + if (wanted_hdr_size >= op->mCurlTempLen) + { + delete [] op->mCurlTemp; + op->mCurlTempLen = 2 * wanted_hdr_size + 1; + op->mCurlTemp = new char [op->mCurlTempLen]; + } + memcpy(op->mCurlTemp, hdr_data, wanted_hdr_size); + op->mCurlTemp[wanted_hdr_size] = '\0'; + char * name(op->mCurlTemp); + char * value(strchr(name, ':')); + if (value) + { + *value++ = '\0'; + os_strlower(name); + name = os_strtrim(name); + value = os_strltrim(value); + } + else + { + // Doesn't look well-formed, do minimal normalization on it + name = os_strltrim(name); + } + + // Normalized, now reject headers with empty names. + if (! *name) + { + // No use continuing + return hdr_size; + } // Save header if caller wants them in the response if (is_header && op->mProcFlags & PF_SAVE_HEADERS) @@ -654,43 +723,53 @@ size_t HttpOpRequest::headerCallback(void * data, size_t size, size_t nmemb, voi { op->mReplyHeaders = new HttpHeaders; } - op->mReplyHeaders->appendNormal(hdr_data, wanted_hdr_size); + op->mReplyHeaders->append(name, value ? value : ""); } + // From this point, header-specific processors are free to + // modify the header value. + // Detect and parse 'Content-Range' headers - if (is_header && op->mProcFlags & PF_SCAN_RANGE_HEADER) + if (is_header + && op->mProcFlags & PF_SCAN_RANGE_HEADER + && value && *value + && ! strcmp(name, con_ran_line)) { - char hdr_buffer[128]; // Enough for a reasonable header - size_t frag_size((std::min)(wanted_hdr_size, sizeof(hdr_buffer) - 1)); - - memcpy(hdr_buffer, hdr_data, frag_size); - hdr_buffer[frag_size] = '\0'; - if (frag_size > con_ran_line_len && - ! os_strncasecmp(hdr_buffer, con_ran_line, con_ran_line_len)) + unsigned int first(0), last(0), length(0); + int status; + + if (! (status = parse_content_range_header(value, &first, &last, &length))) + { + // Success, record the fragment position + op->mReplyOffset = first; + op->mReplyLength = last - first + 1; + op->mReplyFullLength = length; + } + else if (-1 == status) { - unsigned int first(0), last(0), length(0); - int status; + // Response is badly formed and shouldn't be accepted + op->mStatus = HttpStatus(HttpStatus::LLCORE, HE_INV_CONTENT_RANGE_HDR); + } + else + { + // Ignore the unparsable. + LL_INFOS_ONCE("CoreHttp") << "Problem parsing odd Content-Range header: '" + << std::string(hdr_data, wanted_hdr_size) + << "'. Ignoring." + << LL_ENDL; + } + } - if (! (status = parse_content_range_header(hdr_buffer, &first, &last, &length))) - { - // Success, record the fragment position - op->mReplyOffset = first; - op->mReplyLength = last - first + 1; - op->mReplyFullLength = length; - } - else if (-1 == status) - { - // Response is badly formed and shouldn't be accepted - op->mStatus = HttpStatus(HttpStatus::LLCORE, HE_INV_CONTENT_RANGE_HDR); - } - else - { - // Ignore the unparsable. - LL_INFOS_ONCE("CoreHttp") << "Problem parsing odd Content-Range header: '" - << std::string(hdr_data, frag_size) - << "'. Ignoring." - << LL_ENDL; - } + // Detect and parse 'Retry-After' headers + if (is_header + && op->mProcFlags & PF_USE_RETRY_AFTER + && value && *value + && ! strcmp(name, con_retry_line)) + { + int time(0); + if (! parse_retry_after_header(value, &time)) + { + op->mReplyRetryAfter = time; } } @@ -805,14 +884,16 @@ int parse_content_range_header(char * buffer, unsigned int * last, unsigned int * length) { + static const char * const hdr_whitespace(" \t"); + char * tok_state(NULL), * tok(NULL); bool match(true); - if (! os_strtok_r(buffer, hdr_separator, &tok_state)) + if (! (tok = os_strtok_r(buffer, hdr_whitespace, &tok_state))) match = false; - if (match && (tok = os_strtok_r(NULL, hdr_whitespace, &tok_state))) - match = 0 == os_strcasecmp("bytes", tok); - if (match && ! (tok = os_strtok_r(NULL, " \t", &tok_state))) + else + match = (0 == os_strcasecmp("bytes", tok)); + if (match && ! (tok = os_strtok_r(NULL, hdr_whitespace, &tok_state))) match = false; if (match) { @@ -851,6 +932,25 @@ int parse_content_range_header(char * buffer, } +int parse_retry_after_header(char * buffer, int * time) +{ + char * endptr(buffer); + long lcl_time(strtol(buffer, &endptr, 10)); + if (*endptr == '\0' && endptr != buffer && lcl_time > 0) + { + *time = lcl_time; + return 0; + } + + // Could attempt to parse HTTP time here but we're not really + // interested in it. Scheduling based on wallclock time on + // user hardware will lead to tears. + + // Header is there but badly/unexpectedly formed, try to ignore it. + return 1; +} + + void escape_libcurl_debug_data(char * buffer, size_t len, bool scrub, std::string & safe_line) { std::string out; @@ -887,15 +987,6 @@ void escape_libcurl_debug_data(char * buffer, size_t len, bool scrub, std::strin } -int os_strncasecmp(const char *s1, const char *s2, size_t n) -{ -#if LL_WINDOWS - return _strnicmp(s1, s2, n); -#else - return strncasecmp(s1, s2, n); -#endif // LL_WINDOWS -} - int os_strcasecmp(const char *s1, const char *s2) { @@ -917,6 +1008,45 @@ char * os_strtok_r(char *str, const char *delim, char ** savestate) } +void os_strlower(char * str) +{ + for (char c(0); (c = *str); ++str) + { + *str = tolower(c); + } +} + + +char * os_strtrim(char * lstr) +{ + while (' ' == *lstr || '\t' == *lstr) + { + ++lstr; + } + if (*lstr) + { + for (char * rstr(lstr + strlen(lstr)); *--rstr;) + { + if (' ' == *rstr || '\t' == *rstr) + { + *rstr = '\0'; + } + } + } + return lstr; +} + + +char * os_strltrim(char * lstr) +{ + while (' ' == *lstr || '\t' == *lstr) + { + ++lstr; + } + return lstr; +} + + } // end anonymous namespace diff --git a/indra/llcorehttp/_httpoprequest.h b/indra/llcorehttp/_httpoprequest.h index 831e5bebf7..2e737cf1cc 100755 --- a/indra/llcorehttp/_httpoprequest.h +++ b/indra/llcorehttp/_httpoprequest.h @@ -158,6 +158,7 @@ protected: unsigned int mProcFlags; static const unsigned int PF_SCAN_RANGE_HEADER = 0x00000001U; static const unsigned int PF_SAVE_HEADERS = 0x00000002U; + static const unsigned int PF_USE_RETRY_AFTER = 0x00000004U; public: // Request data @@ -175,6 +176,8 @@ public: HttpService * mCurlService; curl_slist * mCurlHeaders; size_t mCurlBodyPos; + char * mCurlTemp; // Scratch buffer for header processing + size_t mCurlTempLen; // Result data HttpStatus mStatus; @@ -184,6 +187,7 @@ public: size_t mReplyFullLength; HttpHeaders * mReplyHeaders; std::string mReplyConType; + int mReplyRetryAfter; // Policy data int mPolicyRetries; diff --git a/indra/llcorehttp/_httppolicy.cpp b/indra/llcorehttp/_httppolicy.cpp index 54c9c6bb1b..5f303dd0fe 100755 --- a/indra/llcorehttp/_httppolicy.cpp +++ b/indra/llcorehttp/_httppolicy.cpp @@ -160,8 +160,12 @@ void HttpPolicy::retryOp(HttpOpRequest * op) const HttpTime now(totalTime()); const int policy_class(op->mReqPolicy); - - const HttpTime delta(retry_deltas[llclamp(op->mPolicyRetries, 0, delta_max)]); + HttpTime delta(retry_deltas[llclamp(op->mPolicyRetries, 0, delta_max)]); + + if (op->mReplyRetryAfter > 0 && op->mReplyRetryAfter < 30) + { + delta = op->mReplyRetryAfter * U64L(1000000); + } op->mPolicyRetryAt = now + delta; ++op->mPolicyRetries; if (error_503 == op->mStatus) @@ -170,10 +174,10 @@ void HttpPolicy::retryOp(HttpOpRequest * op) } LL_WARNS("CoreHttp") << "HTTP request " << static_cast(op) << " retry " << op->mPolicyRetries - << " scheduled for +" << (delta / HttpTime(1000)) + << " scheduled in " << (delta / HttpTime(1000)) << " mS. Status: " << op->mStatus.toHex() << LL_ENDL; - if (op->mTracing > 0) + if (op->mTracing > HTTP_TRACE_OFF) { LL_INFOS("CoreHttp") << "TRACE, ToRetryQueue, Handle: " << static_cast(op) diff --git a/indra/llcorehttp/_httppolicyclass.cpp b/indra/llcorehttp/_httppolicyclass.cpp index a23b81322c..1a55ab1ac6 100755 --- a/indra/llcorehttp/_httppolicyclass.cpp +++ b/indra/llcorehttp/_httppolicyclass.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 @@ -37,7 +37,7 @@ HttpPolicyClass::HttpPolicyClass() : mSetMask(0UL), mConnectionLimit(HTTP_CONNECTION_LIMIT_DEFAULT), mPerHostConnectionLimit(HTTP_CONNECTION_LIMIT_DEFAULT), - mPipelining(0) + mPipelining(HTTP_PIPELINING_DEFAULT) {} diff --git a/indra/llcorehttp/_httpservice.cpp b/indra/llcorehttp/_httpservice.cpp index 0825888d0f..0821401289 100755 --- a/indra/llcorehttp/_httpservice.cpp +++ b/indra/llcorehttp/_httpservice.cpp @@ -55,9 +55,6 @@ HttpService::HttpService() { // Create the default policy class HttpPolicyClass pol_class; - pol_class.set(HttpRequest::CP_CONNECTION_LIMIT, HTTP_CONNECTION_LIMIT_DEFAULT); - pol_class.set(HttpRequest::CP_PER_HOST_CONNECTION_LIMIT, HTTP_CONNECTION_LIMIT_DEFAULT); - pol_class.set(HttpRequest::CP_ENABLE_PIPELINING, 0L); mPolicyClasses.push_back(pol_class); } diff --git a/indra/llcorehttp/httpoptions.cpp b/indra/llcorehttp/httpoptions.cpp index 4dcd862ca4..5bf1ecb4a5 100755 --- a/indra/llcorehttp/httpoptions.cpp +++ b/indra/llcorehttp/httpoptions.cpp @@ -39,7 +39,8 @@ HttpOptions::HttpOptions() mTracing(HTTP_TRACE_OFF), mTimeout(HTTP_REQUEST_TIMEOUT_DEFAULT), mTransferTimeout(HTTP_REQUEST_XFER_TIMEOUT_DEFAULT), - mRetries(HTTP_RETRY_COUNT_DEFAULT) + mRetries(HTTP_RETRY_COUNT_DEFAULT), + mUseRetryAfter(HTTP_USE_RETRY_AFTER_DEFAULT) {} @@ -76,5 +77,10 @@ void HttpOptions::setRetries(unsigned int retries) mRetries = retries; } +void HttpOptions::setUseRetryAfter(bool use_retry) +{ + mUseRetryAfter = use_retry; +} + } // end namespace LLCore diff --git a/indra/llcorehttp/httpoptions.h b/indra/llcorehttp/httpoptions.h index 623d71d3e6..04531425d8 100755 --- a/indra/llcorehttp/httpoptions.h +++ b/indra/llcorehttp/httpoptions.h @@ -98,13 +98,19 @@ public: return mRetries; } + void setUseRetryAfter(bool use_retry); + bool getUseRetryAfter() const + { + return mUseRetryAfter; + } + protected: bool mWantHeaders; int mTracing; unsigned int mTimeout; unsigned int mTransferTimeout; unsigned int mRetries; - + bool mUseRetryAfter; }; // end class HttpOptions diff --git a/indra/newview/llappcorehttp.cpp b/indra/newview/llappcorehttp.cpp index b601b31d21..2467c02d4d 100755 --- a/indra/newview/llappcorehttp.cpp +++ b/indra/newview/llappcorehttp.cpp @@ -72,10 +72,16 @@ void LLAppCoreHttp::init() "texture fetch" }, { - AP_MESH, 8, 1, 32, 4, + // *FIXME: Should become 32, 1, 32, 1 before release + AP_MESH1, 8, 1, 32, 4, "MeshMaxConcurrentRequests", "mesh fetch" }, + { + AP_MESH2, 8, 1, 32, 4, + "MeshMaxConcurrentRequests", + "mesh2 fetch" + }, { AP_LARGE_MESH, 2, 1, 8, 1, "", @@ -171,6 +177,8 @@ void LLAppCoreHttp::init() } // Set it and report + // *TODO: These are intended to be per-host limits when we can + // support that in llcorehttp/libcurl. LLCore::HttpStatus status; status = LLCore::HttpRequest::setPolicyClassOption(mPolicies[policy], LLCore::HttpRequest::CP_CONNECTION_LIMIT, diff --git a/indra/newview/llappcorehttp.h b/indra/newview/llappcorehttp.h index 532e1f5cb0..4a14c35966 100755 --- a/indra/newview/llappcorehttp.h +++ b/indra/newview/llappcorehttp.h @@ -47,7 +47,8 @@ public: { AP_DEFAULT, AP_TEXTURE, - AP_MESH, + AP_MESH1, + AP_MESH2, AP_LARGE_MESH, AP_UPLOADS, AP_COUNT // Must be last diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index 221a797fc7..1cda0b6a71 100755 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -560,6 +560,7 @@ LLMeshRepoThread::LLMeshRepoThread() mHttpLargeOptions(NULL), mHttpHeaders(NULL), mHttpPolicyClass(LLCore::HttpRequest::DEFAULT_POLICY_ID), + mHttpLegacyPolicyClass(LLCore::HttpRequest::DEFAULT_POLICY_ID), mHttpLargePolicyClass(LLCore::HttpRequest::DEFAULT_POLICY_ID), mHttpPriority(0), mHttpGetCount(0U), @@ -574,8 +575,9 @@ LLMeshRepoThread::LLMeshRepoThread() mHttpLargeOptions->setTransferTimeout(LARGE_MESH_XFER_TIMEOUT); mHttpHeaders = new LLCore::HttpHeaders; mHttpHeaders->append("Accept", "application/vnd.ll.mesh"); - mHttpPolicyClass = LLAppViewer::instance()->getAppCoreHttp().getPolicy(LLAppCoreHttp::AP_MESH); - mHttpPolicyClass = LLAppViewer::instance()->getAppCoreHttp().getPolicy(LLAppCoreHttp::AP_LARGE_MESH); + mHttpPolicyClass = LLAppViewer::instance()->getAppCoreHttp().getPolicy(LLAppCoreHttp::AP_MESH2); + mHttpLegacyPolicyClass = LLAppViewer::instance()->getAppCoreHttp().getPolicy(LLAppCoreHttp::AP_MESH1); + mHttpLargePolicyClass = LLAppViewer::instance()->getAppCoreHttp().getPolicy(LLAppCoreHttp::AP_LARGE_MESH); } @@ -795,13 +797,22 @@ void LLMeshRepoThread::loadMeshLOD(const LLVolumeParams& mesh_params, S32 lod) } //static -std::string LLMeshRepoThread::constructUrl(LLUUID mesh_id) +std::string LLMeshRepoThread::constructUrl(LLUUID mesh_id, int * cap_version) { + int version(1); std::string http_url; if (gAgent.getRegion()) { - http_url = gMeshRepo.mGetMeshCapability; + if (! gMeshRepo.mGetMesh2Capability.empty()) + { + version = 2; + http_url = gMeshRepo.mGetMesh2Capability; + } + else + { + http_url = gMeshRepo.mGetMeshCapability; + } } if (!http_url.empty()) @@ -814,20 +825,22 @@ std::string LLMeshRepoThread::constructUrl(LLUUID mesh_id) llwarns << "Current region does not have GetMesh capability! Cannot load " << mesh_id << ".mesh" << llendl; } + *cap_version = version; return http_url; } // May only be called by repo thread -LLCore::HttpHandle LLMeshRepoThread::getByteRange(const std::string & url, - size_t offset, - size_t len, +LLCore::HttpHandle LLMeshRepoThread::getByteRange(const std::string & url, int cap_version, + size_t offset, size_t len, LLCore::HttpHandler * handler) { LLCore::HttpHandle handle(LLCORE_HTTP_HANDLE_INVALID); if (len < LARGE_MESH_FETCH_THRESHOLD) { - handle = mHttpRequest->requestGetByteRange(mHttpPolicyClass, + handle = mHttpRequest->requestGetByteRange((2 == cap_version + ? mHttpPolicyClass + : mHttpLegacyPolicyClass), mHttpPriority, url, offset, @@ -911,12 +924,13 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id) } //reading from VFS failed for whatever reason, fetch from sim - std::string http_url = constructUrl(mesh_id); + int cap_version(1); + std::string http_url = constructUrl(mesh_id, &cap_version); if (!http_url.empty()) { LLMeshSkinInfoHandler * handler = new LLMeshSkinInfoHandler(mesh_id, offset, size); // LL_WARNS("Mesh") << "MESH: Issuing Skin Info Request" << LL_ENDL; - LLCore::HttpHandle handle = getByteRange(http_url, offset, size, handler); + LLCore::HttpHandle handle = getByteRange(http_url, cap_version, offset, size, handler); if (LLCORE_HTTP_HANDLE_INVALID == handle) { // *TODO: Better error message @@ -1000,12 +1014,13 @@ bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id) } //reading from VFS failed for whatever reason, fetch from sim - std::string http_url = constructUrl(mesh_id); + int cap_version(1); + std::string http_url = constructUrl(mesh_id, &cap_version); if (!http_url.empty()) { LLMeshDecompositionHandler * handler = new LLMeshDecompositionHandler(mesh_id, offset, size); // LL_WARNS("Mesh") << "MESH: Issuing Decomp Request" << LL_ENDL; - LLCore::HttpHandle handle = getByteRange(http_url, offset, size, handler); + LLCore::HttpHandle handle = getByteRange(http_url, cap_version, offset, size, handler); if (LLCORE_HTTP_HANDLE_INVALID == handle) { // *TODO: Better error message @@ -1088,12 +1103,13 @@ bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id) } //reading from VFS failed for whatever reason, fetch from sim - std::string http_url = constructUrl(mesh_id); + int cap_version(1); + std::string http_url = constructUrl(mesh_id, &cap_version); if (!http_url.empty()) { LLMeshPhysicsShapeHandler * handler = new LLMeshPhysicsShapeHandler(mesh_id, offset, size); // LL_WARNS("Mesh") << "MESH: Issuing Physics Shape Request" << LL_ENDL; - LLCore::HttpHandle handle = getByteRange(http_url, offset, size, handler); + LLCore::HttpHandle handle = getByteRange(http_url, cap_version, offset, size, handler); if (LLCORE_HTTP_HANDLE_INVALID == handle) { // *TODO: Better error message @@ -1177,7 +1193,8 @@ bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params, U32& c //either cache entry doesn't exist or is corrupt, request header from simulator bool retval = true ; - std::string http_url = constructUrl(mesh_params.getSculptID()); + int cap_version(1); + std::string http_url = constructUrl(mesh_params.getSculptID(), &cap_version); if (!http_url.empty()) { //grab first 4KB if we're going to bother with a fetch. Cache will prevent future fetches if a full mesh fits @@ -1186,7 +1203,7 @@ bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params, U32& c LLMeshHeaderHandler * handler = new LLMeshHeaderHandler(mesh_params); // LL_WARNS("Mesh") << "MESH: Issuing Request" << LL_ENDL; - LLCore::HttpHandle handle = getByteRange(http_url, 0, MESH_HEADER_SIZE, handler); + LLCore::HttpHandle handle = getByteRange(http_url, cap_version, 0, MESH_HEADER_SIZE, handler); if (LLCORE_HTTP_HANDLE_INVALID == handle) { // *TODO: Better error message @@ -1261,12 +1278,13 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, } //reading from VFS failed for whatever reason, fetch from sim - std::string http_url = constructUrl(mesh_id); + int cap_version(1); + std::string http_url = constructUrl(mesh_id, &cap_version); if (!http_url.empty()) { LLMeshLODHandler * handler = new LLMeshLODHandler(mesh_params, lod, offset, size); // LL_WARNS("Mesh") << "MESH: Issuing LOD Request" << LL_ENDL; - LLCore::HttpHandle handle = getByteRange(http_url, offset, size, handler); + LLCore::HttpHandle handle = getByteRange(http_url, cap_version, offset, size, handler); if (LLCORE_HTTP_HANDLE_INVALID == handle) { // *TODO: Better error message @@ -2653,6 +2671,7 @@ void LLMeshRepository::notifyLoadedMeshes() { region_name = gAgent.getRegion()->getName(); mGetMeshCapability = gAgent.getRegion()->getCapability("GetMesh"); + mGetMesh2Capability = gAgent.getRegion()->getCapability("GetMesh2"); } } diff --git a/indra/newview/llmeshrepository.h b/indra/newview/llmeshrepository.h index 0dca29e7d4..74690e5a2a 100755 --- a/indra/newview/llmeshrepository.h +++ b/indra/newview/llmeshrepository.h @@ -328,13 +328,14 @@ public: LLCore::HttpOptions * mHttpLargeOptions; LLCore::HttpHeaders * mHttpHeaders; LLCore::HttpRequest::policy_t mHttpPolicyClass; + LLCore::HttpRequest::policy_t mHttpLegacyPolicyClass; LLCore::HttpRequest::policy_t mHttpLargePolicyClass; LLCore::HttpRequest::priority_t mHttpPriority; typedef std::set http_request_set; http_request_set mHttpRequestSet; // Outstanding HTTP requests - static std::string constructUrl(LLUUID mesh_id); + static std::string constructUrl(LLUUID mesh_id, int * cap_version); LLMeshRepoThread(); ~LLMeshRepoThread(); @@ -384,7 +385,8 @@ private: // or dispose of handler. // // Threads: Repo thread only - LLCore::HttpHandle getByteRange(const std::string & url, size_t offset, size_t len, + LLCore::HttpHandle getByteRange(const std::string & url, int cap_version, + size_t offset, size_t len, LLCore::HttpHandler * handler); private: @@ -595,7 +597,7 @@ public: void updateInventory(inventory_data data); std::string mGetMeshCapability; - + std::string mGetMesh2Capability; }; extern LLMeshRepository gMeshRepo; -- cgit v1.2.3 From 2cdddab384ce0bf4f6786a3fee08bea3f467e7c9 Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Thu, 27 Jun 2013 13:55:05 -0400 Subject: SH-4310/BUG-2810/MAINT-2794 Better status checking and error logging in Mesh code. Pay correct attention to status codes coming back from services. Generate better and consistent error messages when problems arise. There's more to do in error handling, need a way to cleanly fail all request types, only have that for LOD at this point. Do better keeping the HTTP pipeline between the low and high water marks. This was made challenging because the outer most code couldn't really see what's going on internally (whose actions are delayed in a worker thread). More to do here, the debug-like requests don't honor limits, that will come later. Made retry counts available from llcorehttp which can be used by the throttle-anticipating logic to advance the count. It helps but it reinforces the coupling between viewer and server which I do not like. --- indra/llcorehttp/httpresponse.h | 10 +- indra/newview/llmeshrepository.cpp | 354 ++++++++++++++++++++++++------------- indra/newview/llmeshrepository.h | 6 +- 3 files changed, 244 insertions(+), 126 deletions(-) (limited to 'indra') diff --git a/indra/llcorehttp/httpresponse.h b/indra/llcorehttp/httpresponse.h index a7f296e03f..bb4b59c16e 100755 --- a/indra/llcorehttp/httpresponse.h +++ b/indra/llcorehttp/httpresponse.h @@ -147,8 +147,14 @@ public: /// Get and set retry attempt information on the request. void getRetries(unsigned int * retries, unsigned int * retries_503) const { - *retries = mRetries; - *retries_503 = m503Retries; + if (retries) + { + *retries = mRetries; + } + if (retries_503) + { + *retries_503 = m503Retries; + } } void setRetries(unsigned int retries, unsigned int retries_503) diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index 1cda0b6a71..a86f0e86f1 100755 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -78,10 +78,12 @@ LLMeshRepository gMeshRepo; -const S32 MESH_HEADER_SIZE = 4096; +const S32 MESH_HEADER_SIZE = 4096; // Important: assumption is that headers fit in this space const U32 MAX_MESH_REQUESTS_PER_SECOND = 100; const S32 REQUEST_HIGH_WATER_MIN = 32; +const S32 REQUEST_HIGH_WATER_MAX = 80; const S32 REQUEST_LOW_WATER_MIN = 16; +const S32 REQUEST_LOW_WATER_MAX = 40; const U32 LARGE_MESH_FETCH_THRESHOLD = 1U << 21; // Size at which requests goes to narrow/slow queue const long LARGE_MESH_XFER_TIMEOUT = 240L; // Seconds to complete xfer @@ -122,6 +124,7 @@ const std::string header_lod[] = "medium_lod", "high_lod" }; +const char * const LOG_MESH = "Mesh"; // Static data and functions to measure mesh load // time metrics for a new region scene. @@ -211,12 +214,21 @@ void get_vertex_buffer_from_mesh(LLCDMeshData& mesh, LLModel::PhysicsMesh& res, } } -S32 LLMeshRepoThread::sActiveHeaderRequests = 0; -S32 LLMeshRepoThread::sActiveLODRequests = 0; +volatile S32 LLMeshRepoThread::sActiveHeaderRequests = 0; +volatile S32 LLMeshRepoThread::sActiveLODRequests = 0; U32 LLMeshRepoThread::sMaxConcurrentRequests = 1; S32 LLMeshRepoThread::sRequestLowWater = REQUEST_LOW_WATER_MIN; S32 LLMeshRepoThread::sRequestHighWater = REQUEST_HIGH_WATER_MIN; +// Base handler class for all mesh users of llcorehttp. +// This is roughly equivalent to a Responder class in +// traditional LL code. The base is going to perform +// common response/data handling in the inherited +// onCompleted() method. Derived classes, one for each +// type of HTTP action, define processData() and +// processFailure() methods to customize handling and +// error messages. +// class LLMeshHandlerBase : public LLCore::HttpHandler { public: @@ -225,8 +237,10 @@ public: mMeshParams(), mProcessed(false), mHttpHandle(LLCORE_HTTP_HANDLE_INVALID) - {} - virtual ~LLMeshHandlerBase(); + {} + + virtual ~LLMeshHandlerBase() + {} protected: LLMeshHandlerBase(const LLMeshHandlerBase &); // Not defined @@ -244,6 +258,9 @@ public: }; +// Subclass for header fetches. +// +// Thread: repo class LLMeshHeaderHandler : public LLMeshHandlerBase { public: @@ -265,6 +282,9 @@ public: }; +// Subclass for LOD fetches. +// +// Thread: repo class LLMeshLODHandler : public LLMeshHandlerBase { public: @@ -294,6 +314,9 @@ public: }; +// Subclass for skin info fetches. +// +// Thread: repo class LLMeshSkinInfoHandler : public LLMeshHandlerBase { public: @@ -320,6 +343,9 @@ public: }; +// Subclass for decomposition fetches. +// +// Thread: repo class LLMeshDecompositionHandler : public LLMeshHandlerBase { public: @@ -346,6 +372,9 @@ public: }; +// Subclass for physics shape fetches. +// +// Thread: repo class LLMeshPhysicsShapeHandler : public LLMeshHandlerBase { public: @@ -371,6 +400,7 @@ public: U32 mOffset; }; + void log_upload_error(S32 status, const LLSD& content, std::string stage, std::string model_name) { // Add notification popup. @@ -555,6 +585,7 @@ public: LLMeshRepoThread::LLMeshRepoThread() : LLThread("mesh repo"), mWaiting(false), + mHttpRetries(0U), mHttpRequest(NULL), mHttpOptions(NULL), mHttpLargeOptions(NULL), @@ -583,9 +614,9 @@ LLMeshRepoThread::LLMeshRepoThread() LLMeshRepoThread::~LLMeshRepoThread() { - LL_INFOS("Mesh") << "Small GETs issued: " - << mHttpGetCount << ", Large GETs issued: " - << mHttpLargeGetCount << LL_ENDL; + LL_INFOS(LOG_MESH) << "Small GETs issued: " << mHttpGetCount + << ", Large GETs issued: " << mHttpLargeGetCount + << LL_ENDL; for (http_request_set::iterator iter(mHttpRequestSet.begin()); iter != mHttpRequestSet.end(); @@ -631,10 +662,12 @@ void LLMeshRepoThread::run() { if (! mHttpRequestSet.empty()) { + // Dispatch all HttpHandler notifications mHttpRequest->update(0L); } mWaiting = true; + ms_sleep(5); mSignal->wait(); mWaiting = false; @@ -648,42 +681,49 @@ void LLMeshRepoThread::run() last_hundred = gFrameTimeSeconds; count = 0; } - + else + { + count += mHttpRetries; + } + mHttpRetries = 0U; + // NOTE: throttling intentionally favors LOD requests over header requests - while (!mLODReqQ.empty() && count < MAX_MESH_REQUESTS_PER_SECOND) + while (!mLODReqQ.empty() && count < MAX_MESH_REQUESTS_PER_SECOND && mHttpRequestSet.size() < sRequestHighWater) { - if (mMutex) + if (! mMutex) + { + break; + } + mMutex->lock(); + LODRequest req = mLODReqQ.front(); + mLODReqQ.pop(); + LLMeshRepository::sLODProcessing--; + mMutex->unlock(); + if (!fetchMeshLOD(req.mMeshParams, req.mLOD, count))//failed, resubmit { mMutex->lock(); - LODRequest req = mLODReqQ.front(); - mLODReqQ.pop(); - LLMeshRepository::sLODProcessing--; + mLODReqQ.push(req) ; + ++LLMeshRepository::sLODProcessing; mMutex->unlock(); - if (!fetchMeshLOD(req.mMeshParams, req.mLOD, count))//failed, resubmit - { - mMutex->lock(); - mLODReqQ.push(req) ; - ++LLMeshRepository::sLODProcessing; - mMutex->unlock(); - } } } - while (!mHeaderReqQ.empty() && count < MAX_MESH_REQUESTS_PER_SECOND) + while (!mHeaderReqQ.empty() && count < MAX_MESH_REQUESTS_PER_SECOND && mHttpRequestSet.size() < sRequestHighWater) { - if (mMutex) + if (! mMutex) + { + break; + } + mMutex->lock(); + HeaderRequest req = mHeaderReqQ.front(); + mHeaderReqQ.pop(); + mMutex->unlock(); + if (!fetchMeshHeader(req.mMeshParams, count))//failed, resubmit { mMutex->lock(); - HeaderRequest req = mHeaderReqQ.front(); - mHeaderReqQ.pop(); + mHeaderReqQ.push(req) ; mMutex->unlock(); - if (!fetchMeshHeader(req.mMeshParams, count))//failed, resubmit - { - mMutex->lock(); - mHeaderReqQ.push(req) ; - mMutex->unlock(); - } } } @@ -697,7 +737,7 @@ void LLMeshRepoThread::run() incomplete.insert(mesh_id); } } - mSkinRequests = incomplete; + mSkinRequests.swap(incomplete); } { //mDecompositionRequests is protected by mSignal @@ -710,7 +750,7 @@ void LLMeshRepoThread::run() incomplete.insert(mesh_id); } } - mDecompositionRequests = incomplete; + mDecompositionRequests.swap(incomplete); } { //mPhysicsShapeRequests is protected by mSignal @@ -723,7 +763,7 @@ void LLMeshRepoThread::run() incomplete.insert(mesh_id); } } - mPhysicsShapeRequests = incomplete; + mPhysicsShapeRequests.swap(incomplete); } } } @@ -796,6 +836,10 @@ void LLMeshRepoThread::loadMeshLOD(const LLVolumeParams& mesh_params, S32 lod) } } +// Constructs a Cap URL for the mesh. Prefers a GetMesh2 cap +// over a GetMesh cap and returns what it finds to the caller +// as an int ([1..2]). +// //static std::string LLMeshRepoThread::constructUrl(LLUUID mesh_id, int * cap_version) { @@ -822,14 +866,26 @@ std::string LLMeshRepoThread::constructUrl(LLUUID mesh_id, int * cap_version) } else { - llwarns << "Current region does not have GetMesh capability! Cannot load " << mesh_id << ".mesh" << llendl; + LL_WARNS_ONCE(LOG_MESH) << "Current region does not have GetMesh capability! Cannot load " + << mesh_id << ".mesh" << LL_ENDL; } *cap_version = version; return http_url; } -// May only be called by repo thread +// Issue an HTTP GET request with byte range using the right +// policy class. Large requests go to the large request class. +// If the current region supports GetMesh2, we prefer that for +// smaller requests otherwise we try to use the traditional +// GetMesh capability and connection concurrency. +// +// @return Valid handle or LLCORE_HTTP_HANDLE_INVALID. +// If the latter, actual status is found in +// mHttpStatus member which is valid until the +// next call to this method. +// +// Thread: repo LLCore::HttpHandle LLMeshRepoThread::getByteRange(const std::string & url, int cap_version, size_t offset, size_t len, LLCore::HttpHandler * handler) @@ -862,6 +918,11 @@ LLCore::HttpHandle LLMeshRepoThread::getByteRange(const std::string & url, int c handler); ++mHttpLargeGetCount; } + if (LLCORE_HTTP_HANDLE_INVALID == handle) + { + // Something went wrong, capture the error code for caller. + mHttpStatus = mHttpRequest->getStatus(); + } return handle; } @@ -929,12 +990,13 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id) if (!http_url.empty()) { LLMeshSkinInfoHandler * handler = new LLMeshSkinInfoHandler(mesh_id, offset, size); - // LL_WARNS("Mesh") << "MESH: Issuing Skin Info Request" << LL_ENDL; LLCore::HttpHandle handle = getByteRange(http_url, cap_version, offset, size, handler); if (LLCORE_HTTP_HANDLE_INVALID == handle) { - // *TODO: Better error message - llwarns << "HTTP GET request failed for mesh " << mID << llendl; + LL_WARNS(LOG_MESH) << "HTTP GET request failed for skin info on mesh " << mID + << ". Reason: " << mHttpStatus.toString() + << " (" << mHttpStatus.toHex() << ")" + << LL_ENDL; delete handler; ret = false; } @@ -1019,12 +1081,13 @@ bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id) if (!http_url.empty()) { LLMeshDecompositionHandler * handler = new LLMeshDecompositionHandler(mesh_id, offset, size); - // LL_WARNS("Mesh") << "MESH: Issuing Decomp Request" << LL_ENDL; LLCore::HttpHandle handle = getByteRange(http_url, cap_version, offset, size, handler); if (LLCORE_HTTP_HANDLE_INVALID == handle) { - // *TODO: Better error message - llwarns << "HTTP GET request failed for decomposition mesh " << mID << llendl; + LL_WARNS(LOG_MESH) << "HTTP GET request failed for decomposition mesh " << mID + << ". Reason: " << mHttpStatus.toString() + << " (" << mHttpStatus.toHex() << ")" + << LL_ENDL; delete handler; ret = false; } @@ -1108,12 +1171,13 @@ bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id) if (!http_url.empty()) { LLMeshPhysicsShapeHandler * handler = new LLMeshPhysicsShapeHandler(mesh_id, offset, size); - // LL_WARNS("Mesh") << "MESH: Issuing Physics Shape Request" << LL_ENDL; LLCore::HttpHandle handle = getByteRange(http_url, cap_version, offset, size, handler); if (LLCORE_HTTP_HANDLE_INVALID == handle) { - // *TODO: Better error message - llwarns << "HTTP GET request failed for physics shape mesh " << mID << llendl; + LL_WARNS(LOG_MESH) << "HTTP GET request failed for physics shape on mesh " << mID + << ". Reason: " << mHttpStatus.toString() + << " (" << mHttpStatus.toHex() << ")" + << LL_ENDL; delete handler; ret = false; } @@ -1192,7 +1256,7 @@ bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params, U32& c } //either cache entry doesn't exist or is corrupt, request header from simulator - bool retval = true ; + bool retval = true; int cap_version(1); std::string http_url = constructUrl(mesh_params.getSculptID(), &cap_version); if (!http_url.empty()) @@ -1202,12 +1266,13 @@ bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params, U32& c //NOTE -- this will break of headers ever exceed 4KB LLMeshHeaderHandler * handler = new LLMeshHeaderHandler(mesh_params); - // LL_WARNS("Mesh") << "MESH: Issuing Request" << LL_ENDL; LLCore::HttpHandle handle = getByteRange(http_url, cap_version, 0, MESH_HEADER_SIZE, handler); if (LLCORE_HTTP_HANDLE_INVALID == handle) { - // *TODO: Better error message - llwarns << "HTTP GET request failed for mesh " << mID << llendl; + LL_WARNS(LOG_MESH) << "HTTP GET request failed for mesh header " << mID + << ". Reason: " << mHttpStatus.toString() + << " (" << mHttpStatus.toHex() << ")" + << LL_ENDL; delete handler; retval = false; } @@ -1216,8 +1281,8 @@ bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params, U32& c handler->mHttpHandle = handle; mHttpRequestSet.insert(handler); ++LLMeshRepository::sHTTPRequestCount; + ++count; } - count++; } return retval; @@ -1283,12 +1348,13 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, if (!http_url.empty()) { LLMeshLODHandler * handler = new LLMeshLODHandler(mesh_params, lod, offset, size); - // LL_WARNS("Mesh") << "MESH: Issuing LOD Request" << LL_ENDL; LLCore::HttpHandle handle = getByteRange(http_url, cap_version, offset, size, handler); if (LLCORE_HTTP_HANDLE_INVALID == handle) { - // *TODO: Better error message - llwarns << "HTTP GET request failed for LOD mesh " << mID << llendl; + LL_WARNS(LOG_MESH) << "HTTP GET request failed for LOD on mesh " << mID + << ". Reason: " << mHttpStatus.toString() + << " (" << mHttpStatus.toHex() << ")" + << LL_ENDL; delete handler; retval = false; } @@ -1297,8 +1363,8 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, handler->mHttpHandle = handle; mHttpRequestSet.insert(handler); ++LLMeshRepository::sHTTPRequestCount; + ++count; } - count++; } else { @@ -1320,6 +1386,7 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, bool LLMeshRepoThread::headerReceived(const LLVolumeParams& mesh_params, U8* data, S32 data_size) { + const LLUUID mesh_id = mesh_params.getSculptID(); LLSD header; U32 header_size = 0; @@ -1340,7 +1407,8 @@ bool LLMeshRepoThread::headerReceived(const LLVolumeParams& mesh_params, U8* dat if (!LLSDSerialize::fromBinary(header, stream, data_size)) { - llwarns << "Mesh header parse error. Not a valid mesh asset!" << llendl; + LL_WARNS(LOG_MESH) << "Mesh header parse error. Not a valid mesh asset! ID: " << mesh_id + << LL_ENDL; return false; } @@ -1348,13 +1416,12 @@ bool LLMeshRepoThread::headerReceived(const LLVolumeParams& mesh_params, U8* dat } else { - llinfos - << "Marking header as non-existent, will not retry." << llendl; + LL_INFOS(LOG_MESH) << "Non-positive data size. Marking header as non-existent, will not retry. ID: " << mesh_id + << LL_ENDL; header["404"] = 1; } { - LLUUID mesh_id = mesh_params.getSculptID(); { LLMutexLock lock(mHeaderMutex); @@ -1416,7 +1483,8 @@ bool LLMeshRepoThread::skinInfoReceived(const LLUUID& mesh_id, U8* data, S32 dat if (!unzip_llsd(skin, stream, data_size)) { - llwarns << "Mesh skin info parse error. Not a valid mesh asset!" << llendl; + LL_WARNS(LOG_MESH) << "Mesh skin info parse error. Not a valid mesh asset! ID: " << mesh_id + << LL_ENDL; return false; } } @@ -1444,7 +1512,8 @@ bool LLMeshRepoThread::decompositionReceived(const LLUUID& mesh_id, U8* data, S3 if (!unzip_llsd(decomp, stream, data_size)) { - llwarns << "Mesh decomposition parse error. Not a valid mesh asset!" << llendl; + LL_WARNS(LOG_MESH) << "Mesh decomposition parse error. Not a valid mesh asset! ID: " << mesh_id + << LL_ENDL; return false; } } @@ -1860,7 +1929,8 @@ void LLMeshUploadThread::doWholeModelUpload() if (mWholeModelUploadURL.empty()) { - llinfos << "unable to upload, fee request failed" << llendl; + LL_WARNS(LOG_MESH) << "Missing mesh upload capability, unable to upload, fee request failed." + << LL_ENDL; } else { @@ -1941,6 +2011,12 @@ void LLMeshRepoThread::notifyLoadedMeshes() return; } + if (!mLoadedQ.empty() || !mUnavailableQ.empty()) + { + // Ping time-to-load metrics for mesh download operations. + LLMeshRepository::metricsProgress(0); + } + while (!mLoadedQ.empty()) { mMutex->lock(); @@ -2058,12 +2134,17 @@ void LLMeshRepository::cacheOutgoingMesh(LLMeshUploadData& data, LLSD& header) } -LLMeshHandlerBase::~LLMeshHandlerBase() -{} - void LLMeshHandlerBase::onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response) { mProcessed = true; + + // Accumulate retries, we'll use these to offset the HTTP + // count and maybe hold to a throttle better. + unsigned int retries(0U); + response->getRetries(NULL, &retries); + gMeshRepo.mThread->mHttpRetries += retries; + LLMeshRepository::sHTTPRetryCount += retries; + LLCore::HttpStatus status(response->getStatus()); if (! status) { @@ -2090,7 +2171,7 @@ void LLMeshHandlerBase::onCompleted(LLCore::HttpHandle handle, LLCore::HttpRespo // that support BufferArray directly. data = new U8[data_size]; body->read(0, (char *) data, data_size); - LLMeshRepository::sBytesReceived += llmin(data_size, MESH_HEADER_SIZE); + LLMeshRepository::sBytesReceived += data_size; } processData(body, data, data_size); @@ -2100,7 +2181,6 @@ void LLMeshHandlerBase::onCompleted(LLCore::HttpHandle handle, LLCore::HttpRespo // Release handler gMeshRepo.mThread->mHttpRequestSet.erase(this); - delete this; // Must be last statement } @@ -2112,8 +2192,7 @@ LLMeshHeaderHandler::~LLMeshHeaderHandler() if (! mProcessed) { // something went wrong, retry - llwarns << "Timeout or service unavailable, retrying." << llendl; - LLMeshRepository::sHTTPRetryCount++; + LL_WARNS(LOG_MESH) << "Mesh header fetch canceled unexpectedly, retrying." << LL_ENDL; LLMeshRepoThread::HeaderRequest req(mMeshParams); LLMutexLock lock(gMeshRepo.mThread->mMutex); gMeshRepo.mThread->mHeaderReqQ.push(req); @@ -2124,36 +2203,39 @@ LLMeshHeaderHandler::~LLMeshHeaderHandler() void LLMeshHeaderHandler::processFailure(LLCore::HttpStatus status) { - LL_WARNS("Mesh") << "MESH: Processing Failure" << LL_ENDL; if (is_retryable(status)) { - llwarns << "Timeout or service unavailable, retrying." << llendl; - LLMeshRepository::sHTTPRetryCount++; + LL_WARNS(LOG_MESH) << "Error during mesh header handling. Reason: " << status.toString() + << " (" << status.toHex() << "). Retrying." + << LL_ENDL; LLMeshRepoThread::HeaderRequest req(mMeshParams); LLMutexLock lock(gMeshRepo.mThread->mMutex); gMeshRepo.mThread->mHeaderReqQ.push(req); } else { - // *TODO: better error message - llwarns << "Unhandled status." << llendl; + // *TODO: Mark mesh unavailable + LL_WARNS(LOG_MESH) << "Error during mesh header handling. Reason: " << status.toString() + << " (" << status.toHex() << "). Not retrying." + << LL_ENDL; } } void LLMeshHeaderHandler::processData(LLCore::BufferArray * body, U8 * data, S32 data_size) { - // LL_WARNS("Mesh") << "MESH: Processing Data" << LL_ENDL; + LLUUID mesh_id = mMeshParams.getSculptID(); bool success = gMeshRepo.mThread->headerReceived(mMeshParams, data, data_size); llassert(success); if (! success) { + // *TODO: Mark mesh unavailable // *TODO: Get real reason for parse failure here - llwarns << "Unable to parse mesh header: " << llendl; + LL_WARNS(LOG_MESH) << "Unable to parse mesh header. ID: " << mesh_id + << LL_ENDL; } 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(); @@ -2192,15 +2274,14 @@ void LLMeshHeaderHandler::processData(LLCore::BufferArray * body, U8 * data, S32 // zero out the rest of the file U8 block[MESH_HEADER_SIZE]; - memset(block, 0, MESH_HEADER_SIZE); + memset(block, 0, sizeof(block)); - while (bytes-file.tell() > MESH_HEADER_SIZE) + while (bytes-file.tell() > sizeof(block)) { - file.write(block, MESH_HEADER_SIZE); + file.write(block, sizeof(block)); } S32 remaining = bytes-file.tell(); - if (remaining > 0) { file.write(block, remaining); @@ -2216,8 +2297,7 @@ LLMeshLODHandler::~LLMeshLODHandler() { if (! mProcessed) { - llwarns << "Killed without being processed, retrying." << llendl; - LLMeshRepository::sHTTPRetryCount++; + LL_WARNS(LOG_MESH) << "Mesh LOD fetch canceled unexpectedly, retrying." << LL_ENDL; gMeshRepo.mThread->lockAndLoadMeshLOD(mMeshParams, mLOD); } LLMeshRepoThread::decActiveLODRequests(); @@ -2228,15 +2308,21 @@ 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); + LL_WARNS(LOG_MESH) << "Error during mesh header handling. Reason: " << status.toString() + << " (" << status.toHex() << "). Retrying." + << LL_ENDL; + { + LLMutexLock lock(gMeshRepo.mThread->mMutex); + + gMeshRepo.mThread->loadMeshLOD(mMeshParams, mLOD); + } } else { - // *TODO: better error message - llwarns << "Unhandled status." << llendl; + // *TODO: Mark mesh unavailable + LL_WARNS(LOG_MESH) << "Error during mesh LOD handling. Reason: " << status.toString() + << " (" << status.toHex() << "). Not retrying." + << LL_ENDL; } } @@ -2257,6 +2343,7 @@ void LLMeshLODHandler::processData(LLCore::BufferArray * body, U8 * data, S32 da LLMeshRepository::sCacheBytesWritten += size; } } + // *TODO: Mark mesh unavailable on error } LLMeshSkinInfoHandler::~LLMeshSkinInfoHandler() @@ -2268,15 +2355,21 @@ 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); + LL_WARNS(LOG_MESH) << "Error during mesh skin info handling. Reason: " << status.toString() + << " (" << status.toHex() << "). Retrying." + << LL_ENDL; + { + LLMutexLock lock(gMeshRepo.mThread->mMutex); + + gMeshRepo.mThread->loadMeshSkinInfo(mMeshID); + } } else { - // *TODO: better error message - llwarns << "Unhandled status." << llendl; + // *TODO: Mark mesh unavailable on error + LL_WARNS(LOG_MESH) << "Error during mesh skin info handling. Reason: " << status.toString() + << " (" << status.toHex() << "). Not retrying." + << LL_ENDL; } } @@ -2297,6 +2390,7 @@ void LLMeshSkinInfoHandler::processData(LLCore::BufferArray * body, U8 * data, S file.write(data, size); } } + // *TODO: Mark mesh unavailable on error } LLMeshDecompositionHandler::~LLMeshDecompositionHandler() @@ -2308,15 +2402,21 @@ 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); + LL_WARNS(LOG_MESH) << "Error during mesh decomposition handling. Reason: " << status.toString() + << " (" << status.toHex() << "). Retrying." + << LL_ENDL; + { + LLMutexLock lock(gMeshRepo.mThread->mMutex); + + gMeshRepo.mThread->loadMeshDecomposition(mMeshID); + } } else { - // *TODO: better error message - llwarns << "Unhandled status." << llendl; + // *TODO: Mark mesh unavailable on error + LL_WARNS(LOG_MESH) << "Error during mesh decomposition handling. Reason: " << status.toString() + << " (" << status.toHex() << "). Not retrying." + << LL_ENDL; } } @@ -2337,6 +2437,7 @@ void LLMeshDecompositionHandler::processData(LLCore::BufferArray * body, U8 * da file.write(data, size); } } + // *TODO: Mark mesh unavailable on error } LLMeshPhysicsShapeHandler::~LLMeshPhysicsShapeHandler() @@ -2348,15 +2449,21 @@ 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); + LL_WARNS(LOG_MESH) << "Error during mesh physics shape handling. Reason: " << status.toString() + << " (" << status.toHex() << "). Retrying." + << LL_ENDL; + { + LLMutexLock lock(gMeshRepo.mThread->mMutex); + + gMeshRepo.mThread->loadMeshPhysicsShape(mMeshID); + } } else { - // *TODO: better error message - llwarns << "Unhandled status." << llendl; + // *TODO: Mark mesh unavailable on error + LL_WARNS(LOG_MESH) << "Error during mesh physics shape handling. Reason: " << status.toString() + << " (" << status.toHex() << "). Not retrying." + << LL_ENDL; } } @@ -2377,6 +2484,7 @@ void LLMeshPhysicsShapeHandler::processData(LLCore::BufferArray * body, U8 * dat file.write(data, size); } } + // *TODO: Mark mesh unavailable on error } LLMeshRepository::LLMeshRepository() @@ -2574,12 +2682,15 @@ S32 LLMeshRepository::loadMesh(LLVOVolume* vobj, const LLVolumeParams& mesh_para void LLMeshRepository::notifyLoadedMeshes() { //called from main thread // *FIXME: Scaling down the setting by a factor of 4 for now to reflect - // target goal. May want to rename the setting before release. + // target goal. May want to rename the setting before release. Also + // want/need to get these in a coordinated fashion from llappcorehttp. LLMeshRepoThread::sMaxConcurrentRequests = gSavedSettings.getU32("MeshMaxConcurrentRequests") / 4; - LLMeshRepoThread::sRequestHighWater = llmax(50 * S32(LLMeshRepoThread::sMaxConcurrentRequests), - REQUEST_HIGH_WATER_MIN); - LLMeshRepoThread::sRequestLowWater = llmax(LLMeshRepoThread::sRequestLowWater / 2, - REQUEST_LOW_WATER_MIN); + LLMeshRepoThread::sRequestHighWater = llclamp(10 * S32(LLMeshRepoThread::sMaxConcurrentRequests), + REQUEST_HIGH_WATER_MIN, + REQUEST_HIGH_WATER_MAX); + LLMeshRepoThread::sRequestLowWater = llclamp(LLMeshRepoThread::sRequestHighWater / 2, + REQUEST_LOW_WATER_MIN, + REQUEST_LOW_WATER_MAX); //clean up completed upload threads for (std::vector::iterator iter = mUploads.begin(); iter != mUploads.end(); ) @@ -2672,6 +2783,10 @@ void LLMeshRepository::notifyLoadedMeshes() region_name = gAgent.getRegion()->getName(); mGetMeshCapability = gAgent.getRegion()->getCapability("GetMesh"); mGetMesh2Capability = gAgent.getRegion()->getCapability("GetMesh2"); + LL_DEBUGS(LOG_MESH) << "Retrieving caps for region '" << region_name + << "', GetMesh2: " << mGetMesh2Capability + << ", GetMesh: " << mGetMeshCapability + << LL_ENDL; } } @@ -2810,9 +2925,6 @@ 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 @@ -2823,7 +2935,8 @@ void LLMeshRepository::notifyMeshLoaded(const LLVolumeParams& mesh_params, LLVol //make sure target volume is still valid if (volume->getNumVolumeFaces() <= 0) { - llwarns << "Mesh loading returned empty volume." << llendl; + LL_WARNS(LOG_MESH) << "Mesh loading returned empty volume. ID: " << mesh_params.getSculptID() + << LL_ENDL; } { //update system volume @@ -2836,7 +2949,8 @@ void LLMeshRepository::notifyMeshLoaded(const LLVolumeParams& mesh_params, LLVol } else { - llwarns << "Couldn't find system volume for given mesh." << llendl; + LL_WARNS(LOG_MESH) << "Couldn't find system volume for mesh " << mesh_params.getSculptID() + << LL_ENDL; } } @@ -2856,9 +2970,6 @@ 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); @@ -3029,7 +3140,7 @@ LLSD& LLMeshRepoThread::getMeshHeader(const LLUUID& mesh_id) void LLMeshRepository::uploadModel(std::vector& data, LLVector3& scale, bool upload_textures, - bool upload_skin, bool upload_joints, std::string upload_url, bool do_upload, + bool upload_skin, bool upload_joints, std::string upload_url, bool do_upload, LLHandle fee_observer, LLHandle upload_observer) { LLMeshUploadThread* thread = new LLMeshUploadThread(data, scale, upload_textures, upload_skin, upload_joints, upload_url, @@ -3058,7 +3169,6 @@ S32 LLMeshRepository::getMeshSize(const LLUUID& mesh_id, S32 lod) } return -1; - } void LLMeshUploadThread::decomposeMeshMatrix(LLMatrix4& transformation, @@ -3880,7 +3990,7 @@ void LLMeshRepository::metricsUpdate() metrics["scope"] = "Login"; metrics["start"] = started; metrics["stop"] = stopped; - metrics["downloads"] = LLSD::Integer(total_count); + metrics["fetches"] = LLSD::Integer(total_count); metrics["teleports"] = LLSD::Integer(metrics_teleport_start_count); metrics["user_cpu"] = double(user_cpu) / 1.0e6; metrics["sys_cpu"] = double(sys_cpu) / 1.0e6; diff --git a/indra/newview/llmeshrepository.h b/indra/newview/llmeshrepository.h index 74690e5a2a..e90ab4dd23 100755 --- a/indra/newview/llmeshrepository.h +++ b/indra/newview/llmeshrepository.h @@ -221,8 +221,8 @@ class LLMeshRepoThread : public LLThread { public: - static S32 sActiveHeaderRequests; - static S32 sActiveLODRequests; + volatile static S32 sActiveHeaderRequests; + volatile static S32 sActiveLODRequests; static U32 sMaxConcurrentRequests; static S32 sRequestLowWater; static S32 sRequestHighWater; @@ -323,6 +323,8 @@ public: pending_lod_map mPendingLOD; // llcorehttp library interface objects. + LLCore::HttpStatus mHttpStatus; + unsigned int mHttpRetries; LLCore::HttpRequest * mHttpRequest; LLCore::HttpOptions * mHttpOptions; LLCore::HttpOptions * mHttpLargeOptions; -- cgit v1.2.3 From 350e658348431e4b1f0e5038cf999d9722a699aa Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Thu, 27 Jun 2013 20:38:18 -0400 Subject: SH-4311 Get highwater limiting of requests into llhttpcore working. Fixed the logic and have it covering all five types of requests now with validation via an assert (when enabled). Should keep things working smoothly and avoid floods of 503s when in debug modes. Also started a round of file-level documentation detailing thread usage and mutex coverage. More to do, more to describe. But the high- water stuff is functioning correctly. --- indra/newview/llmeshrepository.cpp | 189 ++++++++++++++++++++++++++++++++++--- indra/newview/llmeshrepository.h | 6 +- 2 files changed, 177 insertions(+), 18 deletions(-) (limited to 'indra') diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index a86f0e86f1..aa10e64af8 100755 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -76,9 +76,140 @@ #include + +// [ Disclaimer: this documentation isn't by one of the original authors +// but by someone coming through later and extracting intent and function. +// Some of this will be wrong so use judgement. ] +// +// Purpose +// +// The purpose of this module is to provide access between the viewer +// and the asset system as regards to mesh objects. +// +// * High-throughput download of mesh assets from servers while +// following best industry practices for network profile. +// * Reliable expensing and upload of new mesh assets. +// * Recovery and retry from errors when appropriate. +// * Decomposition of mesh assets for preview and uploads. +// * And most important: all of the above without exposing the +// main thread to stalls due to deep processing or thread +// locking actions. In particular, the following operations +// on LLMeshRepository are very averse to any stalls: +// * loadMesh +// * getMeshHeader (For structural details, see: +// http://wiki.secondlife.com/wiki/Mesh/Mesh_Asset_Format) +// * notifyLoadedMeshes +// +// Threads +// +// main Main rendering thread, very sensitive to locking and other stalls +// repo Overseeing worker thread associated with the LLMeshRepoThread class +// decom Worker thread for mesh decomposition requests +// core HTTP worker thread: does the work but doesn't intrude here +// uploadN 0-N temporary mesh upload threads +// +// Mutexes +// +// LLMeshRepository::mMeshMutex +// LLMeshRepoThread::mMutex +// LLMeshRepoThread::mHeaderMutex +// LLMeshRepoThread::mSignal (LLCondition) +// LLPhysicsDecomp::mSignal (LLCondition) +// LLPhysicsDecomp::mMutex +// LLMeshUploadThread::mMutex +// +// Mutex Order Rules +// +// 1. LLMeshRepoThread::mMutex before LLMeshRepoThread::mHeaderMutex +// 2. LLMeshRepository::mMeshMutex before LLMeshRepoThread::mMutex +// (There are more rules, haven't been extracted.) +// +// Data Member Access/Locking +// +// Description of how shared access to static and instance data +// members is performed. Each member is followed by the name of +// the mutex, if any, covering the data and then a list of data +// access models each of which is a triplet of the following form: +// +// {ro, wo, rw}.{main, repo, any}.{mutex, none} +// Type of access: read-only, write-only, read-write. +// Accessing thread or 'any' +// Relevant mutex held during access (several may be held) or 'none' +// +// A careful eye will notice some unsafe operations. Many of these +// have an alibi of some form. Several types of alibi are identified +// and listed here: +// +// [0] No alibi. Probably unsafe. +// [1] Single-writer, self-consistent readers. Old data must +// be tolerated by any reader but data will come true eventually. +// [2] Like [1] but provides a hint about thread state. These +// may be unsafe. +// [3] empty() check outside of lock. Can me made safish when +// done in double-check lock style. But this depends on +// std:: implementation and memory model. +// [4] Appears to be covered by a mutex but doesn't need one. +// [5] Read of a double-checked lock. +// +// So, in addition to documentation, take this as a to-do/review +// list and see if you can improve things. +// +// LLMeshRepository: +// +// sBytesReceived +// sHTTPRequestCount +// sHTTPRetryCount +// sLODPending +// sLODProcessing +// sCacheBytesRead +// sCacheBytesWritten +// mLoadingMeshes none rw.main.none, rw.main.mMeshMutex [4] +// mSkinMap none rw.main.none +// mDecompositionMap none rw.main.none +// mPendingRequests mMeshMutex [4] rw.main.mMeshMutex +// mLoadingSkins mMeshMutex [4] rw.main.mMeshMutex +// mPendingSkinRequests mMeshMutex [4] rw.main.mMeshMutex +// mLoadingDecompositions mMeshMutex [4] rw.main.mMeshMutex +// mPendingDecompositionRequests mMeshMutex [4] rw.main.mMeshMutex +// mLoadingPhysicsShapes mMeshMutex [4] rw.main.mMeshMutex +// mPendingPhysicsShapeRequests mMeshMutex [4] rw.main.mMeshMutex +// mUploads none rw.main.none (upload thread accessing objects) +// mUploadWaitList none rw.main.none (upload thread accessing objects) +// mInventoryQ mMeshMutex [4] rw.main.mMeshMutex, ro.main.none [5] +// mUploadErrorQ mMeshMutex rw.main.mMeshMutex, rw.any.mMeshMutex +// mGetMeshCapability none rw.main.none [0], ro.any.none +// mGetMesh2Capability none rw.main.none [0], ro.any.none +// +// LLMeshRepoThread: +// +// sActiveHeaderRequests mMutex rw.any.mMutex, ro.repo.none [1] +// sActiveLODRequests mMutex rw.any.mMutex, ro.repo.none [1] +// sMaxConcurrentRequests mMutex wo.main.none, ro.repo.none, ro.main.mMutex +// mWaiting mMutex rw.repo.none, ro.main.none [2] (race - hint) +// mMeshHeader mHeaderMutex rw.repo.mHeaderMutex, ro.main.mHeaderMutex, ro.main.none [0] +// mMeshHeaderSize mHeaderMutex rw.repo.mHeaderMutex +// mSkinRequests none rw.repo.none, rw.main.none [0] +// mSkinInfoQ none rw.repo.none, rw.main.none [0] +// mDecompositionRequests none rw.repo.none, rw.main.none [0] +// mPhysicsShapeRequests none rw.repo.none, rw.main.none [0] +// mDecompositionQ none rw.repo.none, rw.main.none [0] +// mHeaderReqQ mMutex ro.repo.none [3], rw.repo.mMutex, rw.any.mMutex +// mLODReqQ mMutex ro.repo.none [3], rw.repo.mMutex, rw.any.mMutex +// mUnavailableQ mMutex rw.repo.none [0], ro.main.none [3], rw.main.mMutex +// mLoadedQ mMutex rw.repo.mMutex, ro.main.none [3], rw.main.mMutex +// mPendingLOD mMutex rw.repo.mMutex, rw.any.mMutex +// +// LLPhysicsDecomp: +// +// mRequestQ +// mCurRequest +// mCompletedQ +// + + LLMeshRepository gMeshRepo; -const S32 MESH_HEADER_SIZE = 4096; // Important: assumption is that headers fit in this space +const S32 MESH_HEADER_SIZE = 4096; // Important: assumption is that headers fit in this space const U32 MAX_MESH_REQUESTS_PER_SECOND = 100; const S32 REQUEST_HIGH_WATER_MIN = 32; const S32 REQUEST_HIGH_WATER_MAX = 80; @@ -727,12 +858,21 @@ void LLMeshRepoThread::run() } } - { //mSkinRequests is protected by mSignal + // For the final three request lists, if we scan any part of one + // list, we scan the entire thing. This gets us through any requests + // which can be resolved in the cache. It also keeps the request + // set somewhat fresher otherwise items at the end of the set + // order will lose. Keep to the throttle enforcement and pay + // attention to the highwater level (enforced in each fetchXXX() + // method). + if (! mSkinRequests.empty() && count < MAX_MESH_REQUESTS_PER_SECOND && mHttpRequestSet.size() < sRequestHighWater) + { + // *FIXME: this really does need a lock as do the following ones std::set incomplete; for (std::set::iterator iter = mSkinRequests.begin(); iter != mSkinRequests.end(); ++iter) { LLUUID mesh_id = *iter; - if (!fetchMeshSkinInfo(mesh_id)) + if (!fetchMeshSkinInfo(mesh_id, count)) { incomplete.insert(mesh_id); } @@ -740,12 +880,13 @@ void LLMeshRepoThread::run() mSkinRequests.swap(incomplete); } - { //mDecompositionRequests is protected by mSignal + if (! mDecompositionRequests.empty() && count < MAX_MESH_REQUESTS_PER_SECOND && mHttpRequestSet.size() < sRequestHighWater) + { std::set incomplete; for (std::set::iterator iter = mDecompositionRequests.begin(); iter != mDecompositionRequests.end(); ++iter) { LLUUID mesh_id = *iter; - if (!fetchMeshDecomposition(mesh_id)) + if (!fetchMeshDecomposition(mesh_id, count)) { incomplete.insert(mesh_id); } @@ -753,18 +894,23 @@ void LLMeshRepoThread::run() mDecompositionRequests.swap(incomplete); } - { //mPhysicsShapeRequests is protected by mSignal + if (! mPhysicsShapeRequests.empty() && count < MAX_MESH_REQUESTS_PER_SECOND && mHttpRequestSet.size() < sRequestHighWater) + { std::set incomplete; for (std::set::iterator iter = mPhysicsShapeRequests.begin(); iter != mPhysicsShapeRequests.end(); ++iter) { LLUUID mesh_id = *iter; - if (!fetchMeshPhysicsShape(mesh_id)) + if (!fetchMeshPhysicsShape(mesh_id, count)) { incomplete.insert(mesh_id); } } mPhysicsShapeRequests.swap(incomplete); } + + // For dev purposes, a dynamic change could make this false + // and that shouldn't assert. + // llassert_always(mHttpRequestSet.size() <= sRequestHighWater); } } @@ -927,8 +1073,8 @@ LLCore::HttpHandle LLMeshRepoThread::getByteRange(const std::string & url, int c } -bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id) -{ //protected by mMutex +bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id, U32& count) +{ if (!mHeaderMutex) { @@ -985,6 +1131,10 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id) } //reading from VFS failed for whatever reason, fetch from sim + if (count >= MAX_MESH_REQUESTS_PER_SECOND || mHttpRequestSet.size() >= sRequestHighWater) + { + return false; + } int cap_version(1); std::string http_url = constructUrl(mesh_id, &cap_version); if (!http_url.empty()) @@ -1018,8 +1168,8 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id) return ret; } -bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id) -{ //protected by mMutex +bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id, U32& count) +{ if (!mHeaderMutex) { return false; @@ -1076,6 +1226,10 @@ bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id) } //reading from VFS failed for whatever reason, fetch from sim + if (count >= MAX_MESH_REQUESTS_PER_SECOND || mHttpRequestSet.size() >= sRequestHighWater) + { + return false; + } int cap_version(1); std::string http_url = constructUrl(mesh_id, &cap_version); if (!http_url.empty()) @@ -1109,8 +1263,8 @@ bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id) return ret; } -bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id) -{ //protected by mMutex +bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id, U32& count) +{ if (!mHeaderMutex) { return false; @@ -1166,6 +1320,10 @@ bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id) } //reading from VFS failed for whatever reason, fetch from sim + if (count >= MAX_MESH_REQUESTS_PER_SECOND || mHttpRequestSet.size() >= sRequestHighWater) + { + return false; + } int cap_version(1); std::string http_url = constructUrl(mesh_id, &cap_version); if (!http_url.empty()) @@ -1290,7 +1448,7 @@ bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params, U32& c //return false if failed to get mesh lod. bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, U32& count) -{ //protected by mMutex +{ if (!mHeaderMutex) { return false; @@ -2249,7 +2407,7 @@ void LLMeshHeaderHandler::processData(LLCore::BufferArray * body, U8 * data, S32 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]; + const std::string & lod_name = header_lod[i]; lod_bytes = llmax(lod_bytes, header[lod_name]["offset"].asInteger()+header[lod_name]["size"].asInteger()); } @@ -3047,6 +3205,7 @@ void LLMeshRepository::fetchPhysicsShape(const LLUUID& mesh_id) std::set::iterator iter = mLoadingPhysicsShapes.find(mesh_id); if (iter == mLoadingPhysicsShapes.end()) { //no request pending for this skin info + // *FIXME: Nothing ever deletes entries, can't be right mLoadingPhysicsShapes.insert(mesh_id); mPendingPhysicsShapeRequests.push(mesh_id); } diff --git a/indra/newview/llmeshrepository.h b/indra/newview/llmeshrepository.h index e90ab4dd23..9bf14a3715 100755 --- a/indra/newview/llmeshrepository.h +++ b/indra/newview/llmeshrepository.h @@ -365,15 +365,15 @@ public: //send request for skin info, returns true if header info exists // (should hold onto mesh_id and try again later if header info does not exist) - bool fetchMeshSkinInfo(const LLUUID& mesh_id); + bool fetchMeshSkinInfo(const LLUUID& mesh_id, U32& count); //send request for decomposition, returns true if header info exists // (should hold onto mesh_id and try again later if header info does not exist) - bool fetchMeshDecomposition(const LLUUID& mesh_id); + bool fetchMeshDecomposition(const LLUUID& mesh_id, U32& count); //send request for PhysicsShape, returns true if header info exists // (should hold onto mesh_id and try again later if header info does not exist) - bool fetchMeshPhysicsShape(const LLUUID& mesh_id); + bool fetchMeshPhysicsShape(const LLUUID& mesh_id, U32& count); static void incActiveLODRequests(); static void decActiveLODRequests(); -- cgit v1.2.3 From 8c5518f275029094ed42979a0f30d8314afadc3d Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Fri, 28 Jun 2013 17:57:57 -0400 Subject: SH-4257 Prepare for new 'GetMesh2' capability. Needed to move the cap from response to region instance. --- indra/newview/llviewerregion.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/newview/llviewerregion.cpp b/indra/newview/llviewerregion.cpp index b8b53aa6e4..41f63c7ef6 100755 --- a/indra/newview/llviewerregion.cpp +++ b/indra/newview/llviewerregion.cpp @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2000&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 @@ -1597,6 +1597,7 @@ void LLViewerRegionImpl::buildCapabilityNames(LLSD& capabilityNames) capabilityNames.append("GetDisplayNames"); capabilityNames.append("GetMesh"); + capabilityNames.append("GetMesh2"); capabilityNames.append("GetObjectCost"); capabilityNames.append("GetObjectPhysicsData"); capabilityNames.append("GetTexture"); -- cgit v1.2.3 From a12d5d7c6d8d816512ec9fc1355b37ce38a89cce Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Fri, 28 Jun 2013 20:08:29 -0400 Subject: SH-4312 Clumsy configuration coordination between mesh and corehttp Taught llappcorehttp to register signals on the settings values that chagne behavior. Have initialization and settings changes sweep through settings and change them. Dynamic changes are tried but have no effect (produce a warning message) as dynamic settings still aren't supported but the plumbing is now connected. Just need to change llcorehttp. Bounced the 'teleport started' signal around and it ended up back where it started with some cleanup. This is making me less angry... --- indra/newview/llappcorehttp.cpp | 210 +++++++++++++++++++++++-------------- indra/newview/llappcorehttp.h | 9 +- indra/newview/llmeshrepository.cpp | 30 ++---- 3 files changed, 145 insertions(+), 104 deletions(-) (limited to 'indra') diff --git a/indra/newview/llappcorehttp.cpp b/indra/newview/llappcorehttp.cpp index 2467c02d4d..70e349e33d 100755 --- a/indra/newview/llappcorehttp.cpp +++ b/indra/newview/llappcorehttp.cpp @@ -28,10 +28,52 @@ #include "llappcorehttp.h" +#include "llappviewer.h" #include "llviewercontrol.h" const F64 LLAppCoreHttp::MAX_THREAD_WAIT_TIME(10.0); +static const struct +{ + LLAppCoreHttp::EAppPolicy mPolicy; + U32 mDefault; + U32 mMin; + U32 mMax; + U32 mDivisor; + std::string mKey; + const char * mUsage; +} init_data[] = // Default and dynamic values for classes +{ + { + LLAppCoreHttp::AP_TEXTURE, 8, 1, 12, 1, + "TextureFetchConcurrency", + "texture fetch" + }, + { + LLAppCoreHttp::AP_MESH1, 32, 1, 128, 1, + "MeshMaxConcurrentRequests", + "mesh fetch" + }, + { + LLAppCoreHttp::AP_MESH2, 8, 1, 32, 4, + "MeshMaxConcurrentRequests", + "mesh2 fetch" + }, + { + LLAppCoreHttp::AP_LARGE_MESH, 2, 1, 8, 1, + "", + "large mesh fetch" + }, + { + LLAppCoreHttp::AP_UPLOADS, 2, 1, 8, 1, + "", + "asset upload" + } +}; + +static void teleport_started(); +static void setting_changed(); + LLAppCoreHttp::LLAppCoreHttp() : mRequest(NULL), @@ -42,6 +84,7 @@ LLAppCoreHttp::LLAppCoreHttp() for (int i(0); i < LL_ARRAY_SIZE(mPolicies); ++i) { mPolicies[i] = LLCore::HttpRequest::DEFAULT_POLICY_ID; + mSettings[i] = 0U; } } @@ -55,45 +98,6 @@ LLAppCoreHttp::~LLAppCoreHttp() void LLAppCoreHttp::init() { - static const struct - { - EAppPolicy mPolicy; - U32 mDefault; - U32 mMin; - U32 mMax; - U32 mDivisor; - std::string mKey; - const char * mUsage; - } init_data[] = // Default and dynamic values for classes - { - { - AP_TEXTURE, 8, 1, 12, 1, - "TextureFetchConcurrency", - "texture fetch" - }, - { - // *FIXME: Should become 32, 1, 32, 1 before release - AP_MESH1, 8, 1, 32, 4, - "MeshMaxConcurrentRequests", - "mesh fetch" - }, - { - AP_MESH2, 8, 1, 32, 4, - "MeshMaxConcurrentRequests", - "mesh2 fetch" - }, - { - AP_LARGE_MESH, 2, 1, 8, 1, - "", - "large mesh fetch" - }, - { - AP_UPLOADS, 2, 1, 8, 1, - "", - "asset upload" - } - }; - LLCore::HttpStatus status = LLCore::HttpRequest::createService(); if (! status) { @@ -110,14 +114,12 @@ void LLAppCoreHttp::init() << LL_ENDL; } - // Establish HTTP Proxy. "LLProxy" is a special string which directs - // the code to use LLProxy::applyProxySettings() to establish any - // HTTP or SOCKS proxy for http operations. + // Establish HTTP Proxy, if desired. status = LLCore::HttpRequest::setPolicyGlobalOption(LLCore::HttpRequest::GP_LLPROXY, 1); if (! status) { - LL_ERRS("Init") << "Failed to set HTTP proxy for HTTP services. Reason: " << status.toString() - << LL_ENDL; + LL_WARNS("Init") << "Failed to set HTTP proxy for HTTP services. Reason: " << status.toString() + << LL_ENDL; } // Tracing levels for library & libcurl (note that 2 & 3 are beyond spammy): @@ -137,7 +139,6 @@ void LLAppCoreHttp::init() mPolicies[AP_DEFAULT] = LLCore::HttpRequest::DEFAULT_POLICY_ID; // Setup additional policies based on table and some special rules - // *TODO: Make these configurations dynamic later for (int i(0); i < LL_ARRAY_SIZE(init_data); ++i) { const EAppPolicy policy(init_data[i].mPolicy); @@ -162,40 +163,10 @@ void LLAppCoreHttp::init() continue; } } - - // Get target connection concurrency value - U32 setting(init_data[i].mDefault); - if (! init_data[i].mKey.empty() && gSavedSettings.controlExists(init_data[i].mKey)) - { - U32 new_setting(gSavedSettings.getU32(init_data[i].mKey)); - if (new_setting) - { - // Treat zero settings as an ask for default - setting = new_setting / init_data[i].mDivisor; - setting = llclamp(setting, init_data[i].mMin, init_data[i].mMax); - } - } - - // Set it and report - // *TODO: These are intended to be per-host limits when we can - // support that in llcorehttp/libcurl. - LLCore::HttpStatus status; - status = LLCore::HttpRequest::setPolicyClassOption(mPolicies[policy], - LLCore::HttpRequest::CP_CONNECTION_LIMIT, - setting); - if (! status) - { - LL_WARNS("Init") << "Unable to set " << init_data[i].mUsage - << " concurrency. Reason: " << status.toString() - << LL_ENDL; - } - else if (setting != init_data[i].mDefault) - { - LL_INFOS("Init") << "Application settings overriding default " << init_data[i].mUsage - << " concurrency. New value: " << setting - << LL_ENDL; - } } + + // Apply initial settings + refreshSettings(true); // Kick the thread status = LLCore::HttpRequest::startThread(); @@ -206,6 +177,30 @@ void LLAppCoreHttp::init() } mRequest = new LLCore::HttpRequest; + + // Register signals for settings and state changes + for (int i(0); i < LL_ARRAY_SIZE(init_data); ++i) + { + if (! init_data[i].mKey.empty() && gSavedSettings.controlExists(init_data[i].mKey)) + { + LLPointer cntrl_ptr = gSavedSettings.getControl(init_data[i].mKey); + if (cntrl_ptr.isNull()) + { + LL_WARNS("Init") << "Unable to set signal on global setting '" << init_data[i].mKey + << "'" << LL_ENDL; + } + else + { + mSettingsSignal[i] = cntrl_ptr->getCommitSignal()->connect(boost::bind(&setting_changed)); + } + } + } +} + + +void setting_changed() +{ + LLAppViewer::instance()->getAppCoreHttp().refreshSettings(false); } @@ -248,6 +243,11 @@ void LLAppCoreHttp::cleanup() } } + for (int i(0); i < LL_ARRAY_SIZE(init_data); ++i) + { + mSettingsSignal[i].disconnect(); + } + delete mRequest; mRequest = NULL; @@ -260,6 +260,60 @@ void LLAppCoreHttp::cleanup() } } +void LLAppCoreHttp::refreshSettings(bool initial) +{ + for (int i(0); i < LL_ARRAY_SIZE(init_data); ++i) + { + const EAppPolicy policy(init_data[i].mPolicy); + + // Get target connection concurrency value + U32 setting(init_data[i].mDefault); + if (! init_data[i].mKey.empty() && gSavedSettings.controlExists(init_data[i].mKey)) + { + U32 new_setting(gSavedSettings.getU32(init_data[i].mKey)); + if (new_setting) + { + // Treat zero settings as an ask for default + setting = new_setting / init_data[i].mDivisor; + setting = llclamp(setting, init_data[i].mMin, init_data[i].mMax); + } + } + + if (! initial && setting == mSettings[policy]) + { + // Unchanged, try next setting + continue; + } + + // Set it and report + // *TODO: These are intended to be per-host limits when we can + // support that in llcorehttp/libcurl. + LLCore::HttpStatus status; + status = LLCore::HttpRequest::setPolicyClassOption(mPolicies[policy], + LLCore::HttpRequest::CP_CONNECTION_LIMIT, + setting); + if (! status) + { + LL_WARNS("Init") << "Unable to set " << init_data[i].mUsage + << " concurrency. Reason: " << status.toString() + << LL_ENDL; + } + else + { + LL_DEBUGS("Init") << "Changed " << init_data[i].mUsage + << " concurrency. New value: " << setting + << LL_ENDL; + mSettings[policy] = setting; + if (initial && setting != init_data[i].mDefault) + { + LL_INFOS("Init") << "Application settings overriding default " << init_data[i].mUsage + << " concurrency. New value: " << setting + << LL_ENDL; + } + } + } +} + void LLAppCoreHttp::onCompleted(LLCore::HttpHandle, LLCore::HttpResponse *) { diff --git a/indra/newview/llappcorehttp.h b/indra/newview/llappcorehttp.h index 4a14c35966..6dc3bb2130 100755 --- a/indra/newview/llappcorehttp.h +++ b/indra/newview/llappcorehttp.h @@ -85,16 +85,21 @@ public: { return mPolicies[policy]; } + + // Apply initial or new settings from the environment. + void refreshSettings(bool initial); private: static const F64 MAX_THREAD_WAIT_TIME; private: - LLCore::HttpRequest * mRequest; + LLCore::HttpRequest * mRequest; // Request queue to issue shutdowns LLCore::HttpHandle mStopHandle; F64 mStopRequested; bool mStopped; - policy_t mPolicies[AP_COUNT]; + policy_t mPolicies[AP_COUNT]; // Policy class id for each connection set + U32 mSettings[AP_COUNT]; + boost::signals2::connection mSettingsSignal[AP_COUNT]; // Signals to global settings that affect us }; diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index aa10e64af8..3af9ce7342 100755 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -259,10 +259,9 @@ const char * const LOG_MESH = "Mesh"; // 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(); +boost::signals2::connection metrics_teleport_started_signal; +static void teleport_started(); static bool is_retryable(LLCore::HttpStatus status); //get the number of bytes resident in memory for given volume @@ -2667,29 +2666,18 @@ void LLMeshRepository::init() apr_sleep(100); } - + metrics_teleport_started_signal = LLViewerMessage::getInstance()->setTeleportStartedCallback(teleport_started); 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; - } + metrics_teleport_started_signal.disconnect(); for (U32 i = 0; i < mUploads.size(); ++i) { @@ -4109,6 +4097,7 @@ bool LLMeshRepository::meshRezEnabled() // static void LLMeshRepository::metricsStart() { + ++metrics_teleport_start_count; sQuiescentTimer.start(0); } @@ -4127,7 +4116,6 @@ void LLMeshRepository::metricsProgress(unsigned int this_count) if (first_start) { - ++metrics_teleport_start_count; metricsStart(); first_start = false; } @@ -4157,19 +4145,13 @@ void LLMeshRepository::metricsUpdate() } } -// 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() +void 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) -- cgit v1.2.3 From f42d17f6c1d206a3396d9b4629246c4778ae1ca8 Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Sat, 29 Jun 2013 03:08:32 +0000 Subject: Orphaned declaration preventing compilation. --- indra/newview/llappcorehttp.cpp | 1 - 1 file changed, 1 deletion(-) (limited to 'indra') diff --git a/indra/newview/llappcorehttp.cpp b/indra/newview/llappcorehttp.cpp index 70e349e33d..0c242e57db 100755 --- a/indra/newview/llappcorehttp.cpp +++ b/indra/newview/llappcorehttp.cpp @@ -71,7 +71,6 @@ static const struct } }; -static void teleport_started(); static void setting_changed(); -- cgit v1.2.3 From d8e32c58ce020f4d26af417d927a266209a40483 Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Wed, 3 Jul 2013 15:17:26 -0400 Subject: Update the example program so it handles meshes as well. Fix the request feed logic to use high/low-water level logic as is done in viewer code. --- indra/llcorehttp/examples/http_texture_load.cpp | 72 +++++++++++++++---------- 1 file changed, 44 insertions(+), 28 deletions(-) (limited to 'indra') diff --git a/indra/llcorehttp/examples/http_texture_load.cpp b/indra/llcorehttp/examples/http_texture_load.cpp index 909dc5b0cb..119e34f7ec 100755 --- a/indra/llcorehttp/examples/http_texture_load.cpp +++ b/indra/llcorehttp/examples/http_texture_load.cpp @@ -39,6 +39,7 @@ #include "httprequest.h" #include "httphandler.h" #include "httpresponse.h" +#include "httpoptions.h" #include "httpheaders.h" #include "bufferarray.h" #include "_mutex.h" @@ -79,11 +80,11 @@ public: WorkingSet(); ~WorkingSet(); - bool reload(LLCore::HttpRequest *); + bool reload(LLCore::HttpRequest *, LLCore::HttpOptions *); virtual void onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response); - void loadTextureUuids(FILE * in); + void loadAssetUuids(FILE * in); public: struct Spec @@ -93,18 +94,19 @@ public: int mLength; }; typedef std::set handle_set_t; - typedef std::vector texture_list_t; + typedef std::vector asset_list_t; public: bool mVerbose; bool mRandomRange; - int mMaxConcurrency; + int mRequestLowWater; + int mRequestHighWater; handle_set_t mHandles; int mRemaining; int mLimit; int mAt; std::string mUrl; - texture_list_t mTextures; + asset_list_t mAssets; int mErrorsApi; int mErrorsHttp; int mErrorsHttp404; @@ -224,17 +226,23 @@ int main(int argc, char** argv) // Get service point LLCore::HttpRequest * hr = new LLCore::HttpRequest(); + // Get request options + LLCore::HttpOptions * opt = new LLCore::HttpOptions(); + opt->setRetries(12); + opt->setUseRetryAfter(true); + // Get a handler/working set WorkingSet ws; // Fill the working set with work ws.mUrl = url_format; - ws.loadTextureUuids(uuids); + ws.loadAssetUuids(uuids); ws.mRandomRange = do_random; ws.mVerbose = do_verbose; - ws.mMaxConcurrency = 100; + ws.mRequestHighWater = 100; + ws.mRequestLowWater = ws.mRequestHighWater / 2; - if (! ws.mTextures.size()) + if (! ws.mAssets.size()) { std::cerr << "No UUIDs found in file '" << argv[optind] << "'." << std::endl; return 1; @@ -246,9 +254,9 @@ int main(int argc, char** argv) // Run it int passes(0); - while (! ws.reload(hr)) + while (! ws.reload(hr, opt)) { - hr->update(5000000); + hr->update(0); ms_sleep(2); if (0 == (++passes % 200)) { @@ -275,6 +283,8 @@ int main(int argc, char** argv) // Clean up hr->requestStopThread(NULL); ms_sleep(1000); + opt->release(); + opt = NULL; delete hr; LLCore::HttpRequest::destroyService(); term_curl(); @@ -325,7 +335,7 @@ WorkingSet::WorkingSet() mSuccesses(0), mByteCount(0L) { - mTextures.reserve(30000); + mAssets.reserve(30000); mHeaders = new LLCore::HttpHeaders; mHeaders->append("Accept", "image/x-j2c"); @@ -342,29 +352,35 @@ WorkingSet::~WorkingSet() } -bool WorkingSet::reload(LLCore::HttpRequest * hr) +bool WorkingSet::reload(LLCore::HttpRequest * hr, LLCore::HttpOptions * opt) { - int to_do((std::min)(mRemaining, mMaxConcurrency - int(mHandles.size()))); + if (mRequestLowWater <= mHandles.size()) + { + // Haven't fallen below low-water level yet. + return false; + } + + int to_do((std::min)(mRemaining, mRequestHighWater - int(mHandles.size()))); for (int i(0); i < to_do; ++i) { char buffer[1024]; #if defined(WIN32) - _snprintf_s(buffer, sizeof(buffer), sizeof(buffer) - 1, mUrl.c_str(), mTextures[mAt].mUuid.c_str()); + _snprintf_s(buffer, sizeof(buffer), sizeof(buffer) - 1, mUrl.c_str(), mAssets[mAt].mUuid.c_str()); #else - snprintf(buffer, sizeof(buffer), mUrl.c_str(), mTextures[mAt].mUuid.c_str()); + snprintf(buffer, sizeof(buffer), mUrl.c_str(), mAssets[mAt].mUuid.c_str()); #endif - int offset(mRandomRange ? ((unsigned long) rand()) % 1000000UL : mTextures[mAt].mOffset); - int length(mRandomRange ? ((unsigned long) rand()) % 1000000UL : mTextures[mAt].mLength); + int offset(mRandomRange ? ((unsigned long) rand()) % 1000000UL : mAssets[mAt].mOffset); + int length(mRandomRange ? ((unsigned long) rand()) % 1000000UL : mAssets[mAt].mLength); LLCore::HttpHandle handle; if (offset || length) { - handle = hr->requestGetByteRange(0, 0, buffer, offset, length, NULL, mHeaders, this); + handle = hr->requestGetByteRange(0, 0, buffer, offset, length, opt, mHeaders, this); } else { - handle = hr->requestGet(0, 0, buffer, NULL, mHeaders, this); + handle = hr->requestGet(0, 0, buffer, opt, mHeaders, this); } if (! handle) { @@ -459,21 +475,21 @@ void WorkingSet::onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * r } -void WorkingSet::loadTextureUuids(FILE * in) +void WorkingSet::loadAssetUuids(FILE * in) { char buffer[1024]; while (fgets(buffer, sizeof(buffer), in)) { - WorkingSet::Spec texture; + WorkingSet::Spec asset; char * state(NULL); char * token = strtok_r(buffer, " \t\n,", &state); if (token && 36 == strlen(token)) { // Close enough for this function - texture.mUuid = token; - texture.mOffset = 0; - texture.mLength = 0; + asset.mUuid = token; + asset.mOffset = 0; + asset.mLength = 0; token = strtok_r(buffer, " \t\n,", &state); if (token) { @@ -482,14 +498,14 @@ void WorkingSet::loadTextureUuids(FILE * in) if (token) { int length(atoi(token)); - texture.mOffset = offset; - texture.mLength = length; + asset.mOffset = offset; + asset.mLength = length; } } - mTextures.push_back(texture); + mAssets.push_back(asset); } } - mRemaining = mLimit = mTextures.size(); + mRemaining = mLimit = mAssets.size(); } -- cgit v1.2.3 From fb734d621e6fa2004d191849783e81da75992d06 Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Mon, 8 Jul 2013 20:31:09 +0000 Subject: Found the memory corruptor. String trimmer didn't have a valid termination test. Sheesh. Also get some more numbers out of the example/load test program which drives traffic. This should give some useful insights into how the current http throttle works (or doesn't) with varying client demands. --- indra/llcorehttp/_httpoprequest.cpp | 4 ++- indra/llcorehttp/examples/http_texture_load.cpp | 36 ++++++++++++++++++++++--- 2 files changed, 35 insertions(+), 5 deletions(-) (limited to 'indra') diff --git a/indra/llcorehttp/_httpoprequest.cpp b/indra/llcorehttp/_httpoprequest.cpp index 8cb7fee701..ce0fc605ab 100755 --- a/indra/llcorehttp/_httpoprequest.cpp +++ b/indra/llcorehttp/_httpoprequest.cpp @@ -1025,13 +1025,15 @@ char * os_strtrim(char * lstr) } if (*lstr) { - for (char * rstr(lstr + strlen(lstr)); *--rstr;) + char * rstr(lstr + strlen(lstr)); + while (lstr < rstr && *--rstr) { if (' ' == *rstr || '\t' == *rstr) { *rstr = '\0'; } } + llassert(lstr <= rstr); } return lstr; } diff --git a/indra/llcorehttp/examples/http_texture_load.cpp b/indra/llcorehttp/examples/http_texture_load.cpp index 119e34f7ec..88c8102b27 100755 --- a/indra/llcorehttp/examples/http_texture_load.cpp +++ b/indra/llcorehttp/examples/http_texture_load.cpp @@ -58,6 +58,7 @@ void usage(std::ostream & out); // Default command line settings static int concurrency_limit(40); +static int highwater(100); static char url_format[1024] = "http://example.com/some/path?texture_id=%s.texture"; #if defined(WIN32) @@ -113,6 +114,8 @@ public: int mErrorsHttp416; int mErrorsHttp500; int mErrorsHttp503; + int mRetries; + int mRetriesHttp503; int mSuccesses; long mByteCount; LLCore::HttpHeaders * mHeaders; @@ -160,7 +163,7 @@ int main(int argc, char** argv) bool do_verbose(false); int option(-1); - while (-1 != (option = getopt(argc, argv, "u:c:h?Rv"))) + while (-1 != (option = getopt(argc, argv, "u:c:h?RvH:"))) { switch (option) { @@ -184,6 +187,21 @@ int main(int argc, char** argv) } break; + case 'H': + { + unsigned long value; + char * end; + + value = strtoul(optarg, &end, 10); + if (value < 1 || value > 100 || *end != '\0') + { + usage(std::cerr); + return 1; + } + highwater = value; + } + break; + case 'R': do_random = true; break; @@ -239,7 +257,7 @@ int main(int argc, char** argv) ws.loadAssetUuids(uuids); ws.mRandomRange = do_random; ws.mVerbose = do_verbose; - ws.mRequestHighWater = 100; + ws.mRequestHighWater = highwater; ws.mRequestLowWater = ws.mRequestHighWater / 2; if (! ws.mAssets.size()) @@ -273,6 +291,8 @@ int main(int argc, char** argv) std::cout << "HTTP 404 errors: " << ws.mErrorsHttp404 << " HTTP 416 errors: " << ws.mErrorsHttp416 << " HTTP 500 errors: " << ws.mErrorsHttp500 << " HTTP 503 errors: " << ws.mErrorsHttp503 << std::endl; + std::cout << "Retries: " << ws.mRetries << " Retries on 503: " << ws.mRetriesHttp503 + << std::endl; std::cout << "User CPU: " << (metrics.mEndUTime - metrics.mStartUTime) << " uS System CPU: " << (metrics.mEndSTime - metrics.mStartSTime) << " uS Wall Time: " << (metrics.mEndWallTime - metrics.mStartWallTime) @@ -310,8 +330,10 @@ void usage(std::ostream & out) " -u printf-style format string for URL generation\n" " Default: " << url_format << "\n" " -R Issue GETs with random Range: headers\n" - " -c Maximum request concurrency. Range: [1..100]\n" + " -c Maximum connection concurrency. Range: [1..100]\n" " Default: " << concurrency_limit << "\n" + " -H HTTP request highwater (requests fed to llcorehttp).\n" + " Range: [1..100] Default: " << highwater << "\n" " -v Verbose mode. Issue some chatter while running\n" " -h print this help\n" "\n" @@ -332,6 +354,8 @@ WorkingSet::WorkingSet() mErrorsHttp416(0), mErrorsHttp500(0), mErrorsHttp503(0), + mRetries(0), + mRetriesHttp503(0), mSuccesses(0), mByteCount(0L) { @@ -426,7 +450,7 @@ void WorkingSet::onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * r { // More success LLCore::BufferArray * data(response->getBody()); - mByteCount += data->size(); + mByteCount += data ? data->size() : 0; ++mSuccesses; } else @@ -462,6 +486,10 @@ void WorkingSet::onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * r ++mErrorsApi; } } + unsigned int retry(0U), retry_503(0U); + response->getRetries(&retry, &retry_503); + mRetries += int(retry); + mRetriesHttp503 += int(retry_503); mHandles.erase(it); } -- cgit v1.2.3 From eff651cffca60f2b69f6c596a8e9aa9e1ab44d3c Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Fri, 12 Jul 2013 15:00:24 -0400 Subject: SH-4312 Configuration data between viewer and llcorehttp is clumsy. Much improved. Unified the global and class options into a single option list. Implemented static and dynamic setting paths as much as possible. Dynamic path does require packet/RPC but otherwise there's near unification. Dynamic modes can't get values back yet due to the response/notifier scheme but this doesn't bother me. Flatten global and class options into simpler struct-like entities. Setter/getter available on these when needed (external APIs) but code can otherwise fiddle directly when it knows what to do. Much duplicated options/state removed from HttpPolicy. Comments cleaned up. Threads better described and consistently mentioned in API docs. Integration test extended for 503 responses with Reply-After headers. --- indra/llcorehttp/_httpinternal.h | 6 +- indra/llcorehttp/_httplibcurl.h | 15 ++- indra/llcorehttp/_httpoperation.h | 8 +- indra/llcorehttp/_httpoprequest.cpp | 19 ++- indra/llcorehttp/_httpopsetget.cpp | 93 ++++++++++--- indra/llcorehttp/_httpopsetget.h | 27 ++-- indra/llcorehttp/_httppolicy.cpp | 110 ++++++++------- indra/llcorehttp/_httppolicy.h | 39 ++++-- indra/llcorehttp/_httppolicyclass.cpp | 37 ++---- indra/llcorehttp/_httppolicyclass.h | 19 ++- indra/llcorehttp/_httppolicyglobal.cpp | 66 ++++----- indra/llcorehttp/_httppolicyglobal.h | 23 +++- indra/llcorehttp/_httpservice.cpp | 170 +++++++++++++++++++++--- indra/llcorehttp/_httpservice.h | 52 +++++--- indra/llcorehttp/examples/http_texture_load.cpp | 7 +- indra/llcorehttp/httpcommon.h | 109 +++++++++++++-- indra/llcorehttp/httprequest.cpp | 107 ++++++++------- indra/llcorehttp/httprequest.h | 128 +++++++++--------- indra/llcorehttp/tests/test_httprequest.hpp | 140 ++++++++++++++++++- indra/llcorehttp/tests/test_llcorehttp_peer.py | 49 ++++++- indra/newview/llappcorehttp.cpp | 29 ++-- 21 files changed, 881 insertions(+), 372 deletions(-) (limited to 'indra') diff --git a/indra/llcorehttp/_httpinternal.h b/indra/llcorehttp/_httpinternal.h index f085ca3b91..80f4f34942 100755 --- a/indra/llcorehttp/_httpinternal.h +++ b/indra/llcorehttp/_httpinternal.h @@ -36,7 +36,8 @@ // General library to-do list // // - Implement policy classes. Structure is mostly there just didn't -// need it for the first consumer. +// need it for the first consumer. [Classes are there. More +// advanced features, like borrowing, aren't there yet.] // - Consider Removing 'priority' from the request interface. Its use // in an always active class can lead to starvation of low-priority // requests. Requires coodination of priority values across all @@ -46,6 +47,7 @@ // may not really need it. // - Set/get for global policy and policy classes is clumsy. Rework // it heading in a direction that allows for more dynamic behavior. +// [Mostly fixed] // - Move HttpOpRequest::prepareRequest() to HttpLibcurl for the // pedantic. // - Update downloader and other long-duration services are going to @@ -73,7 +75,7 @@ // the main source file. // - Expand areas of usage eventually leading to the removal of LLCurl. // Rough order of expansion: -// . Mesh fetch +// . Mesh fetch [Underway] // . Avatar names // . Group membership lists // . Caps access in general diff --git a/indra/llcorehttp/_httplibcurl.h b/indra/llcorehttp/_httplibcurl.h index 611f029ef5..0ec90437bb 100755 --- a/indra/llcorehttp/_httplibcurl.h +++ b/indra/llcorehttp/_httplibcurl.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 @@ -71,16 +71,22 @@ public: /// /// @return Indication of how long this method is /// willing to wait for next service call. + /// + /// Threading: called by worker thread. HttpService::ELoopSpeed processTransport(); /// Add request to the active list. Caller is expected to have /// provided us with a reference count on the op to hold the /// request. (No additional references will be added.) + /// + /// Threading: called by worker thread. void addOp(HttpOpRequest * op); /// One-time call to set the number of policy classes to be /// serviced and to create the resources for each. Value /// must agree with HttpPolicy::setPolicies() call. + /// + /// Threading: called by init thread. void start(int policy_count); /// Synchronously stop libcurl operations. All active requests @@ -91,9 +97,13 @@ public: /// respective reply queues. /// /// Can be restarted with a start() call. + /// + /// Threading: called by worker thread. void shutdown(); /// Return global and per-class counts of active requests. + /// + /// Threading: called by worker thread. int getActiveCount() const; int getActiveCountInClass(int policy_class) const; @@ -103,6 +113,7 @@ public: /// /// @return True if handle was found and operation canceled. /// + /// Threading: called by worker thread. bool cancel(HttpHandle handle); protected: @@ -121,7 +132,7 @@ protected: HttpService * mService; // Simple reference, not owner active_set_t mActiveOps; int mPolicyCount; - CURLM ** mMultiHandles; + CURLM ** mMultiHandles; // One handle per policy class }; // end class HttpLibcurl } // end namespace LLCore diff --git a/indra/llcorehttp/_httpoperation.h b/indra/llcorehttp/_httpoperation.h index 914627fad0..937a61187d 100755 --- a/indra/llcorehttp/_httpoperation.h +++ b/indra/llcorehttp/_httpoperation.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 @@ -72,7 +72,7 @@ class HttpService; class HttpOperation : public LLCoreInt::RefCounted { public: - /// Threading: called by a consumer/application thread. + /// Threading: called by consumer thread. HttpOperation(); protected: @@ -108,7 +108,7 @@ public: /// by the worker thread. This is passible data /// until notification is performed. /// - /// Threading: called by application thread. + /// Threading: called by consumer thread. /// void setReplyPath(HttpReplyQueue * reply_queue, HttpHandler * handler); @@ -141,7 +141,7 @@ public: /// call to HttpRequest::update(). This method does the necessary /// dispatching. /// - /// Threading: called by application thread. + /// Threading: called by consumer thread. /// virtual void visitNotifier(HttpRequest *); diff --git a/indra/llcorehttp/_httpoprequest.cpp b/indra/llcorehttp/_httpoprequest.cpp index ce0fc605ab..d72f8f6119 100755 --- a/indra/llcorehttp/_httpoprequest.cpp +++ b/indra/llcorehttp/_httpoprequest.cpp @@ -402,7 +402,7 @@ HttpStatus HttpOpRequest::prepareRequest(HttpService * service) // *FIXME: better error handling later HttpStatus status; - // Get policy options + // Get global policy options HttpPolicyGlobal & policy(service->getPolicy().getGlobalOptions()); mCurlHandle = curl_easy_init(); @@ -441,30 +441,27 @@ HttpStatus HttpOpRequest::prepareRequest(HttpService * service) curl_easy_setopt(mCurlHandle, CURLOPT_SSL_VERIFYPEER, 1); curl_easy_setopt(mCurlHandle, CURLOPT_SSL_VERIFYHOST, 0); - const std::string * opt_value(NULL); - long opt_long(0L); - policy.get(HttpRequest::GP_LLPROXY, &opt_long); - if (opt_long) + if (policy.mUseLLProxy) { // Use the viewer-based thread-safe API which has a // fast/safe check for proxy enable. Would like to // encapsulate this someway... LLProxy::getInstance()->applyProxySettings(mCurlHandle); } - else if (policy.get(HttpRequest::GP_HTTP_PROXY, &opt_value)) + else if (policy.mHttpProxy.size()) { // *TODO: This is fine for now but get fuller socks5/ // authentication thing going later.... - curl_easy_setopt(mCurlHandle, CURLOPT_PROXY, opt_value->c_str()); + curl_easy_setopt(mCurlHandle, CURLOPT_PROXY, policy.mHttpProxy.c_str()); curl_easy_setopt(mCurlHandle, CURLOPT_PROXYTYPE, CURLPROXY_HTTP); } - if (policy.get(HttpRequest::GP_CA_PATH, &opt_value)) + if (policy.mCAPath.size()) { - curl_easy_setopt(mCurlHandle, CURLOPT_CAPATH, opt_value->c_str()); + curl_easy_setopt(mCurlHandle, CURLOPT_CAPATH, policy.mCAPath.c_str()); } - if (policy.get(HttpRequest::GP_CA_FILE, &opt_value)) + if (policy.mCAFile.size()) { - curl_easy_setopt(mCurlHandle, CURLOPT_CAINFO, opt_value->c_str()); + curl_easy_setopt(mCurlHandle, CURLOPT_CAINFO, policy.mCAFile.c_str()); } switch (mReqMethod) diff --git a/indra/llcorehttp/_httpopsetget.cpp b/indra/llcorehttp/_httpopsetget.cpp index 8198528a9b..a5363f9170 100755 --- a/indra/llcorehttp/_httpopsetget.cpp +++ b/indra/llcorehttp/_httpopsetget.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 @@ -27,6 +27,7 @@ #include "_httpopsetget.h" #include "httpcommon.h" +#include "httprequest.h" #include "_httpservice.h" #include "_httppolicy.h" @@ -43,10 +44,11 @@ namespace LLCore HttpOpSetGet::HttpOpSetGet() : HttpOperation(), - mIsGlobal(false), - mDoSet(false), - mSetting(-1), // Nothing requested - mLongValue(0L) + mReqOption(HttpRequest::PO_CONNECTION_LIMIT), + mReqClass(HttpRequest::INVALID_POLICY_ID), + mReqDoSet(false), + mReqLongValue(0L), + mReplyLongValue(0L) {} @@ -54,37 +56,84 @@ HttpOpSetGet::~HttpOpSetGet() {} -void HttpOpSetGet::setupGet(HttpRequest::EGlobalPolicy setting) +HttpStatus HttpOpSetGet::setupGet(HttpRequest::EPolicyOption opt, HttpRequest::policy_t pclass) { - mIsGlobal = true; - mSetting = setting; + HttpStatus status; + + mReqOption = opt; + mReqClass = pclass; + return status; } -void HttpOpSetGet::setupSet(HttpRequest::EGlobalPolicy setting, const std::string & value) +HttpStatus HttpOpSetGet::setupSet(HttpRequest::EPolicyOption opt, HttpRequest::policy_t pclass, long value) { - mIsGlobal = true; - mDoSet = true; - mSetting = setting; - mStrValue = value; + HttpStatus status; + + if (! HttpService::sOptionDesc[opt].mIsLong) + { + return HttpStatus(HttpStatus::LLCORE, HE_INVALID_ARG); + } + if (! HttpService::sOptionDesc[opt].mIsDynamic) + { + return HttpStatus(HttpStatus::LLCORE, HE_OPT_NOT_DYNAMIC); + } + + mReqOption = opt; + mReqClass = pclass; + mReqDoSet = true; + mReqLongValue = value; + + return status; } -void HttpOpSetGet::stageFromRequest(HttpService * service) +HttpStatus HttpOpSetGet::setupSet(HttpRequest::EPolicyOption opt, HttpRequest::policy_t pclass, const std::string & value) { - HttpPolicyGlobal & pol_opt(service->getPolicy().getGlobalOptions()); - HttpRequest::EGlobalPolicy setting(static_cast(mSetting)); + HttpStatus status; + + if (HttpService::sOptionDesc[opt].mIsLong) + { + return HttpStatus(HttpStatus::LLCORE, HE_INVALID_ARG); + } + if (! HttpService::sOptionDesc[opt].mIsDynamic) + { + return HttpStatus(HttpStatus::LLCORE, HE_OPT_NOT_DYNAMIC); + } + + mReqOption = opt; + mReqClass = pclass; + mReqDoSet = true; + mReqStrValue = value; - if (mDoSet) + return status; +} + + +void HttpOpSetGet::stageFromRequest(HttpService * service) +{ + if (mReqDoSet) { - mStatus = pol_opt.set(setting, mStrValue); + if (HttpService::sOptionDesc[mReqOption].mIsLong) + { + mStatus = service->setPolicyOption(mReqOption, mReqClass, + mReqLongValue, &mReplyLongValue); + } + else + { + mStatus = service->setPolicyOption(mReqOption, mReqClass, + mReqStrValue, &mReplyStrValue); + } } - if (mStatus) + else { - const std::string * value(NULL); - if ((mStatus = pol_opt.get(setting, &value))) + if (HttpService::sOptionDesc[mReqOption].mIsLong) + { + mStatus = service->getPolicyOption(mReqOption, mReqClass, &mReplyLongValue); + } + else { - mStrValue = *value; + mStatus = service->getPolicyOption(mReqOption, mReqClass, &mReplyStrValue); } } diff --git a/indra/llcorehttp/_httpopsetget.h b/indra/llcorehttp/_httpopsetget.h index 6966b9d94e..a1e76dd429 100755 --- a/indra/llcorehttp/_httpopsetget.h +++ b/indra/llcorehttp/_httpopsetget.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 @@ -46,7 +46,10 @@ namespace LLCore /// configuration settings. /// /// *NOTE: Expect this to change. Don't really like it yet. - +/// +/// *TODO: Can't return values to caller yet. Need to do +/// something better with HttpResponse and visitNotifier(). +/// class HttpOpSetGet : public HttpOperation { public: @@ -61,19 +64,23 @@ private: public: /// Threading: called by application thread - void setupGet(HttpRequest::EGlobalPolicy setting); - void setupSet(HttpRequest::EGlobalPolicy setting, const std::string & value); + HttpStatus setupGet(HttpRequest::EPolicyOption opt, HttpRequest::policy_t pclass); + HttpStatus setupSet(HttpRequest::EPolicyOption opt, HttpRequest::policy_t pclass, long value); + HttpStatus setupSet(HttpRequest::EPolicyOption opt, HttpRequest::policy_t pclass, const std::string & value); virtual void stageFromRequest(HttpService *); public: // Request data - bool mIsGlobal; - bool mDoSet; - int mSetting; - long mLongValue; - std::string mStrValue; - + HttpRequest::EPolicyOption mReqOption; + HttpRequest::policy_t mReqClass; + bool mReqDoSet; + long mReqLongValue; + std::string mReqStrValue; + + // Reply Data + long mReplyLongValue; + std::string mReplyStrValue; }; // end class HttpOpSetGet diff --git a/indra/llcorehttp/_httppolicy.cpp b/indra/llcorehttp/_httppolicy.cpp index 5f303dd0fe..2754e8ef07 100755 --- a/indra/llcorehttp/_httppolicy.cpp +++ b/indra/llcorehttp/_httppolicy.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 @@ -41,57 +41,64 @@ namespace LLCore // Per-policy-class data for a running system. -// Collection of queues, parameters, history, metrics, etc. +// Collection of queues, options and other data // for a single policy class. // // Threading: accessed only by worker thread -struct HttpPolicy::State +struct HttpPolicy::ClassState { public: - State() - : mConnMax(HTTP_CONNECTION_LIMIT_DEFAULT), - mConnAt(HTTP_CONNECTION_LIMIT_DEFAULT), - mConnMin(1), - mNextSample(0), - mErrorCount(0), - mErrorFactor(0) + ClassState() {} HttpReadyQueue mReadyQueue; HttpRetryQueue mRetryQueue; HttpPolicyClass mOptions; - - long mConnMax; - long mConnAt; - long mConnMin; - - HttpTime mNextSample; - unsigned long mErrorCount; - unsigned long mErrorFactor; }; HttpPolicy::HttpPolicy(HttpService * service) - : mActiveClasses(0), - mState(NULL), - mService(service) -{} + : mService(service) +{ + // Create default class + mClasses.push_back(new ClassState()); +} HttpPolicy::~HttpPolicy() { shutdown(); + + for (class_list_t::iterator it(mClasses.begin()); it != mClasses.end(); ++it) + { + delete (*it); + } + mClasses.clear(); mService = NULL; } +HttpRequest::policy_t HttpPolicy::createPolicyClass() +{ + const HttpRequest::policy_t policy_class(mClasses.size()); + if (policy_class >= HTTP_POLICY_CLASS_LIMIT) + { + return HttpRequest::INVALID_POLICY_ID; + } + mClasses.push_back(new ClassState()); + return policy_class; +} + + void HttpPolicy::shutdown() { - for (int policy_class(0); policy_class < mActiveClasses; ++policy_class) + for (int policy_class(0); policy_class < mClasses.size(); ++policy_class) { - HttpRetryQueue & retryq(mState[policy_class].mRetryQueue); + ClassState & state(*mClasses[policy_class]); + + HttpRetryQueue & retryq(state.mRetryQueue); while (! retryq.empty()) { HttpOpRequest * op(retryq.top()); @@ -101,7 +108,7 @@ void HttpPolicy::shutdown() op->release(); } - HttpReadyQueue & readyq(mState[policy_class].mReadyQueue); + HttpReadyQueue & readyq(state.mReadyQueue); while (! readyq.empty()) { HttpOpRequest * op(readyq.top()); @@ -111,28 +118,11 @@ void HttpPolicy::shutdown() op->release(); } } - delete [] mState; - mState = NULL; - mActiveClasses = 0; } -void HttpPolicy::start(const HttpPolicyGlobal & global, - const std::vector & classes) -{ - llassert_always(! mState); - - mGlobalOptions = global; - mActiveClasses = classes.size(); - mState = new State [mActiveClasses]; - for (int i(0); i < mActiveClasses; ++i) - { - mState[i].mOptions = classes[i]; - mState[i].mConnMax = classes[i].mConnectionLimit; - mState[i].mConnAt = mState[i].mConnMax; - mState[i].mConnMin = 2; - } -} +void HttpPolicy::start() +{} void HttpPolicy::addOp(HttpOpRequest * op) @@ -141,7 +131,7 @@ void HttpPolicy::addOp(HttpOpRequest * op) op->mPolicyRetries = 0; op->mPolicy503Retries = 0; - mState[policy_class].mReadyQueue.push(op); + mClasses[policy_class]->mReadyQueue.push(op); } @@ -183,7 +173,7 @@ void HttpPolicy::retryOp(HttpOpRequest * op) << static_cast(op) << LL_ENDL; } - mState[policy_class].mRetryQueue.push(op); + mClasses[policy_class]->mRetryQueue.push(op); } @@ -204,11 +194,11 @@ HttpService::ELoopSpeed HttpPolicy::processReadyQueue() HttpService::ELoopSpeed result(HttpService::REQUEST_SLEEP); HttpLibcurl & transport(mService->getTransport()); - for (int policy_class(0); policy_class < mActiveClasses; ++policy_class) + for (int policy_class(0); policy_class < mClasses.size(); ++policy_class) { - State & state(mState[policy_class]); + ClassState & state(*mClasses[policy_class]); int active(transport.getActiveCountInClass(policy_class)); - int needed(state.mConnAt - active); // Expect negatives here + int needed(state.mOptions.mConnectionLimit - active); // Expect negatives here HttpRetryQueue & retryq(state.mRetryQueue); HttpReadyQueue & readyq(state.mReadyQueue); @@ -256,9 +246,9 @@ HttpService::ELoopSpeed HttpPolicy::processReadyQueue() bool HttpPolicy::changePriority(HttpHandle handle, HttpRequest::priority_t priority) { - for (int policy_class(0); policy_class < mActiveClasses; ++policy_class) + for (int policy_class(0); policy_class < mClasses.size(); ++policy_class) { - State & state(mState[policy_class]); + ClassState & state(*mClasses[policy_class]); // We don't scan retry queue because a priority change there // is meaningless. The request will be issued based on retry // intervals not priority value, which is now moot. @@ -286,9 +276,9 @@ bool HttpPolicy::changePriority(HttpHandle handle, HttpRequest::priority_t prior bool HttpPolicy::cancel(HttpHandle handle) { - for (int policy_class(0); policy_class < mActiveClasses; ++policy_class) + for (int policy_class(0); policy_class < mClasses.size(); ++policy_class) { - State & state(mState[policy_class]); + ClassState & state(*mClasses[policy_class]); // Scan retry queue HttpRetryQueue::container_type & c1(state.mRetryQueue.get_container()); @@ -382,13 +372,21 @@ bool HttpPolicy::stageAfterCompletion(HttpOpRequest * op) return false; // not active } + +HttpPolicyClass & HttpPolicy::getClassOptions(HttpRequest::policy_t pclass) +{ + llassert_always(pclass >= 0 && pclass < mClasses.size()); + + return mClasses[pclass]->mOptions; +} + int HttpPolicy::getReadyCount(HttpRequest::policy_t policy_class) const { - if (policy_class < mActiveClasses) + if (policy_class < mClasses.size()) { - return (mState[policy_class].mReadyQueue.size() - + mState[policy_class].mRetryQueue.size()); + return (mClasses[policy_class]->mReadyQueue.size() + + mClasses[policy_class]->mRetryQueue.size()); } return 0; } diff --git a/indra/llcorehttp/_httppolicy.h b/indra/llcorehttp/_httppolicy.h index 03d92c0b8e..bf1aa74267 100755 --- a/indra/llcorehttp/_httppolicy.h +++ b/indra/llcorehttp/_httppolicy.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 @@ -60,6 +60,9 @@ private: void operator=(const HttpPolicy &); // Not defined public: + /// Threading: called by init thread. + HttpRequest::policy_t createPolicyClass(); + /// Cancel all ready and retry requests sending them to /// their notification queues. Release state resources /// making further request handling impossible. @@ -71,9 +74,8 @@ public: /// requests. One-time call invoked before starting /// the worker thread. /// - /// Threading: called by application thread - void start(const HttpPolicyGlobal & global, - const std::vector & classes); + /// Threading: called by init thread + void start(); /// Give the policy layer some cycles to scan the ready /// queue promoting higher-priority requests to active @@ -93,7 +95,7 @@ public: /// and should not be modified by anyone until retrieved /// from queue. /// - /// Threading: called by any thread + /// Threading: called by worker thread void addOp(HttpOpRequest *); /// Similar to addOp, used when a caller wants to retry a @@ -130,30 +132,39 @@ public: /// Threading: called by worker thread bool stageAfterCompletion(HttpOpRequest * op); - // Get pointer to global policy options. Caller is expected - // to do context checks like no setting once running. + /// Get a reference to global policy options. Caller is expected + /// to do context checks like no setting once running. These + /// are done, for example, in @see HttpService interfaces. /// /// Threading: called by any thread *but* the object may /// only be modified by the worker thread once running. - /// HttpPolicyGlobal & getGlobalOptions() { return mGlobalOptions; } + /// Get a reference to class policy options. Caller is expected + /// to do context checks like no setting once running. These + /// are done, for example, in @see HttpService interfaces. + /// + /// Threading: called by any thread *but* the object may + /// only be modified by the worker thread once running and + /// read accesses by other threads are exposed to races at + /// that point. + HttpPolicyClass & getClassOptions(HttpRequest::policy_t pclass); + /// Get ready counts for a particular policy class /// /// Threading: called by worker thread int getReadyCount(HttpRequest::policy_t policy_class) const; protected: - struct State; - - int mActiveClasses; - State * mState; - HttpService * mService; // Naked pointer, not refcounted, not owner - HttpPolicyGlobal mGlobalOptions; + struct ClassState; + typedef std::vector class_list_t; + HttpPolicyGlobal mGlobalOptions; + class_list_t mClasses; + HttpService * mService; // Naked pointer, not refcounted, not owner }; // end class HttpPolicy } // end namespace LLCore diff --git a/indra/llcorehttp/_httppolicyclass.cpp b/indra/llcorehttp/_httppolicyclass.cpp index 1a55ab1ac6..fe4359081a 100755 --- a/indra/llcorehttp/_httppolicyclass.cpp +++ b/indra/llcorehttp/_httppolicyclass.cpp @@ -34,8 +34,7 @@ namespace LLCore HttpPolicyClass::HttpPolicyClass() - : mSetMask(0UL), - mConnectionLimit(HTTP_CONNECTION_LIMIT_DEFAULT), + : mConnectionLimit(HTTP_CONNECTION_LIMIT_DEFAULT), mPerHostConnectionLimit(HTTP_CONNECTION_LIMIT_DEFAULT), mPipelining(HTTP_PIPELINING_DEFAULT) {} @@ -49,7 +48,6 @@ HttpPolicyClass & HttpPolicyClass::operator=(const HttpPolicyClass & other) { if (this != &other) { - mSetMask = other.mSetMask; mConnectionLimit = other.mConnectionLimit; mPerHostConnectionLimit = other.mPerHostConnectionLimit; mPipelining = other.mPipelining; @@ -59,26 +57,25 @@ HttpPolicyClass & HttpPolicyClass::operator=(const HttpPolicyClass & other) HttpPolicyClass::HttpPolicyClass(const HttpPolicyClass & other) - : mSetMask(other.mSetMask), - mConnectionLimit(other.mConnectionLimit), + : mConnectionLimit(other.mConnectionLimit), mPerHostConnectionLimit(other.mPerHostConnectionLimit), mPipelining(other.mPipelining) {} -HttpStatus HttpPolicyClass::set(HttpRequest::EClassPolicy opt, long value) +HttpStatus HttpPolicyClass::set(HttpRequest::EPolicyOption opt, long value) { switch (opt) { - case HttpRequest::CP_CONNECTION_LIMIT: + case HttpRequest::PO_CONNECTION_LIMIT: mConnectionLimit = llclamp(value, long(HTTP_CONNECTION_LIMIT_MIN), long(HTTP_CONNECTION_LIMIT_MAX)); break; - case HttpRequest::CP_PER_HOST_CONNECTION_LIMIT: + case HttpRequest::PO_PER_HOST_CONNECTION_LIMIT: mPerHostConnectionLimit = llclamp(value, long(HTTP_CONNECTION_LIMIT_MIN), mConnectionLimit); break; - case HttpRequest::CP_ENABLE_PIPELINING: + case HttpRequest::PO_ENABLE_PIPELINING: mPipelining = llclamp(value, 0L, 1L); break; @@ -86,38 +83,30 @@ HttpStatus HttpPolicyClass::set(HttpRequest::EClassPolicy opt, long value) return HttpStatus(HttpStatus::LLCORE, HE_INVALID_ARG); } - mSetMask |= 1UL << int(opt); return HttpStatus(); } -HttpStatus HttpPolicyClass::get(HttpRequest::EClassPolicy opt, long * value) +HttpStatus HttpPolicyClass::get(HttpRequest::EPolicyOption opt, long * value) const { - static const HttpStatus not_set(HttpStatus::LLCORE, HE_OPT_NOT_SET); - long * src(NULL); - switch (opt) { - case HttpRequest::CP_CONNECTION_LIMIT: - src = &mConnectionLimit; + case HttpRequest::PO_CONNECTION_LIMIT: + *value = mConnectionLimit; break; - case HttpRequest::CP_PER_HOST_CONNECTION_LIMIT: - src = &mPerHostConnectionLimit; + case HttpRequest::PO_PER_HOST_CONNECTION_LIMIT: + *value = mPerHostConnectionLimit; break; - case HttpRequest::CP_ENABLE_PIPELINING: - src = &mPipelining; + case HttpRequest::PO_ENABLE_PIPELINING: + *value = mPipelining; break; default: return HttpStatus(HttpStatus::LLCORE, HE_INVALID_ARG); } - if (! (mSetMask & (1UL << int(opt)))) - return not_set; - - *value = *src; return HttpStatus(); } diff --git a/indra/llcorehttp/_httppolicyclass.h b/indra/llcorehttp/_httppolicyclass.h index d175413cbd..69fb459d22 100755 --- a/indra/llcorehttp/_httppolicyclass.h +++ b/indra/llcorehttp/_httppolicyclass.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 @@ -34,6 +34,18 @@ namespace LLCore { +/// Options struct for per-class policy options. +/// +/// Combines both raw blob data access with semantics-enforcing +/// set/get interfaces. For internal operations by the worker +/// thread, just grab the setting directly from instance and test/use +/// as needed. When attached to external APIs (the public API +/// options interfaces) the set/get methods are available to +/// enforce correct ranges, data types, contexts, etc. and suitable +/// status values are returned. +/// +/// Threading: Single-threaded. In practice, init thread before +/// worker starts, worker thread after. class HttpPolicyClass { public: @@ -44,11 +56,10 @@ public: HttpPolicyClass(const HttpPolicyClass &); // Not defined public: - HttpStatus set(HttpRequest::EClassPolicy opt, long value); - HttpStatus get(HttpRequest::EClassPolicy opt, long * value); + HttpStatus set(HttpRequest::EPolicyOption opt, long value); + HttpStatus get(HttpRequest::EPolicyOption opt, long * value) const; public: - unsigned long mSetMask; long mConnectionLimit; long mPerHostConnectionLimit; long mPipelining; diff --git a/indra/llcorehttp/_httppolicyglobal.cpp b/indra/llcorehttp/_httppolicyglobal.cpp index 72f409d3b1..1dc95f3dce 100755 --- a/indra/llcorehttp/_httppolicyglobal.cpp +++ b/indra/llcorehttp/_httppolicyglobal.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 @@ -34,8 +34,7 @@ namespace LLCore HttpPolicyGlobal::HttpPolicyGlobal() - : mSetMask(0UL), - mConnectionLimit(HTTP_CONNECTION_LIMIT_DEFAULT), + : mConnectionLimit(HTTP_CONNECTION_LIMIT_DEFAULT), mTrace(HTTP_TRACE_OFF), mUseLLProxy(0) {} @@ -49,7 +48,6 @@ HttpPolicyGlobal & HttpPolicyGlobal::operator=(const HttpPolicyGlobal & other) { if (this != &other) { - mSetMask = other.mSetMask; mConnectionLimit = other.mConnectionLimit; mCAPath = other.mCAPath; mCAFile = other.mCAFile; @@ -61,19 +59,19 @@ HttpPolicyGlobal & HttpPolicyGlobal::operator=(const HttpPolicyGlobal & other) } -HttpStatus HttpPolicyGlobal::set(HttpRequest::EGlobalPolicy opt, long value) +HttpStatus HttpPolicyGlobal::set(HttpRequest::EPolicyOption opt, long value) { switch (opt) { - case HttpRequest::GP_CONNECTION_LIMIT: + case HttpRequest::PO_CONNECTION_LIMIT: mConnectionLimit = llclamp(value, long(HTTP_CONNECTION_LIMIT_MIN), long(HTTP_CONNECTION_LIMIT_MAX)); break; - case HttpRequest::GP_TRACE: + case HttpRequest::PO_TRACE: mTrace = llclamp(value, long(HTTP_TRACE_MIN), long(HTTP_TRACE_MAX)); break; - case HttpRequest::GP_LLPROXY: + case HttpRequest::PO_LLPROXY: mUseLLProxy = llclamp(value, 0L, 1L); break; @@ -81,24 +79,23 @@ HttpStatus HttpPolicyGlobal::set(HttpRequest::EGlobalPolicy opt, long value) return HttpStatus(HttpStatus::LLCORE, HE_INVALID_ARG); } - mSetMask |= 1UL << int(opt); return HttpStatus(); } -HttpStatus HttpPolicyGlobal::set(HttpRequest::EGlobalPolicy opt, const std::string & value) +HttpStatus HttpPolicyGlobal::set(HttpRequest::EPolicyOption opt, const std::string & value) { switch (opt) { - case HttpRequest::GP_CA_PATH: + case HttpRequest::PO_CA_PATH: mCAPath = value; break; - case HttpRequest::GP_CA_FILE: + case HttpRequest::PO_CA_FILE: mCAFile = value; break; - case HttpRequest::GP_HTTP_PROXY: + case HttpRequest::PO_HTTP_PROXY: mCAFile = value; break; @@ -106,69 +103,54 @@ HttpStatus HttpPolicyGlobal::set(HttpRequest::EGlobalPolicy opt, const std::stri return HttpStatus(HttpStatus::LLCORE, HE_INVALID_ARG); } - mSetMask |= 1UL << int(opt); return HttpStatus(); } -HttpStatus HttpPolicyGlobal::get(HttpRequest::EGlobalPolicy opt, long * value) +HttpStatus HttpPolicyGlobal::get(HttpRequest::EPolicyOption opt, long * value) const { - static const HttpStatus not_set(HttpStatus::LLCORE, HE_OPT_NOT_SET); - long * src(NULL); - switch (opt) { - case HttpRequest::GP_CONNECTION_LIMIT: - src = &mConnectionLimit; + case HttpRequest::PO_CONNECTION_LIMIT: + *value = mConnectionLimit; break; - case HttpRequest::GP_TRACE: - src = &mTrace; + case HttpRequest::PO_TRACE: + *value = mTrace; break; - case HttpRequest::GP_LLPROXY: - src = &mUseLLProxy; + case HttpRequest::PO_LLPROXY: + *value = mUseLLProxy; break; default: return HttpStatus(HttpStatus::LLCORE, HE_INVALID_ARG); } - if (! (mSetMask & (1UL << int(opt)))) - return not_set; - - *value = *src; return HttpStatus(); } -HttpStatus HttpPolicyGlobal::get(HttpRequest::EGlobalPolicy opt, const std::string ** value) +HttpStatus HttpPolicyGlobal::get(HttpRequest::EPolicyOption opt, std::string * value) const { - static const HttpStatus not_set(HttpStatus::LLCORE, HE_OPT_NOT_SET); - const std::string * src(NULL); - switch (opt) { - case HttpRequest::GP_CA_PATH: - src = &mCAPath; + case HttpRequest::PO_CA_PATH: + *value = mCAPath; break; - case HttpRequest::GP_CA_FILE: - src = &mCAFile; + case HttpRequest::PO_CA_FILE: + *value = mCAFile; break; - case HttpRequest::GP_HTTP_PROXY: - src = &mHttpProxy; + case HttpRequest::PO_HTTP_PROXY: + *value = mHttpProxy; break; default: return HttpStatus(HttpStatus::LLCORE, HE_INVALID_ARG); } - if (! (mSetMask & (1UL << int(opt)))) - return not_set; - - *value = src; return HttpStatus(); } diff --git a/indra/llcorehttp/_httppolicyglobal.h b/indra/llcorehttp/_httppolicyglobal.h index a50d0e4188..67c4ba9481 100755 --- a/indra/llcorehttp/_httppolicyglobal.h +++ b/indra/llcorehttp/_httppolicyglobal.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 @@ -34,6 +34,18 @@ namespace LLCore { +/// Options struct for global policy options. +/// +/// Combines both raw blob data access with semantics-enforcing +/// set/get interfaces. For internal operations by the worker +/// thread, just grab the setting directly from instance and test/use +/// as needed. When attached to external APIs (the public API +/// options interfaces) the set/get methods are available to +/// enforce correct ranges, data types, contexts, etc. and suitable +/// status values are returned. +/// +/// Threading: Single-threaded. In practice, init thread before +/// worker starts, worker thread after. class HttpPolicyGlobal { public: @@ -46,13 +58,12 @@ private: HttpPolicyGlobal(const HttpPolicyGlobal &); // Not defined public: - HttpStatus set(HttpRequest::EGlobalPolicy opt, long value); - HttpStatus set(HttpRequest::EGlobalPolicy opt, const std::string & value); - HttpStatus get(HttpRequest::EGlobalPolicy opt, long * value); - HttpStatus get(HttpRequest::EGlobalPolicy opt, const std::string ** value); + HttpStatus set(HttpRequest::EPolicyOption opt, long value); + HttpStatus set(HttpRequest::EPolicyOption opt, const std::string & value); + HttpStatus get(HttpRequest::EPolicyOption opt, long * value) const; + HttpStatus get(HttpRequest::EPolicyOption opt, std::string * value) const; public: - unsigned long mSetMask; long mConnectionLimit; std::string mCAPath; std::string mCAFile; diff --git a/indra/llcorehttp/_httpservice.cpp b/indra/llcorehttp/_httpservice.cpp index 0821401289..e21d196a3e 100755 --- a/indra/llcorehttp/_httpservice.cpp +++ b/indra/llcorehttp/_httpservice.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 @@ -43,6 +43,17 @@ namespace LLCore { +const HttpService::OptionDescriptor HttpService::sOptionDesc[] = +{ // isLong isDynamic isGlobal isClass + { true, true, true, true }, // PO_CONNECTION_LIMIT + { true, true, false, true }, // PO_PER_HOST_CONNECTION_LIMIT + { false, false, true, false }, // PO_CA_PATH + { false, false, true, false }, // PO_CA_FILE + { false, true, true, false }, // PO_HTTP_PROXY + { true, true, true, false }, // PO_LLPROXY + { true, true, true, false }, // PO_TRACE + { true, true, false, true } // PO_ENABLE_PIPELINING +}; HttpService * HttpService::sInstance(NULL); volatile HttpService::EState HttpService::sState(NOT_INITIALIZED); @@ -51,12 +62,9 @@ HttpService::HttpService() mExitRequested(0U), mThread(NULL), mPolicy(NULL), - mTransport(NULL) -{ - // Create the default policy class - HttpPolicyClass pol_class; - mPolicyClasses.push_back(pol_class); -} + mTransport(NULL), + mLastPolicy(0) +{} HttpService::~HttpService() @@ -146,13 +154,8 @@ void HttpService::term() HttpRequest::policy_t HttpService::createPolicyClass() { - const HttpRequest::policy_t policy_class(mPolicyClasses.size()); - if (policy_class >= HTTP_POLICY_CLASS_LIMIT) - { - return 0; - } - mPolicyClasses.push_back(HttpPolicyClass()); - return policy_class; + mLastPolicy = mPolicy->createPolicyClass(); + return mLastPolicy; } @@ -185,8 +188,8 @@ void HttpService::startThread() } // Push current policy definitions, enable policy & transport components - mPolicy->start(mPolicyGlobal, mPolicyClasses); - mTransport->start(mPolicyClasses.size()); + mPolicy->start(); + mTransport->start(mLastPolicy + 1); mThread = new LLCoreInt::HttpThread(boost::bind(&HttpService::threadRun, this, _1)); sState = RUNNING; @@ -319,7 +322,7 @@ HttpService::ELoopSpeed HttpService::processRequestQueue(ELoopSpeed loop) { // Setup for subsequent tracing long tracing(HTTP_TRACE_OFF); - mPolicy->getGlobalOptions().get(HttpRequest::GP_TRACE, &tracing); + mPolicy->getGlobalOptions().get(HttpRequest::PO_TRACE, &tracing); op->mTracing = (std::max)(op->mTracing, int(tracing)); if (op->mTracing > HTTP_TRACE_OFF) @@ -342,4 +345,137 @@ HttpService::ELoopSpeed HttpService::processRequestQueue(ELoopSpeed loop) } +HttpStatus HttpService::getPolicyOption(HttpRequest::EPolicyOption opt, HttpRequest::policy_t pclass, + long * ret_value) +{ + 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) +{ + 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::setPolicyOption(HttpRequest::EPolicyOption opt, HttpRequest::policy_t pclass, + long value, long * ret_value) +{ + 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 && 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) +{ + 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; + } + + // Only string values are 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/_httpservice.h b/indra/llcorehttp/_httpservice.h index ffe0349d4d..cf23f3ab61 100755 --- a/indra/llcorehttp/_httpservice.h +++ b/indra/llcorehttp/_httpservice.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 @@ -53,6 +53,7 @@ namespace LLCore class HttpRequestQueue; class HttpPolicy; class HttpLibcurl; +class HttpOpSetGet; /// The HttpService class does the work behind the request queue. It @@ -106,7 +107,7 @@ public: NORMAL, ///< continuous polling of request, ready, active queues REQUEST_SLEEP ///< can sleep indefinitely waiting for request queue write }; - + static void init(HttpRequestQueue *); static void term(); @@ -136,7 +137,7 @@ public: /// acquires its weaknesses. static bool isStopped(); - /// Threading: callable by consumer thread *once*. + /// Threading: callable by init thread *once*. void startThread(); /// Threading: callable by worker thread. @@ -180,28 +181,39 @@ public: return *mRequestQueue; } - /// Threading: callable by consumer thread. - HttpPolicyGlobal & getGlobalOptions() - { - return mPolicyGlobal; - } - /// Threading: callable by consumer thread. HttpRequest::policy_t createPolicyClass(); - /// Threading: callable by consumer thread. - HttpPolicyClass & getClassOptions(HttpRequest::policy_t policy_class) - { - llassert(policy_class >= 0 && policy_class < mPolicyClasses.size()); - return mPolicyClasses[policy_class]; - } - protected: void threadRun(LLCoreInt::HttpThread * thread); ELoopSpeed processRequestQueue(ELoopSpeed loop); + +protected: + friend class HttpOpSetGet; + friend class HttpRequest; + + // Used internally to describe what operations are allowed + // on each policy option. + struct OptionDescriptor + { + bool mIsLong; + bool mIsDynamic; + bool mIsGlobal; + bool mIsClass; + }; + + HttpStatus getPolicyOption(HttpRequest::EPolicyOption opt, HttpRequest::policy_t, + long * ret_value); + HttpStatus getPolicyOption(HttpRequest::EPolicyOption opt, HttpRequest::policy_t, + std::string * ret_value); + HttpStatus setPolicyOption(HttpRequest::EPolicyOption opt, HttpRequest::policy_t, + long value, long * ret_value); + HttpStatus setPolicyOption(HttpRequest::EPolicyOption opt, HttpRequest::policy_t, + const std::string & value, std::string * ret_value); protected: + static const OptionDescriptor sOptionDesc[HttpRequest::PO_LAST]; static HttpService * sInstance; // === shared data === @@ -210,13 +222,13 @@ protected: LLAtomicU32 mExitRequested; LLCoreInt::HttpThread * mThread; - // === consumer-thread-only data === - HttpPolicyGlobal mPolicyGlobal; - std::vector mPolicyClasses; - // === working-thread-only data === HttpPolicy * mPolicy; // Simple pointer, has ownership HttpLibcurl * mTransport; // Simple pointer, has ownership + + // === main-thread-only data === + HttpRequest::policy_t mLastPolicy; + }; // end class HttpService } // end namespace LLCore diff --git a/indra/llcorehttp/examples/http_texture_load.cpp b/indra/llcorehttp/examples/http_texture_load.cpp index 88c8102b27..73c49687d7 100755 --- a/indra/llcorehttp/examples/http_texture_load.cpp +++ b/indra/llcorehttp/examples/http_texture_load.cpp @@ -236,9 +236,10 @@ int main(int argc, char** argv) // Initialization init_curl(); LLCore::HttpRequest::createService(); - LLCore::HttpRequest::setPolicyClassOption(LLCore::HttpRequest::DEFAULT_POLICY_ID, - LLCore::HttpRequest::CP_CONNECTION_LIMIT, - concurrency_limit); + LLCore::HttpRequest::setStaticPolicyOption(LLCore::HttpRequest::PO_CONNECTION_LIMIT, + LLCore::HttpRequest::DEFAULT_POLICY_ID, + concurrency_limit, + NULL); LLCore::HttpRequest::startThread(); // Get service point diff --git a/indra/llcorehttp/httpcommon.h b/indra/llcorehttp/httpcommon.h index c0d4ec5aad..9db884057f 100755 --- a/indra/llcorehttp/httpcommon.h +++ b/indra/llcorehttp/httpcommon.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 @@ -29,9 +29,9 @@ /// @package LLCore::HTTP /// -/// This library implements a high-level, Indra-code-free client interface to -/// HTTP services based on actual patterns found in the viewer and simulator. -/// Interfaces are similar to those supplied by the legacy classes +/// This library implements a high-level, Indra-code-free (somewhat) client +/// interface to HTTP services based on actual patterns found in the viewer +/// and simulator. Interfaces are similar to those supplied by the legacy classes /// LLCurlRequest and LLHTTPClient. To that is added a policy scheme that /// allows an application to specify connection behaviors: limits on /// connections, HTTP keepalive, HTTP pipelining, retry-on-error limits, etc. @@ -52,7 +52,7 @@ /// - "llcorehttp/httprequest.h" /// - "llcorehttp/httpresponse.h" /// -/// The library is still under early development and particular users +/// The library is still under development and particular users /// may need access to internal implementation details that are found /// in the _*.h header files. But this is a crutch to be avoided if at /// all possible and probably indicates some interface work is neeeded. @@ -66,6 +66,8 @@ /// . CRYPTO_set_id_callback(...) /// - HttpRequest::createService() called to instantiate singletons /// and support objects. +/// - HttpRequest::startThread() to kick off the worker thread and +/// begin servicing requests. /// /// An HTTP consumer in an application, and an application may have many /// consumers, does a few things: @@ -91,10 +93,12 @@ /// objects. /// - Do completion processing in your onCompletion() method. /// -/// Code fragments: -/// Rather than a poorly-maintained example in comments, look in the -/// example subdirectory which is a minimal yet functional tool to do -/// GET request performance testing. With four calls: +/// Code fragments. +/// +/// Initialization. Rather than a poorly-maintained example in +/// comments, look in the example subdirectory which is a minimal +/// yet functional tool to do GET request performance testing. +/// With four calls: /// /// init_curl(); /// LLCore::HttpRequest::createService(); @@ -103,7 +107,85 @@ /// /// the program is basically ready to issue requests. /// - +/// HttpHandler. Having started life as a non-indra library, +/// this code broke away from the classic Responder model and +/// introduced a handler class to represent an interface for +/// request responses. This is a non-reference-counted entity +/// which can be used as a base class or a mixin. An instance +/// of a handler can be used for each request or can be shared +/// among any number of requests. Your choice but expect to +/// code something like the following: +/// +/// class AppHandler : public LLCore::HttpHandler +/// { +/// public: +/// virtual void onCompleted(HttpHandle handle, +/// HttpResponse * response) +/// { +/// ... +/// } +/// ... +/// }; +/// ... +/// handler = new handler(...); +/// +/// +/// Issuing requests. Using 'hr' above, +/// +/// hr->requestGet(HttpRequest::DEFAULT_POLICY_ID, +/// 0, // Priority, not used yet +/// url, +/// NULL, // options +/// NULL, // additional headers +/// handler); +/// +/// If that returns a value other than LLCORE_HTTP_HANDLE_INVALID, +/// the request was successfully issued and there will eventally +/// be a status delivered to the handler. If invalid is returnedd, +/// the actual status can be retrieved by calling hr->getStatus(). +/// +/// Completing requests and delivering notifications. Operations +/// are all performed by the worker thread and will be driven to +/// completion regardless of caller actions. Notification of +/// completion (success or failure) is done by calls to +/// HttpRequest::update() which will invoke handlers for completed +/// requests: +/// +/// hr->update(0); +/// // Callbacks into handler->onCompleted() +/// +/// +/// Threads. +/// +/// Threads are supported and used by this library. The various +/// classes, methods and members are documented with thread +/// constraints which programmers must follow and which are +/// defined as follows: +/// +/// consumer Any thread that has instanced HttpRequest and is +/// issuing requests. A particular instance can only +/// be used by one consumer thread but a consumer may +/// have many instances available to it. +/// init Special consumer thread, usually the main thread, +/// involved in setting up the library at startup. +/// worker Thread used internally by the library to perform +/// HTTP operations. Consumers will not have to deal +/// with this thread directly but some APIs are reserved +/// to it. +/// any Consumer or worker thread. +/// +/// For the most part, API users will not have to do much in the +/// way of ensuring thread safely. However, there is a tremendous +/// amount of sharing between threads of read-only data. So when +/// documentation declares that an option or header instance +/// becomes shared between consumer and worker, the consumer must +/// not modify the shared object. +/// +/// Internally, there is almost no thread synchronization. During +/// normal operations (non-init, non-term), only the request queue +/// and the multiple reply queues are shared between threads and +/// only here are mutexes used. +/// #include "linden_common.h" // Modifies curl/curl.h interfaces @@ -239,9 +321,10 @@ struct HttpStatus return *this; } - static const type_enum_t EXT_CURL_EASY = 0; - static const type_enum_t EXT_CURL_MULTI = 1; - static const type_enum_t LLCORE = 2; + static const type_enum_t EXT_CURL_EASY = 0; ///< mStatus is an error from a curl_easy_*() call + static const type_enum_t EXT_CURL_MULTI = 1; ///< mStatus is an error from a curl_multi_*() call + static const type_enum_t LLCORE = 2; ///< mStatus is an HE_* error code + ///< 100-999 directly represent HTTP status codes type_enum_t mType; short mStatus; diff --git a/indra/llcorehttp/httprequest.cpp b/indra/llcorehttp/httprequest.cpp index 9b739a8825..7b1888e3eb 100755 --- a/indra/llcorehttp/httprequest.cpp +++ b/indra/llcorehttp/httprequest.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 @@ -54,12 +54,8 @@ namespace LLCore // ==================================== -HttpRequest::policy_t HttpRequest::sNextPolicyID(1); - - HttpRequest::HttpRequest() - : //HttpHandler(), - mReplyQueue(NULL), + : mReplyQueue(NULL), mRequestQueue(NULL) { mRequestQueue = HttpRequestQueue::instanceOf(); @@ -90,45 +86,91 @@ HttpRequest::~HttpRequest() // ==================================== -HttpStatus HttpRequest::setPolicyGlobalOption(EGlobalPolicy opt, long value) +HttpRequest::policy_t HttpRequest::createPolicyClass() { if (HttpService::RUNNING == HttpService::instanceOf()->getState()) { - return HttpStatus(HttpStatus::LLCORE, HE_OPT_NOT_DYNAMIC); + return 0; } - return HttpService::instanceOf()->getGlobalOptions().set(opt, value); + return HttpService::instanceOf()->createPolicyClass(); } -HttpStatus HttpRequest::setPolicyGlobalOption(EGlobalPolicy opt, const std::string & value) +HttpStatus HttpRequest::setStaticPolicyOption(EPolicyOption opt, policy_t pclass, + long value, long * ret_value) { if (HttpService::RUNNING == HttpService::instanceOf()->getState()) { return HttpStatus(HttpStatus::LLCORE, HE_OPT_NOT_DYNAMIC); } - return HttpService::instanceOf()->getGlobalOptions().set(opt, value); + return HttpService::instanceOf()->setPolicyOption(opt, pclass, value, ret_value); } -HttpRequest::policy_t HttpRequest::createPolicyClass() +HttpStatus HttpRequest::setStaticPolicyOption(EPolicyOption opt, policy_t pclass, + const std::string & value, std::string * ret_value) { if (HttpService::RUNNING == HttpService::instanceOf()->getState()) { - return 0; + return HttpStatus(HttpStatus::LLCORE, HE_OPT_NOT_DYNAMIC); } - return HttpService::instanceOf()->createPolicyClass(); + return HttpService::instanceOf()->setPolicyOption(opt, pclass, value, ret_value); } -HttpStatus HttpRequest::setPolicyClassOption(policy_t policy_id, - EClassPolicy opt, - long value) +HttpHandle HttpRequest::setPolicyOption(EPolicyOption opt, policy_t pclass, + long value, HttpHandler * handler) { - if (HttpService::RUNNING == HttpService::instanceOf()->getState()) + HttpStatus status; + HttpHandle handle(LLCORE_HTTP_HANDLE_INVALID); + + HttpOpSetGet * op = new HttpOpSetGet(); + if (! (status = op->setupSet(opt, pclass, value))) { - return HttpStatus(HttpStatus::LLCORE, HE_OPT_NOT_DYNAMIC); + op->release(); + mLastReqStatus = status; + return handle; + } + op->setReplyPath(mReplyQueue, handler); + if (! (status = mRequestQueue->addOp(op))) // transfers refcount + { + op->release(); + mLastReqStatus = status; + return handle; + } + + mLastReqStatus = status; + handle = static_cast(op); + + return handle; +} + + +HttpHandle HttpRequest::setPolicyOption(EPolicyOption opt, policy_t pclass, + const std::string & value, HttpHandler * handler) +{ + HttpStatus status; + HttpHandle handle(LLCORE_HTTP_HANDLE_INVALID); + + HttpOpSetGet * op = new HttpOpSetGet(); + if (! (status = op->setupSet(opt, pclass, value))) + { + op->release(); + mLastReqStatus = status; + return handle; + } + op->setReplyPath(mReplyQueue, handler); + if (! (status = mRequestQueue->addOp(op))) // transfers refcount + { + op->release(); + mLastReqStatus = status; + return handle; } - return HttpService::instanceOf()->getClassOptions(policy_id).set(opt, value); + + mLastReqStatus = status; + handle = static_cast(op); + + return handle; } @@ -474,31 +516,6 @@ HttpHandle HttpRequest::requestSpin(int mode) return handle; } -// ==================================== -// Dynamic Policy Methods -// ==================================== - -HttpHandle HttpRequest::requestSetHttpProxy(const std::string & proxy, HttpHandler * handler) -{ - HttpStatus status; - HttpHandle handle(LLCORE_HTTP_HANDLE_INVALID); - - HttpOpSetGet * op = new HttpOpSetGet(); - op->setupSet(GP_HTTP_PROXY, proxy); - op->setReplyPath(mReplyQueue, handler); - if (! (status = mRequestQueue->addOp(op))) // transfers refcount - { - op->release(); - mLastReqStatus = status; - return handle; - } - - mLastReqStatus = status; - handle = static_cast(op); - - return handle; -} - } // end namespace LLCore diff --git a/indra/llcorehttp/httprequest.h b/indra/llcorehttp/httprequest.h index 5000f47d0d..5c54d35a21 100755 --- a/indra/llcorehttp/httprequest.h +++ b/indra/llcorehttp/httprequest.h @@ -56,6 +56,9 @@ class BufferArray; /// The class supports the current HTTP request operations: /// /// - requestGetByteRange: GET with Range header for a single range of bytes +/// - requestGet: +/// - requestPost: +/// - requestPut: /// /// Policy Classes /// @@ -100,9 +103,26 @@ public: /// Represents a default, catch-all policy class that guarantees /// eventual service for any HTTP request. - static const int DEFAULT_POLICY_ID = 0; + static const policy_t DEFAULT_POLICY_ID = 0; + static const policy_t INVALID_POLICY_ID = 0xFFFFFFFFU; + static const policy_t GLOBAL_POLICY_ID = 0xFFFFFFFEU; - enum EGlobalPolicy + /// 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, 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 EPolicyOption { /// Maximum number of connections the library will use to /// perform operations. This is somewhat soft as the underlying @@ -113,24 +133,30 @@ public: /// a somewhat soft value. There may be an additional five /// connections per policy class depending upon runtime /// behavior. - GP_CONNECTION_LIMIT, + /// + /// Both global and per-class + PO_CONNECTION_LIMIT, + + /// Limits the number of connections used for a single + /// literal address/port pair within the class. + PO_PER_HOST_CONNECTION_LIMIT, /// String containing a system-appropriate directory name /// where SSL certs are stored. - GP_CA_PATH, + PO_CA_PATH, /// String giving a full path to a file containing SSL certs. - GP_CA_FILE, + PO_CA_FILE, /// String of host/port to use as simple HTTP proxy. This is /// going to change in the future into something more elaborate /// that may support richer schemes. - GP_HTTP_PROXY, + PO_HTTP_PROXY, /// Long value that if non-zero enables the use of the /// traditional LLProxy code for http/socks5 support. If - /// enabled, has priority over GP_HTTP_PROXY. - GP_LLPROXY, + // enabled, has priority over GP_HTTP_PROXY. + PO_LLPROXY, /// Long value setting the logging trace level for the /// library. Possible values are: @@ -143,57 +169,46 @@ public: /// These values are also used in the trace modes for /// individual requests in HttpOptions. Also be aware that /// tracing tends to impact performance of the viewer. - GP_TRACE - }; - - /// Set a parameter on a global policy option. Calls - /// made after the start of the servicing thread are - /// not honored and return an error status. - /// - /// @param opt Enum of option to be set. - /// @param value Desired value of option. - /// @return Standard status code. - static HttpStatus setPolicyGlobalOption(EGlobalPolicy opt, long value); - static HttpStatus setPolicyGlobalOption(EGlobalPolicy opt, const std::string & value); - - /// 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, 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 - { - /// Limits the number of connections used for the class. - CP_CONNECTION_LIMIT, - - /// Limits the number of connections used for a single - /// literal address/port pair within the class. - CP_PER_HOST_CONNECTION_LIMIT, + PO_TRACE, /// Suitable requests are allowed to pipeline on their /// connections when they ask for it. - CP_ENABLE_PIPELINING + PO_ENABLE_PIPELINING, + + PO_LAST // Always at end }; - + + /// Set a policy option for a global or class parameter at + /// startup time (prior to thread start). + /// + /// @param opt Enum of option to be set. + /// @param pclass For class-based options, the policy class ID to + /// be changed. For globals, specify GLOBAL_POLICY_ID. + /// @param value Desired value of option. + /// @param ret_value Pointer to receive effective set value + /// if successful. May be NULL if effective + /// value not wanted. + /// @return Standard status code. + static HttpStatus setStaticPolicyOption(EPolicyOption opt, policy_t pclass, + long value, long * ret_value); + static HttpStatus setStaticPolicyOption(EPolicyOption opt, policy_t pclass, + const std::string & value, std::string * ret_value); + /// Set a parameter on a class-based policy option. Calls /// made after the start of the servicing thread are /// not honored and return an error status. /// - /// @param policy_id ID of class as returned by @see createPolicyClass(). - /// @param opt Enum of option to be set. - /// @param value Desired value of option. - /// @return Standard status code. - static HttpStatus setPolicyClassOption(policy_t policy_id, EClassPolicy opt, long value); + /// @param opt Enum of option to be set. + /// @param pclass For class-based options, the policy class ID to + /// be changed. Ignored for globals but recommend + /// using INVALID_POLICY_ID in this case. + /// @param value Desired value of option. + /// @return Handle of dynamic request. Use @see getStatus() if + /// the returned handle is invalid. + HttpHandle setPolicyOption(EPolicyOption opt, policy_t pclass, long value, + HttpHandler * handler); + HttpHandle setPolicyOption(EPolicyOption opt, policy_t pclass, const std::string & value, + HttpHandler * handler); /// @} @@ -495,16 +510,6 @@ public: /// @} - /// @name DynamicPolicyMethods - /// - /// @{ - - /// Request that a running transport pick up a new proxy setting. - /// An empty string will indicate no proxy is to be used. - HttpHandle requestSetHttpProxy(const std::string & proxy, HttpHandler * handler); - - /// @} - protected: void generateNotification(HttpOperation * op); @@ -526,7 +531,6 @@ private: /// Must be established before any threading is allowed to /// start. /// - static policy_t sNextPolicyID; /// @} // End Global State diff --git a/indra/llcorehttp/tests/test_httprequest.hpp b/indra/llcorehttp/tests/test_httprequest.hpp index 27d65f171e..f1b9c02393 100755 --- a/indra/llcorehttp/tests/test_httprequest.hpp +++ b/indra/llcorehttp/tests/test_httprequest.hpp @@ -1213,7 +1213,7 @@ void HttpRequestTestObjectType::test<12>() HttpRequest::createService(); // Enable tracing - HttpRequest::setPolicyGlobalOption(LLCore::HttpRequest::GP_TRACE, 2); + HttpRequest::setStaticPolicyOption(HttpRequest::PO_TRACE, HttpRequest::DEFAULT_POLICY_ID, 2, NULL); // Start threading early so that thread memory is invariant // over the test. @@ -1331,7 +1331,7 @@ void HttpRequestTestObjectType::test<13>() HttpRequest::createService(); // Enable tracing - HttpRequest::setPolicyGlobalOption(LLCore::HttpRequest::GP_TRACE, 2); + HttpRequest::setStaticPolicyOption(HttpRequest::PO_TRACE, HttpRequest::DEFAULT_POLICY_ID, 2, NULL); // Start threading early so that thread memory is invariant // over the test. @@ -2972,6 +2972,142 @@ void HttpRequestTestObjectType::test<21>() } +template <> template <> +void HttpRequestTestObjectType::test<22>() +{ + ScopedCurlInit ready; + + set_test_name("HttpRequest GET 503s with 'Retry-After'"); + + // This tests mainly that the code doesn't fall over if + // various well- and mis-formed Retry-After headers are + // sent along with the response. Direct inspection of + // the parsing result isn't supported. + + // Handler can be stack-allocated *if* there are no dangling + // references to it after completion of this method. + // Create before memory record as the string copy will bump numbers. + TestHandler2 handler(this, "handler"); + std::string url_base(get_base_url() + "/503/"); // path to 503 generators + + // record the total amount of dynamically allocated memory + mMemTotal = GetMemTotal(); + mHandlerCalls = 0; + + HttpRequest * req = NULL; + HttpOptions * opts = NULL; + + try + { + // Get singletons created + HttpRequest::createService(); + + // Start threading early so that thread memory is invariant + // over the test. + HttpRequest::startThread(); + + // create a new ref counted object with an implicit reference + req = new HttpRequest(); + ensure("Memory allocated on construction", mMemTotal < GetMemTotal()); + + opts = new HttpOptions(); + opts->setRetries(1); // Retry once only + opts->setUseRetryAfter(true); // Try to parse the retry-after header + + // Issue a GET that 503s with valid retry-after + mStatus = HttpStatus(503); + int url_limit(6); + for (int i(0); i < url_limit; ++i) + { + std::ostringstream url; + url << url_base << i << "/"; + HttpHandle handle = req->requestGetByteRange(HttpRequest::DEFAULT_POLICY_ID, + 0U, + url.str(), + 0, + 0, + opts, + NULL, + &handler); + + std::ostringstream testtag; + testtag << "Valid handle returned for 503 request #" << i; + ensure(testtag.str(), handle != LLCORE_HTTP_HANDLE_INVALID); + } + + + // Run the notification pump. + int count(0); + int limit(300); // One retry but several seconds needed + while (count++ < limit && mHandlerCalls < url_limit) + { + req->update(0); + usleep(100000); + } + ensure("Request executed in reasonable time", count < limit); + ensure("One handler invocation for request", mHandlerCalls == url_limit); + + // Okay, request a shutdown of the servicing thread + mStatus = HttpStatus(); + mHandlerCalls = 0; + HttpHandle handle = req->requestStopThread(&handler); + ensure("Valid handle returned for second request", handle != LLCORE_HTTP_HANDLE_INVALID); + + // Run the notification pump again + count = 0; + limit = 100; + while (count++ < limit && mHandlerCalls < 1) + { + req->update(1000000); + usleep(100000); + } + ensure("Second request executed in reasonable time", count < limit); + ensure("Second handler invocation", mHandlerCalls == 1); + + // See that we actually shutdown the thread + count = 0; + limit = 10; + while (count++ < limit && ! HttpService::isStopped()) + { + usleep(100000); + } + ensure("Thread actually stopped running", HttpService::isStopped()); + + // release options + opts->release(); + opts = NULL; + + // release the request object + delete req; + req = NULL; + + // Shut down service + HttpRequest::destroyService(); + +#if defined(WIN32) + // Can only do this memory test on Windows. On other platforms, + // the LL logging system holds on to memory and produces what looks + // like memory leaks... + + // printf("Old mem: %d, New mem: %d\n", mMemTotal, GetMemTotal()); + ensure("Memory usage back to that at entry", mMemTotal == GetMemTotal()); +#endif + } + catch (...) + { + stop_thread(req); + if (opts) + { + opts->release(); + opts = NULL; + } + delete req; + HttpRequest::destroyService(); + throw; + } +} + + } // end namespace tut namespace diff --git a/indra/llcorehttp/tests/test_llcorehttp_peer.py b/indra/llcorehttp/tests/test_llcorehttp_peer.py index 75a3c39ef2..f6c4d1a820 100755 --- a/indra/llcorehttp/tests/test_llcorehttp_peer.py +++ b/indra/llcorehttp/tests/test_llcorehttp_peer.py @@ -9,7 +9,7 @@ $LicenseInfo:firstyear=2008&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 @@ -47,6 +47,17 @@ from testrunner import freeport, run, debug, VERBOSE class TestHTTPRequestHandler(BaseHTTPRequestHandler): """This subclass of BaseHTTPRequestHandler is to receive and echo LLSD-flavored messages sent by the C++ LLHTTPClient. + + [Merge with viewer-cat later] + - '/503/' Generate 503 responses with various kinds + of 'retry-after' headers + -- '/503/0/' "Retry-After: 2" + -- '/503/1/' "Retry-After: Thu, 31 Dec 2043 23:59:59 GMT" + -- '/503/2/' "Retry-After: Fri, 31 Dec 1999 23:59:59 GMT" + -- '/503/3/' "Retry-After: " + -- '/503/4/' "Retry-After: (*#*(@*(@(")" + -- '/503/5/' "Retry-After: aklsjflajfaklsfaklfasfklasdfklasdgahsdhgasdiogaioshdgo" + -- '/503/6/' "Retry-After: 1 2 3 4 5 6 7 8 9 10" """ def read(self): # The following logic is adapted from the library module @@ -107,7 +118,41 @@ class TestHTTPRequestHandler(BaseHTTPRequestHandler): if "/sleep/" in self.path: time.sleep(30) - if "fail" not in self.path: + if "/503/" in self.path: + # Tests for various kinds of 'Retry-After' header parsing + body = None + if "/503/0/" in self.path: + self.send_response(503) + self.send_header("retry-after", "2") + elif "/503/1/" in self.path: + self.send_response(503) + self.send_header("retry-after", "Thu, 31 Dec 2043 23:59:59 GMT") + elif "/503/2/" in self.path: + self.send_response(503) + self.send_header("retry-after", "Fri, 31 Dec 1999 23:59:59 GMT") + elif "/503/3/" in self.path: + self.send_response(503) + self.send_header("retry-after", "") + elif "/503/4/" in self.path: + self.send_response(503) + self.send_header("retry-after", "(*#*(@*(@(") + elif "/503/5/" in self.path: + self.send_response(503) + self.send_header("retry-after", "aklsjflajfaklsfaklfasfklasdfklasdgahsdhgasdiogaioshdgo") + elif "/503/6/" in self.path: + self.send_response(503) + self.send_header("retry-after", "1 2 3 4 5 6 7 8 9 10") + else: + # Unknown request + self.send_response(400) + body = "Unknown /503/ path in server" + if "/reflect/" in self.path: + self.reflect_headers() + self.send_header("Content-type", "text/plain") + self.end_headers() + if body: + self.wfile.write(body) + elif "fail" not in self.path: data = data.copy() # we're going to modify # Ensure there's a "reply" key in data, even if there wasn't before data["reply"] = data.get("reply", llsd.LLSD("success")) diff --git a/indra/newview/llappcorehttp.cpp b/indra/newview/llappcorehttp.cpp index 0c242e57db..104debe023 100755 --- a/indra/newview/llappcorehttp.cpp +++ b/indra/newview/llappcorehttp.cpp @@ -105,8 +105,9 @@ void LLAppCoreHttp::init() } // Point to our certs or SSH/https: will fail on connect - status = LLCore::HttpRequest::setPolicyGlobalOption(LLCore::HttpRequest::GP_CA_FILE, - gDirUtilp->getCAFile()); + status = LLCore::HttpRequest::setStaticPolicyOption(LLCore::HttpRequest::PO_CA_FILE, + LLCore::HttpRequest::GLOBAL_POLICY_ID, + gDirUtilp->getCAFile(), NULL); if (! status) { LL_ERRS("Init") << "Failed to set CA File for HTTP services. Reason: " << status.toString() @@ -114,7 +115,9 @@ void LLAppCoreHttp::init() } // Establish HTTP Proxy, if desired. - status = LLCore::HttpRequest::setPolicyGlobalOption(LLCore::HttpRequest::GP_LLPROXY, 1); + status = LLCore::HttpRequest::setStaticPolicyOption(LLCore::HttpRequest::PO_LLPROXY, + LLCore::HttpRequest::GLOBAL_POLICY_ID, + 1, NULL); if (! status) { LL_WARNS("Init") << "Failed to set HTTP proxy for HTTP services. Reason: " << status.toString() @@ -131,7 +134,9 @@ void LLAppCoreHttp::init() { long trace_level(0L); trace_level = long(gSavedSettings.getU32(http_trace)); - status = LLCore::HttpRequest::setPolicyGlobalOption(LLCore::HttpRequest::GP_TRACE, trace_level); + status = LLCore::HttpRequest::setStaticPolicyOption(LLCore::HttpRequest::PO_TRACE, + LLCore::HttpRequest::GLOBAL_POLICY_ID, + trace_level, NULL); } // Setup default policy and constrain if directed to @@ -164,6 +169,9 @@ void LLAppCoreHttp::init() } } + // Need a request object to handle dynamic options before setting them + mRequest = new LLCore::HttpRequest; + // Apply initial settings refreshSettings(true); @@ -175,8 +183,6 @@ void LLAppCoreHttp::init() << LL_ENDL; } - mRequest = new LLCore::HttpRequest; - // Register signals for settings and state changes for (int i(0); i < LL_ARRAY_SIZE(init_data); ++i) { @@ -287,12 +293,13 @@ void LLAppCoreHttp::refreshSettings(bool initial) // Set it and report // *TODO: These are intended to be per-host limits when we can // support that in llcorehttp/libcurl. - LLCore::HttpStatus status; - status = LLCore::HttpRequest::setPolicyClassOption(mPolicies[policy], - LLCore::HttpRequest::CP_CONNECTION_LIMIT, - setting); - if (! status) + LLCore::HttpHandle handle; + handle = mRequest->setPolicyOption(LLCore::HttpRequest::PO_CONNECTION_LIMIT, + mPolicies[policy], + setting, NULL); + if (LLCORE_HTTP_HANDLE_INVALID == handle) { + LLCore::HttpStatus status(mRequest->getStatus()); LL_WARNS("Init") << "Unable to set " << init_data[i].mUsage << " concurrency. Reason: " << status.toString() << LL_ENDL; -- cgit v1.2.3 From d027db3cfc7c757cc8846e1a5187d1a816f63d9a Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Thu, 18 Jul 2013 15:14:55 -0400 Subject: SH-4325 Viewer working mixed grid with GetMesh and GetMesh2 caps Viewer modified for preference for GetMesh2 caps. When found, uses this cap and uses 1/4 the connection concurrency specified by MeshMaxConcurrentRequests. Also uses a modified calculation for high/low water feeding into the llcorehttp library. --- indra/newview/llmeshrepository.cpp | 68 ++++++++++++++++++++++---------------- indra/newview/llmeshrepository.h | 3 +- 2 files changed, 41 insertions(+), 30 deletions(-) (limited to 'indra') diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index 3af9ce7342..913841ab71 100755 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -152,7 +152,11 @@ // [5] Read of a double-checked lock. // // So, in addition to documentation, take this as a to-do/review -// list and see if you can improve things. +// list and see if you can improve things. For porters to non-x86 +// architectures, including amd64, the weaker memory models will +// make these platforms probabilistically more susceptible to hitting +// race conditions. True here and in other multi-thread code such +// as texture fetching. // // LLMeshRepository: // @@ -237,8 +241,6 @@ U32 LLMeshRepository::sCacheBytesWritten = 0; LLDeadmanTimer LLMeshRepository::sQuiescentTimer(15.0, true); // true -> gather cpu metrics -const U32 MAX_TEXTURE_UPLOAD_RETRIES = 5; - static S32 dump_num = 0; std::string make_dump_name(std::string prefix, S32 num) { @@ -797,7 +799,6 @@ void LLMeshRepoThread::run() } mWaiting = true; - ms_sleep(5); mSignal->wait(); mWaiting = false; @@ -982,20 +983,17 @@ void LLMeshRepoThread::loadMeshLOD(const LLVolumeParams& mesh_params, S32 lod) } // Constructs a Cap URL for the mesh. Prefers a GetMesh2 cap -// over a GetMesh cap and returns what it finds to the caller -// as an int ([1..2]). +// over a GetMesh cap. // //static -std::string LLMeshRepoThread::constructUrl(LLUUID mesh_id, int * cap_version) +std::string LLMeshRepoThread::constructUrl(LLUUID mesh_id) { - int version(1); std::string http_url; if (gAgent.getRegion()) { if (! gMeshRepo.mGetMesh2Capability.empty()) { - version = 2; http_url = gMeshRepo.mGetMesh2Capability; } else @@ -1015,7 +1013,6 @@ std::string LLMeshRepoThread::constructUrl(LLUUID mesh_id, int * cap_version) << mesh_id << ".mesh" << LL_ENDL; } - *cap_version = version; return http_url; } @@ -1134,8 +1131,8 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id, U32& count) { return false; } - int cap_version(1); - std::string http_url = constructUrl(mesh_id, &cap_version); + int cap_version(gMeshRepo.mGetMeshVersion); + std::string http_url = constructUrl(mesh_id); if (!http_url.empty()) { LLMeshSkinInfoHandler * handler = new LLMeshSkinInfoHandler(mesh_id, offset, size); @@ -1229,8 +1226,8 @@ bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id, U32& count) { return false; } - int cap_version(1); - std::string http_url = constructUrl(mesh_id, &cap_version); + int cap_version(gMeshRepo.mGetMeshVersion); + std::string http_url = constructUrl(mesh_id); if (!http_url.empty()) { LLMeshDecompositionHandler * handler = new LLMeshDecompositionHandler(mesh_id, offset, size); @@ -1323,8 +1320,8 @@ bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id, U32& count) { return false; } - int cap_version(1); - std::string http_url = constructUrl(mesh_id, &cap_version); + int cap_version(gMeshRepo.mGetMeshVersion); + std::string http_url = constructUrl(mesh_id); if (!http_url.empty()) { LLMeshPhysicsShapeHandler * handler = new LLMeshPhysicsShapeHandler(mesh_id, offset, size); @@ -1414,8 +1411,8 @@ bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params, U32& c //either cache entry doesn't exist or is corrupt, request header from simulator bool retval = true; - int cap_version(1); - std::string http_url = constructUrl(mesh_params.getSculptID(), &cap_version); + int cap_version(gMeshRepo.mGetMeshVersion); + std::string http_url = constructUrl(mesh_params.getSculptID()); if (!http_url.empty()) { //grab first 4KB if we're going to bother with a fetch. Cache will prevent future fetches if a full mesh fits @@ -1500,8 +1497,8 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, } //reading from VFS failed for whatever reason, fetch from sim - int cap_version(1); - std::string http_url = constructUrl(mesh_id, &cap_version); + int cap_version(gMeshRepo.mGetMeshVersion); + std::string http_url = constructUrl(mesh_id); if (!http_url.empty()) { LLMeshLODHandler * handler = new LLMeshLODHandler(mesh_params, lod, offset, size); @@ -2647,7 +2644,8 @@ void LLMeshPhysicsShapeHandler::processData(LLCore::BufferArray * body, U8 * dat LLMeshRepository::LLMeshRepository() : mMeshMutex(NULL), mMeshThreadCount(0), - mThread(NULL) + mThread(NULL), + mGetMeshVersion(2) { } @@ -2827,13 +2825,24 @@ S32 LLMeshRepository::loadMesh(LLVOVolume* vobj, const LLVolumeParams& mesh_para void LLMeshRepository::notifyLoadedMeshes() { //called from main thread - // *FIXME: Scaling down the setting by a factor of 4 for now to reflect - // target goal. May want to rename the setting before release. Also - // want/need to get these in a coordinated fashion from llappcorehttp. - LLMeshRepoThread::sMaxConcurrentRequests = gSavedSettings.getU32("MeshMaxConcurrentRequests") / 4; - LLMeshRepoThread::sRequestHighWater = llclamp(10 * S32(LLMeshRepoThread::sMaxConcurrentRequests), - REQUEST_HIGH_WATER_MIN, - REQUEST_HIGH_WATER_MAX); + if (1 == mGetMeshVersion) + { + // Legacy GetMesh operation with high connection concurrency + LLMeshRepoThread::sMaxConcurrentRequests = gSavedSettings.getU32("MeshMaxConcurrentRequests"); + LLMeshRepoThread::sRequestHighWater = llclamp(2 * S32(LLMeshRepoThread::sMaxConcurrentRequests), + REQUEST_HIGH_WATER_MIN, + REQUEST_HIGH_WATER_MAX); + } + else + { + // GetMesh2 operation with keepalives, etc. + // *TODO: Logic here is replicated from llappcorehttp.cpp, should really + // unify this and keep it in one place only. + LLMeshRepoThread::sMaxConcurrentRequests = gSavedSettings.getU32("MeshMaxConcurrentRequests") / 4; + LLMeshRepoThread::sRequestHighWater = llclamp(5 * S32(LLMeshRepoThread::sMaxConcurrentRequests), + REQUEST_HIGH_WATER_MIN, + REQUEST_HIGH_WATER_MAX); + } LLMeshRepoThread::sRequestLowWater = llclamp(LLMeshRepoThread::sRequestHighWater / 2, REQUEST_LOW_WATER_MIN, REQUEST_LOW_WATER_MAX); @@ -2929,6 +2938,7 @@ void LLMeshRepository::notifyLoadedMeshes() region_name = gAgent.getRegion()->getName(); mGetMeshCapability = gAgent.getRegion()->getCapability("GetMesh"); mGetMesh2Capability = gAgent.getRegion()->getCapability("GetMesh2"); + mGetMeshVersion = mGetMesh2Capability.empty() ? 1 : 2; LL_DEBUGS(LOG_MESH) << "Retrieving caps for region '" << region_name << "', GetMesh2: " << mGetMesh2Capability << ", GetMesh: " << mGetMeshCapability @@ -4152,7 +4162,7 @@ void teleport_started() LLMeshRepository::metricsStart(); } -// This comes from an edit in viewer-cat. Unify this once that's +// *TODO: This comes from an edit in viewer-cat. Unify this once that's // available everywhere. bool is_retryable(LLCore::HttpStatus status) { diff --git a/indra/newview/llmeshrepository.h b/indra/newview/llmeshrepository.h index 9bf14a3715..96e9feea9d 100755 --- a/indra/newview/llmeshrepository.h +++ b/indra/newview/llmeshrepository.h @@ -337,7 +337,7 @@ public: typedef std::set http_request_set; http_request_set mHttpRequestSet; // Outstanding HTTP requests - static std::string constructUrl(LLUUID mesh_id, int * cap_version); + static std::string constructUrl(LLUUID mesh_id); LLMeshRepoThread(); ~LLMeshRepoThread(); @@ -600,6 +600,7 @@ public: std::string mGetMeshCapability; std::string mGetMesh2Capability; + int mGetMeshVersion; }; extern LLMeshRepository gMeshRepo; -- cgit v1.2.3 From b7f14a7b39610ac1cb6db5fafeab568aa9b662c5 Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Thu, 25 Jul 2013 18:36:08 -0400 Subject: SH-4365 SH-4367 Conversion of mesh uploaders to llcorehttp. With this checkin, legacy LLCurl is out of the mesh code. Uploaders and responders are converted and functioning. Logging has been cleaned up throughout the code to use the macro form with tag/subtag capability. DEBUGS-level logging added for some upload path milestones. Better error information flow from failed responses to viewer alert boxes but I'd really, really like to do better here. Mesh upload problems are completely opaque as a user. Minor cleanups (removed dead members, method signatures tidied, less data conversion). Could almost call this complete, will likely have platform cleanups, however. --- indra/newview/llmeshrepository.cpp | 514 +++++++++++++++++++++---------------- indra/newview/llmeshrepository.h | 48 ++-- 2 files changed, 328 insertions(+), 234 deletions(-) (limited to 'indra') diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index 2d003dd6d7..1f40026e80 100755 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -68,6 +68,7 @@ #include "llviewerparcelmgr.h" #include "lluploadfloaterobservers.h" #include "bufferarray.h" +#include "bufferstream.h" #include "boost/lexical_cast.hpp" @@ -203,6 +204,7 @@ // mUnavailableQ mMutex rw.repo.none [0], ro.main.none [3], rw.main.mMutex // mLoadedQ mMutex rw.repo.mMutex, ro.main.none [3], rw.main.mMutex // mPendingLOD mMutex rw.repo.mMutex, rw.any.mMutex +// mHttp* none rw.repo.none // // LLPhysicsDecomp: // @@ -210,7 +212,19 @@ // mCurRequest // mCompletedQ // - +// +// QA/Development Testing +// +// Debug variable 'MeshUploadFakeErrors' takes a mask of bits that will +// simulate an error on fee query or upload. Defined bits are: +// +// 0x01 Simulate application error on fee check reading +// response body from file "fake_upload_error.xml" +// 0x02 Same as 0x01 but for actual upload attempt. +// 0x04 Simulate a transport problem on fee check with a +// locally-generated 500 status. +// 0x08 As with 0x04 but for the upload operation. +// LLMeshRepository gMeshRepo; @@ -362,6 +376,14 @@ S32 LLMeshRepoThread::sRequestHighWater = REQUEST_HIGH_WATER_MIN; // processFailure() methods to customize handling and // error messages. // +// LLCore::HttpHandler +// LLMeshHandlerBase +// LLMeshHeaderHandler +// LLMeshLODHandler +// LLMeshSkinInfoHandler +// LLMeshDecompositionHandler +// LLMeshPhysicsShapeHandler + class LLMeshHandlerBase : public LLCore::HttpHandler { public: @@ -534,28 +556,31 @@ public: }; -void log_upload_error(S32 status, const LLSD& content, std::string stage, std::string model_name) +void log_upload_error(LLCore::HttpStatus status, const LLSD& content, + const char * const stage, const std::string & model_name) { // Add notification popup. LLSD args; - std::string message = content["error"]["message"]; - std::string identifier = content["error"]["identifier"]; + std::string message = content["error"]["message"].asString(); + std::string identifier = content["error"]["identifier"].asString(); args["MESSAGE"] = message; args["IDENTIFIER"] = identifier; args["LABEL"] = model_name; gMeshRepo.uploadError(args); // Log details. - llwarns << "stage: " << stage << " http status: " << status << llendl; + LL_WARNS(LOG_MESH) << "Error in stage: " << stage + << ", Reason: " << status.toString() + << " (" << status.toHex() << ")" << LL_ENDL; if (content.has("error")) { const LLSD& err = content["error"]; - llwarns << "err: " << err << llendl; - llwarns << "mesh upload failed, stage '" << stage - << "' error '" << err["error"].asString() - << "', message '" << err["message"].asString() - << "', id '" << err["identifier"].asString() - << "'" << llendl; + LL_WARNS(LOG_MESH) << "error: " << err << LL_ENDL; + LL_WARNS(LOG_MESH) << " mesh upload failed, stage '" << stage + << "', error '" << err["error"].asString() + << "', message '" << err["message"].asString() + << "', id '" << err["identifier"].asString() + << "'" << LL_ENDL; if (err.has("errors")) { S32 error_num = 0; @@ -565,13 +590,13 @@ void log_upload_error(S32 status, const LLSD& content, std::string stage, std::s ++it) { const LLSD& err_entry = *it; - llwarns << "error[" << error_num << "]:" << llendl; + LL_WARNS(LOG_MESH) << " error[" << error_num << "]:" << LL_ENDL; for (LLSD::map_const_iterator map_it = err_entry.beginMap(); map_it != err_entry.endMap(); ++map_it) { - llwarns << "\t" << map_it->first << ": " - << map_it->second << llendl; + LL_WARNS(LOG_MESH) << " " << map_it->first << ": " + << map_it->second << LL_ENDL; } error_num++; } @@ -579,141 +604,10 @@ void log_upload_error(S32 status, const LLSD& content, std::string stage, std::s } else { - llwarns << "bad mesh, no error information available" << llendl; + LL_WARNS(LOG_MESH) << "Bad response to mesh request, no additional error information available." << LL_ENDL; } } -class LLWholeModelFeeResponder: public LLCurl::Responder -{ - LLMeshUploadThread* mThread; - LLSD mModelData; - LLHandle mObserverHandle; -public: - LLWholeModelFeeResponder(LLMeshUploadThread* thread, LLSD& model_data, LLHandle observer_handle): - mThread(thread), - mModelData(model_data), - mObserverHandle(observer_handle) - { - if (mThread) - { - mThread->startRequest(); - } - } - - ~LLWholeModelFeeResponder() - { - if (mThread) - { - mThread->stopRequest(); - } - } - - virtual void completed(U32 status, - const std::string& reason, - const LLSD& content) - { - LLSD cc = content; - if (gSavedSettings.getS32("MeshUploadFakeErrors")&1) - { - cc = llsd_from_file("fake_upload_error.xml"); - } - - dump_llsd_to_file(cc,make_dump_name("whole_model_fee_response_",dump_num)); - - LLWholeModelFeeObserver* observer = mObserverHandle.get(); - - if (isGoodStatus(status) && - cc["state"].asString() == "upload") - { - mThread->mWholeModelUploadURL = cc["uploader"].asString(); - - if (observer) - { - cc["data"]["upload_price"] = cc["upload_price"]; - observer->onModelPhysicsFeeReceived(cc["data"], mThread->mWholeModelUploadURL); - } - } - else - { - llwarns << "fee request failed" << llendl; - log_upload_error(status,cc,"fee",mModelData["name"]); - mThread->mWholeModelUploadURL = ""; - - if (observer) - { - observer->setModelPhysicsFeeErrorStatus(status, reason); - } - } - } - -}; - -class LLWholeModelUploadResponder: public LLCurl::Responder -{ - LLMeshUploadThread* mThread; - LLSD mModelData; - LLHandle mObserverHandle; - -public: - LLWholeModelUploadResponder(LLMeshUploadThread* thread, LLSD& model_data, LLHandle observer_handle): - mThread(thread), - mModelData(model_data), - mObserverHandle(observer_handle) - { - if (mThread) - { - mThread->startRequest(); - } - } - - ~LLWholeModelUploadResponder() - { - if (mThread) - { - mThread->stopRequest(); - } - } - - virtual void completed(U32 status, - const std::string& reason, - const LLSD& content) - { - LLSD cc = content; - if (gSavedSettings.getS32("MeshUploadFakeErrors")&2) - { - cc = llsd_from_file("fake_upload_error.xml"); - } - - dump_llsd_to_file(cc,make_dump_name("whole_model_upload_response_",dump_num)); - - LLWholeModelUploadObserver* observer = mObserverHandle.get(); - - // requested "mesh" asset type isn't actually the type - // of the resultant object, fix it up here. - if (isGoodStatus(status) && - cc["state"].asString() == "complete") - { - mModelData["asset_type"] = "object"; - gMeshRepo.updateInventory(LLMeshRepository::inventory_data(mModelData,cc)); - - if (observer) - { - doOnIdleOneTime(boost::bind(&LLWholeModelUploadObserver::onModelUploadSuccess, observer)); - } - } - else - { - llwarns << "upload failed" << llendl; - std::string model_name = mModelData["name"].asString(); - log_upload_error(status,cc,"upload",model_name); - - if (observer) - { - doOnIdleOneTime(boost::bind(&LLWholeModelUploadObserver::onModelUploadFailure, observer)); - } - } - } -}; LLMeshRepoThread::LLMeshRepoThread() : LLThread("mesh repo"), @@ -788,7 +682,7 @@ void LLMeshRepoThread::run() LLCDResult res = LLConvexDecomposition::initThread(); if (res != LLCD_OK) { - llwarns << "convex decomposition unable to be loaded" << llendl; + LL_WARNS(LOG_MESH) << "Convex decomposition unable to be loaded. Expect severe problems." << LL_ENDL; } while (!LLApp::isQuitting()) @@ -923,7 +817,7 @@ void LLMeshRepoThread::run() res = LLConvexDecomposition::quitThread(); if (res != LLCD_OK) { - llwarns << "convex decomposition unable to be quit" << llendl; + LL_WARNS(LOG_MESH) << "Convex decomposition unable to be quit." << LL_ENDL; } } @@ -951,7 +845,6 @@ void LLMeshRepoThread::lockAndLoadMeshLOD(const LLVolumeParams& mesh_params, S32 } - void LLMeshRepoThread::loadMeshLOD(const LLVolumeParams& mesh_params, S32 lod) { //could be called from any thread LLMutexLock lock(mMutex); @@ -1648,7 +1541,7 @@ bool LLMeshRepoThread::skinInfoReceived(const LLUUID& mesh_id, U8* data, S32 dat LLMeshSkinInfo info(skin); info.mMeshID = mesh_id; - //llinfos<<"info pelvis offset"< fee_observer, LLHandle upload_observer) -: LLThread("mesh upload"), + bool upload_skin, bool upload_joints, const std::string & upload_url, bool do_upload, + LLHandle fee_observer, + LLHandle upload_observer) + : LLThread("mesh upload"), + LLCore::HttpHandler(), mDiscarded(FALSE), mDoUpload(do_upload), mWholeModelUploadURL(upload_url), @@ -1754,7 +1650,6 @@ LLMeshUploadThread::LLMeshUploadThread(LLMeshUploadThread::instance_list& data, mUploadSkin = upload_skin; mUploadJoints = upload_joints; mMutex = new LLMutex(NULL); - mCurlRequest = NULL; mPendingUploads = 0; mFinished = false; mOrigin = gAgent.getPositionAgent(); @@ -1764,12 +1659,31 @@ LLMeshUploadThread::LLMeshUploadThread(LLMeshUploadThread::instance_list& data, mOrigin += gAgent.getAtAxis() * scale.magVec(); - mMeshUploadTimeOut = gSavedSettings.getS32("MeshUploadTimeOut") ; + mMeshUploadTimeOut = gSavedSettings.getS32("MeshUploadTimeOut"); + + mHttpRequest = new LLCore::HttpRequest; + mHttpOptions = new LLCore::HttpOptions; + mHttpOptions->setTransferTimeout(mMeshUploadTimeOut); + mHttpHeaders = new LLCore::HttpHeaders; + mHttpHeaders->append("Content-Type", "application/llsd+xml"); + mHttpPolicyClass = LLAppViewer::instance()->getAppCoreHttp().getPolicy(LLAppCoreHttp::AP_UPLOADS); + mHttpPriority = 0; } LLMeshUploadThread::~LLMeshUploadThread() { - + if (mHttpHeaders) + { + mHttpHeaders->release(); + mHttpHeaders = NULL; + } + if (mHttpOptions) + { + mHttpOptions->release(); + mHttpOptions = NULL; + } + delete mHttpRequest; + mHttpRequest = NULL; } LLMeshUploadThread::DecompRequest::DecompRequest(LLModel* mdl, LLModel* base_model, LLMeshUploadThread* thread) @@ -1811,14 +1725,14 @@ void LLMeshUploadThread::preStart() void LLMeshUploadThread::discard() { - LLMutexLock lock(mMutex) ; - mDiscarded = TRUE ; + LLMutexLock lock(mMutex); + mDiscarded = TRUE; } -BOOL LLMeshUploadThread::isDiscarded() +BOOL LLMeshUploadThread::isDiscarded() const { - LLMutexLock lock(mMutex) ; - return mDiscarded ; + LLMutexLock lock(mMutex); + return mDiscarded; } void LLMeshUploadThread::run() @@ -2069,7 +1983,7 @@ void LLMeshUploadThread::generateHulls() } } - if(has_valid_requests) + if (has_valid_requests) { while (!mPhysicsComplete) { @@ -2080,7 +1994,7 @@ void LLMeshUploadThread::generateHulls() void LLMeshUploadThread::doWholeModelUpload() { - mCurlRequest = new LLCurlRequest(); + LL_DEBUGS(LOG_MESH) << "Starting model upload. Instances: " << mInstance.size() << LL_ENDL; if (mWholeModelUploadURL.empty()) { @@ -2090,75 +2004,240 @@ void LLMeshUploadThread::doWholeModelUpload() else { generateHulls(); - - LLSD full_model_data; - wholeModelToLLSD(full_model_data, true); - LLSD body = full_model_data["asset_resources"]; - dump_llsd_to_file(body,make_dump_name("whole_model_body_",dump_num)); - LLCurlRequest::headers_t headers; - + LL_DEBUGS(LOG_MESH) << "Hull generation completed." << LL_ENDL; + + mModelData = LLSD::emptyMap(); + wholeModelToLLSD(mModelData, true); + LLSD body = mModelData["asset_resources"]; + dump_llsd_to_file(body, make_dump_name("whole_model_body_", dump_num)); + + LLCore::BufferArray * ba = new LLCore::BufferArray; + LLCore::BufferArrayStream bas(ba); + LLSDSerialize::toXML(body, bas); + // LLSDSerialize::toXML(mModelData, bas); // <- This will generate a convenient upload error + LLCore::HttpHandle handle = mHttpRequest->requestPost(mHttpPolicyClass, + mHttpPriority, + mWholeModelUploadURL, + ba, + mHttpOptions, + mHttpHeaders, + this); + ba->release(); + + if (LLCORE_HTTP_HANDLE_INVALID == handle) { - LLCurl::ResponderPtr responder = new LLWholeModelUploadResponder(this, full_model_data, mUploadObserverHandle) ; - - while(!mCurlRequest->post(mWholeModelUploadURL, headers, body, responder, mMeshUploadTimeOut)) + mHttpStatus = mHttpRequest->getStatus(); + + LL_WARNS(LOG_MESH) << "Couldn't issue request for full model upload. Reason: " << mHttpStatus.toString() + << " (" << mHttpStatus.toHex() << ")" + << LL_ENDL; + } + else + { + U32 sleep_time(10); + + LL_DEBUGS(LOG_MESH) << "POST request issued." << LL_ENDL; + + mHttpRequest->update(0); + while (! LLApp::isQuitting() && ! mFinished) { - //sleep for 10ms to prevent eating a whole core - apr_sleep(10000); + ms_sleep(sleep_time); + sleep_time = llmin(250U, sleep_time + sleep_time); + mHttpRequest->update(0); } + LL_DEBUGS(LOG_MESH) << "Mesh upload operation completed." << LL_ENDL; } - - do - { - mCurlRequest->process(); - //sleep for 10ms to prevent eating a whole core - apr_sleep(10000); - } while (!LLAppViewer::isQuitting() && mPendingUploads > 0); } - - delete mCurlRequest; - mCurlRequest = NULL; - - // Currently a no-op. - mFinished = true; } void LLMeshUploadThread::requestWholeModelFee() { dump_num++; - mCurlRequest = new LLCurlRequest(); - generateHulls(); - LLSD model_data; - wholeModelToLLSD(model_data,false); - dump_llsd_to_file(model_data,make_dump_name("whole_model_fee_request_",dump_num)); - - LLCurlRequest::headers_t headers; + mModelData = LLSD::emptyMap(); + wholeModelToLLSD(mModelData, false); + dump_llsd_to_file(mModelData, make_dump_name("whole_model_fee_request_", dump_num)); + LLCore::BufferArray * ba = new LLCore::BufferArray; + LLCore::BufferArrayStream bas(ba); + LLSDSerialize::toXML(mModelData, bas); + + LLCore::HttpHandle handle = mHttpRequest->requestPost(mHttpPolicyClass, + mHttpPriority, + mWholeModelFeeCapability, + ba, + mHttpOptions, + mHttpHeaders, + this); + ba->release(); + if (LLCORE_HTTP_HANDLE_INVALID == handle) { - LLCurl::ResponderPtr responder = new LLWholeModelFeeResponder(this,model_data, mFeeObserverHandle) ; - while(!mCurlRequest->post(mWholeModelFeeCapability, headers, model_data, responder, mMeshUploadTimeOut)) + mHttpStatus = mHttpRequest->getStatus(); + + LL_WARNS(LOG_MESH) << "Couldn't issue request for model fee. Reason: " << mHttpStatus.toString() + << " (" << mHttpStatus.toHex() << ")" + << LL_ENDL; + } + else + { + U32 sleep_time(10); + + mHttpRequest->update(0); + while (! LLApp::isQuitting() && ! mFinished) { - //sleep for 10ms to prevent eating a whole core - apr_sleep(10000); + ms_sleep(sleep_time); + sleep_time = llmin(250U, sleep_time + sleep_time); + mHttpRequest->update(0); } } +} - do - { - mCurlRequest->process(); - //sleep for 10ms to prevent eating a whole core - apr_sleep(10000); - } while (!LLApp::isQuitting() && mPendingUploads > 0); - delete mCurlRequest; - mCurlRequest = NULL; +// Does completion duty for both fee queries and actual uploads. +void LLMeshUploadThread::onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response) +{ + // QA/Devel: 0x2 to enable fake error import on upload, 0x1 on fee check + const S32 fake_error(gSavedSettings.getS32("MeshUploadFakeErrors") & (mDoUpload ? 0xa : 0x5)); + LLCore::HttpStatus status(response->getStatus()); + if (fake_error) + { + status = (fake_error & 0x0c) ? LLCore::HttpStatus(500) : LLCore::HttpStatus(200); + } + std::string reason(status.toString()); + LLSD body; - // Currently a no-op. mFinished = true; + + if (mDoUpload) + { + // model upload case + LLWholeModelUploadObserver * observer(mUploadObserverHandle.get()); + + if (! status) + { + LL_WARNS(LOG_MESH) << "Upload failed. Reason: " << reason + << " (" << status.toHex() << ")" + << LL_ENDL; + + // Build a fake body for the alert generator + body["error"] = LLSD::emptyMap(); + body["error"]["message"] = reason; + body["error"]["identifier"] = "NetworkError"; // from asset-upload/upload_util.py + log_upload_error(status, body, "upload", mModelData["name"].asString()); + + if (observer) + { + doOnIdleOneTime(boost::bind(&LLWholeModelUploadObserver::onModelUploadFailure, observer)); + } + } + else + { + if (fake_error & 0x2) + { + body = llsd_from_file("fake_upload_error.xml"); + } + else + { + LLCore::BufferArray * ba(response->getBody()); + if (ba && ba->size()) + { + LLCore::BufferArrayStream bas(ba); + LLSDSerialize::fromXML(body, bas); + } + } + dump_llsd_to_file(body, make_dump_name("whole_model_upload_response_", dump_num)); + + if (body["state"].asString() == "complete") + { + // requested "mesh" asset type isn't actually the type + // of the resultant object, fix it up here. + mModelData["asset_type"] = "object"; + gMeshRepo.updateInventory(LLMeshRepository::inventory_data(mModelData, body)); + + if (observer) + { + doOnIdleOneTime(boost::bind(&LLWholeModelUploadObserver::onModelUploadSuccess, observer)); + } + } + else + { + LL_WARNS(LOG_MESH) << "Upload failed. Not in expected 'complete' state." << LL_ENDL; + log_upload_error(status, body, "upload", mModelData["name"].asString()); + + if (observer) + { + doOnIdleOneTime(boost::bind(&LLWholeModelUploadObserver::onModelUploadFailure, observer)); + } + } + } + } + else + { + // model fee case + LLWholeModelFeeObserver* observer(mFeeObserverHandle.get()); + mWholeModelUploadURL.clear(); + + if (! status) + { + LL_WARNS(LOG_MESH) << "Fee request failed. Reason: " << reason + << " (" << status.toHex() << ")" + << LL_ENDL; + + // Build a fake body for the alert generator + body["error"] = LLSD::emptyMap(); + body["error"]["message"] = reason; + body["error"]["identifier"] = "NetworkError"; // from asset-upload/upload_util.py + log_upload_error(status, body, "fee", mModelData["name"].asString()); + + if (observer) + { + observer->setModelPhysicsFeeErrorStatus(status.toULong(), reason); + } + } + else + { + if (fake_error & 0x1) + { + body = llsd_from_file("fake_upload_error.xml"); + } + else + { + LLCore::BufferArray * ba(response->getBody()); + if (ba && ba->size()) + { + LLCore::BufferArrayStream bas(ba); + LLSDSerialize::fromXML(body, bas); + } + } + dump_llsd_to_file(body, make_dump_name("whole_model_fee_response_", dump_num)); + + if (body["state"].asString() == "upload") + { + mWholeModelUploadURL = body["uploader"].asString(); + + if (observer) + { + body["data"]["upload_price"] = body["upload_price"]; + observer->onModelPhysicsFeeReceived(body["data"], mWholeModelUploadURL); + } + } + else + { + LL_WARNS(LOG_MESH) << "Fee request failed. Not in expected 'upload' state." << LL_ENDL; + log_upload_error(status, body, "fee", mModelData["name"].asString()); + + if (observer) + { + observer->setModelPhysicsFeeErrorStatus(status.toULong(), reason); + } + } + } + } } + void LLMeshRepoThread::notifyLoadedMeshes() { if (!mMutex) @@ -2674,13 +2753,13 @@ void LLMeshRepository::init() void LLMeshRepository::shutdown() { - llinfos << "Shutting down mesh repository." << llendl; + LL_INFOS(LOG_MESH) << "Shutting down mesh repository." << LL_ENDL; metrics_teleport_started_signal.disconnect(); for (U32 i = 0; i < mUploads.size(); ++i) { - llinfos << "Discard the pending mesh uploads " << llendl; + LL_INFOS(LOG_MESH) << "Discard the pending mesh uploads." << LL_ENDL; mUploads[i]->discard() ; //discard the uploading requests. } @@ -2695,7 +2774,7 @@ void LLMeshRepository::shutdown() for (U32 i = 0; i < mUploads.size(); ++i) { - llinfos << "Waiting for pending mesh upload " << i << "/" << mUploads.size() << llendl; + LL_INFOS(LOG_MESH) << "Waiting for pending mesh upload " << i << "/" << mUploads.size() << LL_ENDL; while (!mUploads[i]->isStopped()) { apr_sleep(10); @@ -2708,7 +2787,7 @@ void LLMeshRepository::shutdown() delete mMeshMutex; mMeshMutex = NULL; - llinfos << "Shutting down decomposition system." << llendl; + LL_INFOS(LOG_MESH) << "Shutting down decomposition system." << LL_ENDL; if (mDecompThread) { @@ -3591,7 +3670,7 @@ void LLPhysicsDecomp::setMeshData(LLCDMeshData& mesh, bool vertex_based) if (ret) { - llerrs << "Convex Decomposition thread valid but could not set mesh data" << llendl; + LL_ERRS(LOG_MESH) << "Convex Decomposition thread valid but could not set mesh data." << LL_ENDL; } } } @@ -3667,7 +3746,8 @@ void LLPhysicsDecomp::doDecomposition() if (ret) { - llwarns << "Convex Decomposition thread valid but could not execute stage " << stage << llendl; + LL_WARNS(LOG_MESH) << "Convex Decomposition thread valid but could not execute stage " << stage << "." + << LL_ENDL; LLMutexLock lock(mMutex); mCurRequest->mHull.clear(); @@ -3796,9 +3876,9 @@ void LLPhysicsDecomp::doDecompositionSingleHull() setMeshData(mesh, true); LLCDResult ret = decomp->buildSingleHull() ; - if(ret) + if (ret) { - llwarns << "Could not execute decomposition stage when attempting to create single hull." << llendl; + LL_WARNS(LOG_MESH) << "Could not execute decomposition stage when attempting to create single hull." << LL_ENDL; make_box(mCurRequest); } else @@ -4152,7 +4232,7 @@ void LLMeshRepository::metricsUpdate() metrics["teleports"] = LLSD::Integer(metrics_teleport_start_count); metrics["user_cpu"] = double(user_cpu) / 1.0e6; metrics["sys_cpu"] = double(sys_cpu) / 1.0e6; - llinfos << "EventMarker " << metrics << llendl; + LL_INFOS(LOG_MESH) << "EventMarker " << metrics << LL_ENDL; } } diff --git a/indra/newview/llmeshrepository.h b/indra/newview/llmeshrepository.h index 96e9feea9d..70079eed23 100755 --- a/indra/newview/llmeshrepository.h +++ b/indra/newview/llmeshrepository.h @@ -45,8 +45,6 @@ #include "lluploadfloaterobservers.h" class LLVOVolume; -class LLMeshResponder; -class LLCurlRequest; class LLMutex; class LLCondition; class LLVFS; @@ -396,7 +394,14 @@ private: U32 mHttpLargeGetCount; }; -class LLMeshUploadThread : public LLThread + +// Class whose instances represent a single upload-type request for +// meshes: one fee query or one actual upload attempt. Yes, it creates +// a unique thread for that single request. As it is 1:1, it can also +// trivially serve as the HttpHandler object for request completion +// notifications. + +class LLMeshUploadThread : public LLThread, public LLCore::HttpHandler { private: S32 mMeshUploadTimeOut ; //maximum time in seconds to execute an uploading request. @@ -417,44 +422,41 @@ public: }; LLPointer mFinalDecomp; - bool mPhysicsComplete; + volatile bool mPhysicsComplete; typedef std::map, std::vector > hull_map; - hull_map mHullMap; + hull_map mHullMap; typedef std::vector instance_list; - instance_list mInstanceList; + instance_list mInstanceList; typedef std::map, instance_list> instance_map; - instance_map mInstance; + instance_map mInstance; - LLMutex* mMutex; - LLCurlRequest* mCurlRequest; + LLMutex* mMutex; S32 mPendingUploads; LLVector3 mOrigin; bool mFinished; bool mUploadTextures; bool mUploadSkin; bool mUploadJoints; - BOOL mDiscarded ; + BOOL mDiscarded; LLHost mHost; std::string mWholeModelFeeCapability; std::string mWholeModelUploadURL; LLMeshUploadThread(instance_list& data, LLVector3& scale, bool upload_textures, - bool upload_skin, bool upload_joints, std::string upload_url, bool do_upload = true, - LLHandle fee_observer= (LLHandle()), LLHandle upload_observer = (LLHandle())); + bool upload_skin, bool upload_joints, const std::string & upload_url, bool do_upload = true, + LLHandle fee_observer = (LLHandle()), + LLHandle upload_observer = (LLHandle())); ~LLMeshUploadThread(); - void startRequest() { ++mPendingUploads; } - void stopRequest() { --mPendingUploads; } - - bool finished() { return mFinished; } + bool finished() const { return mFinished; } virtual void run(); void preStart(); void discard() ; - BOOL isDiscarded(); + BOOL isDiscarded() const; void generateHulls(); @@ -471,11 +473,23 @@ public: void setFeeObserverHandle(LLHandle observer_handle) { mFeeObserverHandle = observer_handle; } void setUploadObserverHandle(LLHandle observer_handle) { mUploadObserverHandle = observer_handle; } + // Inherited from LLCore::HttpHandler + virtual void onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response); + private: LLHandle mFeeObserverHandle; LLHandle mUploadObserverHandle; bool mDoUpload; // if FALSE only model data will be requested, otherwise the model will be uploaded + LLSD mModelData; + + // llcorehttp library interface objects. + LLCore::HttpStatus mHttpStatus; + LLCore::HttpRequest * mHttpRequest; + LLCore::HttpOptions * mHttpOptions; + LLCore::HttpHeaders * mHttpHeaders; + LLCore::HttpRequest::policy_t mHttpPolicyClass; + LLCore::HttpRequest::priority_t mHttpPriority; }; class LLMeshRepository -- cgit v1.2.3 From c9e64823c05a493e6c926deebff1b0aaf0fb50be Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Mon, 29 Jul 2013 12:42:27 -0400 Subject: SH-4368 Adjust upload timeout parameters for slow networks. Generally sorted the mesh timeout parameters for maximum transport time (staying with default 30 for connect). 60S for normal meshes, 600S for large. Also documented default option values in httpoptions.h. Useful to have these. In the future, the timeouts might go into standard llsd options where they can be tracked a bit more. --- indra/llcorehttp/httpoptions.h | 10 ++++++++-- indra/newview/llmeshrepository.cpp | 4 +++- 2 files changed, 11 insertions(+), 3 deletions(-) (limited to 'indra') diff --git a/indra/llcorehttp/httpoptions.h b/indra/llcorehttp/httpoptions.h index 04531425d8..f49a3555aa 100755 --- a/indra/llcorehttp/httpoptions.h +++ b/indra/llcorehttp/httpoptions.h @@ -68,36 +68,42 @@ protected: void operator=(const HttpOptions &); // Not defined public: + // Default: false void setWantHeaders(bool wanted); bool getWantHeaders() const { return mWantHeaders; } - + + // Default: 0 void setTrace(int long); int getTrace() const { return mTracing; } + // Default: 30 void setTimeout(unsigned int timeout); unsigned int getTimeout() const { return mTimeout; } + // Default: 0 void setTransferTimeout(unsigned int timeout); unsigned int getTransferTimeout() const { return mTransferTimeout; } + // Default: 8 void setRetries(unsigned int retries); unsigned int getRetries() const { return mRetries; } - + + // Default: false void setUseRetryAfter(bool use_retry); bool getUseRetryAfter() const { diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index 1f40026e80..d02384f87c 100755 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -235,7 +235,8 @@ const S32 REQUEST_HIGH_WATER_MAX = 80; const S32 REQUEST_LOW_WATER_MIN = 16; const S32 REQUEST_LOW_WATER_MAX = 40; const U32 LARGE_MESH_FETCH_THRESHOLD = 1U << 21; // Size at which requests goes to narrow/slow queue -const long LARGE_MESH_XFER_TIMEOUT = 240L; // Seconds to complete xfer +const long SMALL_MESH_XFER_TIMEOUT = 60L; // Seconds to complete xfer, small mesh downloads +const long LARGE_MESH_XFER_TIMEOUT = 600L; // Seconds to complete xfer, large downloads // Maximum mesh version to support. Three least significant digits are reserved for the minor version, // with major version changes indicating a format change that is not backwards compatible and should not @@ -629,6 +630,7 @@ LLMeshRepoThread::LLMeshRepoThread() mSignal = new LLCondition(NULL); mHttpRequest = new LLCore::HttpRequest; mHttpOptions = new LLCore::HttpOptions; + mHttpOptions->setTransferTimeout(SMALL_MESH_XFER_TIMEOUT); mHttpLargeOptions = new LLCore::HttpOptions; mHttpLargeOptions->setTransferTimeout(LARGE_MESH_XFER_TIMEOUT); mHttpHeaders = new LLCore::HttpHeaders; -- cgit v1.2.3 From eb2869bd7fe3ea856c7d25a1518699cb905a8954 Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Mon, 29 Jul 2013 16:09:22 -0400 Subject: SH-4371 Reduce 22mS inter-conenction latency on HTTP operations. Simple change dropped this value by 7-10mS or so. Any time we complete an operation on a transport pass, arrange to skip the run-loop sleep so that we fill a possible empty slot as quickly as possible. With pipelining, this kind of thing should become unnecessary but for now, we'll do this to increase throughput. --- indra/llcorehttp/_httplibcurl.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'indra') diff --git a/indra/llcorehttp/_httplibcurl.cpp b/indra/llcorehttp/_httplibcurl.cpp index d187697e7b..b079dff864 100755 --- a/indra/llcorehttp/_httplibcurl.cpp +++ b/indra/llcorehttp/_httplibcurl.cpp @@ -132,12 +132,10 @@ HttpService::ELoopSpeed HttpLibcurl::processTransport() CURL * handle(msg->easy_handle); CURLcode result(msg->data.result); - if (completeRequest(mMultiHandles[policy_class], handle, result)) - { - // Request is still active, don't get too sleepy - ret = HttpService::NORMAL; - } - handle = NULL; // No longer valid on return + completeRequest(mMultiHandles[policy_class], handle, result); + handle = NULL; // No longer valid on return + ret = HttpService::NORMAL; // If anything completes, we may have a free slot. + // Turning around quickly reduces connection gap by 7-10mS. } else if (CURLMSG_NONE == msg->msg) { -- cgit v1.2.3 From 46dd3df73370590f61eb9a2cffcd732463a4319b Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Mon, 29 Jul 2013 16:26:02 -0400 Subject: Reduce HTTP request retry log spam. Thought I'd done this in an earlier maintenance branch but the code never showed up. I'll do it again. Spam is still available by bumping 'CoreHttp' tag up to DEBUGS level logging. Needed for QA. Can also get this data from tracing. --- indra/llcorehttp/_httppolicy.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) (limited to 'indra') diff --git a/indra/llcorehttp/_httppolicy.cpp b/indra/llcorehttp/_httppolicy.cpp index 2754e8ef07..32a9ba282a 100755 --- a/indra/llcorehttp/_httppolicy.cpp +++ b/indra/llcorehttp/_httppolicy.cpp @@ -162,15 +162,17 @@ void HttpPolicy::retryOp(HttpOpRequest * op) { ++op->mPolicy503Retries; } - LL_WARNS("CoreHttp") << "HTTP request " << static_cast(op) - << " retry " << op->mPolicyRetries - << " scheduled in " << (delta / HttpTime(1000)) - << " mS. Status: " << op->mStatus.toHex() - << LL_ENDL; + LL_DEBUGS("CoreHttp") << "HTTP request " << static_cast(op) + << " retry " << op->mPolicyRetries + << " scheduled in " << (delta / HttpTime(1000)) + << " mS. Status: " << op->mStatus.toHex() + << LL_ENDL; if (op->mTracing > HTTP_TRACE_OFF) { LL_INFOS("CoreHttp") << "TRACE, ToRetryQueue, Handle: " << static_cast(op) + << ", Delta: " << (delta / HttpTime(1000)) + << ", Retries: " << op->mPolicyRetries << LL_ENDL; } mClasses[policy_class]->mRetryQueue.push(op); @@ -362,9 +364,9 @@ bool HttpPolicy::stageAfterCompletion(HttpOpRequest * op) } else if (op->mPolicyRetries) { - LL_WARNS("CoreHttp") << "HTTP request " << static_cast(op) - << " succeeded on retry " << op->mPolicyRetries << "." - << LL_ENDL; + LL_DEBUGS("CoreHttp") << "HTTP request " << static_cast(op) + << " succeeded on retry " << op->mPolicyRetries << "." + << LL_ENDL; } op->stageFromActive(mService); -- cgit v1.2.3 From f3927c6ca2aad757fe88fdd59b87986ca8b207a8 Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Tue, 30 Jul 2013 15:21:31 -0400 Subject: SH-4371 Reduce 22mS inter-connection latency. This really extended into the client-side request throttling. Moved this from llmeshrepository (which doesn't really want to do connection management) into llcorehttp. It's now a class option with configurable rate. This still isn't the right thing to do as it creates coupling between viewer and services. When we get to pipelining, this notion becomes invalid. --- indra/llcorehttp/_httpinternal.h | 1 + indra/llcorehttp/_httppolicy.cpp | 67 ++++++++++++++++++++++++++++++++--- indra/llcorehttp/_httppolicyclass.cpp | 15 ++++++-- indra/llcorehttp/_httppolicyclass.h | 1 + indra/llcorehttp/_httpservice.cpp | 3 +- indra/llcorehttp/httprequest.h | 23 ++++++++++++ indra/newview/llappcorehttp.cpp | 31 ++++++++++++---- indra/newview/llmeshrepository.cpp | 59 ++++++++++-------------------- indra/newview/llmeshrepository.h | 11 +++--- 9 files changed, 152 insertions(+), 59 deletions(-) (limited to 'indra') diff --git a/indra/llcorehttp/_httpinternal.h b/indra/llcorehttp/_httpinternal.h index 80f4f34942..effc6a42c5 100755 --- a/indra/llcorehttp/_httpinternal.h +++ b/indra/llcorehttp/_httpinternal.h @@ -143,6 +143,7 @@ const int HTTP_CONNECTION_LIMIT_MAX = 256; // Miscellaneous defaults const long HTTP_PIPELINING_DEFAULT = 0L; const bool HTTP_USE_RETRY_AFTER_DEFAULT = true; +const long HTTP_THROTTLE_RATE_DEFAULT = 0L; // Tuning parameters diff --git a/indra/llcorehttp/_httppolicy.cpp b/indra/llcorehttp/_httppolicy.cpp index 32a9ba282a..808eebc6cc 100755 --- a/indra/llcorehttp/_httppolicy.cpp +++ b/indra/llcorehttp/_httppolicy.cpp @@ -49,12 +49,18 @@ struct HttpPolicy::ClassState { public: ClassState() + : mThrottleEnd(0), + mThrottleLeft(0L), + mRequestCount(0L) {} HttpReadyQueue mReadyQueue; HttpRetryQueue mRetryQueue; HttpPolicyClass mOptions; + HttpTime mThrottleEnd; + long mThrottleLeft; + long mRequestCount; }; @@ -190,6 +196,13 @@ void HttpPolicy::retryOp(HttpOpRequest * op) // the worker thread may sleep hard otherwise will ask for // normal polling frequency. // +// Implements a client-side request rate throttle as well. +// This is intended to mimic and predict throttling behavior +// of grid services but that is difficult to do with different +// time bases. This also represents a rigid coupling between +// viewer and server that makes it hard to change parameters +// and I hope we can make this go away with pipelining. +// HttpService::ELoopSpeed HttpPolicy::processReadyQueue() { const HttpTime now(totalTime()); @@ -199,12 +212,22 @@ HttpService::ELoopSpeed HttpPolicy::processReadyQueue() for (int policy_class(0); policy_class < mClasses.size(); ++policy_class) { ClassState & state(*mClasses[policy_class]); + const bool throttle_enabled(state.mOptions.mThrottleRate > 0L); + const bool throttle_current(throttle_enabled && now < state.mThrottleEnd); + + if (throttle_current && state.mThrottleLeft <= 0) + { + // Throttled condition, don't serve this class but don't sleep hard. + result = HttpService::NORMAL; + continue; + } + int active(transport.getActiveCountInClass(policy_class)); int needed(state.mOptions.mConnectionLimit - active); // Expect negatives here HttpRetryQueue & retryq(state.mRetryQueue); HttpReadyQueue & readyq(state.mReadyQueue); - + if (needed > 0) { // First see if we have any retries... @@ -218,10 +241,27 @@ HttpService::ELoopSpeed HttpPolicy::processReadyQueue() op->stageFromReady(mService); op->release(); - + + ++state.mRequestCount; --needed; + if (throttle_enabled) + { + if (now >= state.mThrottleEnd) + { + // Throttle expired, move to next window + LL_DEBUGS("CoreHttp") << "Throttle expired with " << state.mThrottleLeft + << " requests to go and " << state.mRequestCount + << " requests issued." << LL_ENDL; + state.mThrottleLeft = state.mOptions.mThrottleRate; + state.mThrottleEnd = now + HttpTime(1000000); + } + if (--state.mThrottleLeft <= 0) + { + goto throttle_on; + } + } } - + // Now go on to the new requests... while (needed > 0 && ! readyq.empty()) { @@ -231,10 +271,29 @@ HttpService::ELoopSpeed HttpPolicy::processReadyQueue() op->stageFromReady(mService); op->release(); + ++state.mRequestCount; --needed; + if (throttle_enabled) + { + if (now >= state.mThrottleEnd) + { + // Throttle expired, move to next window + LL_DEBUGS("CoreHttp") << "Throttle expired with " << state.mThrottleLeft + << " requests to go and " << state.mRequestCount + << " requests issued." << LL_ENDL; + state.mThrottleLeft = state.mOptions.mThrottleRate; + state.mThrottleEnd = now + HttpTime(1000000); + } + if (--state.mThrottleLeft <= 0) + { + goto throttle_on; + } + } } } - + + throttle_on: + if (! readyq.empty() || ! retryq.empty()) { // If anything is ready, continue looping... diff --git a/indra/llcorehttp/_httppolicyclass.cpp b/indra/llcorehttp/_httppolicyclass.cpp index fe4359081a..f34a8e9f1e 100755 --- a/indra/llcorehttp/_httppolicyclass.cpp +++ b/indra/llcorehttp/_httppolicyclass.cpp @@ -36,7 +36,8 @@ namespace LLCore HttpPolicyClass::HttpPolicyClass() : mConnectionLimit(HTTP_CONNECTION_LIMIT_DEFAULT), mPerHostConnectionLimit(HTTP_CONNECTION_LIMIT_DEFAULT), - mPipelining(HTTP_PIPELINING_DEFAULT) + mPipelining(HTTP_PIPELINING_DEFAULT), + mThrottleRate(HTTP_THROTTLE_RATE_DEFAULT) {} @@ -51,6 +52,7 @@ HttpPolicyClass & HttpPolicyClass::operator=(const HttpPolicyClass & other) mConnectionLimit = other.mConnectionLimit; mPerHostConnectionLimit = other.mPerHostConnectionLimit; mPipelining = other.mPipelining; + mThrottleRate = other.mThrottleRate; } return *this; } @@ -59,7 +61,8 @@ HttpPolicyClass & HttpPolicyClass::operator=(const HttpPolicyClass & other) HttpPolicyClass::HttpPolicyClass(const HttpPolicyClass & other) : mConnectionLimit(other.mConnectionLimit), mPerHostConnectionLimit(other.mPerHostConnectionLimit), - mPipelining(other.mPipelining) + mPipelining(other.mPipelining), + mThrottleRate(other.mThrottleRate) {} @@ -79,6 +82,10 @@ HttpStatus HttpPolicyClass::set(HttpRequest::EPolicyOption opt, long value) mPipelining = llclamp(value, 0L, 1L); break; + case HttpRequest::PO_THROTTLE_RATE: + mThrottleRate = llclamp(value, 0L, 1000000L); + break; + default: return HttpStatus(HttpStatus::LLCORE, HE_INVALID_ARG); } @@ -103,6 +110,10 @@ HttpStatus HttpPolicyClass::get(HttpRequest::EPolicyOption opt, long * value) co *value = mPipelining; break; + case HttpRequest::PO_THROTTLE_RATE: + *value = mThrottleRate; + break; + default: return HttpStatus(HttpStatus::LLCORE, HE_INVALID_ARG); } diff --git a/indra/llcorehttp/_httppolicyclass.h b/indra/llcorehttp/_httppolicyclass.h index 69fb459d22..38f1194ded 100755 --- a/indra/llcorehttp/_httppolicyclass.h +++ b/indra/llcorehttp/_httppolicyclass.h @@ -63,6 +63,7 @@ public: long mConnectionLimit; long mPerHostConnectionLimit; long mPipelining; + long mThrottleRate; }; // end class HttpPolicyClass } // end namespace LLCore diff --git a/indra/llcorehttp/_httpservice.cpp b/indra/llcorehttp/_httpservice.cpp index e21d196a3e..c94249dc2d 100755 --- a/indra/llcorehttp/_httpservice.cpp +++ b/indra/llcorehttp/_httpservice.cpp @@ -52,7 +52,8 @@ const HttpService::OptionDescriptor HttpService::sOptionDesc[] = { false, true, true, false }, // PO_HTTP_PROXY { true, true, true, false }, // PO_LLPROXY { true, true, true, false }, // PO_TRACE - { true, true, false, true } // PO_ENABLE_PIPELINING + { true, true, false, true }, // PO_ENABLE_PIPELINING + { true, true, false, true } // PO_THROTTLE_RATE }; HttpService * HttpService::sInstance(NULL); volatile HttpService::EState HttpService::sState(NOT_INITIALIZED); diff --git a/indra/llcorehttp/httprequest.h b/indra/llcorehttp/httprequest.h index 5c54d35a21..651654844a 100755 --- a/indra/llcorehttp/httprequest.h +++ b/indra/llcorehttp/httprequest.h @@ -139,23 +139,33 @@ public: /// Limits the number of connections used for a single /// literal address/port pair within the class. + /// + /// Per-class only PO_PER_HOST_CONNECTION_LIMIT, /// String containing a system-appropriate directory name /// where SSL certs are stored. + /// + /// Global only PO_CA_PATH, /// String giving a full path to a file containing SSL certs. + /// + /// Global only PO_CA_FILE, /// String of host/port to use as simple HTTP proxy. This is /// going to change in the future into something more elaborate /// that may support richer schemes. + /// + /// Global only PO_HTTP_PROXY, /// Long value that if non-zero enables the use of the /// traditional LLProxy code for http/socks5 support. If // enabled, has priority over GP_HTTP_PROXY. + /// + /// Global only PO_LLPROXY, /// Long value setting the logging trace level for the @@ -169,12 +179,25 @@ public: /// These values are also used in the trace modes for /// individual requests in HttpOptions. Also be aware that /// tracing tends to impact performance of the viewer. + /// + /// Global only PO_TRACE, /// Suitable requests are allowed to pipeline on their /// connections when they ask for it. + /// + /// Per-class only PO_ENABLE_PIPELINING, + /// Controls whether client-side throttling should be + /// performed on this policy class. Positive values + /// enable throttling and specify the request rate + /// (requests per second) that should be targetted. + /// A value of zero, the default, specifies no throttling. + /// + /// Per-class only + PO_THROTTLE_RATE, + PO_LAST // Always at end }; diff --git a/indra/newview/llappcorehttp.cpp b/indra/newview/llappcorehttp.cpp index 104debe023..b90b9749f9 100755 --- a/indra/newview/llappcorehttp.cpp +++ b/indra/newview/llappcorehttp.cpp @@ -40,32 +40,33 @@ static const struct U32 mMin; U32 mMax; U32 mDivisor; + U32 mRate; std::string mKey; const char * mUsage; } init_data[] = // Default and dynamic values for classes { { - LLAppCoreHttp::AP_TEXTURE, 8, 1, 12, 1, + LLAppCoreHttp::AP_TEXTURE, 8, 1, 12, 1, 0, "TextureFetchConcurrency", "texture fetch" }, { - LLAppCoreHttp::AP_MESH1, 32, 1, 128, 1, + LLAppCoreHttp::AP_MESH1, 32, 1, 128, 1, 100, "MeshMaxConcurrentRequests", "mesh fetch" }, { - LLAppCoreHttp::AP_MESH2, 8, 1, 32, 4, + LLAppCoreHttp::AP_MESH2, 8, 1, 32, 4, 100, "MeshMaxConcurrentRequests", "mesh2 fetch" }, { - LLAppCoreHttp::AP_LARGE_MESH, 2, 1, 8, 1, + LLAppCoreHttp::AP_LARGE_MESH, 2, 1, 8, 1, 0, "", "large mesh fetch" }, { - LLAppCoreHttp::AP_UPLOADS, 2, 1, 8, 1, + LLAppCoreHttp::AP_UPLOADS, 2, 1, 8, 1, 0, "", "asset upload" } @@ -267,10 +268,28 @@ void LLAppCoreHttp::cleanup() void LLAppCoreHttp::refreshSettings(bool initial) { + LLCore::HttpStatus status; + for (int i(0); i < LL_ARRAY_SIZE(init_data); ++i) { const EAppPolicy policy(init_data[i].mPolicy); + // Set any desired throttle + if (initial && init_data[i].mRate) + { + // Init-time only, can use the static setters here + status = LLCore::HttpRequest::setStaticPolicyOption(LLCore::HttpRequest::PO_THROTTLE_RATE, + mPolicies[policy], + init_data[i].mRate, + NULL); + if (! status) + { + LL_WARNS("Init") << "Unable to set " << init_data[i].mUsage + << " throttle rate. Reason: " << status.toString() + << LL_ENDL; + } + } + // Get target connection concurrency value U32 setting(init_data[i].mDefault); if (! init_data[i].mKey.empty() && gSavedSettings.controlExists(init_data[i].mKey)) @@ -299,7 +318,7 @@ void LLAppCoreHttp::refreshSettings(bool initial) setting, NULL); if (LLCORE_HTTP_HANDLE_INVALID == handle) { - LLCore::HttpStatus status(mRequest->getStatus()); + status = mRequest->getStatus(); LL_WARNS("Init") << "Unable to set " << init_data[i].mUsage << " concurrency. Reason: " << status.toString() << LL_ENDL; diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index d02384f87c..e9b1a10e73 100755 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -229,7 +229,6 @@ LLMeshRepository gMeshRepo; const S32 MESH_HEADER_SIZE = 4096; // Important: assumption is that headers fit in this space -const U32 MAX_MESH_REQUESTS_PER_SECOND = 100; const S32 REQUEST_HIGH_WATER_MIN = 32; const S32 REQUEST_HIGH_WATER_MAX = 80; const S32 REQUEST_LOW_WATER_MIN = 16; @@ -613,7 +612,6 @@ void log_upload_error(LLCore::HttpStatus status, const LLSD& content, LLMeshRepoThread::LLMeshRepoThread() : LLThread("mesh repo"), mWaiting(false), - mHttpRetries(0U), mHttpRequest(NULL), mHttpOptions(NULL), mHttpLargeOptions(NULL), @@ -701,23 +699,9 @@ void LLMeshRepoThread::run() if (! LLApp::isQuitting()) { - static U32 count = 0; - static F32 last_hundred = gFrameTimeSeconds; - - if (gFrameTimeSeconds - last_hundred > 1.f) - { //a second has gone by, clear count - last_hundred = gFrameTimeSeconds; - count = 0; - } - else - { - count += mHttpRetries; - } - mHttpRetries = 0U; - - // NOTE: throttling intentionally favors LOD requests over header requests + // NOTE: order of queue processing intentionally favors LOD requests over header requests - while (!mLODReqQ.empty() && count < MAX_MESH_REQUESTS_PER_SECOND && mHttpRequestSet.size() < sRequestHighWater) + while (!mLODReqQ.empty() && mHttpRequestSet.size() < sRequestHighWater) { if (! mMutex) { @@ -728,7 +712,7 @@ void LLMeshRepoThread::run() mLODReqQ.pop(); LLMeshRepository::sLODProcessing--; mMutex->unlock(); - if (!fetchMeshLOD(req.mMeshParams, req.mLOD, count))//failed, resubmit + if (!fetchMeshLOD(req.mMeshParams, req.mLOD))//failed, resubmit { mMutex->lock(); mLODReqQ.push(req) ; @@ -737,7 +721,7 @@ void LLMeshRepoThread::run() } } - while (!mHeaderReqQ.empty() && count < MAX_MESH_REQUESTS_PER_SECOND && mHttpRequestSet.size() < sRequestHighWater) + while (!mHeaderReqQ.empty() && mHttpRequestSet.size() < sRequestHighWater) { if (! mMutex) { @@ -747,7 +731,7 @@ void LLMeshRepoThread::run() HeaderRequest req = mHeaderReqQ.front(); mHeaderReqQ.pop(); mMutex->unlock(); - if (!fetchMeshHeader(req.mMeshParams, count))//failed, resubmit + if (!fetchMeshHeader(req.mMeshParams))//failed, resubmit { mMutex->lock(); mHeaderReqQ.push(req) ; @@ -762,14 +746,14 @@ void LLMeshRepoThread::run() // order will lose. Keep to the throttle enforcement and pay // attention to the highwater level (enforced in each fetchXXX() // method). - if (! mSkinRequests.empty() && count < MAX_MESH_REQUESTS_PER_SECOND && mHttpRequestSet.size() < sRequestHighWater) + if (! mSkinRequests.empty() && mHttpRequestSet.size() < sRequestHighWater) { // *FIXME: this really does need a lock as do the following ones std::set incomplete; for (std::set::iterator iter = mSkinRequests.begin(); iter != mSkinRequests.end(); ++iter) { LLUUID mesh_id = *iter; - if (!fetchMeshSkinInfo(mesh_id, count)) + if (!fetchMeshSkinInfo(mesh_id)) { incomplete.insert(mesh_id); } @@ -777,13 +761,13 @@ void LLMeshRepoThread::run() mSkinRequests.swap(incomplete); } - if (! mDecompositionRequests.empty() && count < MAX_MESH_REQUESTS_PER_SECOND && mHttpRequestSet.size() < sRequestHighWater) + if (! mDecompositionRequests.empty() && mHttpRequestSet.size() < sRequestHighWater) { std::set incomplete; for (std::set::iterator iter = mDecompositionRequests.begin(); iter != mDecompositionRequests.end(); ++iter) { LLUUID mesh_id = *iter; - if (!fetchMeshDecomposition(mesh_id, count)) + if (!fetchMeshDecomposition(mesh_id)) { incomplete.insert(mesh_id); } @@ -791,13 +775,13 @@ void LLMeshRepoThread::run() mDecompositionRequests.swap(incomplete); } - if (! mPhysicsShapeRequests.empty() && count < MAX_MESH_REQUESTS_PER_SECOND && mHttpRequestSet.size() < sRequestHighWater) + if (! mPhysicsShapeRequests.empty() && mHttpRequestSet.size() < sRequestHighWater) { std::set incomplete; for (std::set::iterator iter = mPhysicsShapeRequests.begin(); iter != mPhysicsShapeRequests.end(); ++iter) { LLUUID mesh_id = *iter; - if (!fetchMeshPhysicsShape(mesh_id, count)) + if (!fetchMeshPhysicsShape(mesh_id)) { incomplete.insert(mesh_id); } @@ -965,7 +949,7 @@ LLCore::HttpHandle LLMeshRepoThread::getByteRange(const std::string & url, int c } -bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id, U32& count) +bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id) { if (!mHeaderMutex) @@ -1023,7 +1007,7 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id, U32& count) } //reading from VFS failed for whatever reason, fetch from sim - if (count >= MAX_MESH_REQUESTS_PER_SECOND || mHttpRequestSet.size() >= sRequestHighWater) + if (mHttpRequestSet.size() >= sRequestHighWater) { return false; } @@ -1060,7 +1044,7 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id, U32& count) return ret; } -bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id, U32& count) +bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id) { if (!mHeaderMutex) { @@ -1118,7 +1102,7 @@ bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id, U32& count) } //reading from VFS failed for whatever reason, fetch from sim - if (count >= MAX_MESH_REQUESTS_PER_SECOND || mHttpRequestSet.size() >= sRequestHighWater) + if (mHttpRequestSet.size() >= sRequestHighWater) { return false; } @@ -1155,7 +1139,7 @@ bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id, U32& count) return ret; } -bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id, U32& count) +bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id) { if (!mHeaderMutex) { @@ -1212,7 +1196,7 @@ bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id, U32& count) } //reading from VFS failed for whatever reason, fetch from sim - if (count >= MAX_MESH_REQUESTS_PER_SECOND || mHttpRequestSet.size() >= sRequestHighWater) + if (mHttpRequestSet.size() >= sRequestHighWater) { return false; } @@ -1282,7 +1266,7 @@ void LLMeshRepoThread::decActiveHeaderRequests() } //return false if failed to get header -bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params, U32& count) +bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params) { { //look for mesh in asset in vfs @@ -1331,7 +1315,6 @@ bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params, U32& c handler->mHttpHandle = handle; mHttpRequestSet.insert(handler); ++LLMeshRepository::sHTTPRequestCount; - ++count; } } @@ -1339,7 +1322,7 @@ bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params, U32& c } //return false if failed to get mesh lod. -bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, U32& count) +bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod) { if (!mHeaderMutex) { @@ -1413,7 +1396,6 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, handler->mHttpHandle = handle; mHttpRequestSet.insert(handler); ++LLMeshRepository::sHTTPRequestCount; - ++count; } } else @@ -2374,11 +2356,8 @@ void LLMeshHandlerBase::onCompleted(LLCore::HttpHandle handle, LLCore::HttpRespo { mProcessed = true; - // Accumulate retries, we'll use these to offset the HTTP - // count and maybe hold to a throttle better. unsigned int retries(0U); response->getRetries(NULL, &retries); - gMeshRepo.mThread->mHttpRetries += retries; LLMeshRepository::sHTTPRetryCount += retries; LLCore::HttpStatus status(response->getStatus()); diff --git a/indra/newview/llmeshrepository.h b/indra/newview/llmeshrepository.h index 70079eed23..400ceb4ad7 100755 --- a/indra/newview/llmeshrepository.h +++ b/indra/newview/llmeshrepository.h @@ -322,7 +322,6 @@ public: // llcorehttp library interface objects. LLCore::HttpStatus mHttpStatus; - unsigned int mHttpRetries; LLCore::HttpRequest * mHttpRequest; LLCore::HttpOptions * mHttpOptions; LLCore::HttpOptions * mHttpLargeOptions; @@ -345,8 +344,8 @@ public: void lockAndLoadMeshLOD(const LLVolumeParams& mesh_params, S32 lod); void loadMeshLOD(const LLVolumeParams& mesh_params, S32 lod); - bool fetchMeshHeader(const LLVolumeParams& mesh_params, U32& count); - bool fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, U32& count); + bool fetchMeshHeader(const LLVolumeParams& mesh_params); + bool fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod); bool headerReceived(const LLVolumeParams& mesh_params, U8* data, S32 data_size); bool lodReceived(const LLVolumeParams& mesh_params, S32 lod, U8* data, S32 data_size); bool skinInfoReceived(const LLUUID& mesh_id, U8* data, S32 data_size); @@ -363,15 +362,15 @@ public: //send request for skin info, returns true if header info exists // (should hold onto mesh_id and try again later if header info does not exist) - bool fetchMeshSkinInfo(const LLUUID& mesh_id, U32& count); + bool fetchMeshSkinInfo(const LLUUID& mesh_id); //send request for decomposition, returns true if header info exists // (should hold onto mesh_id and try again later if header info does not exist) - bool fetchMeshDecomposition(const LLUUID& mesh_id, U32& count); + bool fetchMeshDecomposition(const LLUUID& mesh_id); //send request for PhysicsShape, returns true if header info exists // (should hold onto mesh_id and try again later if header info does not exist) - bool fetchMeshPhysicsShape(const LLUUID& mesh_id, U32& count); + bool fetchMeshPhysicsShape(const LLUUID& mesh_id); static void incActiveLODRequests(); static void decActiveLODRequests(); -- cgit v1.2.3 From 549e20cf7766dc7fba2f42883659a6bcac863d91 Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Mon, 5 Aug 2013 13:54:14 -0400 Subject: Change the setting for GetMesh2 meshes to Mesh2MaxConcurrentRequests. While linking GetMesh2 to the old setting was simpler from a user point-of-view, they really shouldn't be linked and the old one will go away. This one may be renamed to AssetMaxConcurrentRequests or something similar if we get to the mesh/texture unification step. --- indra/newview/app_settings/settings.xml | 14 ++++++++++++-- indra/newview/llappcorehttp.cpp | 23 ++++++++++++++--------- indra/newview/llmeshrepository.cpp | 4 +--- 3 files changed, 27 insertions(+), 14 deletions(-) (limited to 'indra') diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 344079b640..770a8529f1 100755 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -9708,11 +9708,21 @@ Value 16 - + Mesh2MaxConcurrentRequests + + Comment + Number of connections to use for loading meshes. + Persist + 1 + Type + U32 + Value + 8 + MeshMaxConcurrentRequests Comment - Number of threads to use for loading meshes. + Number of connections to use for loading meshes (legacy system). Persist 1 Type diff --git a/indra/newview/llappcorehttp.cpp b/indra/newview/llappcorehttp.cpp index b90b9749f9..01317fe32f 100755 --- a/indra/newview/llappcorehttp.cpp +++ b/indra/newview/llappcorehttp.cpp @@ -32,6 +32,13 @@ #include "llviewercontrol.h" +// Here is where we begin to get our connection usage under control. +// This establishes llcorehttp policy classes that, among other +// things, limit the maximum number of connections to outside +// services. Each of the entries below maps to a policy class and +// has a limit, sometimes configurable, of how many connections can +// be open at a time. + const F64 LLAppCoreHttp::MAX_THREAD_WAIT_TIME(10.0); static const struct { @@ -39,34 +46,33 @@ static const struct U32 mDefault; U32 mMin; U32 mMax; - U32 mDivisor; U32 mRate; std::string mKey; const char * mUsage; } init_data[] = // Default and dynamic values for classes { { - LLAppCoreHttp::AP_TEXTURE, 8, 1, 12, 1, 0, + LLAppCoreHttp::AP_TEXTURE, 8, 1, 12, 0, "TextureFetchConcurrency", "texture fetch" }, { - LLAppCoreHttp::AP_MESH1, 32, 1, 128, 1, 100, + LLAppCoreHttp::AP_MESH1, 32, 1, 128, 100, "MeshMaxConcurrentRequests", "mesh fetch" }, { - LLAppCoreHttp::AP_MESH2, 8, 1, 32, 4, 100, - "MeshMaxConcurrentRequests", + LLAppCoreHttp::AP_MESH2, 8, 1, 32, 100, + "Mesh2MaxConcurrentRequests", "mesh2 fetch" }, { - LLAppCoreHttp::AP_LARGE_MESH, 2, 1, 8, 1, 0, + LLAppCoreHttp::AP_LARGE_MESH, 2, 1, 8, 0, "", "large mesh fetch" }, { - LLAppCoreHttp::AP_UPLOADS, 2, 1, 8, 1, 0, + LLAppCoreHttp::AP_UPLOADS, 2, 1, 8, 0, "", "asset upload" } @@ -298,8 +304,7 @@ void LLAppCoreHttp::refreshSettings(bool initial) if (new_setting) { // Treat zero settings as an ask for default - setting = new_setting / init_data[i].mDivisor; - setting = llclamp(setting, init_data[i].mMin, init_data[i].mMax); + setting = llclamp(new_setting, init_data[i].mMin, init_data[i].mMax); } } diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index e9b1a10e73..7146c7f4f5 100755 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -2897,9 +2897,7 @@ void LLMeshRepository::notifyLoadedMeshes() else { // GetMesh2 operation with keepalives, etc. - // *TODO: Logic here is replicated from llappcorehttp.cpp, should really - // unify this and keep it in one place only. - LLMeshRepoThread::sMaxConcurrentRequests = gSavedSettings.getU32("MeshMaxConcurrentRequests") / 4; + LLMeshRepoThread::sMaxConcurrentRequests = gSavedSettings.getU32("Mesh2MaxConcurrentRequests"); LLMeshRepoThread::sRequestHighWater = llclamp(5 * S32(LLMeshRepoThread::sMaxConcurrentRequests), REQUEST_HIGH_WATER_MIN, REQUEST_HIGH_WATER_MAX); -- cgit v1.2.3 From 43788d612042deb6f9329746a101774370f7c67b Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Mon, 5 Aug 2013 19:04:08 -0400 Subject: Added some simple counters to the mesh repository code and then added a Mesh status line to the texture fetch console. Mesh is often in competition with textures and so the mesh information seems appropriate there. Do get a nice feel for progress and you definitely see when the throttles kick in. --- indra/newview/llmeshrepository.cpp | 134 ++++++++++++++++++++++++------------- indra/newview/llmeshrepository.h | 11 +-- indra/newview/lltextureview.cpp | 41 ++++++++---- 3 files changed, 123 insertions(+), 63 deletions(-) (limited to 'indra') diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index 7146c7f4f5..97d6c57a78 100755 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -155,20 +155,26 @@ // // So, in addition to documentation, take this as a to-do/review // list and see if you can improve things. For porters to non-x86 -// architectures, including amd64, the weaker memory models will -// make these platforms probabilistically more susceptible to hitting -// race conditions. True here and in other multi-thread code such -// as texture fetching. +// architectures, the weaker memory models will make these platforms +// probabilistically more susceptible to hitting race conditions. +// True here and in other multi-thread code such as texture fetching. +// (Strong memory models make weak programmers. Weak memory models +// make strong programmers. Ref: arm, ppc, mips, alpha) // // LLMeshRepository: // // sBytesReceived +// sMeshRequestCount // sHTTPRequestCount +// sHTTPLargeRequestCount // sHTTPRetryCount +// sHTTPErrorCount // sLODPending // sLODProcessing // sCacheBytesRead // sCacheBytesWritten +// sCacheReads +// sCacheWrites // mLoadingMeshes none rw.main.none, rw.main.mMeshMutex [4] // mSkinMap none rw.main.none // mDecompositionMap none rw.main.none @@ -246,13 +252,19 @@ const long LARGE_MESH_XFER_TIMEOUT = 600L; // Seconds to complete xfer, large const S32 MAX_MESH_VERSION = 999; U32 LLMeshRepository::sBytesReceived = 0; +U32 LLMeshRepository::sMeshRequestCount = 0; U32 LLMeshRepository::sHTTPRequestCount = 0; +U32 LLMeshRepository::sHTTPLargeRequestCount = 0; U32 LLMeshRepository::sHTTPRetryCount = 0; +U32 LLMeshRepository::sHTTPErrorCount = 0; U32 LLMeshRepository::sLODProcessing = 0; U32 LLMeshRepository::sLODPending = 0; U32 LLMeshRepository::sCacheBytesRead = 0; U32 LLMeshRepository::sCacheBytesWritten = 0; +U32 LLMeshRepository::sCacheReads = 0; +U32 LLMeshRepository::sCacheWrites = 0; + LLDeadmanTimer LLMeshRepository::sQuiescentTimer(15.0, true); // true -> gather cpu metrics @@ -366,6 +378,7 @@ volatile S32 LLMeshRepoThread::sActiveLODRequests = 0; U32 LLMeshRepoThread::sMaxConcurrentRequests = 1; S32 LLMeshRepoThread::sRequestLowWater = REQUEST_LOW_WATER_MIN; S32 LLMeshRepoThread::sRequestHighWater = REQUEST_HIGH_WATER_MIN; +S32 LLMeshRepoThread::sRequestWaterLevel = 0; // Base handler class for all mesh users of llcorehttp. // This is roughly equivalent to a Responder class in @@ -619,9 +632,7 @@ LLMeshRepoThread::LLMeshRepoThread() mHttpPolicyClass(LLCore::HttpRequest::DEFAULT_POLICY_ID), mHttpLegacyPolicyClass(LLCore::HttpRequest::DEFAULT_POLICY_ID), mHttpLargePolicyClass(LLCore::HttpRequest::DEFAULT_POLICY_ID), - mHttpPriority(0), - mHttpGetCount(0U), - mHttpLargeGetCount(0U) + mHttpPriority(0) { mMutex = new LLMutex(NULL); mHeaderMutex = new LLMutex(NULL); @@ -641,8 +652,8 @@ LLMeshRepoThread::LLMeshRepoThread() LLMeshRepoThread::~LLMeshRepoThread() { - LL_INFOS(LOG_MESH) << "Small GETs issued: " << mHttpGetCount - << ", Large GETs issued: " << mHttpLargeGetCount + LL_INFOS(LOG_MESH) << "Small GETs issued: " << LLMeshRepository::sHTTPRequestCount + << ", Large GETs issued: " << LLMeshRepository::sHTTPLargeRequestCount << LL_ENDL; for (http_request_set::iterator iter(mHttpRequestSet.begin()); @@ -700,7 +711,8 @@ void LLMeshRepoThread::run() if (! LLApp::isQuitting()) { // NOTE: order of queue processing intentionally favors LOD requests over header requests - + + sRequestWaterLevel = mHttpRequestSet.size(); while (!mLODReqQ.empty() && mHttpRequestSet.size() < sRequestHighWater) { if (! mMutex) @@ -743,19 +755,26 @@ void LLMeshRepoThread::run() // list, we scan the entire thing. This gets us through any requests // which can be resolved in the cache. It also keeps the request // set somewhat fresher otherwise items at the end of the set - // order will lose. Keep to the throttle enforcement and pay - // attention to the highwater level (enforced in each fetchXXX() - // method). + // order will lose. if (! mSkinRequests.empty() && mHttpRequestSet.size() < sRequestHighWater) { // *FIXME: this really does need a lock as do the following ones std::set incomplete; for (std::set::iterator iter = mSkinRequests.begin(); iter != mSkinRequests.end(); ++iter) { - LLUUID mesh_id = *iter; - if (!fetchMeshSkinInfo(mesh_id)) + if (mHttpRequestSet.size() < sRequestHighWater) + { + LLUUID mesh_id = *iter; + if (!fetchMeshSkinInfo(mesh_id)) + { + incomplete.insert(mesh_id); + } + } + else { - incomplete.insert(mesh_id); + // Hit high-water mark, copy remaining to incomplete. + incomplete.insert(iter, mSkinRequests.end()); + break; } } mSkinRequests.swap(incomplete); @@ -766,10 +785,19 @@ void LLMeshRepoThread::run() std::set incomplete; for (std::set::iterator iter = mDecompositionRequests.begin(); iter != mDecompositionRequests.end(); ++iter) { - LLUUID mesh_id = *iter; - if (!fetchMeshDecomposition(mesh_id)) + if (mHttpRequestSet.size() < sRequestHighWater) { - incomplete.insert(mesh_id); + LLUUID mesh_id = *iter; + if (!fetchMeshDecomposition(mesh_id)) + { + incomplete.insert(mesh_id); + } + } + else + { + // Hit high-water mark, copy remaining to incomplete. + incomplete.insert(iter, mDecompositionRequests.end()); + break; } } mDecompositionRequests.swap(incomplete); @@ -780,10 +808,19 @@ void LLMeshRepoThread::run() std::set incomplete; for (std::set::iterator iter = mPhysicsShapeRequests.begin(); iter != mPhysicsShapeRequests.end(); ++iter) { - LLUUID mesh_id = *iter; - if (!fetchMeshPhysicsShape(mesh_id)) + if (mHttpRequestSet.size() < sRequestHighWater) + { + LLUUID mesh_id = *iter; + if (!fetchMeshPhysicsShape(mesh_id)) + { + incomplete.insert(mesh_id); + } + } + else { - incomplete.insert(mesh_id); + // Hit high-water mark, copy remaining to incomplete. + incomplete.insert(iter, mPhysicsShapeRequests.end()); + break; } } mPhysicsShapeRequests.swap(incomplete); @@ -926,7 +963,10 @@ LLCore::HttpHandle LLMeshRepoThread::getByteRange(const std::string & url, int c mHttpOptions, mHttpHeaders, handler); - ++mHttpGetCount; + if (LLCORE_HTTP_HANDLE_INVALID != handle) + { + ++LLMeshRepository::sHTTPRequestCount; + } } else { @@ -938,7 +978,10 @@ LLCore::HttpHandle LLMeshRepoThread::getByteRange(const std::string & url, int c mHttpLargeOptions, mHttpHeaders, handler); - ++mHttpLargeGetCount; + if (LLCORE_HTTP_HANDLE_INVALID != handle) + { + ++LLMeshRepository::sHTTPLargeRequestCount; + } } if (LLCORE_HTTP_HANDLE_INVALID == handle) { @@ -965,7 +1008,8 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id) return false; } - bool ret = true ; + ++LLMeshRepository::sMeshRequestCount; + bool ret = true; U32 header_size = mMeshHeaderSize[mesh_id]; if (header_size > 0) @@ -983,6 +1027,7 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id) if (file.getSize() >= offset+size) { LLMeshRepository::sCacheBytesRead += size; + ++LLMeshRepository::sCacheReads; file.seek(offset); U8* buffer = new U8[size]; file.read(buffer, size); @@ -1007,10 +1052,6 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id) } //reading from VFS failed for whatever reason, fetch from sim - if (mHttpRequestSet.size() >= sRequestHighWater) - { - return false; - } int cap_version(gMeshRepo.mGetMeshVersion); std::string http_url = constructUrl(mesh_id); if (!http_url.empty()) @@ -1025,12 +1066,12 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id) << LL_ENDL; delete handler; ret = false; + } else { handler->mHttpHandle = handle; mHttpRequestSet.insert(handler); - ++LLMeshRepository::sHTTPRequestCount; } } } @@ -1059,8 +1100,9 @@ bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id) return false; } + ++LLMeshRepository::sMeshRequestCount; U32 header_size = mMeshHeaderSize[mesh_id]; - bool ret = true ; + bool ret = true; if (header_size > 0) { @@ -1077,6 +1119,7 @@ bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id) if (file.getSize() >= offset+size) { LLMeshRepository::sCacheBytesRead += size; + ++LLMeshRepository::sCacheReads; file.seek(offset); U8* buffer = new U8[size]; @@ -1102,10 +1145,6 @@ bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id) } //reading from VFS failed for whatever reason, fetch from sim - if (mHttpRequestSet.size() >= sRequestHighWater) - { - return false; - } int cap_version(gMeshRepo.mGetMeshVersion); std::string http_url = constructUrl(mesh_id); if (!http_url.empty()) @@ -1125,7 +1164,6 @@ bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id) { handler->mHttpHandle = handle; mHttpRequestSet.insert(handler); - ++LLMeshRepository::sHTTPRequestCount; } } } @@ -1154,8 +1192,9 @@ bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id) return false; } + ++LLMeshRepository::sMeshRequestCount; U32 header_size = mMeshHeaderSize[mesh_id]; - bool ret = true ; + bool ret = true; if (header_size > 0) { @@ -1172,6 +1211,7 @@ bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id) if (file.getSize() >= offset+size) { LLMeshRepository::sCacheBytesRead += size; + ++LLMeshRepository::sCacheReads; file.seek(offset); U8* buffer = new U8[size]; file.read(buffer, size); @@ -1196,10 +1236,6 @@ bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id) } //reading from VFS failed for whatever reason, fetch from sim - if (mHttpRequestSet.size() >= sRequestHighWater) - { - return false; - } int cap_version(gMeshRepo.mGetMeshVersion); std::string http_url = constructUrl(mesh_id); if (!http_url.empty()) @@ -1219,7 +1255,6 @@ bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id) { handler->mHttpHandle = handle; mHttpRequestSet.insert(handler); - ++LLMeshRepository::sHTTPRequestCount; } } } @@ -1268,6 +1303,8 @@ void LLMeshRepoThread::decActiveHeaderRequests() //return false if failed to get header bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params) { + ++LLMeshRepository::sMeshRequestCount; + { //look for mesh in asset in vfs LLVFile file(gVFS, mesh_params.getSculptID(), LLAssetType::AT_MESH); @@ -1280,6 +1317,7 @@ bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params) U8 buffer[MESH_HEADER_SIZE]; S32 bytes = llmin(size, MESH_HEADER_SIZE); LLMeshRepository::sCacheBytesRead += bytes; + ++LLMeshRepository::sCacheReads; file.read(buffer, bytes); if (headerReceived(mesh_params, buffer, bytes)) { @@ -1314,7 +1352,6 @@ bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params) { handler->mHttpHandle = handle; mHttpRequestSet.insert(handler); - ++LLMeshRepository::sHTTPRequestCount; } } @@ -1331,6 +1368,7 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod) mHeaderMutex->lock(); + ++LLMeshRepository::sMeshRequestCount; bool retval = true; LLUUID mesh_id = mesh_params.getSculptID(); @@ -1352,6 +1390,7 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod) if (file.getSize() >= offset+size) { LLMeshRepository::sCacheBytesRead += size; + ++LLMeshRepository::sCacheReads; file.seek(offset); U8* buffer = new U8[size]; file.read(buffer, size); @@ -1395,7 +1434,6 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod) { handler->mHttpHandle = handle; mHttpRequestSet.insert(handler); - ++LLMeshRepository::sHTTPRequestCount; } } else @@ -2364,6 +2402,7 @@ void LLMeshHandlerBase::onCompleted(LLCore::HttpHandle handle, LLCore::HttpRespo if (! status) { processFailure(status); + ++LLMeshRepository::sHTTPErrorCount; } else { @@ -2484,6 +2523,7 @@ void LLMeshHeaderHandler::processData(LLCore::BufferArray * body, U8 * data, S32 if (file.getMaxSize() >= bytes || file.setMaxSize(bytes)) { LLMeshRepository::sCacheBytesWritten += data_size; + ++LLMeshRepository::sCacheWrites; file.write(data, data_size); @@ -2556,6 +2596,7 @@ void LLMeshLODHandler::processData(LLCore::BufferArray * body, U8 * data, S32 da file.seek(offset); file.write(data, size); LLMeshRepository::sCacheBytesWritten += size; + ++LLMeshRepository::sCacheWrites; } } // *TODO: Mark mesh unavailable on error @@ -2601,6 +2642,7 @@ void LLMeshSkinInfoHandler::processData(LLCore::BufferArray * body, U8 * data, S if (file.getSize() >= offset+size) { LLMeshRepository::sCacheBytesWritten += size; + ++LLMeshRepository::sCacheWrites; file.seek(offset); file.write(data, size); } @@ -2648,6 +2690,7 @@ void LLMeshDecompositionHandler::processData(LLCore::BufferArray * body, U8 * da if (file.getSize() >= offset+size) { LLMeshRepository::sCacheBytesWritten += size; + ++LLMeshRepository::sCacheWrites; file.seek(offset); file.write(data, size); } @@ -2695,6 +2738,7 @@ void LLMeshPhysicsShapeHandler::processData(LLCore::BufferArray * body, U8 * dat if (file.getSize() >= offset+size) { LLMeshRepository::sCacheBytesWritten += size; + ++LLMeshRepository::sCacheWrites; file.seek(offset); file.write(data, size); } @@ -4204,7 +4248,7 @@ void LLMeshRepository::metricsUpdate() LLSD metrics; metrics["reason"] = "Mesh Download Quiescent"; - metrics["scope"] = "Login"; + metrics["scope"] = metrics_teleport_start_count > 1 ? "Teleport" : "Login"; metrics["start"] = started; metrics["stop"] = stopped; metrics["fetches"] = LLSD::Integer(total_count); diff --git a/indra/newview/llmeshrepository.h b/indra/newview/llmeshrepository.h index 400ceb4ad7..7e89f60bc3 100755 --- a/indra/newview/llmeshrepository.h +++ b/indra/newview/llmeshrepository.h @@ -224,6 +224,7 @@ public: static U32 sMaxConcurrentRequests; static S32 sRequestLowWater; static S32 sRequestHighWater; + static S32 sRequestWaterLevel; // Stats-use only, may read outside of thread LLMutex* mMutex; LLMutex* mHeaderMutex; @@ -387,10 +388,6 @@ private: LLCore::HttpHandle getByteRange(const std::string & url, int cap_version, size_t offset, size_t len, LLCore::HttpHandler * handler); - -private: - U32 mHttpGetCount; - U32 mHttpLargeGetCount; }; @@ -497,12 +494,18 @@ public: //metrics static U32 sBytesReceived; + static U32 sMeshRequestCount; static U32 sHTTPRequestCount; + static U32 sHTTPLargeRequestCount; static U32 sHTTPRetryCount; + static U32 sHTTPErrorCount; static U32 sLODPending; static U32 sLODProcessing; static U32 sCacheBytesRead; static U32 sCacheBytesWritten; + static U32 sCacheReads; + static U32 sCacheWrites; + 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); diff --git a/indra/newview/lltextureview.cpp b/indra/newview/lltextureview.cpp index e80136b286..50edbb61a8 100755 --- a/indra/newview/lltextureview.cpp +++ b/indra/newview/lltextureview.cpp @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2001&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 @@ -49,6 +49,7 @@ #include "llviewertexturelist.h" #include "llvovolume.h" #include "llviewerstats.h" +#include "llmeshrepository.h" // For avatar texture view #include "llvoavatarself.h" @@ -517,6 +518,8 @@ void LLGLTexMemBar::draw() F32 total_texture_downloaded = (F32)gTotalTextureBytes / (1024 * 1024); F32 total_object_downloaded = (F32)gTotalObjectBytes / (1024 * 1024); U32 total_http_requests = LLAppViewer::getTextureFetch()->getTotalNumHTTPRequests(); + F32 x_right = 0.0; + //---------------------------------------------------------------------------- LLGLSUIDefault gls_ui; LLColor4 text_color(1.f, 1.f, 1.f, 0.75f); @@ -543,7 +546,7 @@ void LLGLTexMemBar::draw() cache_max_usage); //, cache_entries, cache_max_entries - LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height*4, + LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height*5, text_color, LLFontGL::LEFT, LLFontGL::TOP); U32 cache_read(0U), cache_write(0U), res_wait(0U); @@ -557,13 +560,12 @@ void LLGLTexMemBar::draw() cache_write, res_wait); - LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height*3, + LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height*4, text_color, LLFontGL::LEFT, LLFontGL::TOP); - S32 left = 0 ; //---------------------------------------------------------------------------- - text = llformat("Textures: %d Fetch: %d(%d) Pkts:%d(%d) Cache R/W: %d/%d LFS:%d RAW:%d HTP:%d DEC:%d CRE:%d", + text = llformat("Textures: %d Fetch: %d(%d) Pkts:%d(%d) Cache R/W: %d/%d LFS:%d RAW:%d HTP:%d DEC:%d CRE:%d ", gTextureList.getNumImages(), LLAppViewer::getTextureFetch()->getNumRequests(), LLAppViewer::getTextureFetch()->getNumDeletes(), LLAppViewer::getTextureFetch()->mPacketCount, LLAppViewer::getTextureFetch()->mBadPacketCount, @@ -574,19 +576,30 @@ void LLGLTexMemBar::draw() LLAppViewer::getImageDecodeThread()->getPending(), gTextureList.mCreateTextureList.size()); - LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height*2, - text_color, LLFontGL::LEFT, LLFontGL::TOP); - + x_right = 550.0; + LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height*3, + text_color, LLFontGL::LEFT, LLFontGL::TOP, + LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, S32_MAX, + &x_right, FALSE); - left = 550; F32 bandwidth = LLAppViewer::getTextureFetch()->getTextureBandwidth(); F32 max_bandwidth = gSavedSettings.getF32("ThrottleBandwidthKBPS"); - color = bandwidth > max_bandwidth ? LLColor4::red : bandwidth > max_bandwidth*.75f ? LLColor4::yellow : text_color; + color = bandwidth > max_bandwidth ? LLColor4::red : bandwidth > max_bandwidth * .75f ? LLColor4::yellow : text_color; color[VALPHA] = text_color[VALPHA]; - text = llformat("BW:%.0f/%.0f",bandwidth, max_bandwidth); - LLFontGL::getFontMonospace()->renderUTF8(text, 0, left, v_offset + line_height*2, + text = llformat("BW:%.0f/%.0f", bandwidth, max_bandwidth); + LLFontGL::getFontMonospace()->renderUTF8(text, 0, x_right, v_offset + line_height*3, color, LLFontGL::LEFT, LLFontGL::TOP); - + + // Mesh status line + text = llformat("Mesh: Reqs(Tot/Htp/Big): %u/%u/%u Rtr/Err: %u/%u Cread/Cwrite: %u/%u Low/At/High: %d/%d/%d", + LLMeshRepository::sMeshRequestCount, LLMeshRepository::sHTTPRequestCount, LLMeshRepository::sHTTPLargeRequestCount, + LLMeshRepository::sHTTPRetryCount, LLMeshRepository::sHTTPErrorCount, + LLMeshRepository::sCacheReads, LLMeshRepository::sCacheWrites, + LLMeshRepoThread::sRequestLowWater, LLMeshRepoThread::sRequestWaterLevel, LLMeshRepoThread::sRequestHighWater); + LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height*2, + text_color, LLFontGL::LEFT, LLFontGL::TOP); + + // Header for texture table columns S32 dx1 = 0; if (LLAppViewer::getTextureFetch()->mDebugPause) { @@ -629,7 +642,7 @@ BOOL LLGLTexMemBar::handleMouseDown(S32 x, S32 y, MASK mask) LLRect LLGLTexMemBar::getRequiredRect() { LLRect rect; - rect.mTop = 50; //LLFontGL::getFontMonospace()->getLineHeight() * 6; + rect.mTop = 68; //LLFontGL::getFontMonospace()->getLineHeight() * 6; return rect; } -- cgit v1.2.3 From 0d93247359531388f88f22f85326e1142d801e85 Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Tue, 6 Aug 2013 18:05:34 -0400 Subject: SH-4411 Thread/mutex rework between main and worker thread Have the ::notifyLoadedMeshes() method doing correct locking and stall avoidance at the same time. This method now does lazy mutex lock acquisition (trylock()) and if it fails on either, it gives up and comes back later. Capture the maximum number of sequential failures and report this at the end of the run in the log. (So far, with big mesh regions, I've only seen 1s and 2s.) Locking/mutex requirements sorted in other locations as well. LLMutex gets trylock() method as well as new LLMutexTrylock scoped locking class. Clean up some documentation, more to do. --- indra/llcommon/llthread.cpp | 32 +++- indra/llcommon/llthread.h | 46 +++++- indra/newview/llmeshrepository.cpp | 292 ++++++++++++++++++++++--------------- indra/newview/llmeshrepository.h | 17 +-- 4 files changed, 257 insertions(+), 130 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/llthread.cpp b/indra/llcommon/llthread.cpp index 1d56a52c32..7563975959 100755 --- a/indra/llcommon/llthread.cpp +++ b/indra/llcommon/llthread.cpp @@ -3,7 +3,7 @@ * * $LicenseInfo:firstyear=2004&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 @@ -372,6 +372,36 @@ void LLMutex::lock() #endif } +bool LLMutex::trylock() +{ + if(isSelfLocked()) + { //redundant lock + mCount++; + return true; + } + + apr_status_t status(apr_thread_mutex_trylock(mAPRMutexp)); + if (APR_STATUS_IS_EBUSY(status)) + { + return false; + } + +#if MUTEX_DEBUG + // Have to have the lock before we can access the debug info + U32 id = LLThread::currentID(); + if (mIsLocked[id] != FALSE) + llerrs << "Already locked in Thread: " << id << llendl; + mIsLocked[id] = TRUE; +#endif + +#if LL_DARWIN + mLockingThread = LLThread::currentID(); +#else + mLockingThread = sThreadID; +#endif + return true; +} + void LLMutex::unlock() { if (mCount > 0) diff --git a/indra/llcommon/llthread.h b/indra/llcommon/llthread.h index 0fb89c5613..376df398d7 100755 --- a/indra/llcommon/llthread.h +++ b/indra/llcommon/llthread.h @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2004&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 @@ -156,7 +156,8 @@ public: virtual ~LLMutex(); void lock(); // blocks - void unlock(); + bool trylock(); // non-blocking, returns true if lock held. + void unlock(); // undefined behavior when called on mutex not being held bool isLocked(); // non-blocking, but does do a lock/unlock so not free bool isSelfLocked(); //return true if locked in a same thread U32 lockingThread() const; //get ID of locking thread @@ -174,6 +175,8 @@ protected: #endif }; +//============================================================================ + // Actually a condition/mutex pair (since each condition needs to be associated with a mutex). class LL_COMMON_API LLCondition : public LLMutex { @@ -189,6 +192,8 @@ protected: apr_thread_cond_t *mAPRCondp; }; +//============================================================================ + class LLMutexLock { public: @@ -210,6 +215,43 @@ private: //============================================================================ +// Scoped locking class similar in function to LLMutexLock but uses +// the trylock() method to conditionally acquire lock without +// blocking. Caller resolves the resulting condition by calling +// the isLocked() method and either punts or continues as indicated. +// +// Mostly of interest to callers needing to avoid stalls who can +// guarantee another attempt at a later time. + +class LLMutexTrylock +{ +public: + LLMutexTrylock(LLMutex* mutex) + : mMutex(mutex), + mLocked(false) + { + if (mMutex) + mLocked = mMutex->trylock(); + } + + ~LLMutexTrylock() + { + if (mMutex && mLocked) + mMutex->unlock(); + } + + bool isLocked() const + { + return mLocked; + } + +private: + LLMutex* mMutex; + bool mLocked; +}; + +//============================================================================ + void LLThread::lockData() { mDataLock->lock(); diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index 97d6c57a78..59100a68f9 100755 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -197,7 +197,6 @@ // sActiveHeaderRequests mMutex rw.any.mMutex, ro.repo.none [1] // sActiveLODRequests mMutex rw.any.mMutex, ro.repo.none [1] // sMaxConcurrentRequests mMutex wo.main.none, ro.repo.none, ro.main.mMutex -// mWaiting mMutex rw.repo.none, ro.main.none [2] (race - hint) // mMeshHeader mHeaderMutex rw.repo.mHeaderMutex, ro.main.mHeaderMutex, ro.main.none [0] // mMeshHeaderSize mHeaderMutex rw.repo.mHeaderMutex // mSkinRequests none rw.repo.none, rw.main.none [0] @@ -264,6 +263,7 @@ U32 LLMeshRepository::sCacheBytesRead = 0; U32 LLMeshRepository::sCacheBytesWritten = 0; U32 LLMeshRepository::sCacheReads = 0; U32 LLMeshRepository::sCacheWrites = 0; +U32 LLMeshRepository::sMaxLockHoldoffs = 0; LLDeadmanTimer LLMeshRepository::sQuiescentTimer(15.0, true); // true -> gather cpu metrics @@ -288,7 +288,7 @@ const char * const LOG_MESH = "Mesh"; // Static data and functions to measure mesh load // time metrics for a new region scene. -static unsigned int metrics_teleport_start_count(0); +static unsigned int metrics_teleport_start_count = 0; boost::signals2::connection metrics_teleport_started_signal; static void teleport_started(); static bool is_retryable(LLCore::HttpStatus status); @@ -396,6 +396,7 @@ S32 LLMeshRepoThread::sRequestWaterLevel = 0; // LLMeshSkinInfoHandler // LLMeshDecompositionHandler // LLMeshPhysicsShapeHandler +// LLMeshUploadThread class LLMeshHandlerBase : public LLCore::HttpHandler { @@ -624,7 +625,6 @@ void log_upload_error(LLCore::HttpStatus status, const LLSD& content, LLMeshRepoThread::LLMeshRepoThread() : LLThread("mesh repo"), - mWaiting(false), mHttpRequest(NULL), mHttpOptions(NULL), mHttpLargeOptions(NULL), @@ -654,6 +654,7 @@ LLMeshRepoThread::~LLMeshRepoThread() { LL_INFOS(LOG_MESH) << "Small GETs issued: " << LLMeshRepository::sHTTPRequestCount << ", Large GETs issued: " << LLMeshRepository::sHTTPLargeRequestCount + << ", Max Lock Holdoffs: " << LLMeshRepository::sMaxLockHoldoffs << LL_ENDL; for (http_request_set::iterator iter(mHttpRequestSet.begin()); @@ -698,138 +699,171 @@ void LLMeshRepoThread::run() while (!LLApp::isQuitting()) { + // *TODO: Revise sleep/wake strategy and try to move away' + // from polling operations in this thread. We can sleep + // this thread hard when: + // * All Http requests are serviced + // * LOD request queue empty + // * Header request queue empty + // * Skin info request queue empty + // * Decomposition request queue empty + // * Physics shape request queue empty + // We wake the thread when any of the above become untrue. + // Will likely need a correctly-implemented condition variable to do this. + + mSignal->wait(); + + if (LLApp::isQuitting()) + { + break; + } + if (! mHttpRequestSet.empty()) { // Dispatch all HttpHandler notifications mHttpRequest->update(0L); } + sRequestWaterLevel = mHttpRequestSet.size(); // Stats data update + + // NOTE: order of queue processing intentionally favors LOD requests over header requests - mWaiting = true; - mSignal->wait(); - mWaiting = false; - - if (! LLApp::isQuitting()) + while (!mLODReqQ.empty() && mHttpRequestSet.size() < sRequestHighWater) { - // NOTE: order of queue processing intentionally favors LOD requests over header requests - - sRequestWaterLevel = mHttpRequestSet.size(); - while (!mLODReqQ.empty() && mHttpRequestSet.size() < sRequestHighWater) + if (! mMutex) + { + break; + } + mMutex->lock(); + LODRequest req = mLODReqQ.front(); + mLODReqQ.pop(); + LLMeshRepository::sLODProcessing--; + mMutex->unlock(); + if (!fetchMeshLOD(req.mMeshParams, req.mLOD))//failed, resubmit { - if (! mMutex) - { - break; - } mMutex->lock(); - LODRequest req = mLODReqQ.front(); - mLODReqQ.pop(); - LLMeshRepository::sLODProcessing--; + mLODReqQ.push(req) ; + ++LLMeshRepository::sLODProcessing; mMutex->unlock(); - if (!fetchMeshLOD(req.mMeshParams, req.mLOD))//failed, resubmit - { - mMutex->lock(); - mLODReqQ.push(req) ; - ++LLMeshRepository::sLODProcessing; - mMutex->unlock(); - } } + } - while (!mHeaderReqQ.empty() && mHttpRequestSet.size() < sRequestHighWater) + while (!mHeaderReqQ.empty() && mHttpRequestSet.size() < sRequestHighWater) + { + if (! mMutex) + { + break; + } + mMutex->lock(); + HeaderRequest req = mHeaderReqQ.front(); + mHeaderReqQ.pop(); + mMutex->unlock(); + if (!fetchMeshHeader(req.mMeshParams))//failed, resubmit { - if (! mMutex) - { - break; - } mMutex->lock(); - HeaderRequest req = mHeaderReqQ.front(); - mHeaderReqQ.pop(); + mHeaderReqQ.push(req) ; mMutex->unlock(); - if (!fetchMeshHeader(req.mMeshParams))//failed, resubmit - { - mMutex->lock(); - mHeaderReqQ.push(req) ; - mMutex->unlock(); - } } + } - // For the final three request lists, if we scan any part of one - // list, we scan the entire thing. This gets us through any requests - // which can be resolved in the cache. It also keeps the request - // set somewhat fresher otherwise items at the end of the set - // order will lose. + // For the final three request lists, similar goal to above but + // slightly different queue structures. Stay off the mutex when + // performing long-duration actions. + + if (mHttpRequestSet.size() < sRequestHighWater + && (! mSkinRequests.empty() + || ! mDecompositionRequests.empty() + || ! mPhysicsShapeRequests.empty())) + { + // Something to do probably, lock and double-check. We don't want + // to hold the lock long here. That will stall main thread activities + // so we bounce it. + + mMutex->lock(); if (! mSkinRequests.empty() && mHttpRequestSet.size() < sRequestHighWater) { - // *FIXME: this really does need a lock as do the following ones std::set incomplete; - for (std::set::iterator iter = mSkinRequests.begin(); iter != mSkinRequests.end(); ++iter) + std::set::iterator iter(mSkinRequests.begin()); + while (iter != mSkinRequests.end() && mHttpRequestSet.size() < sRequestHighWater) { - if (mHttpRequestSet.size() < sRequestHighWater) - { - LLUUID mesh_id = *iter; - if (!fetchMeshSkinInfo(mesh_id)) - { - incomplete.insert(mesh_id); - } - } - else + LLUUID mesh_id = *iter; + mSkinRequests.erase(iter); + mMutex->unlock(); + + if (! fetchMeshSkinInfo(mesh_id)) { - // Hit high-water mark, copy remaining to incomplete. - incomplete.insert(iter, mSkinRequests.end()); - break; + incomplete.insert(mesh_id); } + + mMutex->lock(); + iter = mSkinRequests.begin(); + } + + if (! incomplete.empty()) + { + mSkinRequests.insert(incomplete.begin(), incomplete.end()); } - mSkinRequests.swap(incomplete); } + // holding lock, try next list + // *TODO: For UI/debug-oriented lists, we might drop the fine- + // grained locking as there's lowered expectations of smoothness + // in these cases. if (! mDecompositionRequests.empty() && mHttpRequestSet.size() < sRequestHighWater) { std::set incomplete; - for (std::set::iterator iter = mDecompositionRequests.begin(); iter != mDecompositionRequests.end(); ++iter) + std::set::iterator iter(mDecompositionRequests.begin()); + while (iter != mDecompositionRequests.end() && mHttpRequestSet.size() < sRequestHighWater) { - if (mHttpRequestSet.size() < sRequestHighWater) - { - LLUUID mesh_id = *iter; - if (!fetchMeshDecomposition(mesh_id)) - { - incomplete.insert(mesh_id); - } - } - else + LLUUID mesh_id = *iter; + mDecompositionRequests.erase(iter); + mMutex->unlock(); + + if (! fetchMeshDecomposition(mesh_id)) { - // Hit high-water mark, copy remaining to incomplete. - incomplete.insert(iter, mDecompositionRequests.end()); - break; + incomplete.insert(mesh_id); } + + mMutex->lock(); + iter = mDecompositionRequests.begin(); + } + + if (! incomplete.empty()) + { + mDecompositionRequests.insert(incomplete.begin(), incomplete.end()); } - mDecompositionRequests.swap(incomplete); } + // holding lock, final list if (! mPhysicsShapeRequests.empty() && mHttpRequestSet.size() < sRequestHighWater) { std::set incomplete; - for (std::set::iterator iter = mPhysicsShapeRequests.begin(); iter != mPhysicsShapeRequests.end(); ++iter) + std::set::iterator iter(mPhysicsShapeRequests.begin()); + while (iter != mPhysicsShapeRequests.end() && mHttpRequestSet.size() < sRequestHighWater) { - if (mHttpRequestSet.size() < sRequestHighWater) - { - LLUUID mesh_id = *iter; - if (!fetchMeshPhysicsShape(mesh_id)) - { - incomplete.insert(mesh_id); - } - } - else + LLUUID mesh_id = *iter; + mPhysicsShapeRequests.erase(iter); + mMutex->unlock(); + + if (! fetchMeshPhysicsShape(mesh_id)) { - // Hit high-water mark, copy remaining to incomplete. - incomplete.insert(iter, mPhysicsShapeRequests.end()); - break; + incomplete.insert(mesh_id); } + + mMutex->lock(); + iter = mPhysicsShapeRequests.begin(); } - mPhysicsShapeRequests.swap(incomplete); - } - // For dev purposes, a dynamic change could make this false - // and that shouldn't assert. - // llassert_always(mHttpRequestSet.size() <= sRequestHighWater); + if (! incomplete.empty()) + { + mPhysicsShapeRequests.insert(incomplete.begin(), incomplete.end()); + } + } + mMutex->unlock(); } + + // For dev purposes only. A dynamic change could make this false + // and that shouldn't assert. + // llassert_always(mHttpRequestSet.size() <= sRequestHighWater); } if (mSignal->isLocked()) @@ -844,18 +878,21 @@ void LLMeshRepoThread::run() } } +// Mutex: LLMeshRepoThread::mMutex must be held on entry void LLMeshRepoThread::loadMeshSkinInfo(const LLUUID& mesh_id) -{ //protected by mSignal, no locking needed here +{ mSkinRequests.insert(mesh_id); } +// Mutex: LLMeshRepoThread::mMutex must be held on entry void LLMeshRepoThread::loadMeshDecomposition(const LLUUID& mesh_id) -{ //protected by mSignal, no locking needed here +{ mDecompositionRequests.insert(mesh_id); } +// Mutex: LLMeshRepoThread::mMutex must be held on entry void LLMeshRepoThread::loadMeshPhysicsShape(const LLUUID& mesh_id) -{ //protected by mSignal, no locking needed here +{ mPhysicsShapeRequests.insert(mesh_id); } @@ -2406,13 +2443,18 @@ void LLMeshHandlerBase::onCompleted(LLCore::HttpHandle handle, LLCore::HttpRespo } else { - // From texture fetch code and applies here: + // From texture fetch code and may apply 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. + // + // May also need to deal with 200 status (full asset returned + // rather than partial) and 416 (request completely unsatisfyable). + // Always been exposed to these but are less likely here where + // speculative loads aren't done. static const LLCore::HttpStatus par_status(HTTP_PARTIAL_CONTENT); LLCore::BufferArray * body(response->getBody()); @@ -2422,7 +2464,9 @@ void LLMeshHandlerBase::onCompleted(LLCore::HttpHandle handle, LLCore::HttpRespo if (data_size > 0) { // *TODO: Try to get rid of data copying and add interfaces - // that support BufferArray directly. + // that support BufferArray directly. Introduce a two-phase + // handler, optional first that takes a body, fallback second + // that requires a temporary allocation and data copy. data = new U8[data_size]; body->read(0, (char *) data, data_size); LLMeshRepository::sBytesReceived += data_size; @@ -2459,6 +2503,10 @@ void LLMeshHeaderHandler::processFailure(LLCore::HttpStatus status) { if (is_retryable(status)) { + // *TODO: This and the other processFailure() methods should + // probably just fail hard (as llcorehttp has done the retries). + // Or we could implement a slow/forever retry class. + LL_WARNS(LOG_MESH) << "Error during mesh header handling. Reason: " << status.toString() << " (" << status.toHex() << "). Retrying." << LL_ENDL; @@ -3026,32 +3074,40 @@ void LLMeshRepository::notifyLoadedMeshes() //call completed callbacks on finished decompositions mDecompThread->notifyCompleted(); - - if (!mThread->mWaiting && mPendingRequests.empty()) - { //curl thread is churning, wait for it to go idle - return; - } - static std::string region_name("never name a region this"); + // For major operations, attempt to get the required locks + // without blocking and punt if they're not available. + { + LLMutexTrylock lock1(mMeshMutex); + LLMutexTrylock lock2(mThread->mMutex); - if (gAgent.getRegion()) - { //update capability url - if (gAgent.getRegion()->getName() != region_name && gAgent.getRegion()->capabilitiesReceived()) + static U32 hold_offs(0); + if (! lock1.isLocked() || ! lock2.isLocked()) { - region_name = gAgent.getRegion()->getName(); - mGetMeshCapability = gAgent.getRegion()->getCapability("GetMesh"); - mGetMesh2Capability = gAgent.getRegion()->getCapability("GetMesh2"); - mGetMeshVersion = mGetMesh2Capability.empty() ? 1 : 2; - LL_DEBUGS(LOG_MESH) << "Retrieving caps for region '" << region_name - << "', GetMesh2: " << mGetMesh2Capability - << ", GetMesh: " << mGetMeshCapability - << LL_ENDL; + // If we can't get the locks, skip and pick this up later. + ++hold_offs; + sMaxLockHoldoffs = llmax(sMaxLockHoldoffs, hold_offs); + return; + } + hold_offs = 0; + + if (gAgent.getRegion()) + { + // Update capability urls + static std::string region_name("never name a region this"); + + if (gAgent.getRegion()->getName() != region_name && gAgent.getRegion()->capabilitiesReceived()) + { + region_name = gAgent.getRegion()->getName(); + mGetMeshCapability = gAgent.getRegion()->getCapability("GetMesh"); + mGetMesh2Capability = gAgent.getRegion()->getCapability("GetMesh2"); + mGetMeshVersion = mGetMesh2Capability.empty() ? 1 : 2; + LL_DEBUGS(LOG_MESH) << "Retrieving caps for region '" << region_name + << "', GetMesh2: " << mGetMesh2Capability + << ", GetMesh: " << mGetMeshCapability + << LL_ENDL; + } } - } - - { - LLMutexLock lock1(mMeshMutex); - LLMutexLock lock2(mThread->mMutex); //popup queued error messages from background threads while (!mUploadErrorQ.empty()) diff --git a/indra/newview/llmeshrepository.h b/indra/newview/llmeshrepository.h index 7e89f60bc3..c79278da1a 100755 --- a/indra/newview/llmeshrepository.h +++ b/indra/newview/llmeshrepository.h @@ -230,8 +230,6 @@ public: LLMutex* mHeaderMutex; LLCondition* mSignal; - volatile bool mWaiting; - //map of known mesh headers typedef std::map mesh_header_map; mesh_header_map mMeshHeader; @@ -494,19 +492,20 @@ public: //metrics static U32 sBytesReceived; - static U32 sMeshRequestCount; - static U32 sHTTPRequestCount; - static U32 sHTTPLargeRequestCount; - static U32 sHTTPRetryCount; - static U32 sHTTPErrorCount; + static U32 sMeshRequestCount; // Total request count, http or cached, all component types + static U32 sHTTPRequestCount; // Http GETs issued (not large) + static U32 sHTTPLargeRequestCount; // Http GETs issued for large requests + static U32 sHTTPRetryCount; // Total request retries whether successful or failed + static U32 sHTTPErrorCount; // Requests ending in error static U32 sLODPending; static U32 sLODProcessing; static U32 sCacheBytesRead; static U32 sCacheBytesWritten; - static U32 sCacheReads; + static U32 sCacheReads; static U32 sCacheWrites; + static U32 sMaxLockHoldoffs; // Maximum sequential locking failures - static LLDeadmanTimer sQuiescentTimer; // time-to-complete-mesh-downloads after significant events + 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); -- cgit v1.2.3 From 8b78642a475d9a1095970a3be39de47117b35d9f Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Wed, 7 Aug 2013 21:31:41 -0400 Subject: Create separate high/low water level limits for GetMesh and GetMesh2 capabilities. They should be independent now. --- indra/newview/llmeshrepository.cpp | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) (limited to 'indra') diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index 59100a68f9..b19f6281e7 100755 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -235,9 +235,13 @@ LLMeshRepository gMeshRepo; const S32 MESH_HEADER_SIZE = 4096; // Important: assumption is that headers fit in this space const S32 REQUEST_HIGH_WATER_MIN = 32; -const S32 REQUEST_HIGH_WATER_MAX = 80; +const S32 REQUEST_HIGH_WATER_MAX = 200; const S32 REQUEST_LOW_WATER_MIN = 16; -const S32 REQUEST_LOW_WATER_MAX = 40; +const S32 REQUEST_LOW_WATER_MAX = 100; +const S32 REQUEST2_HIGH_WATER_MIN = 32; +const S32 REQUEST2_HIGH_WATER_MAX = 80; +const S32 REQUEST2_LOW_WATER_MIN = 16; +const S32 REQUEST2_LOW_WATER_MAX = 40; const U32 LARGE_MESH_FETCH_THRESHOLD = 1U << 21; // Size at which requests goes to narrow/slow queue const long SMALL_MESH_XFER_TIMEOUT = 60L; // Seconds to complete xfer, small mesh downloads const long LARGE_MESH_XFER_TIMEOUT = 600L; // Seconds to complete xfer, large downloads @@ -376,8 +380,8 @@ void get_vertex_buffer_from_mesh(LLCDMeshData& mesh, LLModel::PhysicsMesh& res, volatile S32 LLMeshRepoThread::sActiveHeaderRequests = 0; volatile S32 LLMeshRepoThread::sActiveLODRequests = 0; U32 LLMeshRepoThread::sMaxConcurrentRequests = 1; -S32 LLMeshRepoThread::sRequestLowWater = REQUEST_LOW_WATER_MIN; -S32 LLMeshRepoThread::sRequestHighWater = REQUEST_HIGH_WATER_MIN; +S32 LLMeshRepoThread::sRequestLowWater = REQUEST2_LOW_WATER_MIN; +S32 LLMeshRepoThread::sRequestHighWater = REQUEST2_HIGH_WATER_MIN; S32 LLMeshRepoThread::sRequestWaterLevel = 0; // Base handler class for all mesh users of llcorehttp. @@ -2985,18 +2989,21 @@ void LLMeshRepository::notifyLoadedMeshes() LLMeshRepoThread::sRequestHighWater = llclamp(2 * S32(LLMeshRepoThread::sMaxConcurrentRequests), REQUEST_HIGH_WATER_MIN, REQUEST_HIGH_WATER_MAX); + LLMeshRepoThread::sRequestLowWater = llclamp(LLMeshRepoThread::sRequestHighWater / 2, + REQUEST_LOW_WATER_MIN, + REQUEST_LOW_WATER_MAX); } else { // GetMesh2 operation with keepalives, etc. LLMeshRepoThread::sMaxConcurrentRequests = gSavedSettings.getU32("Mesh2MaxConcurrentRequests"); LLMeshRepoThread::sRequestHighWater = llclamp(5 * S32(LLMeshRepoThread::sMaxConcurrentRequests), - REQUEST_HIGH_WATER_MIN, - REQUEST_HIGH_WATER_MAX); + REQUEST2_HIGH_WATER_MIN, + REQUEST2_HIGH_WATER_MAX); + LLMeshRepoThread::sRequestLowWater = llclamp(LLMeshRepoThread::sRequestHighWater / 2, + REQUEST2_LOW_WATER_MIN, + REQUEST2_LOW_WATER_MAX); } - LLMeshRepoThread::sRequestLowWater = llclamp(LLMeshRepoThread::sRequestHighWater / 2, - REQUEST_LOW_WATER_MIN, - REQUEST_LOW_WATER_MAX); //clean up completed upload threads for (std::vector::iterator iter = mUploads.begin(); iter != mUploads.end(); ) -- cgit v1.2.3 From f84a8104b80eacad78b30c09cb016774e5c459c5 Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Thu, 15 Aug 2013 19:00:43 -0400 Subject: SH-4410 Internal Documentation. Update and correct the mutex/data lists to reflect current. Describe the functional flow of things for a single LOD request. Put together to-do list for follow on work. Knock down the low/high water limits for GetMesh a bit, 100/200 too high, 75/150 should be better, vis-a-vis pathological failures. --- indra/newview/llmeshrepository.cpp | 178 ++++++++++++++++++++++++++----------- 1 file changed, 127 insertions(+), 51 deletions(-) (limited to 'indra') diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index b19f6281e7..7d64a9f63f 100755 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -79,10 +79,6 @@ #include -// [ Disclaimer: this documentation isn't by one of the original authors -// but by someone coming through later and extracting intent and function. -// Some of this will be wrong so use judgement. ] -// // Purpose // // The purpose of this module is to provide access between the viewer @@ -101,6 +97,7 @@ // * getMeshHeader (For structural details, see: // http://wiki.secondlife.com/wiki/Mesh/Mesh_Asset_Format) // * notifyLoadedMeshes +// * getSkinInfo // // Threads // @@ -108,7 +105,54 @@ // repo Overseeing worker thread associated with the LLMeshRepoThread class // decom Worker thread for mesh decomposition requests // core HTTP worker thread: does the work but doesn't intrude here -// uploadN 0-N temporary mesh upload threads +// uploadN 0-N temporary mesh upload threads (0-1 in practice) +// +// Sequence of Operations +// +// What follows is a description of the retrieval of one LOD for +// a new mesh object. Work is performed by a series of short, quick +// actions distributed over a number of threads. Each is meant +// to proceed without stalling and the whole forms a deep request +// pipeline to achieve throughput. Ellipsis indicates a return +// or break in processing which is resumed elsewhere. +// +// main thread repo thread (run() method) +// +// loadMesh() invoked to request LOD +// append LODRequest to mPendingRequests +// ... +// other mesh requests may be made +// ... +// notifyLoadedMeshes() invoked to stage work +// append HeaderRequest to mHeaderReqQ +// ... +// scan mHeaderReqQ +// issue 4096-byte GET for header +// ... +// onCompleted() invoked for GET +// data copied +// headerReceived() invoked +// LLSD parsed +// mMeshHeader, mMeshHeaderSize updated +// scan mPendingLOD for LOD request +// push LODRequest to mLODReqQ +// ... +// scan mLODReqQ +// fetchMeshLOD() invoked +// issue Byte-Range GET for LOD +// ... +// onCompleted() invoked for GET +// data copied +// lodReceived() invoked +// unpack data into LLVolume +// append LoadedMesh to mLoadedQ +// ... +// notifyLoadedMeshes() invoked again +// scan mLoadedQ +// notifyMeshLoaded() for LOD +// setMeshAssetLoaded() invoked for system volume +// notifyMeshLoaded() invoked for each interested object +// ... // // Mutexes // @@ -163,19 +207,19 @@ // // LLMeshRepository: // -// sBytesReceived -// sMeshRequestCount -// sHTTPRequestCount -// sHTTPLargeRequestCount -// sHTTPRetryCount -// sHTTPErrorCount -// sLODPending -// sLODProcessing -// sCacheBytesRead -// sCacheBytesWritten -// sCacheReads -// sCacheWrites -// mLoadingMeshes none rw.main.none, rw.main.mMeshMutex [4] +// sBytesReceived none rw.repo.none, ro.main.none [1] +// sMeshRequestCount " +// sHTTPRequestCount " +// sHTTPLargeRequestCount " +// sHTTPRetryCount " +// sHTTPErrorCount " +// sLODPending mMeshMutex [4] rw.main.mMeshMutex +// sLODProcessing Repo::mMutex rw.any.Repo::mMutex +// sCacheBytesRead none rw.repo.none, ro.main.none [1] +// sCacheBytesWritten " +// sCacheReads " +// sCacheWrites " +// mLoadingMeshes mMeshMutex [4] rw.main.none, rw.any.mMeshMutex // mSkinMap none rw.main.none // mDecompositionMap none rw.main.none // mPendingRequests mMeshMutex [4] rw.main.mMeshMutex @@ -199,25 +243,18 @@ // sMaxConcurrentRequests mMutex wo.main.none, ro.repo.none, ro.main.mMutex // mMeshHeader mHeaderMutex rw.repo.mHeaderMutex, ro.main.mHeaderMutex, ro.main.none [0] // mMeshHeaderSize mHeaderMutex rw.repo.mHeaderMutex -// mSkinRequests none rw.repo.none, rw.main.none [0] -// mSkinInfoQ none rw.repo.none, rw.main.none [0] -// mDecompositionRequests none rw.repo.none, rw.main.none [0] -// mPhysicsShapeRequests none rw.repo.none, rw.main.none [0] -// mDecompositionQ none rw.repo.none, rw.main.none [0] -// mHeaderReqQ mMutex ro.repo.none [3], rw.repo.mMutex, rw.any.mMutex -// mLODReqQ mMutex ro.repo.none [3], rw.repo.mMutex, rw.any.mMutex -// mUnavailableQ mMutex rw.repo.none [0], ro.main.none [3], rw.main.mMutex -// mLoadedQ mMutex rw.repo.mMutex, ro.main.none [3], rw.main.mMutex +// mSkinRequests mMutex rw.repo.mMutex, ro.repo.none [5] +// mSkinInfoQ none rw.repo.none, rw.main.mMutex [0] +// mDecompositionRequests mMutex rw.repo.mMutex, ro.repo.none [5] +// mPhysicsShapeRequests mMutex rw.repo.mMutex, ro.repo.none [5] +// mDecompositionQ none rw.repo.none, rw.main.mMutex [0] +// mHeaderReqQ mMutex ro.repo.none [5], rw.repo.mMutex, rw.any.mMutex +// mLODReqQ mMutex ro.repo.none [5], rw.repo.mMutex, rw.any.mMutex +// mUnavailableQ mMutex rw.repo.none [0], ro.main.none [5], rw.main.mMutex +// mLoadedQ mMutex rw.repo.mMutex, ro.main.none [5], rw.main.mMutex // mPendingLOD mMutex rw.repo.mMutex, rw.any.mMutex // mHttp* none rw.repo.none // -// LLPhysicsDecomp: -// -// mRequestQ -// mCurRequest -// mCompletedQ -// -// // QA/Development Testing // // Debug variable 'MeshUploadFakeErrors' takes a mask of bits that will @@ -230,15 +267,27 @@ // locally-generated 500 status. // 0x08 As with 0x04 but for the upload operation. // +// *TODO: Work list for followup actions: +// * Review anything marked as unsafe above, verify if there are real issues. +// * See if we can put ::run() into a hard sleep. May not actually perform better +// than the current scheme so be prepared for disappointment. You'll likely +// need to introduce a condition variable class that references a mutex in +// methods rather than derives from mutex which isn't correct. +// * On upload failures, make more information available to the alerting +// dialog. Get the structured information going into the log into a +// tree there. +// * Header parse failures come without much explanation. Elaborate. +// * Need a final failure state for requests that are retried and just won't +// complete. We can fail a LOD request, others we don't. LLMeshRepository gMeshRepo; const S32 MESH_HEADER_SIZE = 4096; // Important: assumption is that headers fit in this space -const S32 REQUEST_HIGH_WATER_MIN = 32; -const S32 REQUEST_HIGH_WATER_MAX = 200; +const S32 REQUEST_HIGH_WATER_MIN = 32; // Limits for GetMesh regions +const S32 REQUEST_HIGH_WATER_MAX = 150; // Should remain under 2X throttle const S32 REQUEST_LOW_WATER_MIN = 16; -const S32 REQUEST_LOW_WATER_MAX = 100; -const S32 REQUEST2_HIGH_WATER_MIN = 32; +const S32 REQUEST_LOW_WATER_MAX = 75; +const S32 REQUEST2_HIGH_WATER_MIN = 32; // Limits for GetMesh2 regions const S32 REQUEST2_HIGH_WATER_MAX = 80; const S32 REQUEST2_LOW_WATER_MIN = 16; const S32 REQUEST2_LOW_WATER_MAX = 40; @@ -269,7 +318,7 @@ U32 LLMeshRepository::sCacheReads = 0; U32 LLMeshRepository::sCacheWrites = 0; U32 LLMeshRepository::sMaxLockHoldoffs = 0; -LLDeadmanTimer LLMeshRepository::sQuiescentTimer(15.0, true); // true -> gather cpu metrics +LLDeadmanTimer LLMeshRepository::sQuiescentTimer(15.0, false); // true -> gather cpu metrics static S32 dump_num = 0; @@ -703,7 +752,7 @@ void LLMeshRepoThread::run() while (!LLApp::isQuitting()) { - // *TODO: Revise sleep/wake strategy and try to move away' + // *TODO: Revise sleep/wake strategy and try to move away // from polling operations in this thread. We can sleep // this thread hard when: // * All Http requests are serviced @@ -714,7 +763,8 @@ void LLMeshRepoThread::run() // * Physics shape request queue empty // We wake the thread when any of the above become untrue. // Will likely need a correctly-implemented condition variable to do this. - + // On the other hand, this may actually be an effective and efficient scheme... + mSignal->wait(); if (LLApp::isQuitting()) @@ -810,7 +860,7 @@ void LLMeshRepoThread::run() // holding lock, try next list // *TODO: For UI/debug-oriented lists, we might drop the fine- - // grained locking as there's lowered expectations of smoothness + // grained locking as there's a lowered expectation of smoothness // in these cases. if (! mDecompositionRequests.empty() && mHttpRequestSet.size() < sRequestHighWater) { @@ -2303,24 +2353,26 @@ void LLMeshUploadThread::onCompleted(LLCore::HttpHandle handle, LLCore::HttpResp void LLMeshRepoThread::notifyLoadedMeshes() { + bool update_metrics(false); + if (!mMutex) { return; } - if (!mLoadedQ.empty() || !mUnavailableQ.empty()) - { - // Ping time-to-load metrics for mesh download operations. - LLMeshRepository::metricsProgress(0); - } - while (!mLoadedQ.empty()) { mMutex->lock(); + if (mLoadedQ.empty()) + { + mMutex->unlock(); + break; + } LoadedMesh mesh = mLoadedQ.front(); mLoadedQ.pop(); mMutex->unlock(); + update_metrics = true; if (mesh.mVolume && mesh.mVolume->getNumVolumeFaces() > 0) { gMeshRepo.notifyMeshLoaded(mesh.mMeshParams, mesh.mVolume); @@ -2335,10 +2387,17 @@ void LLMeshRepoThread::notifyLoadedMeshes() while (!mUnavailableQ.empty()) { mMutex->lock(); + if (mUnavailableQ.empty()) + { + mMutex->unlock(); + break; + } + LODRequest req = mUnavailableQ.front(); mUnavailableQ.pop(); mMutex->unlock(); - + + update_metrics = true; gMeshRepo.notifyMeshUnavailable(req.mMeshParams, req.mLOD); } @@ -2353,6 +2412,13 @@ void LLMeshRepoThread::notifyLoadedMeshes() gMeshRepo.notifyDecompositionReceived(mDecompositionQ.front()); mDecompositionQ.pop(); } + + if (update_metrics) + { + // Ping time-to-load metrics for mesh download operations. + LLMeshRepository::metricsProgress(0); + } + } S32 LLMeshRepoThread::getActualMeshLOD(const LLVolumeParams& mesh_params, S32 lod) @@ -2461,6 +2527,12 @@ void LLMeshHandlerBase::onCompleted(LLCore::HttpHandle handle, LLCore::HttpRespo // speculative loads aren't done. static const LLCore::HttpStatus par_status(HTTP_PARTIAL_CONTENT); + if (par_status != status) + { + LL_WARNS_ONCE(LOG_MESH) << "Non-206 successful status received for fetch: " + << status.toHex() << LL_ENDL; + } + LLCore::BufferArray * body(response->getBody()); S32 data_size(body ? body->size() : 0); U8 * data(NULL); @@ -2995,7 +3067,8 @@ void LLMeshRepository::notifyLoadedMeshes() } else { - // GetMesh2 operation with keepalives, etc. + // GetMesh2 operation with keepalives, etc. With pipelining, + // we'll increase this. LLMeshRepoThread::sMaxConcurrentRequests = gSavedSettings.getU32("Mesh2MaxConcurrentRequests"); LLMeshRepoThread::sRequestHighWater = llclamp(5 * S32(LLMeshRepoThread::sMaxConcurrentRequests), REQUEST2_HIGH_WATER_MIN, @@ -3083,7 +3156,10 @@ void LLMeshRepository::notifyLoadedMeshes() mDecompThread->notifyCompleted(); // For major operations, attempt to get the required locks - // without blocking and punt if they're not available. + // without blocking and punt if they're not available. The + // longest run of holdoffs is kept in sMaxLockHoldoffs just + // to collect the data. In testing, I've never seen a value + // greater than 2 (written to log on exit). { LLMutexTrylock lock1(mMeshMutex); LLMutexTrylock lock2(mThread->mMutex); -- cgit v1.2.3 From e764a2a565e18ce2157788f634e85bc3641976b3 Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Fri, 16 Aug 2013 18:07:49 -0400 Subject: SH-4407 Tuning to get new code working as well. Do some runtime code avoidance and skip unnecessary libcurl and syscall invocations. --- indra/llcorehttp/_httplibcurl.cpp | 32 +++++++++++++++++--------------- indra/llcorehttp/_httplibcurl.h | 1 + indra/llcorehttp/_httppolicy.cpp | 11 ++++++++--- 3 files changed, 26 insertions(+), 18 deletions(-) (limited to 'indra') diff --git a/indra/llcorehttp/_httplibcurl.cpp b/indra/llcorehttp/_httplibcurl.cpp index b079dff864..0cb4e9d8b7 100755 --- a/indra/llcorehttp/_httplibcurl.cpp +++ b/indra/llcorehttp/_httplibcurl.cpp @@ -41,7 +41,8 @@ namespace LLCore HttpLibcurl::HttpLibcurl(HttpService * service) : mService(service), mPolicyCount(0), - mMultiHandles(NULL) + mMultiHandles(NULL), + mActiveHandles(NULL) {} @@ -77,6 +78,9 @@ void HttpLibcurl::shutdown() delete [] mMultiHandles; mMultiHandles = NULL; + + delete [] mActiveHandles; + mActiveHandles = NULL; } mPolicyCount = 0; @@ -90,9 +94,12 @@ void HttpLibcurl::start(int policy_count) mPolicyCount = policy_count; mMultiHandles = new CURLM * [mPolicyCount]; + mActiveHandles = new int [mPolicyCount]; + for (int policy_class(0); policy_class < mPolicyCount; ++policy_class) { mMultiHandles[policy_class] = curl_multi_init(); + mActiveHandles[policy_class] = 0; } } @@ -110,8 +117,10 @@ HttpService::ELoopSpeed HttpLibcurl::processTransport() // Give libcurl some cycles to do I/O & callbacks for (int policy_class(0); policy_class < mPolicyCount; ++policy_class) { - if (! mMultiHandles[policy_class]) + if (! mActiveHandles[policy_class] || ! mMultiHandles[policy_class]) + { continue; + } int running(0); CURLMcode status(CURLM_CALL_MULTI_PERFORM); @@ -191,6 +200,7 @@ void HttpLibcurl::addOp(HttpOpRequest * op) // On success, make operation active mActiveOps.insert(op); + ++mActiveHandles[op->mReqPolicy]; } @@ -212,6 +222,7 @@ bool HttpLibcurl::cancel(HttpHandle handle) // Drop references mActiveOps.erase(it); + --mActiveHandles[op->mReqPolicy]; op->release(); return true; @@ -273,6 +284,7 @@ bool HttpLibcurl::completeRequest(CURLM * multi_handle, CURL * handle, CURLcode // Deactivate request mActiveOps.erase(it); + --mActiveHandles[op->mReqPolicy]; op->mCurlActive = false; // Set final status of request if it hasn't failed by other mechanisms yet @@ -334,19 +346,9 @@ int HttpLibcurl::getActiveCount() const int HttpLibcurl::getActiveCountInClass(int policy_class) const { - int count(0); - - for (active_set_t::const_iterator iter(mActiveOps.begin()); - mActiveOps.end() != iter; - ++iter) - { - if ((*iter)->mReqPolicy == policy_class) - { - ++count; - } - } - - return count; + llassert_always(policy_class < mPolicyCount); + + return mActiveHandles ? mActiveHandles[policy_class] : 0; } diff --git a/indra/llcorehttp/_httplibcurl.h b/indra/llcorehttp/_httplibcurl.h index 0ec90437bb..67f98dd4f0 100755 --- a/indra/llcorehttp/_httplibcurl.h +++ b/indra/llcorehttp/_httplibcurl.h @@ -133,6 +133,7 @@ protected: active_set_t mActiveOps; int mPolicyCount; CURLM ** mMultiHandles; // One handle per policy class + int * mActiveHandles; // Active count per policy class }; // end class HttpLibcurl } // end namespace LLCore diff --git a/indra/llcorehttp/_httppolicy.cpp b/indra/llcorehttp/_httppolicy.cpp index 808eebc6cc..c4758aee88 100755 --- a/indra/llcorehttp/_httppolicy.cpp +++ b/indra/llcorehttp/_httppolicy.cpp @@ -212,6 +212,14 @@ HttpService::ELoopSpeed HttpPolicy::processReadyQueue() for (int policy_class(0); policy_class < mClasses.size(); ++policy_class) { ClassState & state(*mClasses[policy_class]); + HttpRetryQueue & retryq(state.mRetryQueue); + HttpReadyQueue & readyq(state.mReadyQueue); + + if (retryq.empty() && readyq.empty()) + { + continue; + } + const bool throttle_enabled(state.mOptions.mThrottleRate > 0L); const bool throttle_current(throttle_enabled && now < state.mThrottleEnd); @@ -225,9 +233,6 @@ HttpService::ELoopSpeed HttpPolicy::processReadyQueue() int active(transport.getActiveCountInClass(policy_class)); int needed(state.mOptions.mConnectionLimit - active); // Expect negatives here - HttpRetryQueue & retryq(state.mRetryQueue); - HttpReadyQueue & readyq(state.mReadyQueue); - if (needed > 0) { // First see if we have any retries... -- cgit v1.2.3 From 146a5c3f6c3d1b8e9e92f71dce1e7f058091ea20 Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Mon, 19 Aug 2013 12:01:26 -0400 Subject: Add 'internal'/'external' token to DEBUG retry message so that dev/QA can know exactly where a retry value was sourced. --- indra/llcorehttp/_httppolicy.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'indra') diff --git a/indra/llcorehttp/_httppolicy.cpp b/indra/llcorehttp/_httppolicy.cpp index c4758aee88..ac79a77659 100755 --- a/indra/llcorehttp/_httppolicy.cpp +++ b/indra/llcorehttp/_httppolicy.cpp @@ -153,14 +153,16 @@ void HttpPolicy::retryOp(HttpOpRequest * op) }; static const int delta_max(int(LL_ARRAY_SIZE(retry_deltas)) - 1); static const HttpStatus error_503(503); - + const HttpTime now(totalTime()); const int policy_class(op->mReqPolicy); HttpTime delta(retry_deltas[llclamp(op->mPolicyRetries, 0, delta_max)]); + bool external_delta(false); if (op->mReplyRetryAfter > 0 && op->mReplyRetryAfter < 30) { delta = op->mReplyRetryAfter * U64L(1000000); + external_delta = true; } op->mPolicyRetryAt = now + delta; ++op->mPolicyRetries; @@ -171,7 +173,8 @@ void HttpPolicy::retryOp(HttpOpRequest * op) LL_DEBUGS("CoreHttp") << "HTTP request " << static_cast(op) << " retry " << op->mPolicyRetries << " scheduled in " << (delta / HttpTime(1000)) - << " mS. Status: " << op->mStatus.toHex() + << " mS (" << (external_delta ? "external" : "internal") + << "). Status: " << op->mStatus.toHex() << LL_ENDL; if (op->mTracing > HTTP_TRACE_OFF) { -- cgit v1.2.3 From 42e3d55fb2d0579b968ff8df20e9a814229b3e72 Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Wed, 28 Aug 2013 18:55:29 -0400 Subject: Add conditional LLFastTimer blocks to stallable main thread methods --- indra/newview/llmeshrepository.cpp | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) (limited to 'indra') diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index 6dc834e852..0599fcfd2d 100755 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -67,6 +67,7 @@ #include "llfoldertype.h" #include "llviewerparcelmgr.h" #include "lluploadfloaterobservers.h" +#include "llfasttimer.h" #include "boost/lexical_cast.hpp" @@ -125,6 +126,16 @@ 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 LLFastTimer::DeclareTimer FTM_MESH_FETCH("Mesh Fetch"); + +// Enable here or in build environment to get fasttimer data on mesh fetches. +#define LL_FASTTIMER_MESH_ENABLE 1 +#if LL_FASTTIMER_MESH_ENABLE +#define MESH_FASTTIMER_DEFBLOCK LLFastTimer meshtimer(FTM_MESH_FETCH) +#else +#define MESH_FASTTIMER_DEFBLOCK +#endif // LL_FASTTIMER_MESH_ENABLE + //get the number of bytes resident in memory for given volume U32 get_volume_memory_size(const LLVolume* volume) @@ -2403,6 +2414,8 @@ S32 LLMeshRepository::update() S32 LLMeshRepository::loadMesh(LLVOVolume* vobj, const LLVolumeParams& mesh_params, S32 detail, S32 last_lod) { + MESH_FASTTIMER_DEFBLOCK; + // Manage time-to-load metrics for mesh download operations. metricsProgress(1); @@ -2484,6 +2497,7 @@ S32 LLMeshRepository::loadMesh(LLVOVolume* vobj, const LLVolumeParams& mesh_para void LLMeshRepository::notifyLoadedMeshes() { //called from main thread + MESH_FASTTIMER_DEFBLOCK; LLMeshRepoThread::sMaxConcurrentRequests = gSavedSettings.getU32("MeshMaxConcurrentRequests"); @@ -2791,6 +2805,8 @@ S32 LLMeshRepository::getActualMeshLOD(const LLVolumeParams& mesh_params, S32 lo const LLMeshSkinInfo* LLMeshRepository::getSkinInfo(const LLUUID& mesh_id, const LLVOVolume* requesting_obj) { + MESH_FASTTIMER_DEFBLOCK; + if (mesh_id.notNull()) { skin_map::iterator iter = mSkinMap.find(mesh_id); @@ -2817,6 +2833,8 @@ const LLMeshSkinInfo* LLMeshRepository::getSkinInfo(const LLUUID& mesh_id, const void LLMeshRepository::fetchPhysicsShape(const LLUUID& mesh_id) { + MESH_FASTTIMER_DEFBLOCK; + if (mesh_id.notNull()) { LLModel::Decomposition* decomp = NULL; @@ -2844,6 +2862,8 @@ void LLMeshRepository::fetchPhysicsShape(const LLUUID& mesh_id) LLModel::Decomposition* LLMeshRepository::getDecomposition(const LLUUID& mesh_id) { + MESH_FASTTIMER_DEFBLOCK; + LLModel::Decomposition* ret = NULL; if (mesh_id.notNull()) @@ -2906,6 +2926,8 @@ bool LLMeshRepository::hasPhysicsShape(const LLUUID& mesh_id) LLSD& LLMeshRepository::getMeshHeader(const LLUUID& mesh_id) { + MESH_FASTTIMER_DEFBLOCK; + return mThread->getMeshHeader(mesh_id); } -- cgit v1.2.3 From d706b5717785a318c053055c49589b16f9633681 Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Thu, 29 Aug 2013 13:04:05 -0400 Subject: Document the fast timer testing apparatus a bit. --- indra/newview/llmeshrepository.cpp | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) (limited to 'indra') diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index 2f43ee3ccb..3c964160b5 100755 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -268,6 +268,9 @@ // locally-generated 500 status. // 0x08 As with 0x04 but for the upload operation. // +// For major changes, see the LL_MESH_FASTTIMER_ENABLE below and +// instructions for looking for frame stalls using fast timers. +// // *TODO: Work list for followup actions: // * Review anything marked as unsafe above, verify if there are real issues. // * See if we can put ::run() into a hard sleep. May not actually perform better @@ -346,15 +349,25 @@ static unsigned int metrics_teleport_start_count = 0; boost::signals2::connection metrics_teleport_started_signal; static void teleport_started(); static bool is_retryable(LLCore::HttpStatus status); -static LLFastTimer::DeclareTimer FTM_MESH_FETCH("Mesh Fetch"); // Enable here or in build environment to get fasttimer data on mesh fetches. -#define LL_FASTTIMER_MESH_ENABLE 1 -#if LL_FASTTIMER_MESH_ENABLE +// +// Typically, this is used to perform A/B testing using the +// fasttimer console (shift-ctrl-9). This is done by looking +// for stalls due to lock contention between the main thread +// and the repository and HTTP code. In a release viewer, +// these appear as ping-time or worse spikes in frame time. +// With this instrumentation enabled, a stall will appear +// under the 'Mesh Fetch' timer which will be either top-level +// or under 'Render' time. +#define LL_MESH_FASTTIMER_ENABLE 1 +#if LL_MESH_FASTTIMER_ENABLE +static LLFastTimer::DeclareTimer FTM_MESH_FETCH("Mesh Fetch"); + #define MESH_FASTTIMER_DEFBLOCK LLFastTimer meshtimer(FTM_MESH_FETCH) #else #define MESH_FASTTIMER_DEFBLOCK -#endif // LL_FASTTIMER_MESH_ENABLE +#endif // LL_MESH_FASTTIMER_ENABLE //get the number of bytes resident in memory for given volume U32 get_volume_memory_size(const LLVolume* volume) -- cgit v1.2.3 From 6a1f91fa3e0f82ad58fd13add8a093d88eff2c70 Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Fri, 6 Sep 2013 16:37:31 -0400 Subject: SH-4478 Corrected/updated error handling for all retrieval operations. In case of HTTP errors or parsing/processing errors, fail the fetch request rather than do a retry spin. Add logging for all such failure paths. Added a development/debug flag to create probabilistic failures to test these modes and general error recovery by higher-level layers. --- indra/newview/llmeshrepository.cpp | 277 +++++++++++++++++-------------------- 1 file changed, 126 insertions(+), 151 deletions(-) (limited to 'indra') diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index 3c964160b5..f6a85ac94f 100755 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -44,6 +44,7 @@ #include "lleconomy.h" #include "llimagej2c.h" #include "llhost.h" +#include "llmath.h" #include "llnotificationsutil.h" #include "llsd.h" #include "llsdutil_math.h" @@ -284,6 +285,52 @@ // * Need a final failure state for requests that are retried and just won't // complete. We can fail a LOD request, others we don't. + +// -------------------------------------------------------------------------- +// Development/Debug/QA Tools +// +// Enable here or in build environment to get fasttimer data on mesh fetches. +// +// Typically, this is used to perform A/B testing using the +// fasttimer console (shift-ctrl-9). This is done by looking +// for stalls due to lock contention between the main thread +// and the repository and HTTP code. In a release viewer, +// these appear as ping-time or worse spikes in frame time. +// With this instrumentation enabled, a stall will appear +// under the 'Mesh Fetch' timer which will be either top-level +// or under 'Render' time. +#define LL_MESH_FASTTIMER_ENABLE 1 +#if LL_MESH_FASTTIMER_ENABLE +static LLFastTimer::DeclareTimer FTM_MESH_FETCH("Mesh Fetch"); + +#define MESH_FASTTIMER_DEFBLOCK LLFastTimer meshtimer(FTM_MESH_FETCH) +#else +#define MESH_FASTTIMER_DEFBLOCK +#endif // LL_MESH_FASTTIMER_ENABLE + + +// Random failure testing for development/QA. +// +// Set the MESH_*_FAILED macros to either 'false' or to +// an invocation of MESH_RANDOM_NTH_TRUE() with some +// suitable number. In production, all must be false. +// +// Example: +// #define MESH_HTTP_RESPONSE_FAILED MESH_RANDOM_NTH_TRUE(9) + +// 1-in-N calls will test true +#define MESH_RANDOM_NTH_TRUE(_N) ( ll_rand(S32(_N)) == 0 ) + +#define MESH_HTTP_RESPONSE_FAILED false +#define MESH_HEADER_PROCESS_FAILED false +#define MESH_LOD_PROCESS_FAILED false +#define MESH_SKIN_INFO_PROCESS_FAILED false +#define MESH_DECOMP_PROCESS_FAILED false +#define MESH_PHYS_SHAPE_PROCESS_FAILED false + +// -------------------------------------------------------------------------- + + LLMeshRepository gMeshRepo; const S32 MESH_HEADER_SIZE = 4096; // Important: assumption is that headers fit in this space @@ -348,27 +395,7 @@ const char * const LOG_MESH = "Mesh"; static unsigned int metrics_teleport_start_count = 0; boost::signals2::connection metrics_teleport_started_signal; static void teleport_started(); -static bool is_retryable(LLCore::HttpStatus status); -// Enable here or in build environment to get fasttimer data on mesh fetches. -// -// Typically, this is used to perform A/B testing using the -// fasttimer console (shift-ctrl-9). This is done by looking -// for stalls due to lock contention between the main thread -// and the repository and HTTP code. In a release viewer, -// these appear as ping-time or worse spikes in frame time. -// With this instrumentation enabled, a stall will appear -// under the 'Mesh Fetch' timer which will be either top-level -// or under 'Render' time. -#define LL_MESH_FASTTIMER_ENABLE 1 -#if LL_MESH_FASTTIMER_ENABLE -static LLFastTimer::DeclareTimer FTM_MESH_FETCH("Mesh Fetch"); - -#define MESH_FASTTIMER_DEFBLOCK LLFastTimer meshtimer(FTM_MESH_FETCH) -#else -#define MESH_FASTTIMER_DEFBLOCK -#endif // LL_MESH_FASTTIMER_ENABLE - //get the number of bytes resident in memory for given volume U32 get_volume_memory_size(const LLVolume* volume) { @@ -815,7 +842,7 @@ void LLMeshRepoThread::run() mLODReqQ.pop(); LLMeshRepository::sLODProcessing--; mMutex->unlock(); - if (!fetchMeshLOD(req.mMeshParams, req.mLOD))//failed, resubmit + if (!fetchMeshLOD(req.mMeshParams, req.mLOD)) // failed, resubmit { mMutex->lock(); mLODReqQ.push(req) ; @@ -1548,6 +1575,7 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod) { handler->mHttpHandle = handle; mHttpRequestSet.insert(handler); + // *NOTE: Allowing a re-request, not marking as unavailable. Is that correct? } } else @@ -2529,7 +2557,7 @@ void LLMeshHandlerBase::onCompleted(LLCore::HttpHandle handle, LLCore::HttpRespo LLMeshRepository::sHTTPRetryCount += retries; LLCore::HttpStatus status(response->getStatus()); - if (! status) + if (! status || MESH_HTTP_RESPONSE_FAILED) { processFailure(status); ++LLMeshRepository::sHTTPErrorCount; @@ -2600,39 +2628,37 @@ LLMeshHeaderHandler::~LLMeshHeaderHandler() void LLMeshHeaderHandler::processFailure(LLCore::HttpStatus status) { - if (is_retryable(status)) - { - // *TODO: This and the other processFailure() methods should - // probably just fail hard (as llcorehttp has done the retries). - // Or we could implement a slow/forever retry class. - - LL_WARNS(LOG_MESH) << "Error during mesh header handling. Reason: " << status.toString() - << " (" << status.toHex() << "). Retrying." - << LL_ENDL; - LLMeshRepoThread::HeaderRequest req(mMeshParams); - LLMutexLock lock(gMeshRepo.mThread->mMutex); - gMeshRepo.mThread->mHeaderReqQ.push(req); - } - else + LL_WARNS(LOG_MESH) << "Error during mesh header handling. ID: " << mMeshParams.getSculptID() + << ", Reason: " << status.toString() + << " (" << status.toHex() << "). Not retrying." + << LL_ENDL; + + // Can't get the header so none of the LODs will be available + LLMutexLock lock(gMeshRepo.mThread->mMutex); + for (int i(0); i < 4; ++i) { - // *TODO: Mark mesh unavailable - LL_WARNS(LOG_MESH) << "Error during mesh header handling. Reason: " << status.toString() - << " (" << status.toHex() << "). Not retrying." - << LL_ENDL; + gMeshRepo.mThread->mUnavailableQ.push(LLMeshRepoThread::LODRequest(mMeshParams, i)); } } void LLMeshHeaderHandler::processData(LLCore::BufferArray * body, U8 * data, S32 data_size) { LLUUID mesh_id = mMeshParams.getSculptID(); - bool success = gMeshRepo.mThread->headerReceived(mMeshParams, data, data_size); + bool success = (! MESH_HEADER_PROCESS_FAILED) && gMeshRepo.mThread->headerReceived(mMeshParams, data, data_size); llassert(success); if (! success) { - // *TODO: Mark mesh unavailable - // *TODO: Get real reason for parse failure here + // *TODO: Get real reason for parse failure here. Might we want to retry? LL_WARNS(LOG_MESH) << "Unable to parse mesh header. ID: " << mesh_id + << ", Unknown reason. Not retrying." << LL_ENDL; + + // Can't get the header so none of the LODs will be available + LLMutexLock lock(gMeshRepo.mThread->mMutex); + for (int i(0); i < 4; ++i) + { + gMeshRepo.mThread->mUnavailableQ.push(LLMeshRepoThread::LODRequest(mMeshParams, i)); + } } else if (data && data_size > 0) { @@ -2708,29 +2734,18 @@ LLMeshLODHandler::~LLMeshLODHandler() void LLMeshLODHandler::processFailure(LLCore::HttpStatus status) { - if (is_retryable(status)) - { - LL_WARNS(LOG_MESH) << "Error during mesh header handling. Reason: " << status.toString() - << " (" << status.toHex() << "). Retrying." - << LL_ENDL; - { - LLMutexLock lock(gMeshRepo.mThread->mMutex); + LL_WARNS(LOG_MESH) << "Error during mesh LOD handling. ID: " << mMeshParams.getSculptID() + << ", Reason: " << status.toString() + << " (" << status.toHex() << "). Not retrying." + << LL_ENDL; - gMeshRepo.mThread->loadMeshLOD(mMeshParams, mLOD); - } - } - else - { - // *TODO: Mark mesh unavailable - LL_WARNS(LOG_MESH) << "Error during mesh LOD handling. Reason: " << status.toString() - << " (" << status.toHex() << "). Not retrying." - << LL_ENDL; - } + LLMutexLock lock(gMeshRepo.mThread->mMutex); + gMeshRepo.mThread->mUnavailableQ.push(LLMeshRepoThread::LODRequest(mMeshParams, mLOD)); } void LLMeshLODHandler::processData(LLCore::BufferArray * body, U8 * data, S32 data_size) { - if (gMeshRepo.mThread->lodReceived(mMeshParams, mLOD, data, data_size)) + if ((! MESH_LOD_PROCESS_FAILED) && 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); @@ -2746,7 +2761,14 @@ void LLMeshLODHandler::processData(LLCore::BufferArray * body, U8 * data, S32 da ++LLMeshRepository::sCacheWrites; } } - // *TODO: Mark mesh unavailable on error + else + { + LL_WARNS(LOG_MESH) << "Error during mesh LOD processing. ID: " << mMeshParams.getSculptID() + << ", Unknown reason. Not retrying." + << LL_ENDL; + LLMutexLock lock(gMeshRepo.mThread->mMutex); + gMeshRepo.mThread->mUnavailableQ.push(LLMeshRepoThread::LODRequest(mMeshParams, mLOD)); + } } LLMeshSkinInfoHandler::~LLMeshSkinInfoHandler() @@ -2756,29 +2778,18 @@ LLMeshSkinInfoHandler::~LLMeshSkinInfoHandler() void LLMeshSkinInfoHandler::processFailure(LLCore::HttpStatus status) { - if (is_retryable(status)) - { - LL_WARNS(LOG_MESH) << "Error during mesh skin info handling. Reason: " << status.toString() - << " (" << status.toHex() << "). Retrying." - << LL_ENDL; - { - LLMutexLock lock(gMeshRepo.mThread->mMutex); + LL_WARNS(LOG_MESH) << "Error during mesh skin info handling. ID: " << mMeshID + << ", Reason: " << status.toString() + << " (" << status.toHex() << "). Not retrying." + << LL_ENDL; - gMeshRepo.mThread->loadMeshSkinInfo(mMeshID); - } - } - else - { - // *TODO: Mark mesh unavailable on error - LL_WARNS(LOG_MESH) << "Error during mesh skin info handling. Reason: " << status.toString() - << " (" << status.toHex() << "). Not retrying." - << LL_ENDL; - } + // *TODO: Mark mesh unavailable on error. For now, simply leave + // request unfulfilled rather than retry forever. } void LLMeshSkinInfoHandler::processData(LLCore::BufferArray * body, U8 * data, S32 data_size) { - if (gMeshRepo.mThread->skinInfoReceived(mMeshID, data, data_size)) + if ((! MESH_SKIN_INFO_PROCESS_FAILED) && 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); @@ -2794,7 +2805,13 @@ void LLMeshSkinInfoHandler::processData(LLCore::BufferArray * body, U8 * data, S file.write(data, size); } } - // *TODO: Mark mesh unavailable on error + else + { + LL_WARNS(LOG_MESH) << "Error during mesh skin info processing. ID: " << mMeshID + << ", Unknown reason. Not retrying." + << LL_ENDL; + // *TODO: Mark mesh unavailable on error + } } LLMeshDecompositionHandler::~LLMeshDecompositionHandler() @@ -2804,29 +2821,17 @@ LLMeshDecompositionHandler::~LLMeshDecompositionHandler() void LLMeshDecompositionHandler::processFailure(LLCore::HttpStatus status) { - if (is_retryable(status)) - { - LL_WARNS(LOG_MESH) << "Error during mesh decomposition handling. Reason: " << status.toString() - << " (" << status.toHex() << "). Retrying." - << LL_ENDL; - { - LLMutexLock lock(gMeshRepo.mThread->mMutex); - - gMeshRepo.mThread->loadMeshDecomposition(mMeshID); - } - } - else - { - // *TODO: Mark mesh unavailable on error - LL_WARNS(LOG_MESH) << "Error during mesh decomposition handling. Reason: " << status.toString() - << " (" << status.toHex() << "). Not retrying." - << LL_ENDL; - } + LL_WARNS(LOG_MESH) << "Error during mesh decomposition handling. ID: " << mMeshID + << ", Reason: " << status.toString() + << " (" << status.toHex() << "). Not retrying." + << LL_ENDL; + // *TODO: Mark mesh unavailable on error. For now, simply leave + // request unfulfilled rather than retry forever. } void LLMeshDecompositionHandler::processData(LLCore::BufferArray * body, U8 * data, S32 data_size) { - if (gMeshRepo.mThread->decompositionReceived(mMeshID, data, data_size)) + if ((! MESH_DECOMP_PROCESS_FAILED) && 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); @@ -2842,7 +2847,13 @@ void LLMeshDecompositionHandler::processData(LLCore::BufferArray * body, U8 * da file.write(data, size); } } - // *TODO: Mark mesh unavailable on error + else + { + LL_WARNS(LOG_MESH) << "Error during mesh decomposition processing. ID: " << mMeshID + << ", Unknown reason. Not retrying." + << LL_ENDL; + // *TODO: Mark mesh unavailable on error + } } LLMeshPhysicsShapeHandler::~LLMeshPhysicsShapeHandler() @@ -2852,29 +2863,16 @@ LLMeshPhysicsShapeHandler::~LLMeshPhysicsShapeHandler() void LLMeshPhysicsShapeHandler::processFailure(LLCore::HttpStatus status) { - if (is_retryable(status)) - { - LL_WARNS(LOG_MESH) << "Error during mesh physics shape handling. Reason: " << status.toString() - << " (" << status.toHex() << "). Retrying." - << LL_ENDL; - { - LLMutexLock lock(gMeshRepo.mThread->mMutex); - - gMeshRepo.mThread->loadMeshPhysicsShape(mMeshID); - } - } - else - { - // *TODO: Mark mesh unavailable on error - LL_WARNS(LOG_MESH) << "Error during mesh physics shape handling. Reason: " << status.toString() - << " (" << status.toHex() << "). Not retrying." - << LL_ENDL; - } + LL_WARNS(LOG_MESH) << "Error during mesh physics shape handling. ID: " << mMeshID + << ", Reason: " << status.toString() + << " (" << status.toHex() << "). Not retrying." + << LL_ENDL; + // *TODO: Mark mesh unavailable on error } void LLMeshPhysicsShapeHandler::processData(LLCore::BufferArray * body, U8 * data, S32 data_size) { - if (gMeshRepo.mThread->physicsShapeReceived(mMeshID, data, data_size)) + if ((! MESH_PHYS_SHAPE_PROCESS_FAILED) && 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); @@ -2890,7 +2888,13 @@ void LLMeshPhysicsShapeHandler::processData(LLCore::BufferArray * body, U8 * dat file.write(data, size); } } - // *TODO: Mark mesh unavailable on error + else + { + LL_WARNS(LOG_MESH) << "Error during mesh physics shape processing. ID: " << mMeshID + << ", Unknown reason. Not retrying." + << LL_ENDL; + // *TODO: Mark mesh unavailable on error + } } LLMeshRepository::LLMeshRepository() @@ -3000,7 +3004,7 @@ S32 LLMeshRepository::loadMesh(LLVOVolume* vobj, const LLVolumeParams& mesh_para // Manage time-to-load metrics for mesh download operations. metricsProgress(1); - if (detail < 0 || detail > 4) + if (detail < 0 || detail >= 4) { return detail; } @@ -4439,32 +4443,3 @@ void teleport_started() LLMeshRepository::metricsStart(); } -// *TODO: 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 -} - -- cgit v1.2.3 From 2e8e40cf7974a4ab6ca13d264104dbb8b80419b7 Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Wed, 11 Sep 2013 18:00:55 -0400 Subject: SH-4489 New debug/dev settings for control over new mesh behavior Added 'MeshUseGetMesh1' and 'MeshUseHttpRetryAfter' debug settings to control mesh transport behavior. First forces the use of the legacy mesh fetch style with high concurrency and connection churn. The second, on by default, honors Retry-After values if they are reasonable. If off or unreasonable, internal delay times are used. --- indra/llcorehttp/httpoptions.h | 2 +- indra/newview/app_settings/settings.xml | 22 ++++++++++++++++++++++ indra/newview/llmeshrepository.cpp | 8 +++++++- 3 files changed, 30 insertions(+), 2 deletions(-) (limited to 'indra') diff --git a/indra/llcorehttp/httpoptions.h b/indra/llcorehttp/httpoptions.h index f49a3555aa..4ab5ff18c4 100755 --- a/indra/llcorehttp/httpoptions.h +++ b/indra/llcorehttp/httpoptions.h @@ -103,7 +103,7 @@ public: return mRetries; } - // Default: false + // Default: true void setUseRetryAfter(bool use_retry); bool getUseRetryAfter() const { diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 47a2f402af..9948f9974a 100755 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -9979,6 +9979,28 @@ U32 Value 32 + + MeshUseHttpRetryAfter + + Comment + If TRUE, use Retry-After response headers when rescheduling a mesh request that fails with an HTTP 503 status. + Persist + 1 + Type + Boolean + Value + 1 + + MeshUseGetMesh1 + + Comment + If TRUE, use the legacy GetMesh capability for mesh download requests. + Persist + 1 + Type + Boolean + Value + 0 RunMultipleThreads diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index f6a85ac94f..507797a85d 100755 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -743,8 +743,10 @@ LLMeshRepoThread::LLMeshRepoThread() mHttpRequest = new LLCore::HttpRequest; mHttpOptions = new LLCore::HttpOptions; mHttpOptions->setTransferTimeout(SMALL_MESH_XFER_TIMEOUT); + mHttpOptions->setUseRetryAfter(gSavedSettings.getBOOL("MeshUseHttpRetryAfter")); mHttpLargeOptions = new LLCore::HttpOptions; mHttpLargeOptions->setTransferTimeout(LARGE_MESH_XFER_TIMEOUT); + mHttpLargeOptions->setUseRetryAfter(gSavedSettings.getBOOL("MeshUseHttpRetryAfter")); mHttpHeaders = new LLCore::HttpHeaders; mHttpHeaders->append("Accept", "application/vnd.ll.mesh"); mHttpPolicyClass = LLAppViewer::instance()->getAppCoreHttp().getPolicy(LLAppCoreHttp::AP_MESH2); @@ -1828,6 +1830,7 @@ LLMeshUploadThread::LLMeshUploadThread(LLMeshUploadThread::instance_list& data, mHttpRequest = new LLCore::HttpRequest; mHttpOptions = new LLCore::HttpOptions; mHttpOptions->setTransferTimeout(mMeshUploadTimeOut); + mHttpOptions->setUseRetryAfter(gSavedSettings.getBOOL("MeshUseHttpRetryAfter")); mHttpHeaders = new LLCore::HttpHeaders; mHttpHeaders->append("Content-Type", "application/llsd+xml"); mHttpPolicyClass = LLAppViewer::instance()->getAppCoreHttp().getPolicy(LLAppCoreHttp::AP_UPLOADS); @@ -3211,13 +3214,16 @@ void LLMeshRepository::notifyLoadedMeshes() if (gAgent.getRegion()->getName() != region_name && gAgent.getRegion()->capabilitiesReceived()) { + const bool use_v1(gSavedSettings.getBOOL("MeshUseGetMesh1")); + region_name = gAgent.getRegion()->getName(); mGetMeshCapability = gAgent.getRegion()->getCapability("GetMesh"); mGetMesh2Capability = gAgent.getRegion()->getCapability("GetMesh2"); - mGetMeshVersion = mGetMesh2Capability.empty() ? 1 : 2; + mGetMeshVersion = (mGetMesh2Capability.empty() || use_v1) ? 1 : 2; LL_DEBUGS(LOG_MESH) << "Retrieving caps for region '" << region_name << "', GetMesh2: " << mGetMesh2Capability << ", GetMesh: " << mGetMeshCapability + << ", using version: " << mGetMeshVersion << LL_ENDL; } } -- cgit v1.2.3 From 622eae65551df9a4ca6843a6a657777ff5e2140e Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Wed, 11 Sep 2013 19:21:31 -0400 Subject: SH-4490 More 'humane' error code presentation from llcorehttp callers Added toTerseString() conversion on HttpStatus to generate a string that's more descriptive than the hex value of the HttpStatus value but still forms a short, searchable token (e.g. "Http_503" or "Core_7"). Using this throughout the viewer now, no live cases of toHex(), I believe. --- indra/llcorehttp/_httplibcurl.cpp | 4 +- indra/llcorehttp/_httppolicy.cpp | 4 +- indra/llcorehttp/httpcommon.cpp | 42 +++++++++++++++++++- indra/llcorehttp/httpcommon.h | 8 ++++ indra/llcorehttp/tests/test_httpstatus.hpp | 61 +++++++++++++++++++++++++++++- indra/newview/llmeshrepository.cpp | 32 ++++++++-------- indra/newview/lltexturefetch.cpp | 12 +++--- 7 files changed, 135 insertions(+), 28 deletions(-) (limited to 'indra') diff --git a/indra/llcorehttp/_httplibcurl.cpp b/indra/llcorehttp/_httplibcurl.cpp index 0cb4e9d8b7..fc257fb0c1 100755 --- a/indra/llcorehttp/_httplibcurl.cpp +++ b/indra/llcorehttp/_httplibcurl.cpp @@ -249,7 +249,7 @@ void HttpLibcurl::cancelRequest(HttpOpRequest * op) { LL_INFOS("CoreHttp") << "TRACE, RequestCanceled, Handle: " << static_cast(op) - << ", Status: " << op->mStatus.toHex() + << ", Status: " << op->mStatus.toTerseString() << LL_ENDL; } @@ -326,7 +326,7 @@ bool HttpLibcurl::completeRequest(CURLM * multi_handle, CURL * handle, CURLcode { LL_INFOS("CoreHttp") << "TRACE, RequestComplete, Handle: " << static_cast(op) - << ", Status: " << op->mStatus.toHex() + << ", Status: " << op->mStatus.toTerseString() << LL_ENDL; } diff --git a/indra/llcorehttp/_httppolicy.cpp b/indra/llcorehttp/_httppolicy.cpp index ac79a77659..edaf0a5307 100755 --- a/indra/llcorehttp/_httppolicy.cpp +++ b/indra/llcorehttp/_httppolicy.cpp @@ -174,7 +174,7 @@ void HttpPolicy::retryOp(HttpOpRequest * op) << " retry " << op->mPolicyRetries << " scheduled in " << (delta / HttpTime(1000)) << " mS (" << (external_delta ? "external" : "internal") - << "). Status: " << op->mStatus.toHex() + << "). Status: " << op->mStatus.toTerseString() << LL_ENDL; if (op->mTracing > HTTP_TRACE_OFF) { @@ -426,7 +426,7 @@ bool HttpPolicy::stageAfterCompletion(HttpOpRequest * op) LL_WARNS("CoreHttp") << "HTTP request " << static_cast(op) << " failed after " << op->mPolicyRetries << " retries. Reason: " << op->mStatus.toString() - << " (" << op->mStatus.toHex() << ")" + << " (" << op->mStatus.toTerseString() << ")" << LL_ENDL; } else if (op->mPolicyRetries) diff --git a/indra/llcorehttp/httpcommon.cpp b/indra/llcorehttp/httpcommon.cpp index f2fcbf77a3..ca57a18578 100755 --- a/indra/llcorehttp/httpcommon.cpp +++ b/indra/llcorehttp/httpcommon.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 @@ -174,6 +174,46 @@ std::string HttpStatus::toString() const } return std::string("Unknown error"); } + + +std::string HttpStatus::toTerseString() const +{ + std::ostringstream result; + + unsigned int error_value((unsigned short) mStatus); + + switch (mType) + { + case EXT_CURL_EASY: + result << "Easy_"; + break; + + case EXT_CURL_MULTI: + result << "Multi_"; + break; + + case LLCORE: + result << "Core_"; + break; + + default: + if (isHttpStatus()) + { + result << "Http_"; + error_value = mType; + } + else + { + result << "Unknown_"; + } + break; + } + + result << error_value; + return result.str(); +} + + } // end namespace LLCore diff --git a/indra/llcorehttp/httpcommon.h b/indra/llcorehttp/httpcommon.h index 9db884057f..a04b344a9e 100755 --- a/indra/llcorehttp/httpcommon.h +++ b/indra/llcorehttp/httpcommon.h @@ -380,6 +380,14 @@ struct HttpStatus /// LLCore itself). std::string toString() const; + /// Convert status to a compact string representation + /// of the form: "_". The will be + /// one of: Core, Http, Easy, Multi, Unknown. And + /// will be an unsigned integer. More easily + /// interpreted than the hex representation, it's still + /// compact and easily searched. + std::string toTerseString() const; + /// Returns true if the status value represents an /// HTTP response status (100 - 999). bool isHttpStatus() const diff --git a/indra/llcorehttp/tests/test_httpstatus.hpp b/indra/llcorehttp/tests/test_httpstatus.hpp index 887315befc..ae8f665f8e 100755 --- a/indra/llcorehttp/tests/test_httpstatus.hpp +++ b/indra/llcorehttp/tests/test_httpstatus.hpp @@ -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 @@ -277,6 +277,65 @@ void HttpStatusTestObjectType::test<7>() ensure(msg == "Unknown error"); } + +template <> template <> +void HttpStatusTestObjectType::test<8>() +{ + set_test_name("HttpStatus toHex() nominal function"); + + HttpStatus status(404); + std::string msg = status.toHex(); + // std::cout << "Result: " << msg << std::endl; + ensure(msg == "01940001"); +} + + +template <> template <> +void HttpStatusTestObjectType::test<9>() +{ + set_test_name("HttpStatus toTerseString() nominal function"); + + HttpStatus status(404); + std::string msg = status.toTerseString(); + // std::cout << "Result: " << msg << std::endl; + ensure("Normal HTTP 404", msg == "Http_404"); + + status = HttpStatus(200); + msg = status.toTerseString(); + // std::cout << "Result: " << msg << std::endl; + ensure("Normal HTTP 200", msg == "Http_200"); + + status = HttpStatus(200, HE_REPLY_ERROR); + msg = status.toTerseString(); + // std::cout << "Result: " << msg << std::endl; + ensure("Unsuccessful HTTP 200", msg == "Http_200"); // No distinction for error + + status = HttpStatus(HttpStatus::EXT_CURL_EASY, CURLE_COULDNT_CONNECT); + msg = status.toTerseString(); + // std::cout << "Result: " << msg << std::endl; + ensure("Easy couldn't connect error", msg == "Easy_7"); + + status = HttpStatus(HttpStatus::EXT_CURL_MULTI, CURLM_OUT_OF_MEMORY); + msg = status.toTerseString(); + // std::cout << "Result: " << msg << std::endl; + ensure("Multi out-of-memory error", msg == "Multi_3"); + + status = HttpStatus(HttpStatus::LLCORE, HE_OPT_NOT_SET); + msg = status.toTerseString(); + // std::cout << "Result: " << msg << std::endl; + ensure("Core option not set error", msg == "Core_7"); + + status = HttpStatus(22000, 1); + msg = status.toTerseString(); + // std::cout << "Result: " << msg << std::endl; + ensure("Undecodable error", msg == "Unknown_1"); + + status = HttpStatus(22000, -1); + msg = status.toTerseString(); + // std::cout << "Result: " << msg << std::endl; + ensure("Undecodable error 65535", msg == "Unknown_65535"); +} + } // end namespace tut #endif // TEST_HTTP_STATUS_H diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index 507797a85d..353e7e9a7f 100755 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -688,7 +688,7 @@ void log_upload_error(LLCore::HttpStatus status, const LLSD& content, // Log details. LL_WARNS(LOG_MESH) << "Error in stage: " << stage << ", Reason: " << status.toString() - << " (" << status.toHex() << ")" << LL_ENDL; + << " (" << status.toTerseString() << ")" << LL_ENDL; if (content.has("error")) { const LLSD& err = content["error"]; @@ -1205,7 +1205,7 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id) { LL_WARNS(LOG_MESH) << "HTTP GET request failed for skin info on mesh " << mID << ". Reason: " << mHttpStatus.toString() - << " (" << mHttpStatus.toHex() << ")" + << " (" << mHttpStatus.toTerseString() << ")" << LL_ENDL; delete handler; ret = false; @@ -1298,7 +1298,7 @@ bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id) { LL_WARNS(LOG_MESH) << "HTTP GET request failed for decomposition mesh " << mID << ". Reason: " << mHttpStatus.toString() - << " (" << mHttpStatus.toHex() << ")" + << " (" << mHttpStatus.toTerseString() << ")" << LL_ENDL; delete handler; ret = false; @@ -1389,7 +1389,7 @@ bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id) { LL_WARNS(LOG_MESH) << "HTTP GET request failed for physics shape on mesh " << mID << ". Reason: " << mHttpStatus.toString() - << " (" << mHttpStatus.toHex() << ")" + << " (" << mHttpStatus.toTerseString() << ")" << LL_ENDL; delete handler; ret = false; @@ -1486,7 +1486,7 @@ bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params) { LL_WARNS(LOG_MESH) << "HTTP GET request failed for mesh header " << mID << ". Reason: " << mHttpStatus.toString() - << " (" << mHttpStatus.toHex() << ")" + << " (" << mHttpStatus.toTerseString() << ")" << LL_ENDL; delete handler; retval = false; @@ -1568,7 +1568,7 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod) { LL_WARNS(LOG_MESH) << "HTTP GET request failed for LOD on mesh " << mID << ". Reason: " << mHttpStatus.toString() - << " (" << mHttpStatus.toHex() << ")" + << " (" << mHttpStatus.toTerseString() << ")" << LL_ENDL; delete handler; retval = false; @@ -2196,7 +2196,7 @@ void LLMeshUploadThread::doWholeModelUpload() mHttpStatus = mHttpRequest->getStatus(); LL_WARNS(LOG_MESH) << "Couldn't issue request for full model upload. Reason: " << mHttpStatus.toString() - << " (" << mHttpStatus.toHex() << ")" + << " (" << mHttpStatus.toTerseString() << ")" << LL_ENDL; } else @@ -2244,7 +2244,7 @@ void LLMeshUploadThread::requestWholeModelFee() mHttpStatus = mHttpRequest->getStatus(); LL_WARNS(LOG_MESH) << "Couldn't issue request for model fee. Reason: " << mHttpStatus.toString() - << " (" << mHttpStatus.toHex() << ")" + << " (" << mHttpStatus.toTerseString() << ")" << LL_ENDL; } else @@ -2285,7 +2285,7 @@ void LLMeshUploadThread::onCompleted(LLCore::HttpHandle handle, LLCore::HttpResp if (! status) { LL_WARNS(LOG_MESH) << "Upload failed. Reason: " << reason - << " (" << status.toHex() << ")" + << " (" << status.toTerseString() << ")" << LL_ENDL; // Build a fake body for the alert generator @@ -2349,7 +2349,7 @@ void LLMeshUploadThread::onCompleted(LLCore::HttpHandle handle, LLCore::HttpResp if (! status) { LL_WARNS(LOG_MESH) << "Fee request failed. Reason: " << reason - << " (" << status.toHex() << ")" + << " (" << status.toTerseString() << ")" << LL_ENDL; // Build a fake body for the alert generator @@ -2584,7 +2584,7 @@ void LLMeshHandlerBase::onCompleted(LLCore::HttpHandle handle, LLCore::HttpRespo if (par_status != status) { LL_WARNS_ONCE(LOG_MESH) << "Non-206 successful status received for fetch: " - << status.toHex() << LL_ENDL; + << status.toTerseString() << LL_ENDL; } LLCore::BufferArray * body(response->getBody()); @@ -2633,7 +2633,7 @@ void LLMeshHeaderHandler::processFailure(LLCore::HttpStatus status) { LL_WARNS(LOG_MESH) << "Error during mesh header handling. ID: " << mMeshParams.getSculptID() << ", Reason: " << status.toString() - << " (" << status.toHex() << "). Not retrying." + << " (" << status.toTerseString() << "). Not retrying." << LL_ENDL; // Can't get the header so none of the LODs will be available @@ -2739,7 +2739,7 @@ void LLMeshLODHandler::processFailure(LLCore::HttpStatus status) { LL_WARNS(LOG_MESH) << "Error during mesh LOD handling. ID: " << mMeshParams.getSculptID() << ", Reason: " << status.toString() - << " (" << status.toHex() << "). Not retrying." + << " (" << status.toTerseString() << "). Not retrying." << LL_ENDL; LLMutexLock lock(gMeshRepo.mThread->mMutex); @@ -2783,7 +2783,7 @@ void LLMeshSkinInfoHandler::processFailure(LLCore::HttpStatus status) { LL_WARNS(LOG_MESH) << "Error during mesh skin info handling. ID: " << mMeshID << ", Reason: " << status.toString() - << " (" << status.toHex() << "). Not retrying." + << " (" << status.toTerseString() << "). Not retrying." << LL_ENDL; // *TODO: Mark mesh unavailable on error. For now, simply leave @@ -2826,7 +2826,7 @@ void LLMeshDecompositionHandler::processFailure(LLCore::HttpStatus status) { LL_WARNS(LOG_MESH) << "Error during mesh decomposition handling. ID: " << mMeshID << ", Reason: " << status.toString() - << " (" << status.toHex() << "). Not retrying." + << " (" << status.toTerseString() << "). Not retrying." << LL_ENDL; // *TODO: Mark mesh unavailable on error. For now, simply leave // request unfulfilled rather than retry forever. @@ -2868,7 +2868,7 @@ void LLMeshPhysicsShapeHandler::processFailure(LLCore::HttpStatus status) { LL_WARNS(LOG_MESH) << "Error during mesh physics shape handling. ID: " << mMeshID << ", Reason: " << status.toString() - << " (" << status.toHex() << "). Not retrying." + << " (" << status.toTerseString() << "). Not retrying." << LL_ENDL; // *TODO: Mark mesh unavailable on error } diff --git a/indra/newview/lltexturefetch.cpp b/indra/newview/lltexturefetch.cpp index 141198bc16..bcb55c4bbe 100644 --- a/indra/newview/lltexturefetch.cpp +++ b/indra/newview/lltexturefetch.cpp @@ -1552,7 +1552,7 @@ bool LLTextureFetchWorker::doWork(S32 param) else { llinfos << "HTTP GET failed for: " << mUrl - << " Status: " << mGetStatus.toHex() + << " Status: " << mGetStatus.toTerseString() << " Reason: '" << mGetReason << "'" << llendl; } @@ -1896,7 +1896,7 @@ void LLTextureFetchWorker::onCompleted(LLCore::HttpHandle handle, LLCore::HttpRe LLCore::HttpStatus status(response->getStatus()); LL_DEBUGS("Texture") << "HTTP COMPLETE: " << mID - << " status: " << status.toHex() + << " status: " << status.toTerseString() << " '" << status.toString() << "'" << llendl; // unsigned int offset(0), length(0), full_length(0); @@ -1912,7 +1912,7 @@ void LLTextureFetchWorker::onCompleted(LLCore::HttpHandle handle, LLCore::HttpRe success = false; std::string reason(status.toString()); setGetStatus(status, reason); - llwarns << "CURL GET FAILED, status: " << status.toHex() + llwarns << "CURL GET FAILED, status: " << status.toTerseString() << " reason: " << reason << llendl; } else @@ -3781,7 +3781,7 @@ public: else { LL_WARNS("Texture") << "Error delivering asset metrics to grid. Status: " - << status.toHex() + << status.toTerseString() << ", Reason: " << status.toString() << LL_ENDL; } } @@ -4470,7 +4470,7 @@ S32 LLTextureFetchDebugger::fillCurlQueue() LL_WARNS("Texture") << "Couldn't issue HTTP request in debugger for texture " << mFetchingHistory[i].mID - << ", status: " << status.toHex() + << ", status: " << status.toTerseString() << " reason: " << status.toString() << LL_ENDL; mFetchingHistory[i].mCurlState = FetchEntry::CURL_DONE; @@ -4863,7 +4863,7 @@ void LLTextureFetchDebugger::callbackHTTP(FetchEntry & fetch, LLCore::HttpRespon else //failed { llinfos << "Fetch Debugger : CURL GET FAILED, ID = " << fetch.mID - << ", status: " << status.toHex() + << ", status: " << status.toTerseString() << " reason: " << status.toString() << llendl; } } -- cgit v1.2.3 From 99c60b83f15a3c40ecf0e5f144cfc060b37c4cf8 Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Thu, 12 Sep 2013 20:15:20 -0400 Subject: SH-4489 New debug/dev settings for control over new mesh behavior While giving myself a full project code review, found a bug in the MeshUseGetMesh1 setting. Mostly defaulted to old configuration but used the GetMesh2 caps which would have been a huge DoS resource sink. Did some documentation maintenance as well while I was in there. More for the to-do list, etc. --- indra/newview/app_settings/settings.xml | 4 ++-- indra/newview/llmeshrepository.cpp | 17 +++++++++++------ 2 files changed, 13 insertions(+), 8 deletions(-) (limited to 'indra') diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 9948f9974a..60fc2d349d 100755 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -9983,7 +9983,7 @@ MeshUseHttpRetryAfter Comment - If TRUE, use Retry-After response headers when rescheduling a mesh request that fails with an HTTP 503 status. + If TRUE, use Retry-After response headers when rescheduling a mesh request that fails with an HTTP 503 status. Static. Persist 1 Type @@ -9994,7 +9994,7 @@ MeshUseGetMesh1 Comment - If TRUE, use the legacy GetMesh capability for mesh download requests. + If TRUE, use the legacy GetMesh capability for mesh download requests. Semi-dynamic (read at region crossings). Persist 1 Type diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index 353e7e9a7f..c3d149db9a 100755 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -282,10 +282,15 @@ // dialog. Get the structured information going into the log into a // tree there. // * Header parse failures come without much explanation. Elaborate. -// * Need a final failure state for requests that are retried and just won't -// complete. We can fail a LOD request, others we don't. - - +// * Work queue for uploads? Any need for this or is the current scheme good +// enough? +// * Various temp buffers used in VFS I/O might be allocated once or even +// statically. Look for some wins here. +// * Move data structures holding mesh data used by main thread into main- +// thread-only access so that no locking is needed. May require duplication +// of some data so that worker thread has a minimal data set to guide +// operations. +// // -------------------------------------------------------------------------- // Development/Debug/QA Tools // @@ -1052,7 +1057,7 @@ std::string LLMeshRepoThread::constructUrl(LLUUID mesh_id) if (gAgent.getRegion()) { - if (! gMeshRepo.mGetMesh2Capability.empty()) + if (! gMeshRepo.mGetMesh2Capability.empty() && gMeshRepo.mGetMeshVersion > 1) { http_url = gMeshRepo.mGetMesh2Capability; } @@ -2181,7 +2186,7 @@ void LLMeshUploadThread::doWholeModelUpload() LLCore::BufferArray * ba = new LLCore::BufferArray; LLCore::BufferArrayStream bas(ba); LLSDSerialize::toXML(body, bas); - // LLSDSerialize::toXML(mModelData, bas); // <- This will generate a convenient upload error + // LLSDSerialize::toXML(mModelData, bas); // <- Enabling this will generate a convenient upload error LLCore::HttpHandle handle = mHttpRequest->requestPost(mHttpPolicyClass, mHttpPriority, mWholeModelUploadURL, -- cgit v1.2.3 From dab920c26b36e032876592ca827a3a31f067a9ba Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Tue, 17 Sep 2013 21:55:44 +0000 Subject: SH-4492 Create a useful README for llcorehttp. First edit complete. Use the library in 15 minutes. Describe the code. Refinements to the initial try. Describe that code. Still to do: more refinements, how to choose a policy class, FAQ. --- indra/llcorehttp/README.Linden | 467 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 467 insertions(+) create mode 100644 indra/llcorehttp/README.Linden (limited to 'indra') diff --git a/indra/llcorehttp/README.Linden b/indra/llcorehttp/README.Linden new file mode 100644 index 0000000000..e5ff824388 --- /dev/null +++ b/indra/llcorehttp/README.Linden @@ -0,0 +1,467 @@ + + + +1. HTTP fetching in 15 Minutes + + Let's start with a trivial working example. You'll need a throwaway + build of the viewer. And we'll use indra/newview/llappviewer.cpp as + our host. + + Add some needed headers: + + + #include "httpcommon.h" + #include "httprequest.h" + #include "httphandler.h" + + + You'll need to derive a class from HttpHandler (not HttpHandle). + This is used to deliver notifications of HTTP completion to your + code. Place it near the top, before LLDeferredTaskList, say: + + + class MyHandler : public LLCore::HttpHandler + { + public: + MyHandler() + : LLCore::HttpHandler() + {} + + virtual void onCompleted(LLCore::HttpHandle /* handle */, + LLCore::HttpResponse * /* response */) + { + LL_INFOS("Hack") << "It is happening again." << LL_ENDL; + + delete this; // Last statement + } + }; + + + Add some statics up there as well: + + + // Our request object. Allocate during initialiation. + static LLCore::HttpRequest * my_request(NULL); + + // The policy class for HTTP traffic. + // Use HttpRequest::DEFAULT_POLICY_ID, but DO NOT SHIP WITH THIS VALUE!! + static LLCore::HttpRequest::policy_t my_policy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + + // Priority for HTTP requests. Use 0U. + static LLCore::HttpRequest::priority_t my_priority(0U); + + + In LLAppViewer::init() after mAppCoreHttp.init(), create a request object: + + + my_request = new LLCore::HttpRequest(); + + + In LLAppViewer::mainLoop(), just before entering the while loop, + we'll kick off one HTTP request: + + + // Construct a handler object (we'll use the heap this time): + MyHandler * my_handler = new MyHandler; + + // Issue a GET request to 'http://www.example.com/' kicking off + // all the I/O, retry logic, etc. + LLCore::HttpHandle handle; + handle = my_request->requestGet(my_policy, + my_priority, + "http://www.example.com/", + NULL, + NULL, + my_handler); + if (LLCORE_HTTP_HANDLE_INVALID == handle) + { + LL_WARNS("Hack") << "Failed to launch HTTP request. Try again." + << LL_ENDL; + } + + + Finally, arrange to periodically call update() on the request object + to find out when the request completes. This will be done by + calling the onCompleted() method with status information and + response data from the HTTP operation. Add this to the + LLAppViewer::idle() method after the ping: + + + my_request->update(0); + + + That's it. Build it, run it and watch the log file. You should get + the "It is happening again." message indicating that the HTTP + operation completed in some manner. + + +2. What Does All That Mean + + MyHandler/HttpHandler. This class replaces the Responder-style in + legacy code. One method is currently defined. It is used for all + request completions, successful or failed: + + + void onCompleted(LLCore::HttpHandle /* handle */, + LLCore::HttpResponse * /* response */); + + + The onCompleted() method is invoked as a callback during calls to + HttpRequest::update(). All I/O is completed asynchronously in + another thread. But notifications are polled by calling update() + and invoking a handler for completed requests. + + In this example, the invocation also deletes the handler (which is + never referenced by the llcorehttp code again). But other + allocation models are possible including handlers shared by many + requests, stack-based handlers and handlers mixed in with other, + unrelated classes. + + LLCore::HttpRequest(). Instances of this class are used to request + all major functions of the library. Initialization, starting + requests, delivering final notification of completion and various + utility operations are all done via instances. There is one very + important rule for instances: + + Request objects may NOT be shared between threads. + + my_priority. The APIs support the idea of priority ordering of + requests but it hasn't been implemented and the hope is that this + will become useless and removed from the interface. Use 0U except + as noted. + + my_policy. This is an important one. This library attempts to + manage TCP connection usage more rigorously than in the past. This + is done by issuing requests to a queue that has various settable + properties. These establish connection usage for the queue as well + as how queues compete with one another. (This is patterned after + class-based queueing used in various networking stacks.) Several + classes are pre-defined. Deciding when to use an existing class and + when to create a new one will determine what kind of experience + users have. We'll pick up this question in detail below. + + requestGet(). Issues an ordinary HTTP GET request to a given URL + and associating the request with a policy class, a priority and an + response handler. Two additional arguments, not used here, allow + for additional headers on the request and for per-request options. + If successful, the call returns a handle whose value is other than + LLCORE_HTTP_HANDLE_INVALID. The HTTP operation is then performed + asynchronously by another thread without any additional work by the + caller. If the handle returned is invalid, you can get the status + code by calling my_request->getStatus(). + + update(). To get notification that the request has completed, a + call to update() will invoke onCompleted() methods. + + +3. Refinements, Necessary and Otherwise + + MyHandler::onCompleted(). You'll want to do something useful with + your response. Distinguish errors from successes and getting the + response body back in some form. + + Add a new header: + + + #include "bufferarray.h" + + + Replace the existing MyHandler::onCompleted() definition with: + + + virtual void onCompleted(LLCore::HttpHandle /* handle */, + LLCore::HttpResponse * response) + { + LLCore::HttpStatus status = response->getStatus(); + if (status) + { + // Successful request. Try to fetch the data + LLCore::BufferArray * data = response->getBody(); + + if (data && data->size()) + { + // There's some data. A BufferArray is a linked list + // of buckets. We'll create a linear buffer and copy + // it into it. + size_t data_len = data->size(); + char * data_blob = new char [data_len + 1]; + data->read(0, data_blob, data_len); + data_blob[data_len] = '\0'; + + // Process the data now in NUL-terminated string. + // Needs more scrubbing but this will do. + LL_INFOS("Hack") << "Received: " << data_blob << LL_ENDL; + + // Free the temporary data + delete [] data_blob; + } + } + else + { + // Something went wrong. Translate the status to + // a meaningful message. + LL_WARNS("Hack") << "HTTP GET failed. Status: " + << status.toTerseString() + << ", Reason: " << status.toString() + << LL_ENDL; + } + + delete this; // Last statement + } + + + HttpHeaders. The header file "httprequest.h" documents the expected + important headers that will go out with the request. You can add to + these by including an HttpHeaders object with the requestGet() call. + These are typically setup once as part of init rather than + dynamically created. + + Add another header: + + + #include "httpheaders.h" + + + In LLAppViewer::mainLoop(), add this alongside the allocation of + my_handler: + + + // Additional headers for all requests + LLCore::HttpHeaders * my_headers = new LLCore::HttpHeaders(); + my_headers->append("Accept", "text/html, application/llsd+xml"); + + + HttpOptions. Options are similar and include a mix of value types. + One interesting per-request option is the trace setting. This + enables various debug-type messages in the log file that show the + progress of the request through the library. It takes values from + zero to three with higher values giving more verbose logging. We'll + use '2' and this will also give us a chance to verify that + HttpHeaders works as expected. + + Same as above, a new header: + + + #include "httpoptions.h" + + + And in LLAppView::mainLoop(): + + + // Special options for requests + LLCore::HttpOptions * my_options = new LLCore::HttpOptions(); + my_options->setTrace(2); + + + Now let's put that all together into a more complete requesting + sequence. Replace the existing invocation of requestGet() with this + slightly more elaborate block: + + + LLCore::HttpHandle handle; + handle = my_request->requestGet(my_policy, + my_priority, + "http://www.example.com/", + my_options, + my_headers, + my_handler); + if (LLCORE_HTTP_HANDLE_INVALID == handle) + { + LLCore::HttpStatus status = my_request->getStatus(); + + LL_WARNS("Hack") << "Failed to request HTTP GET. Status: " + << status.toTerseString() + << ", Reason: " << status.toString() + << LL_ENDL; + + delete my_handler; // No longer needed. + my_handler = NULL; + } + + + Build, run and examine the log file. You'll get some new data with + this run. First, you should get the www.example.com home page + content: + + +---------------------------------------------------------------------------- +2013-09-17T20:26:51Z INFO: MyHandler::onCompleted: Received: + + + Example Domain + + + + + + + + +
+

Example Domain

+

This domain is established to be used for illustrative examples in documents. You may use this + domain in examples without prior coordination or asking for permission.

+

More information...

+
+ + +---------------------------------------------------------------------------- + + + You'll also get a detailed trace of the HTTP operation itself. Note + the HEADEROUT line which shows the additional header added to the + request. + + +---------------------------------------------------------------------------- +HttpService::processRequestQueue: TRACE, FromRequestQueue, Handle: 086D3148 +HttpLibcurl::addOp: TRACE, ToActiveQueue, Handle: 086D3148, Actives: 0, Readies: 0 +HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle: 086D3148, Type: TEXT, Data: About to connect() to www.example.com port 80 (#0) +HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle: 086D3148, Type: TEXT, Data: Trying 93.184.216.119... +HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle: 086D3148, Type: TEXT, Data: Connected to www.example.com (93.184.216.119) port 80 (#0) +HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle: 086D3148, Type: TEXT, Data: Connected to www.example.com (93.184.216.119) port 80 (#0) +HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle: 086D3148, Type: HEADEROUT, Data: GET / HTTP/1.1 Host: www.example.com Accept-Encoding: deflate, gzip Connection: keep-alive Keep-alive: 300 Accept: text/html, application/llsd+xml +HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle: 086D3148, Type: HEADERIN, Data: HTTP/1.1 200 OK +HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle: 086D3148, Type: HEADERIN, Data: Accept-Ranges: bytes +HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle: 086D3148, Type: HEADERIN, Data: Cache-Control: max-age=604800 +HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle: 086D3148, Type: HEADERIN, Data: Content-Type: text/html +HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle: 086D3148, Type: HEADERIN, Data: Date: Tue, 17 Sep 2013 20:26:56 GMT +HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle: 086D3148, Type: HEADERIN, Data: Etag: "3012602696" +HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle: 086D3148, Type: HEADERIN, Data: Expires: Tue, 24 Sep 2013 20:26:56 GMT +HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle: 086D3148, Type: HEADERIN, Data: Last-Modified: Fri, 09 Aug 2013 23:54:35 GMT +HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle: 086D3148, Type: HEADERIN, Data: Server: ECS (ewr/1590) +HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle: 086D3148, Type: HEADERIN, Data: X-Cache: HIT +HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle: 086D3148, Type: HEADERIN, Data: x-ec-custom-error: 1 +HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle: 086D3148, Type: HEADERIN, Data: Content-Length: 1270 +HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle: 086D3148, Type: HEADERIN, Data: +HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle: 086D3148, Type: DATAIN, Data: 256 Bytes +HttpOpRequest::debugCallback: TRACE, LibcurlDebug, Handle: 086D3148, Type: TEXT, Data: Connection #0 to host www.example.com left intact +HttpLibcurl::completeRequest: TRACE, RequestComplete, Handle: 086D3148, Status: Http_200 +HttpOperation::addAsReply: TRACE, ToReplyQueue, Handle: 086D3148 +---------------------------------------------------------------------------- + + +4. What Does All That Mean, Part 2 + + HttpStatus. The HttpStatus object encodes errors from libcurl, the + library itself and HTTP status values. It does this to avoid + collapsing all non-HTTP error into a single '499' HTTP status and to + make errors distinct. + + To aid programming, the usual bool conversions are available so that + you can write 'if (status)' and the expected thing will happen + whether it's an HTTP, libcurl or library error. There's also + provision to override the treatment of HTTP errors (making 404 a + success, say). + + Share data, don't copy it. The library was started with the goal of + avoiding data copies as much as possible. Instead, read-only data + sharing across threads with atomic reference counts is used for a + number of data types. These currently are: + + * BufferArray. Linked list of data blocks/HTTP bodies. + * HttpHeaders. Shared headers for both requests and responses. + * HttpOptions. Request-only data modifying HTTP behavior. + * HttpResponse. HTTP response description given to onCompleted. + + Using objects of these types requires a few rules: + + * Constructor always gives a reference to caller. + * References are dropped with release() not delete. + * Additional references may be taken out with addRef(). + * Unless otherwise stated, once an object is shared with another + thread it should be treated as read-only. There's no + synchronization on the objects themselves. + + HttpResponse. You'll encounter this mainly in onCompleted() methods. + Commonly-used interfaces on this object: + + * getStatus() to return the final status of the request. + * getBody() to retrieve the response body which may be NULL or + zero-length. + * getContentType() to return the value of the 'Content-Type' + header or an empty string if none was sent. + + This is a reference-counted object so you can call addRef() on it + and hold onto the response for an arbitrary time. But you'll + usually just call a few methods and return from onCompleted() whose + caller will release the object. + + BufferArray. The core data representation for request and response + bodies. In HTTP responses, it's fetched with the getBody() method + and may be NULL or non-NULL but zero length. All successful data + handling should check both conditions before attempting to fetch + data from the object. Data access model uses simple read/write + semantics: + + * append() + * size() + * read() + * write() + + There is a more sophisticated stream adapter that extends these + methods and will be covered below. So, one way to retrieve data + from a request is as follows: + + + LLCore::BufferArray * data = response->getBody(); + if (data && data->size()) + { + size_t data_len = data->size(); + char * data_blob = new char [data_len + 1]; + data->read(0, data_blob, data_len); + + + HttpOptions and HttpResponse. Really just simple containers of POD + and std::string pairs. But reference counted and the rule about not + modifying after sharing must be followed. You'll have the urge to + change options dynamically at some point. And you'll try to do that + by just writing new values to the shared object. And in tests + everything will appear to work. Then you ship and people in the + real world start hitting read/write races in strings and then crash. + + HttpHandle. Uniquely identifies a request and can be used to + identify it in an onCompleted() method or cancel it if it's still + queued. But as soon as a request's onCompleted() invocation + returns, the handle becomes invalid and may be reused immediately + for new requests. Don't hold on to handles after notification. + + +5. And Still More Refinements + + +6. Choosing a Policy Class + + +7. FAQ + -- cgit v1.2.3 From 195d319f65239238577ae15c22188da3839ae5cf Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Wed, 18 Sep 2013 18:44:41 -0400 Subject: SH-4492 Create a useful README for llcorehttp Last bit for this release. Describe stream adapters and how to select a policy class. Slight changes to setup code to make reality reflect documentation. --- indra/llcorehttp/README.Linden | 205 ++++++++++++++++++++++++++++++++++++++-- indra/newview/llappcorehttp.cpp | 38 ++++---- indra/newview/llappcorehttp.h | 97 +++++++++++++++++++ 3 files changed, 316 insertions(+), 24 deletions(-) (limited to 'indra') diff --git a/indra/llcorehttp/README.Linden b/indra/llcorehttp/README.Linden index e5ff824388..8d18ed1a11 100644 --- a/indra/llcorehttp/README.Linden +++ b/indra/llcorehttp/README.Linden @@ -1,13 +1,13 @@ -1. HTTP fetching in 15 Minutes +1. HTTP Fetching in 15 Minutes Let's start with a trivial working example. You'll need a throwaway build of the viewer. And we'll use indra/newview/llappviewer.cpp as - our host. + the host module for these hacks. - Add some needed headers: + First, add some headers: #include "httpcommon.h" @@ -182,7 +182,7 @@ { // There's some data. A BufferArray is a linked list // of buckets. We'll create a linear buffer and copy - // it into it. + // the data into it. size_t data_len = data->size(); char * data_blob = new char [data_len + 1]; data->read(0, data_blob, data_len); @@ -419,7 +419,7 @@ HttpOperation::addAsReply: TRACE, ToReplyQueue, Handle: 086D3148 BufferArray. The core data representation for request and response bodies. In HTTP responses, it's fetched with the getBody() method - and may be NULL or non-NULL but zero length. All successful data + and may be NULL or non-NULL with zero length. All successful data handling should check both conditions before attempting to fetch data from the object. Data access model uses simple read/write semantics: @@ -429,8 +429,8 @@ HttpOperation::addAsReply: TRACE, ToReplyQueue, Handle: 086D3148 * read() * write() - There is a more sophisticated stream adapter that extends these - methods and will be covered below. So, one way to retrieve data + (There is a more sophisticated stream adapter that extends these + methods and will be covered below.) So, one way to retrieve data from a request is as follows: @@ -449,6 +449,7 @@ HttpOperation::addAsReply: TRACE, ToReplyQueue, Handle: 086D3148 by just writing new values to the shared object. And in tests everything will appear to work. Then you ship and people in the real world start hitting read/write races in strings and then crash. + Don't be lazy. HttpHandle. Uniquely identifies a request and can be used to identify it in an onCompleted() method or cancel it if it's still @@ -459,9 +460,197 @@ HttpOperation::addAsReply: TRACE, ToReplyQueue, Handle: 086D3148 5. And Still More Refinements + (Note: The following refinements are just code fragments. They + don't directly fit into the working example above. But they + demonstrate several idioms you'll want to copy.) -6. Choosing a Policy Class + LLSD, std::streambuf, std::iostream. The read(), write() and + append() methods may be adequate for your purposes. But we use a + lot of LLSD. Its interfaces aren't particularly compatible with + BufferArray. And so two adapters are available to give + stream-like behaviors: BufferArrayStreamBuf and BufferArrayStream, + which implement the std::streambuf and std::iostream interfaces, + respectively. + + A std::streambuf interface isn't something you'll want to use + directly. Instead, you'll use the much friendlier std::iostream + interface found in BufferArrayStream. This adapter gives you all + the '>>' and '<<' operators you'll want as well as working + directly with the LLSD conversion operators. + + Some new headers: + + + #include "bufferstream.h" + #include "llsdserialize.h" + + + And an updated fragment based on onCompleted() above: + + + // Successful request. Try to fetch the data + LLCore::BufferArray * data = response->getBody(); + LLSD resp_llsd; + + if (data && data->size()) + { + // There's some data and we expect this to be + // LLSD. Checking of content type and validation + // during parsing would be admirable additions. + // But we'll forgo that now. + LLCore::BufferArrayStream data_stream(data); + LLSDSerialize::fromXML(resp_llsd, data_stream); + } + LL_INFOS("Hack") << "LLSD Received: " << resp_llsd << LL_ENDL; + } + else + { + + + Converting an LLSD object into an XML stream stored in a + BufferArray is just the reverse of the above: + BufferArray * data = new BufferArray(); + LLCore::BufferArrayStream data_stream(data); + + LLSD src_llsd; + src_llsd["foo"] = "bar"; + + LLSDSerialize::toXML(src_llsd, data_stream); + + // 'data' now contains an XML payload and can be sent + // to a web service using the requestPut() or requestPost() + // methods. + ... requestPost(...); + + // And don't forget to release the BufferArray. + data->release(); + data = NULL; + + + LLSD will often go hand-in-hand with BufferArray and data + transport. But you can also do all the streaming I/O you'd expect + of a std::iostream object: + + + BufferArray * data = new BufferArray(); + LLCore::BufferArrayStream data_stream(data); + + data_stream << "Hello, World!" << 29.4 << '\n'; + std::string str; + data_stream >> str; + std::cout << str << std::endl; + + data->release(); + // Actual delete will occur when 'data_stream' + // falls out of scope and is destructed. + + + Scoping objects and cleaning up. The examples haven't bothered + with cleanup of objects that are no longer needed. Instead, most + objects have been allocated as if they were global and eternal. + You'll put the objects in more appropriate feature objects and + clean them up as a group. Here's a checklist for actions you may + need to take on cleanup: + + * Call delete on: + o HttpHandlers created on the heap + o HttpRequest objects + * Call release() on: + o BufferArray objects + o HttpHeaders objects + o HttpOptions objects + o HttpResponse objects + + On program exit, as threads wind down, the library continues to + operate safely. Threads don't interact via the library and even + dangling references to HttpHandler objects are safe. If you don't + call HttpRequest::update(), handler references are never + dereferenced. + + You can take a more thorough approach to wind-down. Keep a list + of HttpHandles (not HttpHandlers) of outstanding requests. For + each of these, call HttpRequest::requestCancel() to cancel the + operation. (Don't add the cancel requests' handled to the list.) + This will cancel the outstanding requests that haven't completed. + Canceled or completed, all requests will queue notifications. You + can now cycle calling update() discarding responses. Continue + until all requests notify or a few seconds have passed. + + Global startup and shutdown is handled in the viewer. But you can + learn about it in the code or in the documentation in the headers. + + +6. Choosing a Policy Class + + Now it's time to get rid of the default policy class. Take a look + at the policy class definitions in newview/llappcorehttp.h. + Ideally, you'll find one that's compatible with what you're doing. + Some of the compatibility guidelines are: + + * Destination: Pair of host and port. Mixing requests with + different destinations may cause more connection setup and tear + down. + + * Method: http or https. Usually moot given destination. But + mixing these may also cause connection churn. + + * Transfer size: If you're moving 100MB at a time and you make your + requests to the same policy class as a lot of small, fast event + information that fast traffic is going to get stuck behind you + and someone's experience is going to be miserable. + + * Long poll requests: These are long-lived, must- do operations. + They have a special home called AP_LONG_POLL. + + * Concurrency: High concurrency (5 or more) and large transfer + sizes are incompatible. Another head-of-the-line problem. High + concurrency is tolerated when it's desired to get maximal + throughput. Mesh and texture downloads, for example. + + * Pipelined: If your requests are not idempotent, stay away from + anything marked 'soon' or 'yes'. Hidden retries may be a + problem for you. For now, would also recommend keeping PUT and + POST requests out of classes that may be pipelined. Support for + that is still a bit new. + + If you haven't found a compatible match, you can either create a + new class (llappcorehttp.*) or just use AP_DEFAULT, the catchall + class when all else fails. Inventory query operations might be a + candidate for a new class that supported pipelining on https:. + Same with display name lookups and other bursty-at-login + operations. For other things, AP_DEFAULT will do what it can and + will, in some way or another, tolerate any usage. Whether the + users' experiences are good are for you to determine. + + 7. FAQ + Q1. What do these policy classes achieve? + + A1. Previously, HTTP-using code in the viewer was written as if + it were some isolated, local operation that didn't have to + consider resources, contention or impact on services and the + larger environment. The result was an application with on the + order of 100 HTTP launch points in its codebase that could create + dozens or even 100's of TCP connections zeroing in on grid + services and disrupting networking equipment, web services and + innocent users. The use of policy classes (modeled on + http://en.wikipedia.org/wiki/Class-based_queueing) is a means to + restrict connection concurrency, good and necessary in itself. In + turn, that reduces demands on an expensive resource (connection + setup and concurrency) which relieves strain on network points. + That enables connection keepalive and opportunites for true + improvements in throughput and user experience. + + Another aspect of the classes is that they give some control over + how competing demands for the network will be apportioned. If + mesh fetches, texture fetches and inventory queries are all being + made at once, the relative weights of their classes' concurrency + limits established that apportioning. We now have an opportunity + to balance the entire viewer system. + + Q2. How's that data sharing with refcounts working for you? + + A2. Meh. diff --git a/indra/newview/llappcorehttp.cpp b/indra/newview/llappcorehttp.cpp index 01317fe32f..70dcffefb2 100755 --- a/indra/newview/llappcorehttp.cpp +++ b/indra/newview/llappcorehttp.cpp @@ -51,6 +51,11 @@ static const struct const char * mUsage; } init_data[] = // Default and dynamic values for classes { + { + LLAppCoreHttp::AP_DEFAULT, 8, 8, 8, 0, + "", + "other" + }, { LLAppCoreHttp::AP_TEXTURE, 8, 1, 12, 0, "TextureFetchConcurrency", @@ -75,6 +80,11 @@ static const struct LLAppCoreHttp::AP_UPLOADS, 2, 1, 8, 0, "", "asset upload" + }, + { + LLAppCoreHttp::AP_LONG_POLL, 32, 32, 32, 0, + "", + "long poll" } }; @@ -154,25 +164,21 @@ void LLAppCoreHttp::init() { const EAppPolicy policy(init_data[i].mPolicy); - // Create a policy class but use default for texture for now. - // This also has the side-effect of initializing the default - // class to desired values. - if (AP_TEXTURE == policy) + if (AP_DEFAULT == policy) { - mPolicies[policy] = mPolicies[AP_DEFAULT]; + // Pre-created + continue; } - else + + mPolicies[policy] = LLCore::HttpRequest::createPolicyClass(); + if (! mPolicies[policy]) { - mPolicies[policy] = LLCore::HttpRequest::createPolicyClass(); - if (! mPolicies[policy]) - { - // Use default policy (but don't accidentally modify default) - LL_WARNS("Init") << "Failed to create HTTP policy class for " << init_data[i].mUsage - << ". Using default policy." - << LL_ENDL; - mPolicies[policy] = mPolicies[AP_DEFAULT]; - continue; - } + // Use default policy (but don't accidentally modify default) + LL_WARNS("Init") << "Failed to create HTTP policy class for " << init_data[i].mUsage + << ". Using default policy." + << LL_ENDL; + mPolicies[policy] = mPolicies[AP_DEFAULT]; + continue; } } diff --git a/indra/newview/llappcorehttp.h b/indra/newview/llappcorehttp.h index 6dc3bb2130..40e3042b84 100755 --- a/indra/newview/llappcorehttp.h +++ b/indra/newview/llappcorehttp.h @@ -45,12 +45,109 @@ public: enum EAppPolicy { + /// Catchall policy class. Not used yet + /// but will have a generous concurrency + /// limit. Deep queueing possible by having + /// a chatty HTTP user. + /// + /// Destination: anywhere + /// Protocol: http: or https: + /// Transfer size: KB-MB + /// Long poll: no + /// Concurrency: high + /// Request rate: unknown + /// Pipelined: no AP_DEFAULT, + + /// Texture fetching policy class. Used to + /// download textures via capability or SSA + /// baking service. Deep queueing of requests. + /// Do not share. + /// + /// Destination: simhost:12046 & bake-texture:80 + /// Protocol: http: + /// Transfer size: KB-MB + /// Long poll: no + /// Concurrency: high + /// Request rate: high + /// Pipelined: soon AP_TEXTURE, + + /// Legacy mesh fetching policy class. Used to + /// download textures via 'GetMesh' capability. + /// To be deprecated. Do not share. + /// + /// Destination: simhost:12046 + /// Protocol: http: + /// Transfer size: KB-MB + /// Long poll: no + /// Concurrency: dangerously high + /// Request rate: high + /// Pipelined: no AP_MESH1, + + /// New mesh fetching policy class. Used to + /// download textures via 'GetMesh2' capability. + /// Used when fetch request (typically one LOD) + /// is 'small', currently defined as 2MB. + /// Very deeply queued. Do not share. + /// + /// Destination: simhost:12046 + /// Protocol: http: + /// Transfer size: KB-MB + /// Long poll: no + /// Concurrency: high + /// Request rate: high + /// Pipelined: soon AP_MESH2, + + /// Large mesh fetching policy class. Used to + /// download textures via 'GetMesh' or 'GetMesh2' + /// capability. Used when fetch request + /// is not small to avoid head-of-line problem + /// when large requests block a sequence of small, + /// fast requests. Can be shared with similar + /// traffic that can wait for longish stalls + /// (default timeout 600S). + /// + /// Destination: simhost:12046 + /// Protocol: http: + /// Transfer size: MB + /// Long poll: no + /// Concurrency: low + /// Request rate: low + /// Pipelined: soon AP_LARGE_MESH, + + /// Asset upload policy class. Used to store + /// assets (mesh only at the moment) via + /// changeable URL. Responses may take some + /// time (default timeout 240S). + /// + /// Destination: simhost:12043 + /// Protocol: https: + /// Transfer size: KB-MB + /// Long poll: no + /// Concurrency: low + /// Request rate: low + /// Pipelined: no AP_UPLOADS, + + /// Long-poll-type HTTP requests. Not + /// bound by a connection limit. Requests + /// will typically hang around for a long + /// time (~30S). Only shareable with other + /// long-poll requests. + /// + /// Destination: simhost:12043 + /// Protocol: https: + /// Transfer size: KB + /// Long poll: yes + /// Concurrency: unlimited but low in practice + /// Request rate: low + /// Pipelined: no + AP_LONG_POLL, + AP_COUNT // Must be last }; -- cgit v1.2.3 From a232fa4d9f4bf8f29d2783da8c2e01f7c9f80fd9 Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Thu, 19 Sep 2013 19:20:31 -0400 Subject: SH-4516 Fix a subset of thread races in llmeshrepository Much earlier identified some thread races including two on mskininfoq and mdecompositionq. I actually hit one in testing the other day so I'm fixing them now. Put them under the mMutex lock and use the mutex in such a way that main thread stalls are not added. --- indra/newview/llmeshrepository.cpp | 49 ++++++++++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 13 deletions(-) (limited to 'indra') diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index c3d149db9a..5174a7af00 100755 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -246,10 +246,10 @@ // mMeshHeader mHeaderMutex rw.repo.mHeaderMutex, ro.main.mHeaderMutex, ro.main.none [0] // mMeshHeaderSize mHeaderMutex rw.repo.mHeaderMutex // mSkinRequests mMutex rw.repo.mMutex, ro.repo.none [5] -// mSkinInfoQ none rw.repo.none, rw.main.mMutex [0] +// mSkinInfoQ mMutex rw.repo.mMutex, rw.main.mMutex [5] (was: [0]) // mDecompositionRequests mMutex rw.repo.mMutex, ro.repo.none [5] // mPhysicsShapeRequests mMutex rw.repo.mMutex, ro.repo.none [5] -// mDecompositionQ none rw.repo.none, rw.main.mMutex [0] +// mDecompositionQ mMutex rw.repo.mMutex, rw.main.mMutex [5] (was: [0]) // mHeaderReqQ mMutex ro.repo.none [5], rw.repo.mMutex, rw.any.mMutex // mLODReqQ mMutex ro.repo.none [5], rw.repo.mMutex, rw.any.mMutex // mUnavailableQ mMutex rw.repo.none [0], ro.main.none [5], rw.main.mMutex @@ -1713,7 +1713,10 @@ bool LLMeshRepoThread::skinInfoReceived(const LLUUID& mesh_id, U8* data, S32 dat info.mMeshID = mesh_id; // LL_DEBUGS(LOG_MESH) << "info pelvis offset" << info.mPelvisOffset << LL_ENDL; - mSkinInfoQ.push(info); + { + LLMutexLock lock(mMutex); + mSkinInfoQ.push(info); + } } return true; @@ -1740,7 +1743,10 @@ bool LLMeshRepoThread::decompositionReceived(const LLUUID& mesh_id, U8* data, S3 { LLModel::Decomposition* d = new LLModel::Decomposition(decomp); d->mMeshID = mesh_id; - mDecompositionQ.push(d); + { + LLMutexLock lock(mMutex); + mDecompositionQ.push(d); + } } return true; @@ -1799,7 +1805,10 @@ bool LLMeshRepoThread::physicsShapeReceived(const LLUUID& mesh_id, U8* data, S32 } } - mDecompositionQ.push(d); + { + LLMutexLock lock(mMutex); + mDecompositionQ.push(d); + } return true; } @@ -2460,16 +2469,30 @@ void LLMeshRepoThread::notifyLoadedMeshes() gMeshRepo.notifyMeshUnavailable(req.mMeshParams, req.mLOD); } - while (!mSkinInfoQ.empty()) + if (! mSkinInfoQ.empty() || ! mDecompositionQ.empty()) { - gMeshRepo.notifySkinInfoReceived(mSkinInfoQ.front()); - mSkinInfoQ.pop(); - } + std::queue skin_info_q; + std::queue decomp_q; - while (!mDecompositionQ.empty()) - { - gMeshRepo.notifyDecompositionReceived(mDecompositionQ.front()); - mDecompositionQ.pop(); + if (mMutex->trylock()) + { + // Make thread-shared data private with swap under lock. + skin_info_q.swap(mSkinInfoQ); + decomp_q.swap(mDecompositionQ); + mMutex->unlock(); + + while (! skin_info_q.empty()) + { + gMeshRepo.notifySkinInfoReceived(skin_info_q.front()); + skin_info_q.pop(); + } + + while (! decomp_q.empty()) + { + gMeshRepo.notifyDecompositionReceived(decomp_q.front()); + decomp_q.pop(); + } + } } if (update_metrics) -- cgit v1.2.3 From cbf7e90954e69f86cbf530676e165f2feb6501bb Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Fri, 20 Sep 2013 05:58:32 +0000 Subject: Used a c++11 feature unintentionally. Use a more traditional swap-less object transfer. --- indra/newview/llmeshrepository.cpp | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) (limited to 'indra') diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index 5174a7af00..23a9640761 100755 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -2471,16 +2471,25 @@ void LLMeshRepoThread::notifyLoadedMeshes() if (! mSkinInfoQ.empty() || ! mDecompositionQ.empty()) { - std::queue skin_info_q; - std::queue decomp_q; - if (mMutex->trylock()) { - // Make thread-shared data private with swap under lock. - skin_info_q.swap(mSkinInfoQ); - decomp_q.swap(mDecompositionQ); + std::queue skin_info_q; + std::queue decomp_q; + + // swap() comes to std::queue in c++11 so copy manually for now + while (! mSkinInfoQ.empty()) + { + skin_info_q.push(mSkinInfoQ.front()); + mSkinInfoQ.pop(); + } + while (! mDecompositionQ.empty()) + { + decomp_q.push(mDecompositionQ.front()); + mDecompositionQ.pop(); + } mMutex->unlock(); + // Process the elements free of the lock while (! skin_info_q.empty()) { gMeshRepo.notifySkinInfoReceived(skin_info_q.front()); -- cgit v1.2.3 From 2462162539053e8cbf26aea68940f300bca5aa87 Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Fri, 20 Sep 2013 07:13:14 +0000 Subject: Move from std::queue to std::list which has better behavior and swap() as well. Should probably do this for the other queues at some point. --- indra/newview/llmeshrepository.cpp | 26 ++++++++++++-------------- indra/newview/llmeshrepository.h | 8 ++++---- 2 files changed, 16 insertions(+), 18 deletions(-) (limited to 'indra') diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index 23a9640761..b55ba758e1 100755 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -1715,7 +1715,7 @@ bool LLMeshRepoThread::skinInfoReceived(const LLUUID& mesh_id, U8* data, S32 dat // LL_DEBUGS(LOG_MESH) << "info pelvis offset" << info.mPelvisOffset << LL_ENDL; { LLMutexLock lock(mMutex); - mSkinInfoQ.push(info); + mSkinInfoQ.push_back(info); } } @@ -1745,7 +1745,7 @@ bool LLMeshRepoThread::decompositionReceived(const LLUUID& mesh_id, U8* data, S3 d->mMeshID = mesh_id; { LLMutexLock lock(mMutex); - mDecompositionQ.push(d); + mDecompositionQ.push_back(d); } } @@ -1807,7 +1807,7 @@ bool LLMeshRepoThread::physicsShapeReceived(const LLUUID& mesh_id, U8* data, S32 { LLMutexLock lock(mMutex); - mDecompositionQ.push(d); + mDecompositionQ.push_back(d); } return true; } @@ -2473,33 +2473,31 @@ void LLMeshRepoThread::notifyLoadedMeshes() { if (mMutex->trylock()) { - std::queue skin_info_q; - std::queue decomp_q; + std::list skin_info_q; + std::list decomp_q; - // swap() comes to std::queue in c++11 so copy manually for now - while (! mSkinInfoQ.empty()) + if (! mSkinInfoQ.empty()) { - skin_info_q.push(mSkinInfoQ.front()); - mSkinInfoQ.pop(); + skin_info_q.swap(mSkinInfoQ); } - while (! mDecompositionQ.empty()) + if (! mDecompositionQ.empty()) { - decomp_q.push(mDecompositionQ.front()); - mDecompositionQ.pop(); + decomp_q.swap(mDecompositionQ); } + mMutex->unlock(); // Process the elements free of the lock while (! skin_info_q.empty()) { gMeshRepo.notifySkinInfoReceived(skin_info_q.front()); - skin_info_q.pop(); + skin_info_q.pop_front(); } while (! decomp_q.empty()) { gMeshRepo.notifyDecompositionReceived(decomp_q.front()); - decomp_q.pop(); + decomp_q.pop_front(); } } } diff --git a/indra/newview/llmeshrepository.h b/indra/newview/llmeshrepository.h index c79278da1a..e09f39f7a8 100755 --- a/indra/newview/llmeshrepository.h +++ b/indra/newview/llmeshrepository.h @@ -291,8 +291,8 @@ public: //set of requested skin info std::set mSkinRequests; - //queue of completed skin info requests - std::queue mSkinInfoQ; + // list of completed skin info requests + std::list mSkinInfoQ; //set of requested decompositions std::set mDecompositionRequests; @@ -300,8 +300,8 @@ public: //set of requested physics shapes std::set mPhysicsShapeRequests; - //queue of completed Decomposition info requests - std::queue mDecompositionQ; + // list of completed Decomposition info requests + std::list mDecompositionQ; //queue of requested headers std::queue mHeaderReqQ; -- cgit v1.2.3 From 200bea5b418a3dc61431d26271932f8c489f0d18 Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Tue, 24 Sep 2013 14:49:26 -0400 Subject: SH-3690 SH-4505 Cleanup pass through code. Start using DNS cache in legacy LLCurl code. Go to 15 seconds particularly as we're using threaded resolver at this point. Documentation cleanup. Add libcurl status checking and logging for curl_easy_setopt() operations that fail. Shouldn't happen and we'll just continue anyway but there's info in the logs to track these down now. Cleaned up logic around FASTTIMER enable defines used to evaluate pipeline stalls in main thread. Removed long-standing thread race around caps strings and URL construction. Not a significant risk but refactoring the code to get rid of them removed one huge eyesore. It can be made even slicker if desired (see notes). --- indra/llcorehttp/_httpoperation.cpp | 6 +- indra/llcorehttp/_httpoprequest.cpp | 156 +++++++++++++++++++++++++++--------- indra/llcorehttp/httpcommon.cpp | 3 +- indra/llcorehttp/httpcommon.h | 5 +- indra/llmessage/llcurl.cpp | 11 ++- indra/newview/llmeshrepository.cpp | 101 +++++++++++++++-------- indra/newview/llmeshrepository.h | 19 ++++- 7 files changed, 214 insertions(+), 87 deletions(-) (limited to 'indra') diff --git a/indra/llcorehttp/_httpoperation.cpp b/indra/llcorehttp/_httpoperation.cpp index 7acd728bbd..5bb0654652 100755 --- a/indra/llcorehttp/_httpoperation.cpp +++ b/indra/llcorehttp/_httpoperation.cpp @@ -94,7 +94,7 @@ void HttpOperation::stageFromRequest(HttpService *) // Default implementation should never be called. This // indicates an operation making a transition that isn't // defined. - LL_ERRS("HttpCore") << "Default stageFromRequest method may not be called." + LL_ERRS("CoreHttp") << "Default stageFromRequest method may not be called." << LL_ENDL; } @@ -104,7 +104,7 @@ void HttpOperation::stageFromReady(HttpService *) // Default implementation should never be called. This // indicates an operation making a transition that isn't // defined. - LL_ERRS("HttpCore") << "Default stageFromReady method may not be called." + LL_ERRS("CoreHttp") << "Default stageFromReady method may not be called." << LL_ENDL; } @@ -114,7 +114,7 @@ void HttpOperation::stageFromActive(HttpService *) // Default implementation should never be called. This // indicates an operation making a transition that isn't // defined. - LL_ERRS("HttpCore") << "Default stageFromActive method may not be called." + LL_ERRS("CoreHttp") << "Default stageFromActive method may not be called." << LL_ENDL; } diff --git a/indra/llcorehttp/_httpoprequest.cpp b/indra/llcorehttp/_httpoprequest.cpp index d72f8f6119..63c4e71258 100755 --- a/indra/llcorehttp/_httpoprequest.cpp +++ b/indra/llcorehttp/_httpoprequest.cpp @@ -90,6 +90,10 @@ char * os_strtrim(char * str); char * os_strltrim(char * str); void os_strlower(char * str); +// Error testing and reporting for libcurl status codes +void check_curl_easy_code(CURLcode code); +void check_curl_easy_code(CURLcode code, int curl_setopt_option); + } // end anonymous namespace @@ -373,6 +377,8 @@ void HttpOpRequest::setupCommon(HttpRequest::policy_t policy_id, // HttpStatus HttpOpRequest::prepareRequest(HttpService * service) { + CURLcode code; + // Scrub transport and result data for retried op case mCurlActive = false; mCurlHandle = NULL; @@ -406,12 +412,25 @@ HttpStatus HttpOpRequest::prepareRequest(HttpService * service) HttpPolicyGlobal & policy(service->getPolicy().getGlobalOptions()); mCurlHandle = curl_easy_init(); - curl_easy_setopt(mCurlHandle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); - curl_easy_setopt(mCurlHandle, CURLOPT_NOSIGNAL, 1); - curl_easy_setopt(mCurlHandle, CURLOPT_NOPROGRESS, 1); - curl_easy_setopt(mCurlHandle, CURLOPT_URL, mReqURL.c_str()); - curl_easy_setopt(mCurlHandle, CURLOPT_PRIVATE, this); - curl_easy_setopt(mCurlHandle, CURLOPT_ENCODING, ""); + if (! mCurlHandle) + { + // We're in trouble. We'll continue but it won't go well. + LL_WARNS("CoreHttp") << "Failed to allocate libcurl easy handle. Continuing." + << LL_ENDL; + return HttpStatus(HttpStatus::LLCORE, HE_BAD_ALLOC); + } + code = curl_easy_setopt(mCurlHandle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); + check_curl_easy_code(code, CURLOPT_IPRESOLVE); + code = curl_easy_setopt(mCurlHandle, CURLOPT_NOSIGNAL, 1); + check_curl_easy_code(code, CURLOPT_NOSIGNAL); + code = curl_easy_setopt(mCurlHandle, CURLOPT_NOPROGRESS, 1); + check_curl_easy_code(code, CURLOPT_NOPROGRESS); + code = curl_easy_setopt(mCurlHandle, CURLOPT_URL, mReqURL.c_str()); + check_curl_easy_code(code, CURLOPT_URL); + code = curl_easy_setopt(mCurlHandle, CURLOPT_PRIVATE, this); + check_curl_easy_code(code, CURLOPT_PRIVATE); + code = curl_easy_setopt(mCurlHandle, CURLOPT_ENCODING, ""); + check_curl_easy_code(code, CURLOPT_ENCODING); if (HTTP_ENABLE_LINKSYS_WRT54G_V5_DNS_FIX) { @@ -421,7 +440,8 @@ HttpStatus HttpOpRequest::prepareRequest(HttpService * service) // about 700 or so requests and starts issuing TCP RSTs to // new connections. Reuse the DNS lookups for even a few // seconds and no RSTs. - curl_easy_setopt(mCurlHandle, CURLOPT_DNS_CACHE_TIMEOUT, 15); + code = curl_easy_setopt(mCurlHandle, CURLOPT_DNS_CACHE_TIMEOUT, 15); + check_curl_easy_code(code, CURLOPT_DNS_CACHE_TIMEOUT); } else { @@ -429,17 +449,27 @@ HttpStatus HttpOpRequest::prepareRequest(HttpService * service) // I don't think this is valid anymore, the Multi shared DNS // cache is working well. For the case of naked easy handles, // consider using a shared DNS object. - curl_easy_setopt(mCurlHandle, CURLOPT_DNS_CACHE_TIMEOUT, 0); + code = curl_easy_setopt(mCurlHandle, CURLOPT_DNS_CACHE_TIMEOUT, 0); + check_curl_easy_code(code, CURLOPT_DNS_CACHE_TIMEOUT); } - curl_easy_setopt(mCurlHandle, CURLOPT_AUTOREFERER, 1); - curl_easy_setopt(mCurlHandle, CURLOPT_FOLLOWLOCATION, 1); - curl_easy_setopt(mCurlHandle, CURLOPT_MAXREDIRS, HTTP_REDIRECTS_DEFAULT); - curl_easy_setopt(mCurlHandle, CURLOPT_WRITEFUNCTION, writeCallback); - curl_easy_setopt(mCurlHandle, CURLOPT_WRITEDATA, this); - curl_easy_setopt(mCurlHandle, CURLOPT_READFUNCTION, readCallback); - curl_easy_setopt(mCurlHandle, CURLOPT_READDATA, this); - curl_easy_setopt(mCurlHandle, CURLOPT_SSL_VERIFYPEER, 1); - curl_easy_setopt(mCurlHandle, CURLOPT_SSL_VERIFYHOST, 0); + code = curl_easy_setopt(mCurlHandle, CURLOPT_AUTOREFERER, 1); + check_curl_easy_code(code, CURLOPT_AUTOREFERER); + code = curl_easy_setopt(mCurlHandle, CURLOPT_FOLLOWLOCATION, 1); + check_curl_easy_code(code, CURLOPT_FOLLOWLOCATION); + code = curl_easy_setopt(mCurlHandle, CURLOPT_MAXREDIRS, HTTP_REDIRECTS_DEFAULT); + check_curl_easy_code(code, CURLOPT_MAXREDIRS); + code = curl_easy_setopt(mCurlHandle, CURLOPT_WRITEFUNCTION, writeCallback); + check_curl_easy_code(code, CURLOPT_WRITEFUNCTION); + code = curl_easy_setopt(mCurlHandle, CURLOPT_WRITEDATA, this); + check_curl_easy_code(code, CURLOPT_WRITEDATA); + code = curl_easy_setopt(mCurlHandle, CURLOPT_READFUNCTION, readCallback); + check_curl_easy_code(code, CURLOPT_READFUNCTION); + code = curl_easy_setopt(mCurlHandle, CURLOPT_READDATA, this); + check_curl_easy_code(code, CURLOPT_READDATA); + code = curl_easy_setopt(mCurlHandle, CURLOPT_SSL_VERIFYPEER, 1); + check_curl_easy_code(code, CURLOPT_SSL_VERIFYPEER); + code = curl_easy_setopt(mCurlHandle, CURLOPT_SSL_VERIFYHOST, 0); + check_curl_easy_code(code, CURLOPT_SSL_VERIFYHOST); if (policy.mUseLLProxy) { @@ -452,37 +482,46 @@ HttpStatus HttpOpRequest::prepareRequest(HttpService * service) { // *TODO: This is fine for now but get fuller socks5/ // authentication thing going later.... - curl_easy_setopt(mCurlHandle, CURLOPT_PROXY, policy.mHttpProxy.c_str()); - curl_easy_setopt(mCurlHandle, CURLOPT_PROXYTYPE, CURLPROXY_HTTP); + code = curl_easy_setopt(mCurlHandle, CURLOPT_PROXY, policy.mHttpProxy.c_str()); + check_curl_easy_code(code, CURLOPT_PROXY); + code = curl_easy_setopt(mCurlHandle, CURLOPT_PROXYTYPE, CURLPROXY_HTTP); + check_curl_easy_code(code, CURLOPT_PROXYTYPE); } if (policy.mCAPath.size()) { - curl_easy_setopt(mCurlHandle, CURLOPT_CAPATH, policy.mCAPath.c_str()); + code = curl_easy_setopt(mCurlHandle, CURLOPT_CAPATH, policy.mCAPath.c_str()); + check_curl_easy_code(code, CURLOPT_CAPATH); } if (policy.mCAFile.size()) { - curl_easy_setopt(mCurlHandle, CURLOPT_CAINFO, policy.mCAFile.c_str()); + code = curl_easy_setopt(mCurlHandle, CURLOPT_CAINFO, policy.mCAFile.c_str()); + check_curl_easy_code(code, CURLOPT_CAINFO); } switch (mReqMethod) { case HOR_GET: - curl_easy_setopt(mCurlHandle, CURLOPT_HTTPGET, 1); + code = curl_easy_setopt(mCurlHandle, CURLOPT_HTTPGET, 1); + check_curl_easy_code(code, CURLOPT_HTTPGET); mCurlHeaders = curl_slist_append(mCurlHeaders, "Connection: keep-alive"); mCurlHeaders = curl_slist_append(mCurlHeaders, "Keep-alive: 300"); break; case HOR_POST: { - curl_easy_setopt(mCurlHandle, CURLOPT_POST, 1); - curl_easy_setopt(mCurlHandle, CURLOPT_ENCODING, ""); + code = curl_easy_setopt(mCurlHandle, CURLOPT_POST, 1); + check_curl_easy_code(code, CURLOPT_POST); + code = curl_easy_setopt(mCurlHandle, CURLOPT_ENCODING, ""); + check_curl_easy_code(code, CURLOPT_ENCODING); long data_size(0); if (mReqBody) { data_size = mReqBody->size(); } - curl_easy_setopt(mCurlHandle, CURLOPT_POSTFIELDS, static_cast(NULL)); - curl_easy_setopt(mCurlHandle, CURLOPT_POSTFIELDSIZE, data_size); + code = curl_easy_setopt(mCurlHandle, CURLOPT_POSTFIELDS, static_cast(NULL)); + check_curl_easy_code(code, CURLOPT_POSTFIELDS); + code = curl_easy_setopt(mCurlHandle, CURLOPT_POSTFIELDSIZE, data_size); + check_curl_easy_code(code, CURLOPT_POSTFIELDSIZE); mCurlHeaders = curl_slist_append(mCurlHeaders, "Expect:"); mCurlHeaders = curl_slist_append(mCurlHeaders, "Connection: keep-alive"); mCurlHeaders = curl_slist_append(mCurlHeaders, "Keep-alive: 300"); @@ -491,14 +530,17 @@ HttpStatus HttpOpRequest::prepareRequest(HttpService * service) case HOR_PUT: { - curl_easy_setopt(mCurlHandle, CURLOPT_UPLOAD, 1); + code = curl_easy_setopt(mCurlHandle, CURLOPT_UPLOAD, 1); + check_curl_easy_code(code, CURLOPT_UPLOAD); long data_size(0); if (mReqBody) { data_size = mReqBody->size(); } - curl_easy_setopt(mCurlHandle, CURLOPT_INFILESIZE, data_size); - curl_easy_setopt(mCurlHandle, CURLOPT_POSTFIELDS, (void *) NULL); + code = curl_easy_setopt(mCurlHandle, CURLOPT_INFILESIZE, data_size); + check_curl_easy_code(code, CURLOPT_INFILESIZE); + code = curl_easy_setopt(mCurlHandle, CURLOPT_POSTFIELDS, (void *) NULL); + check_curl_easy_code(code, CURLOPT_POSTFIELDS); mCurlHeaders = curl_slist_append(mCurlHeaders, "Expect:"); mCurlHeaders = curl_slist_append(mCurlHeaders, "Connection: keep-alive"); mCurlHeaders = curl_slist_append(mCurlHeaders, "Keep-alive: 300"); @@ -515,9 +557,12 @@ HttpStatus HttpOpRequest::prepareRequest(HttpService * service) // Tracing if (mTracing >= HTTP_TRACE_CURL_HEADERS) { - curl_easy_setopt(mCurlHandle, CURLOPT_VERBOSE, 1); - curl_easy_setopt(mCurlHandle, CURLOPT_DEBUGDATA, this); - curl_easy_setopt(mCurlHandle, CURLOPT_DEBUGFUNCTION, debugCallback); + code = curl_easy_setopt(mCurlHandle, CURLOPT_VERBOSE, 1); + check_curl_easy_code(code, CURLOPT_VERBOSE); + code = curl_easy_setopt(mCurlHandle, CURLOPT_DEBUGDATA, this); + check_curl_easy_code(code, CURLOPT_DEBUGDATA); + code = curl_easy_setopt(mCurlHandle, CURLOPT_DEBUGFUNCTION, debugCallback); + check_curl_easy_code(code, CURLOPT_DEBUGFUNCTION); } // There's a CURLOPT for this now... @@ -557,8 +602,10 @@ HttpStatus HttpOpRequest::prepareRequest(HttpService * service) { xfer_timeout = timeout; } - curl_easy_setopt(mCurlHandle, CURLOPT_TIMEOUT, xfer_timeout); - curl_easy_setopt(mCurlHandle, CURLOPT_CONNECTTIMEOUT, timeout); + code = curl_easy_setopt(mCurlHandle, CURLOPT_TIMEOUT, xfer_timeout); + check_curl_easy_code(code, CURLOPT_TIMEOUT); + code = curl_easy_setopt(mCurlHandle, CURLOPT_CONNECTTIMEOUT, timeout); + check_curl_easy_code(code, CURLOPT_CONNECTTIMEOUT); // Request headers if (mReqHeaders) @@ -566,12 +613,15 @@ HttpStatus HttpOpRequest::prepareRequest(HttpService * service) // Caller's headers last to override mCurlHeaders = append_headers_to_slist(mReqHeaders, mCurlHeaders); } - curl_easy_setopt(mCurlHandle, CURLOPT_HTTPHEADER, mCurlHeaders); + code = curl_easy_setopt(mCurlHandle, CURLOPT_HTTPHEADER, mCurlHeaders); + check_curl_easy_code(code, CURLOPT_HTTPHEADER); if (mProcFlags & (PF_SCAN_RANGE_HEADER | PF_SAVE_HEADERS | PF_USE_RETRY_AFTER)) { - curl_easy_setopt(mCurlHandle, CURLOPT_HEADERFUNCTION, headerCallback); - curl_easy_setopt(mCurlHandle, CURLOPT_HEADERDATA, this); + code = curl_easy_setopt(mCurlHandle, CURLOPT_HEADERFUNCTION, headerCallback); + check_curl_easy_code(code, CURLOPT_HEADERFUNCTION); + code = curl_easy_setopt(mCurlHandle, CURLOPT_HEADERDATA, this); + check_curl_easy_code(code, CURLOPT_HEADERDATA); } if (status) @@ -612,7 +662,7 @@ size_t HttpOpRequest::readCallback(void * data, size_t size, size_t nmemb, void { // Warn but continue if the read position moves beyond end-of-body // for some reason. - LL_WARNS("HttpCore") << "Request body position beyond body size. Truncating request body." + LL_WARNS("CoreHttp") << "Request body position beyond body size. Truncating request body." << LL_ENDL; } return 0; @@ -1046,6 +1096,32 @@ char * os_strltrim(char * lstr) } -} // end anonymous namespace +void check_curl_easy_code(CURLcode code, int curl_setopt_option) +{ + if (CURLE_OK != code) + { + // Comment from old llcurl code which may no longer apply: + // + // linux appears to throw a curl error once per session for a bad initialization + // at a pretty random time (when enabling cookies). + LL_WARNS("CoreHttp") << "libcurl error detected: " << curl_easy_strerror(code) + << ", curl_easy_setopt option: " << curl_setopt_option + << LL_ENDL; + } +} - + +void check_curl_easy_code(CURLcode code) +{ + if (CURLE_OK != code) + { + // Comment from old llcurl code which may no longer apply: + // + // linux appears to throw a curl error once per session for a bad initialization + // at a pretty random time (when enabling cookies). + LL_WARNS("CoreHttp") << "libcurl error detected: " << curl_easy_strerror(code) + << LL_ENDL; + } +} + +} // end anonymous namespace diff --git a/indra/llcorehttp/httpcommon.cpp b/indra/llcorehttp/httpcommon.cpp index ca57a18578..0cf415223e 100755 --- a/indra/llcorehttp/httpcommon.cpp +++ b/indra/llcorehttp/httpcommon.cpp @@ -70,7 +70,8 @@ std::string HttpStatus::toString() const "Invalid datatype for argument or option", "Option has not been explicitly set", "Option is not dynamic and must be set early", - "Invalid HTTP status code received from server" + "Invalid HTTP status code received from server", + "Could not allocate required resource" }; static const int llcore_errors_count(sizeof(llcore_errors) / sizeof(llcore_errors[0])); diff --git a/indra/llcorehttp/httpcommon.h b/indra/llcorehttp/httpcommon.h index a04b344a9e..3a0bd68108 100755 --- a/indra/llcorehttp/httpcommon.h +++ b/indra/llcorehttp/httpcommon.h @@ -246,7 +246,10 @@ enum HttpError HE_OPT_NOT_DYNAMIC = 8, // Invalid HTTP status code returned by server - HE_INVALID_HTTP_STATUS = 9 + HE_INVALID_HTTP_STATUS = 9, + + // Couldn't allocate resource, typically libcurl handle + HE_BAD_ALLOC = 10 }; // end enum HttpError diff --git a/indra/llmessage/llcurl.cpp b/indra/llmessage/llcurl.cpp index f2a3e059ef..5193799ade 100755 --- a/indra/llmessage/llcurl.cpp +++ b/indra/llmessage/llcurl.cpp @@ -6,7 +6,7 @@ * * $LicenseInfo:firstyear=2006&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 @@ -293,9 +293,12 @@ LLCurl::Easy* LLCurl::Easy::getEasy() return NULL; } - // set no DNS caching as default for all easy handles. This prevents them adopting a - // multi handles cache if they are added to one. - CURLcode result = curl_easy_setopt(easy->mCurlEasyHandle, CURLOPT_DNS_CACHE_TIMEOUT, 0); + // Enable a brief cache period for now. This was zero for the longest time + // which caused some routers grief and generated unneeded traffic. For the + // threded resolver, we're using system resolution libraries and non-zero values + // are preferred. The c-ares resolver is another matter and it might not + // track server changes as well. + CURLcode result = curl_easy_setopt(easy->mCurlEasyHandle, CURLOPT_DNS_CACHE_TIMEOUT, 15); check_curl_code(result); result = curl_easy_setopt(easy->mCurlEasyHandle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); check_curl_code(result); diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index b55ba758e1..42952909d7 100755 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -78,8 +78,6 @@ #include "netdb.h" #endif -#include - // Purpose // @@ -235,8 +233,7 @@ // mUploadWaitList none rw.main.none (upload thread accessing objects) // mInventoryQ mMeshMutex [4] rw.main.mMeshMutex, ro.main.none [5] // mUploadErrorQ mMeshMutex rw.main.mMeshMutex, rw.any.mMeshMutex -// mGetMeshCapability none rw.main.none [0], ro.any.none -// mGetMesh2Capability none rw.main.none [0], ro.any.none +// mGetMeshVersion none rw.main.none // // LLMeshRepoThread: // @@ -255,6 +252,9 @@ // mUnavailableQ mMutex rw.repo.none [0], ro.main.none [5], rw.main.mMutex // mLoadedQ mMutex rw.repo.mMutex, ro.main.none [5], rw.main.mMutex // mPendingLOD mMutex rw.repo.mMutex, rw.any.mMutex +// mGetMeshCapability mMutex rw.main.mMutex, ro.repo.mMutex (was: [0]) +// mGetMesh2Capability mMutex rw.main.mMutex, ro.repo.mMutex (was: [0]) +// mGetMeshVersion mMutex rw.main.mMutex, ro.repo.mMutex // mHttp* none rw.repo.none // // QA/Development Testing @@ -304,7 +304,10 @@ // With this instrumentation enabled, a stall will appear // under the 'Mesh Fetch' timer which will be either top-level // or under 'Render' time. + +#ifndef LL_MESH_FASTTIMER_ENABLE #define LL_MESH_FASTTIMER_ENABLE 1 +#endif #if LL_MESH_FASTTIMER_ENABLE static LLFastTimer::DeclareTimer FTM_MESH_FETCH("Mesh Fetch"); @@ -381,7 +384,6 @@ static S32 dump_num = 0; std::string make_dump_name(std::string prefix, S32 num) { return prefix + boost::lexical_cast(num) + std::string(".xml"); - } void dump_llsd_to_file(const LLSD& content, std::string filename); LLSD llsd_from_file(std::string filename); @@ -740,7 +742,8 @@ LLMeshRepoThread::LLMeshRepoThread() mHttpPolicyClass(LLCore::HttpRequest::DEFAULT_POLICY_ID), mHttpLegacyPolicyClass(LLCore::HttpRequest::DEFAULT_POLICY_ID), mHttpLargePolicyClass(LLCore::HttpRequest::DEFAULT_POLICY_ID), - mHttpPriority(0) + mHttpPriority(0), + mGetMeshVersion(2) { mMutex = new LLMutex(NULL); mHeaderMutex = new LLMutex(NULL); @@ -1047,30 +1050,50 @@ void LLMeshRepoThread::loadMeshLOD(const LLVolumeParams& mesh_params, S32 lod) } } +// Mutex: must be holding mMutex when called +void LLMeshRepoThread::setGetMeshCaps(const std::string & get_mesh1, + const std::string & get_mesh2, + int pref_version) +{ + mGetMeshCapability = get_mesh1; + mGetMesh2Capability = get_mesh2; + mGetMeshVersion = pref_version; +} + + // Constructs a Cap URL for the mesh. Prefers a GetMesh2 cap // over a GetMesh cap. // -//static -std::string LLMeshRepoThread::constructUrl(LLUUID mesh_id) +// Mutex: acquires mMutex +void LLMeshRepoThread::constructUrl(LLUUID mesh_id, std::string * url, int * version) { - std::string http_url; + std::string res_url; + int res_version(2); if (gAgent.getRegion()) { - if (! gMeshRepo.mGetMesh2Capability.empty() && gMeshRepo.mGetMeshVersion > 1) + LLMutexLock lock(mMutex); + + // Get a consistent pair of (cap string, version). The + // locking could be eliminated here without loss of safety + // by using a set of staging values in setGetMeshCaps(). + + if (! mGetMesh2Capability.empty() && mGetMeshVersion > 1) { - http_url = gMeshRepo.mGetMesh2Capability; + res_url = mGetMesh2Capability; + res_version = 2; } else { - http_url = gMeshRepo.mGetMeshCapability; + res_url = mGetMeshCapability; + res_version = 1; } } - if (!http_url.empty()) + if (! res_url.empty()) { - http_url += "/?mesh_id="; - http_url += mesh_id.asString().c_str(); + res_url += "/?mesh_id="; + res_url += mesh_id.asString().c_str(); } else { @@ -1078,7 +1101,8 @@ std::string LLMeshRepoThread::constructUrl(LLUUID mesh_id) << mesh_id << ".mesh" << LL_ENDL; } - return http_url; + *url = res_url; + *version = res_version; } // Issue an HTTP GET request with byte range using the right @@ -1200,8 +1224,10 @@ bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id) } //reading from VFS failed for whatever reason, fetch from sim - int cap_version(gMeshRepo.mGetMeshVersion); - std::string http_url = constructUrl(mesh_id); + int cap_version(2); + std::string http_url; + constructUrl(mesh_id, &http_url, &cap_version); + if (!http_url.empty()) { LLMeshSkinInfoHandler * handler = new LLMeshSkinInfoHandler(mesh_id, offset, size); @@ -1293,8 +1319,10 @@ bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id) } //reading from VFS failed for whatever reason, fetch from sim - int cap_version(gMeshRepo.mGetMeshVersion); - std::string http_url = constructUrl(mesh_id); + int cap_version(2); + std::string http_url; + constructUrl(mesh_id, &http_url, &cap_version); + if (!http_url.empty()) { LLMeshDecompositionHandler * handler = new LLMeshDecompositionHandler(mesh_id, offset, size); @@ -1384,8 +1412,10 @@ bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id) } //reading from VFS failed for whatever reason, fetch from sim - int cap_version(gMeshRepo.mGetMeshVersion); - std::string http_url = constructUrl(mesh_id); + int cap_version(2); + std::string http_url; + constructUrl(mesh_id, &http_url, &cap_version); + if (!http_url.empty()) { LLMeshPhysicsShapeHandler * handler = new LLMeshPhysicsShapeHandler(mesh_id, offset, size); @@ -1477,8 +1507,10 @@ bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params) //either cache entry doesn't exist or is corrupt, request header from simulator bool retval = true; - int cap_version(gMeshRepo.mGetMeshVersion); - std::string http_url = constructUrl(mesh_params.getSculptID()); + int cap_version(2); + std::string http_url; + constructUrl(mesh_params.getSculptID(), &http_url, &cap_version); + if (!http_url.empty()) { //grab first 4KB if we're going to bother with a fetch. Cache will prevent future fetches if a full mesh fits @@ -1563,8 +1595,10 @@ bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod) } //reading from VFS failed for whatever reason, fetch from sim - int cap_version(gMeshRepo.mGetMeshVersion); - std::string http_url = constructUrl(mesh_id); + int cap_version(2); + std::string http_url; + constructUrl(mesh_id, &http_url, &cap_version); + if (!http_url.empty()) { LLMeshLODHandler * handler = new LLMeshLODHandler(mesh_params, lod, offset, size); @@ -1812,7 +1846,6 @@ bool LLMeshRepoThread::physicsShapeReceived(const LLUUID& mesh_id, U8* data, S32 return true; } - LLMeshUploadThread::LLMeshUploadThread(LLMeshUploadThread::instance_list& data, LLVector3& scale, bool upload_textures, bool upload_skin, bool upload_joints, const std::string & upload_url, bool do_upload, LLHandle fee_observer, @@ -3249,15 +3282,15 @@ void LLMeshRepository::notifyLoadedMeshes() if (gAgent.getRegion()->getName() != region_name && gAgent.getRegion()->capabilitiesReceived()) { - const bool use_v1(gSavedSettings.getBOOL("MeshUseGetMesh1")); - region_name = gAgent.getRegion()->getName(); - mGetMeshCapability = gAgent.getRegion()->getCapability("GetMesh"); - mGetMesh2Capability = gAgent.getRegion()->getCapability("GetMesh2"); - mGetMeshVersion = (mGetMesh2Capability.empty() || use_v1) ? 1 : 2; + const bool use_v1(gSavedSettings.getBOOL("MeshUseGetMesh1")); + const std::string mesh1(gAgent.getRegion()->getCapability("GetMesh")); + const std::string mesh2(gAgent.getRegion()->getCapability("GetMesh2")); + mGetMeshVersion = (mesh2.empty() || use_v1) ? 1 : 2; + mThread->setGetMeshCaps(mesh1, mesh2, mGetMeshVersion); LL_DEBUGS(LOG_MESH) << "Retrieving caps for region '" << region_name - << "', GetMesh2: " << mGetMesh2Capability - << ", GetMesh: " << mGetMeshCapability + << "', GetMesh2: " << mesh2 + << ", GetMesh: " << mesh1 << ", using version: " << mGetMeshVersion << LL_ENDL; } diff --git a/indra/newview/llmeshrepository.h b/indra/newview/llmeshrepository.h index e09f39f7a8..9d8b102110 100755 --- a/indra/newview/llmeshrepository.h +++ b/indra/newview/llmeshrepository.h @@ -333,7 +333,9 @@ public: typedef std::set http_request_set; http_request_set mHttpRequestSet; // Outstanding HTTP requests - static std::string constructUrl(LLUUID mesh_id); + std::string mGetMeshCapability; + std::string mGetMesh2Capability; + int mGetMeshVersion; LLMeshRepoThread(); ~LLMeshRepoThread(); @@ -376,6 +378,17 @@ public: static void incActiveHeaderRequests(); static void decActiveHeaderRequests(); + // Set the caps strings and preferred version for constructing + // mesh fetch URLs. + // + // Mutex: must be holding mMutex when called + void setGetMeshCaps(const std::string & get_mesh1, + const std::string & get_mesh2, + int pref_version); + + // Mutex: acquires mMutex + void constructUrl(LLUUID mesh_id, std::string * url, int * version); + private: // Issue a GET request to a URL with 'Range' header using // the correct policy class and other attributes. If an invalid @@ -613,9 +626,7 @@ public: void uploadError(LLSD& args); void updateInventory(inventory_data data); - std::string mGetMeshCapability; - std::string mGetMesh2Capability; - int mGetMeshVersion; + int mGetMeshVersion; // Shadows value in LLMeshRepoThread }; extern LLMeshRepository gMeshRepo; -- cgit v1.2.3 From 56e2f11417183d8dcc3d681f79fc63446b236abb Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Fri, 27 Sep 2013 17:22:31 -0400 Subject: Up the transfer timeout of small meshes to 120S. This matches the change made for MAINT-2347. Large transfers are still 10 minutes. Add/update to-do list and add some more info to the FAQ in the Readme. --- indra/llcorehttp/README.Linden | 17 ++++++++++++++++- indra/llcorehttp/_httpinternal.h | 7 ++++++- indra/newview/llmeshrepository.cpp | 2 +- 3 files changed, 23 insertions(+), 3 deletions(-) (limited to 'indra') diff --git a/indra/llcorehttp/README.Linden b/indra/llcorehttp/README.Linden index 8d18ed1a11..eb6ccab3bc 100644 --- a/indra/llcorehttp/README.Linden +++ b/indra/llcorehttp/README.Linden @@ -653,4 +653,19 @@ HttpOperation::addAsReply: TRACE, ToReplyQueue, Handle: 086D3148 Q2. How's that data sharing with refcounts working for you? - A2. Meh. + A2. Meh. It does reduce memory churn and the frequency at which + free blocks must be moved between threads. But it's also a design + for static configuration and dynamic reconfiguration (not + requiring a restart) is favored. Creating new options for every + request isn't too bad, it a sequence of "new, fill, request, + release" for each requested operation. That in contrast to doing + the "new, fill, release" at startup. The bad comes in getting at + the source data. One rule in this work was "no new thread + problems." And one source for those is pulling setting values out + of gSettings in threads. None of that is thread safe though we + tend to get away with it. + + Q3. What needs to be done? + + A3. There's a To-Do list in _httpinternal.h. It has both large + and small projects here if someone would like to try changes. diff --git a/indra/llcorehttp/_httpinternal.h b/indra/llcorehttp/_httpinternal.h index effc6a42c5..54e87b0a4d 100755 --- a/indra/llcorehttp/_httpinternal.h +++ b/indra/llcorehttp/_httpinternal.h @@ -66,6 +66,12 @@ // This won't help in the face of the router problems we've looked // at, however. Detect starvation due to UDP activity and provide // feedback to it. +// - Change the transfer timeout scheme. We're less interested in +// absolute time, in most cases, than in continuous progress. +// - Many of the policy class settings are currently applied to the +// entire class. Some, like connection limits, would be better +// applied to each destination target making multiple targets +// independent. // // Integration to-do list // - LLTextureFetch still needs a major refactor. The use of @@ -75,7 +81,6 @@ // the main source file. // - Expand areas of usage eventually leading to the removal of LLCurl. // Rough order of expansion: -// . Mesh fetch [Underway] // . Avatar names // . Group membership lists // . Caps access in general diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index 42952909d7..044cf27b07 100755 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -351,7 +351,7 @@ const S32 REQUEST2_HIGH_WATER_MAX = 80; const S32 REQUEST2_LOW_WATER_MIN = 16; const S32 REQUEST2_LOW_WATER_MAX = 40; const U32 LARGE_MESH_FETCH_THRESHOLD = 1U << 21; // Size at which requests goes to narrow/slow queue -const long SMALL_MESH_XFER_TIMEOUT = 60L; // Seconds to complete xfer, small mesh downloads +const long SMALL_MESH_XFER_TIMEOUT = 120L; // Seconds to complete xfer, small mesh downloads const long LARGE_MESH_XFER_TIMEOUT = 600L; // Seconds to complete xfer, large downloads // Maximum mesh version to support. Three least significant digits are reserved for the minor version, -- cgit v1.2.3 From 6d405e2d018e6722288e1cfbf477d985b8384a54 Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Fri, 4 Oct 2013 18:24:59 -0400 Subject: Convert one more unit test over to improved waiting scheme to avoid build failures. --- indra/llcorehttp/tests/test_httprequest.hpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'indra') diff --git a/indra/llcorehttp/tests/test_httprequest.hpp b/indra/llcorehttp/tests/test_httprequest.hpp index 2311753c65..43f7e36da5 100755 --- a/indra/llcorehttp/tests/test_httprequest.hpp +++ b/indra/llcorehttp/tests/test_httprequest.hpp @@ -3241,11 +3241,11 @@ void HttpRequestTestObjectType::test<23>() // Run the notification pump. int count(0); - int limit(300); // One retry but several seconds needed + int limit(LOOP_COUNT_LONG); while (count++ < limit && mHandlerCalls < url_limit) { req->update(0); - usleep(100000); + usleep(LOOP_SLEEP_INTERVAL); } ensure("Request executed in reasonable time", count < limit); ensure("One handler invocation for request", mHandlerCalls == url_limit); @@ -3258,21 +3258,21 @@ void HttpRequestTestObjectType::test<23>() // Run the notification pump again count = 0; - limit = 100; + limit = LOOP_COUNT_LONG; while (count++ < limit && mHandlerCalls < 1) { req->update(1000000); - usleep(100000); + usleep(LOOP_SLEEP_INTERVAL); } ensure("Second request executed in reasonable time", count < limit); ensure("Second handler invocation", mHandlerCalls == 1); // See that we actually shutdown the thread count = 0; - limit = 10; + limit = LOOP_COUNT_SHORT; while (count++ < limit && ! HttpService::isStopped()) { - usleep(100000); + usleep(LOOP_SLEEP_INTERVAL); } ensure("Thread actually stopped running", HttpService::isStopped()); -- cgit v1.2.3 From b2d769534c82de2ac7b36f11ce6fab61f3e0d378 Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Mon, 18 Nov 2013 13:33:19 -0500 Subject: Code review updates. All comments so far. --- indra/llcommon/lldeadmantimer.h | 10 ++++------ indra/llmessage/llcurl.cpp | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) (limited to 'indra') diff --git a/indra/llcommon/lldeadmantimer.h b/indra/llcommon/lldeadmantimer.h index 0dde16b717..980976e176 100644 --- a/indra/llcommon/lldeadmantimer.h +++ b/indra/llcommon/lldeadmantimer.h @@ -155,11 +155,9 @@ public: /// 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. + /// Checks the status of the timer. If the timer has expired, + /// also returns various timer-related stats. Unlike ringBell(), + /// does not extend the horizon, it only checks for expiration. /// /// @param now Current time as returned by @see /// LLTimer::getCurrentClockCount(). If zero, @@ -192,7 +190,7 @@ public: bool isExpired(time_type now, F64 & started, F64 & stopped, U64 & count, U64 & user_cpu, U64 & sys_cpu); - /// Identical to the six-arugment form except is does without the + /// Identical to the six-arugment form except it does without the /// CPU time return if the caller isn't interested in it. bool isExpired(time_type now, F64 & started, F64 & stopped, U64 & count); diff --git a/indra/llmessage/llcurl.cpp b/indra/llmessage/llcurl.cpp index efbc804cbc..25e175f2cc 100755 --- a/indra/llmessage/llcurl.cpp +++ b/indra/llmessage/llcurl.cpp @@ -296,7 +296,7 @@ LLCurl::Easy* LLCurl::Easy::getEasy() // Enable a brief cache period for now. This was zero for the longest time // which caused some routers grief and generated unneeded traffic. For the - // threded resolver, we're using system resolution libraries and non-zero values + // threaded resolver, we're using system resolution libraries and non-zero values // are preferred. The c-ares resolver is another matter and it might not // track server changes as well. CURLcode result = curl_easy_setopt(easy->mCurlEasyHandle, CURLOPT_DNS_CACHE_TIMEOUT, 15); -- cgit v1.2.3 From ea1f6a6343fe83f1352a8a839265c471640acdce Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Fri, 6 Dec 2013 16:02:53 -0500 Subject: SH-4645 Viewer hangs on exit after cancelling a mesh upload. Problem involved a 3-way livelock between the main, upload and decomposition threads. Viewer is shutting down but an upload is in the 'generate hulls' state. Main thread asks upload request to discard and spins waiting for it to finish. Upload thread is in generateHulls spinning waiting for the decomposition thread to process a mesh request. Decomposition thread is sleeping waiting for main thread to deliver work that upload thread has asked the decomposition thread to do. --- indra/newview/llmeshrepository.cpp | 39 ++++++++++++++++++++++++++++++-------- indra/newview/llmeshrepository.h | 4 ++-- 2 files changed, 33 insertions(+), 10 deletions(-) (limited to 'indra') diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index ebfb22a360..4296abb2cc 100755 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -257,6 +257,11 @@ // mGetMeshVersion mMutex rw.main.mMutex, ro.repo.mMutex // mHttp* none rw.repo.none // +// LLMeshUploadThread: +// +// mDiscarded mMutex rw.main.mMutex, ro.uploadN.none [1] +// ... more ... +// // QA/Development Testing // // Debug variable 'MeshUploadFakeErrors' takes a mask of bits that will @@ -1852,7 +1857,7 @@ LLMeshUploadThread::LLMeshUploadThread(LLMeshUploadThread::instance_list& data, LLHandle upload_observer) : LLThread("mesh upload"), LLCore::HttpHandler(), - mDiscarded(FALSE), + mDiscarded(false), mDoUpload(do_upload), mWholeModelUploadURL(upload_url), mFeeObserverHandle(fee_observer), @@ -1940,10 +1945,10 @@ void LLMeshUploadThread::preStart() void LLMeshUploadThread::discard() { LLMutexLock lock(mMutex); - mDiscarded = TRUE; + mDiscarded = true; } -BOOL LLMeshUploadThread::isDiscarded() const +bool LLMeshUploadThread::isDiscarded() const { LLMutexLock lock(mMutex); return mDiscarded; @@ -2199,7 +2204,13 @@ void LLMeshUploadThread::generateHulls() if (has_valid_requests) { - while (!mPhysicsComplete) + // *NOTE: Interesting livelock condition on shutdown. If there + // is an upload request in generateHulls() when shutdown starts, + // the main thread isn't available to manage communication between + // the decomposition thread and the upload thread and this loop + // wouldn't complete in turn stalling the main thread. The check + // on isDiscarded() prevents that. + while (! mPhysicsComplete && ! isDiscarded()) { apr_sleep(100); } @@ -2253,13 +2264,21 @@ void LLMeshUploadThread::doWholeModelUpload() LL_DEBUGS(LOG_MESH) << "POST request issued." << LL_ENDL; mHttpRequest->update(0); - while (! LLApp::isQuitting() && ! mFinished) + while (! LLApp::isQuitting() && ! finished() && ! isDiscarded()) { ms_sleep(sleep_time); sleep_time = llmin(250U, sleep_time + sleep_time); mHttpRequest->update(0); } - LL_DEBUGS(LOG_MESH) << "Mesh upload operation completed." << LL_ENDL; + + if (isDiscarded()) + { + LL_DEBUGS(LOG_MESH) << "Mesh upload operation discarded." << LL_ENDL; + } + else + { + LL_DEBUGS(LOG_MESH) << "Mesh upload operation completed." << LL_ENDL; + } } } } @@ -2299,12 +2318,16 @@ void LLMeshUploadThread::requestWholeModelFee() U32 sleep_time(10); mHttpRequest->update(0); - while (! LLApp::isQuitting() && ! mFinished) + while (! LLApp::isQuitting() && ! finished() && ! isDiscarded()) { ms_sleep(sleep_time); sleep_time = llmin(250U, sleep_time + sleep_time); mHttpRequest->update(0); } + if (isDiscarded()) + { + LL_DEBUGS(LOG_MESH) << "Mesh fee query operation discarded." << LL_ENDL; + } } } @@ -3020,7 +3043,7 @@ void LLMeshRepository::shutdown() for (U32 i = 0; i < mUploads.size(); ++i) { - LL_INFOS(LOG_MESH) << "Waiting for pending mesh upload " << i << "/" << mUploads.size() << LL_ENDL; + LL_INFOS(LOG_MESH) << "Waiting for pending mesh upload " << (i + 1) << "/" << mUploads.size() << LL_ENDL; while (!mUploads[i]->isStopped()) { apr_sleep(10); diff --git a/indra/newview/llmeshrepository.h b/indra/newview/llmeshrepository.h index 9d8b102110..39280bea3a 100755 --- a/indra/newview/llmeshrepository.h +++ b/indra/newview/llmeshrepository.h @@ -447,7 +447,7 @@ public: bool mUploadTextures; bool mUploadSkin; bool mUploadJoints; - BOOL mDiscarded; + volatile bool mDiscarded; LLHost mHost; std::string mWholeModelFeeCapability; @@ -463,7 +463,7 @@ public: virtual void run(); void preStart(); void discard() ; - BOOL isDiscarded() const; + bool isDiscarded() const; void generateHulls(); -- cgit v1.2.3 From b2fe32e5725e13930437ae8939d7213de69b35ca Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Thu, 9 Jan 2014 15:18:54 -0500 Subject: SH-4667 HTTP Viewer reports network error instead of a misnamed joint on mesh upload Tried to add consistent mesh upload retries in the HTTP work but a combination of bad status choices in the upload service and the one-shot nature of the upload capabilities means that status information can be lost. For now, retain the wonderful manual retry logic. At some future point, we might fix the services or add application-level retry. --- indra/newview/llmeshrepository.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index 4296abb2cc..5afd2cb329 100755 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -5,7 +5,7 @@ * * $LicenseInfo:firstyear=2005&license=viewerlgpl$ * Second Life Viewer Source Code - * Copyright (C) 2010-2013, Linden Research, Inc. + * Copyright (C) 2010-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 @@ -359,6 +359,16 @@ const U32 LARGE_MESH_FETCH_THRESHOLD = 1U << 21; // Size at which requests goes const long SMALL_MESH_XFER_TIMEOUT = 120L; // Seconds to complete xfer, small mesh downloads const long LARGE_MESH_XFER_TIMEOUT = 600L; // Seconds to complete xfer, large downloads +// Would normally like to retry on uploads as some +// retryable failures would be recoverable. Unfortunately, +// the mesh service is using 500 (retryable) rather than +// 400/bad request (permanent) for a bad payload and +// retrying that just leads to revocation of the one-shot +// cap which then produces a 404 on retry destroying some +// (occasionally) useful error information. We'll leave +// upload retries to the user as in the past. SH-4667. +const long UPLOAD_RETRY_LIMIT = 0L; + // Maximum mesh version to support. Three least significant digits are reserved for the minor version, // with major version changes indicating a format change that is not backwards compatible and should not // be parsed by viewers that don't specifically support that version. For example, if the integer "1" is @@ -1883,6 +1893,7 @@ LLMeshUploadThread::LLMeshUploadThread(LLMeshUploadThread::instance_list& data, mHttpOptions = new LLCore::HttpOptions; mHttpOptions->setTransferTimeout(mMeshUploadTimeOut); mHttpOptions->setUseRetryAfter(gSavedSettings.getBOOL("MeshUseHttpRetryAfter")); + mHttpOptions->setRetries(UPLOAD_RETRY_LIMIT); mHttpHeaders = new LLCore::HttpHeaders; mHttpHeaders->append("Content-Type", "application/llsd+xml"); mHttpPolicyClass = LLAppViewer::instance()->getAppCoreHttp().getPolicy(LLAppCoreHttp::AP_UPLOADS); -- cgit v1.2.3 From dae8c4158aa028be026700d5c7d03d601c40c58a Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Mon, 27 Jan 2014 16:21:56 -0500 Subject: Trivial edit to stimulate a new build. --- indra/edit-me-to-trigger-new-build.txt | 1 + 1 file changed, 1 insertion(+) (limited to 'indra') diff --git a/indra/edit-me-to-trigger-new-build.txt b/indra/edit-me-to-trigger-new-build.txt index 774e8c0676..beeb570496 100755 --- a/indra/edit-me-to-trigger-new-build.txt +++ b/indra/edit-me-to-trigger-new-build.txt @@ -4,3 +4,4 @@ Wed Nov 7 00:25:19 UTC 2012 + -- cgit v1.2.3 From 636953b7d74f79c5ada56719f90245f686df4604 Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Wed, 29 Jan 2014 16:27:59 -0500 Subject: another trigger edit --- indra/edit-me-to-trigger-new-build.txt | 1 - 1 file changed, 1 deletion(-) (limited to 'indra') diff --git a/indra/edit-me-to-trigger-new-build.txt b/indra/edit-me-to-trigger-new-build.txt index beeb570496..774e8c0676 100755 --- a/indra/edit-me-to-trigger-new-build.txt +++ b/indra/edit-me-to-trigger-new-build.txt @@ -4,4 +4,3 @@ Wed Nov 7 00:25:19 UTC 2012 - -- cgit v1.2.3 From 80ee8a8ca3725265636f8052a2442a74a742338f Mon Sep 17 00:00:00 2001 From: Monty Brandenberg Date: Thu, 30 Jan 2014 13:17:17 -0500 Subject: Another build trigger --- indra/edit-me-to-trigger-new-build.txt | 1 + 1 file changed, 1 insertion(+) (limited to 'indra') diff --git a/indra/edit-me-to-trigger-new-build.txt b/indra/edit-me-to-trigger-new-build.txt index 774e8c0676..beeb570496 100755 --- a/indra/edit-me-to-trigger-new-build.txt +++ b/indra/edit-me-to-trigger-new-build.txt @@ -4,3 +4,4 @@ Wed Nov 7 00:25:19 UTC 2012 + -- cgit v1.2.3 From de8fea13627cc5978b8a6135802a52864a11c39a Mon Sep 17 00:00:00 2001 From: Oz Linden Date: Mon, 24 Feb 2014 14:50:35 -0500 Subject: increment viewer version to 3.7.3 --- indra/newview/VIEWER_VERSION.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra') diff --git a/indra/newview/VIEWER_VERSION.txt b/indra/newview/VIEWER_VERSION.txt index 0b2eb36f50..c1e43e6d45 100644 --- a/indra/newview/VIEWER_VERSION.txt +++ b/indra/newview/VIEWER_VERSION.txt @@ -1 +1 @@ -3.7.2 +3.7.3 -- cgit v1.2.3