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