From da72aaa23512fda18f34292a4af99535da4f3e0b Mon Sep 17 00:00:00 2001 From: Andy Wingo Date: Wed, 23 Nov 2005 12:36:00 +0000 Subject: [PATCH] check/net/gstnetclientclock.c (test_functioning): Adjust to rate_num/rate_denom change. Original commit message from CVS: 2005-11-23 Andy Wingo * check/net/gstnetclientclock.c (test_functioning): Adjust to rate_num/rate_denom change. * gst/net/gstnetclientclock.c (gst_net_client_clock_observe_times): Take the SLAVE_LOCK not the OBJECT_LOCK. Don't call add_observation with the lock. * gst/gstclock.c (gst_clock_init): Initialize the rate as a fraction. (gst_clock_adjust_unlocked): Adjust using uint64_scale and the rate fraction. (gst_clock_set_calibration, gst_clock_get_calibration): Change to deal with rate as a fraction whose numerator and denominator are GstClockTime values. (gst_clock_set_master): Only use the OBJECT_LOCK to set the master; the other fields are protected by the SLAVE_LOCK. (do_linear_regression): Note that this must be called with the SLAVE_LOCK. (gst_clock_add_observation): Take the SLAVE_LOCK, not the OBJECT_LOCK. Call set_calibration instead of touching the variables directly. (gst_clock_set_property, gst_clock_get_property): Protect master/slave parameters with the SLAVE_LOCK. * gst/gstclock.h (GstClock): Remove rate, add rate_numerator and rate_denominator. PR3C1S3. Add a new lock, the SLAVE_LOCK, and note that all of the instance variables that add_observation and the set_master functions use are protected by that lock and not the OBJECT_LOCK. (GST_CLOCK_SLAVE_LOCK, GST_CLOCK_SLAVE_UNLOCK): New macros. * gst/gstclock.c (gst_clock_add_observation): No longer requires the caller to take the object lock. --- ChangeLog | 36 ++++++++++ check/net/gstnetclientclock.c | 7 +- gst/gstclock.c | 130 +++++++++++++++++++++-------------- gst/gstclock.h | 21 ++++-- gst/net/gstnetclientclock.c | 6 +- libs/gst/net/gstnetclientclock.c | 6 +- tests/check/libs/gstnetclientclock.c | 7 +- 7 files changed, 143 insertions(+), 70 deletions(-) diff --git a/ChangeLog b/ChangeLog index 49bf9ee..b4bd5cf 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,39 @@ +2005-11-23 Andy Wingo + + * check/net/gstnetclientclock.c (test_functioning): Adjust to + rate_num/rate_denom change. + + * gst/net/gstnetclientclock.c + (gst_net_client_clock_observe_times): Take the SLAVE_LOCK not the + OBJECT_LOCK. Don't call add_observation with the lock. + + * gst/gstclock.c (gst_clock_init): Initialize the rate as a + fraction. + (gst_clock_adjust_unlocked): Adjust using uint64_scale and the + rate fraction. + (gst_clock_set_calibration, gst_clock_get_calibration): Change to + deal with rate as a fraction whose numerator and denominator are + GstClockTime values. + (gst_clock_set_master): Only use the OBJECT_LOCK to set the + master; the other fields are protected by the SLAVE_LOCK. + (do_linear_regression): Note that this must be called with the + SLAVE_LOCK. + (gst_clock_add_observation): Take the SLAVE_LOCK, not the + OBJECT_LOCK. Call set_calibration instead of touching the + variables directly. + (gst_clock_set_property, gst_clock_get_property): Protect + master/slave parameters with the SLAVE_LOCK. + + * gst/gstclock.h (GstClock): Remove rate, add rate_numerator and + rate_denominator. PR3C1S3. Add a new lock, the SLAVE_LOCK, and + note that all of the instance variables that add_observation and + the set_master functions use are protected by that lock and not + the OBJECT_LOCK. + (GST_CLOCK_SLAVE_LOCK, GST_CLOCK_SLAVE_UNLOCK): New macros. + + * gst/gstclock.c (gst_clock_add_observation): No longer requires + the caller to take the object lock. + 2005-11-23 Wim Taymans * gst/gsterror.c: (_gst_core_errors_init): diff --git a/check/net/gstnetclientclock.c b/check/net/gstnetclientclock.c index e62a01e..8ff6bc4 100644 --- a/check/net/gstnetclientclock.c +++ b/check/net/gstnetclientclock.c @@ -50,18 +50,17 @@ GST_START_TEST (test_functioning) { GstNetTimeProvider *ntp; GstClock *client, *server; - GstClockTime basex, basey; + GstClockTime basex, basey, rate_num, rate_denom; GstClockTime servtime, clienttime; gint port; - gdouble rate; server = gst_system_clock_obtain (); fail_unless (server != NULL, "failed to get system clock"); /* move the clock ahead 100 seconds */ - gst_clock_get_calibration (server, &basex, &basey, &rate); + gst_clock_get_calibration (server, &basex, &basey, &rate_num, &rate_denom); basey += 100 * GST_SECOND; - gst_clock_set_calibration (server, basex, basey, rate); + gst_clock_set_calibration (server, basex, basey, rate_num, rate_denom); ntp = gst_net_time_provider_new (server, "127.0.0.1", 0); fail_unless (ntp != NULL, "failed to create network time provider"); diff --git a/gst/gstclock.c b/gst/gstclock.c index 81e4aa7..46bf9f9 100644 --- a/gst/gstclock.c +++ b/gst/gstclock.c @@ -563,7 +563,10 @@ gst_clock_init (GstClock * clock) clock->internal_calibration = 0; clock->external_calibration = 0; - clock->rate = 1.0; + clock->rate_numerator = 1; + clock->rate_denominator = 1; + + clock->slave_lock = g_mutex_new (); clock->filling = TRUE; clock->window_size = DEFAULT_WINDOW_SIZE; clock->window_threshold = DEFAULT_WINDOW_THRESHOLD; @@ -590,6 +593,8 @@ gst_clock_finalize (GObject * object) clock->times = NULL; GST_OBJECT_UNLOCK (clock); + g_mutex_free (clock->slave_lock); + G_OBJECT_CLASS (parent_class)->finalize (object); } @@ -652,7 +657,7 @@ gst_clock_get_resolution (GstClock * clock) * Converts the given @internal clock time to the real time, adjusting for the * rate and reference time set with gst_clock_set_calibration() and making sure * that the returned time is increasing. This function should be called with the - * clock LOCK held and is mainly used by clock subclasses. + * clock's OBJECT_LOCK held and is mainly used by clock subclasses. * * Returns: the converted time of the clock. * @@ -663,7 +668,8 @@ gst_clock_adjust_unlocked (GstClock * clock, GstClockTime internal) { GstClockTime ret; - ret = (internal - clock->internal_calibration) * clock->rate; + ret = gst_util_uint64_scale (internal - clock->internal_calibration, + clock->rate_numerator, clock->rate_denominator); ret += clock->external_calibration; /* make sure the time is increasing */ @@ -743,11 +749,12 @@ gst_clock_get_time (GstClock * clock) * @clock: a #GstClock to calibrate * @internal: a reference internal time * @external: a reference external time - * @rate: the rate of the clock relative to its internal time + * @rate_num: the numerator of the rate of the clock relative to its + * internal time + * @rate_denom: the denominator of the rate of the clock * - * Adjusts the rate and time of @clock. A @rate of 1.0 is the normal speed of - * the clock. Values bigger than 1.0 make the clock go faster. @rate must be - * positive. + * Adjusts the rate and time of @clock. A rate of 1/1 is the normal speed of + * the clock. Values bigger than 1/1 make the clock go faster. * * @internal and @external are calibration parameters that arrange that * gst_clock_get_time() should have been @external at internal time @internal. @@ -758,9 +765,12 @@ gst_clock_get_time (GstClock * clock) * follows: * * - * time = (internal_time - @internal) * @rate + @external + * time = (internal_time - @internal) * @rate_num / @rate_denom + @external * * + * This formula is implemented in gst_clock_adjust_unlocked(). Of course, it + * tries to do the integer arithmetic as precisely as possible. + * * Note that gst_clock_get_time() always returns increasing values so when you * move the clock backwards, gst_clock_get_time() will report the previous value * until the clock catches up. @@ -769,16 +779,18 @@ gst_clock_get_time (GstClock * clock) */ void gst_clock_set_calibration (GstClock * clock, GstClockTime internal, GstClockTime - external, gdouble rate) + external, GstClockTime rate_num, GstClockTime rate_denom) { g_return_if_fail (GST_IS_CLOCK (clock)); - g_return_if_fail (rate > 0.0); + g_return_if_fail (rate_num > 0); + g_return_if_fail (rate_denom > 0); g_return_if_fail (internal <= gst_clock_get_internal_time (clock)); GST_OBJECT_LOCK (clock); clock->internal_calibration = internal; clock->external_calibration = external; - clock->rate = rate; + clock->rate_numerator = rate_num; + clock->rate_denominator = rate_denom; GST_OBJECT_UNLOCK (clock); } @@ -787,25 +799,28 @@ gst_clock_set_calibration (GstClock * clock, GstClockTime internal, GstClockTime * @clock: a #GstClock * @internal: a location to store the internal time * @external: a location to store the external time - * @rate: a location to store the rate + * @rate_num: a location to store the rate numerator + * @rate_denom: a location to store the rate denominator * * Gets the internal rate and reference time of @clock. See * gst_clock_set_calibration() for more information. * - * @internal, @external and @rate can be left NULL if the caller - * is not interested in the values. + * @internal, @external, @rate_num, and @rate_denom can be left NULL if the + * caller is not interested in the values. * * MT safe. */ void gst_clock_get_calibration (GstClock * clock, GstClockTime * internal, - GstClockTime * external, gdouble * rate) + GstClockTime * external, GstClockTime * rate_num, GstClockTime * rate_denom) { g_return_if_fail (GST_IS_CLOCK (clock)); GST_OBJECT_LOCK (clock); - if (rate) - *rate = clock->rate; + if (rate_num) + *rate_num = clock->rate_numerator; + if (rate_denom) + *rate_denom = clock->rate_denominator; if (external) *external = clock->external_calibration; if (internal) @@ -859,6 +874,7 @@ gst_clock_set_master (GstClock * clock, GstClock * master) g_return_val_if_fail (GST_IS_CLOCK (clock), FALSE); GST_OBJECT_LOCK (clock); + /* we always allow setting the master to NULL */ if (master && !GST_OBJECT_FLAG_IS_SET (clock, GST_CLOCK_FLAG_CAN_SET_MASTER)) goto not_supported; @@ -866,6 +882,10 @@ gst_clock_set_master (GstClock * clock, GstClock * master) GST_DEBUG_OBJECT (clock, "slaving to master clock %p", master); gst_object_replace ((GstObject **) & clock->master, (GstObject *) master); + GST_OBJECT_UNLOCK (clock); + + GST_CLOCK_SLAVE_LOCK (clock); + if (clock->clockid) { gst_clock_id_unschedule (clock->clockid); gst_clock_id_unref (clock->clockid); @@ -881,7 +901,8 @@ gst_clock_set_master (GstClock * clock, GstClock * master) gst_clock_id_wait_async (clock->clockid, (GstClockCallback) gst_clock_slave_callback, clock); } - GST_OBJECT_UNLOCK (clock); + + GST_CLOCK_SLAVE_UNLOCK (clock); return TRUE; @@ -919,10 +940,13 @@ gst_clock_get_master (GstClock * clock) return result; } -/* http://mathworld.wolfram.com/LeastSquaresFitting.html */ +/* http://mathworld.wolfram.com/LeastSquaresFitting.html + * with SLAVE_LOCK + */ static gboolean -do_linear_regression (GstClock * clock, gdouble * m, - GstClockTime * b, GstClockTime * xbase, gdouble * r_squared) +do_linear_regression (GstClock * clock, GstClockTime * m_num, + GstClockTime * m_denom, GstClockTime * b, GstClockTime * xbase, + gdouble * r_squared) { GstClockTime *newx, *newy; GstClockTime xmin, ymin, xbar, ybar, xbar4, ybar4; @@ -934,13 +958,13 @@ do_linear_regression (GstClock * clock, gdouble * m, xbar = ybar = sxx = syy = sxy = 0; x = clock->times; - y = clock->times + 2, - n = clock->filling ? clock->time_index : clock->window_size; + y = clock->times + 2; + n = clock->filling ? clock->time_index : clock->window_size; #ifdef DEBUGGING_ENABLED DEBUG ("doing regression on:"); - for (i = 0; i < n; i++) - DEBUG (" %" G_GUINT64_FORMAT " %" G_GUINT64_FORMAT, x[i], y[i]); + for (i = j = 0; i < n; i++, j += 4) + DEBUG (" %" G_GUINT64_FORMAT " %" G_GUINT64_FORMAT, x[j], y[j]); #endif xmin = ymin = G_MAXUINT64; @@ -1001,12 +1025,13 @@ do_linear_regression (GstClock * clock, gdouble * m, sxy += newx4 * newy4 - xbar4 * ybar4; } - *m = ((double) sxy) / sxx; + *m_num = sxy; + *m_denom = sxx; *xbase = xmin; - *b = (ybar + ymin) - (GstClockTime) (xbar * *m); + *b = (ybar + ymin) - gst_util_uint64_scale (xbar, *m_num, *m_denom); *r_squared = ((double) sxy * (double) sxy) / ((double) sxx * (double) syy); - DEBUG (" m = %g", *m); + DEBUG (" m = %g", ((double) *m_num) / *m_denom); DEBUG (" b = %" G_GUINT64_FORMAT, *b); DEBUG (" xbase = %" G_GUINT64_FORMAT, *xbase); DEBUG (" r2 = %g", *r_squared); @@ -1017,8 +1042,8 @@ do_linear_regression (GstClock * clock, gdouble * m, /** * gst_clock_add_observation * @clock: a #GstClock - * @slave: an time on the slave - * @master: an time on the master + * @slave: a time on the slave + * @master: a time on the master * @r_squared: a pointer to hold the result * * The time @master of the master clock and the time @slave of the slave @@ -1026,8 +1051,6 @@ do_linear_regression (GstClock * clock, gdouble * m, * are available, a linear regression algorithm is run on the * observations and @clock is recalibrated. * - * This function should be called with @clock OBJECT_LOCK. - * * Returns: TRUE if enough observations were added to run the * regression algorithm. * @@ -1037,12 +1060,13 @@ gboolean gst_clock_add_observation (GstClock * clock, GstClockTime slave, GstClockTime master, gdouble * r_squared) { - GstClockTime b, xbase; - gdouble m; + GstClockTime m_num, m_denom, b, xbase; g_return_val_if_fail (GST_IS_CLOCK (clock), FALSE); g_return_val_if_fail (r_squared != NULL, FALSE); + GST_CLOCK_SLAVE_LOCK (clock); + clock->times[(4 * clock->time_index)] = slave; clock->times[(4 * clock->time_index) + 2] = master; @@ -1055,20 +1079,22 @@ gst_clock_add_observation (GstClock * clock, GstClockTime slave, if (clock->filling && clock->time_index < clock->window_threshold) goto filling; - do_linear_regression (clock, &m, &b, &xbase, r_squared); + do_linear_regression (clock, &m_num, &m_denom, &b, &xbase, r_squared); + + GST_CLOCK_SLAVE_UNLOCK (clock); GST_CAT_LOG_OBJECT (GST_CAT_CLOCK, clock, - "adjusting clock to m=%g, b=%" G_GINT64_FORMAT " (rsquared=%g)", m, b, - *r_squared); + "adjusting clock to m=%" G_GUINT64_FORMAT "/%" G_GUINT64_FORMAT ", b=%" + G_GUINT64_FORMAT " (rsquared=%g)", m_num, m_denom, b, *r_squared); - clock->internal_calibration = xbase; - clock->external_calibration = b; - clock->rate = m; + gst_clock_set_calibration (clock, xbase, b, m_num, m_denom); return TRUE; filling: { + GST_CLOCK_SLAVE_UNLOCK (clock); + return FALSE; } } @@ -1094,24 +1120,24 @@ gst_clock_set_property (GObject * object, guint prop_id, g_object_notify (object, "stats"); break; case PROP_WINDOW_SIZE: - GST_OBJECT_LOCK (clock); + GST_CLOCK_SLAVE_LOCK (clock); clock->window_size = g_value_get_int (value); clock->window_threshold = MIN (clock->window_threshold, clock->window_size); clock->times = g_renew (GstClockTime, clock->times, 4 * clock->window_size); - GST_OBJECT_UNLOCK (clock); + GST_CLOCK_SLAVE_UNLOCK (clock); break; case PROP_WINDOW_THRESHOLD: - GST_OBJECT_LOCK (clock); + GST_CLOCK_SLAVE_LOCK (clock); clock->window_threshold = MIN (g_value_get_int (value), clock->window_size); - GST_OBJECT_UNLOCK (clock); + GST_CLOCK_SLAVE_UNLOCK (clock); break; case PROP_TIMEOUT: - GST_OBJECT_LOCK (clock); + GST_CLOCK_SLAVE_LOCK (clock); clock->timeout = g_value_get_uint64 (value); - GST_OBJECT_UNLOCK (clock); + GST_CLOCK_SLAVE_UNLOCK (clock); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); @@ -1134,19 +1160,19 @@ gst_clock_get_property (GObject * object, guint prop_id, GST_OBJECT_UNLOCK (clock); break; case PROP_WINDOW_SIZE: - GST_OBJECT_LOCK (clock); + GST_CLOCK_SLAVE_LOCK (clock); g_value_set_int (value, clock->window_size); - GST_OBJECT_UNLOCK (clock); + GST_CLOCK_SLAVE_UNLOCK (clock); break; case PROP_WINDOW_THRESHOLD: - GST_OBJECT_LOCK (clock); + GST_CLOCK_SLAVE_LOCK (clock); g_value_set_int (value, clock->window_threshold); - GST_OBJECT_UNLOCK (clock); + GST_CLOCK_SLAVE_UNLOCK (clock); break; case PROP_TIMEOUT: - GST_OBJECT_LOCK (clock); + GST_CLOCK_SLAVE_LOCK (clock); g_value_set_uint64 (value, clock->timeout); - GST_OBJECT_UNLOCK (clock); + GST_CLOCK_SLAVE_UNLOCK (clock); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); diff --git a/gst/gstclock.h b/gst/gstclock.h index c8f36db..e9dd2af 100644 --- a/gst/gstclock.h +++ b/gst/gstclock.h @@ -37,6 +37,9 @@ G_BEGIN_DECLS #define GST_CLOCK_GET_CLASS(clock) (G_TYPE_INSTANCE_GET_CLASS ((clock), GST_TYPE_CLOCK, GstClockClass)) #define GST_CLOCK_CAST(clock) ((GstClock*)(clock)) +#define GST_CLOCK_SLAVE_LOCK(clock) g_mutex_lock (GST_CLOCK_CAST (clock)->slave_lock) +#define GST_CLOCK_SLAVE_UNLOCK(clock) g_mutex_unlock (GST_CLOCK_CAST (clock)->slave_lock) + /** * GstClockTime: * @@ -374,20 +377,25 @@ typedef enum { struct _GstClock { GstObject object; + GMutex *slave_lock; /* order: SLAVE_LOCK, OBJECT_LOCK */ + /*< protected >*/ /* with LOCK */ GstClockTime internal_calibration; GstClockTime external_calibration; - gdouble rate; + GstClockTime rate_numerator; + GstClockTime rate_denominator; GstClockTime last_time; GList *entries; GCond *entries_changed; - /*< private >*/ + /*< private >*/ /* with LOCK */ GstClockTime resolution; gboolean stats; /* for master/slave clocks */ GstClock *master; + + /* with SLAVE_LOCK */ gboolean filling; gint window_size; gint window_threshold; @@ -429,9 +437,14 @@ GstClockTime gst_clock_get_resolution (GstClock *clock); GstClockTime gst_clock_get_time (GstClock *clock); void gst_clock_set_calibration (GstClock *clock, GstClockTime internal, - GstClockTime external, gdouble rate); + GstClockTime external, + GstClockTime rate_num, + GstClockTime rate_denom); void gst_clock_get_calibration (GstClock *clock, GstClockTime *internal, - GstClockTime *external, gdouble *rate); + GstClockTime *external, + GstClockTime *rate_num, + GstClockTime *rate_denom); + /* master/slave clocks */ gboolean gst_clock_set_master (GstClock *clock, GstClock *master); GstClock* gst_clock_get_master (GstClock *clock); diff --git a/gst/net/gstnetclientclock.c b/gst/net/gstnetclientclock.c index faf01ec..f508f94 100644 --- a/gst/net/gstnetclientclock.c +++ b/gst/net/gstnetclientclock.c @@ -216,9 +216,9 @@ gst_net_client_clock_observe_times (GstNetClientClock * self, clock = GST_CLOCK_CAST (self); - GST_OBJECT_LOCK (self); gst_clock_add_observation (GST_CLOCK (self), local_avg, remote, &r_squared); + GST_CLOCK_SLAVE_LOCK (self); if (clock->filling) { self->current_timeout = 0; } else { @@ -227,7 +227,7 @@ gst_net_client_clock_observe_times (GstNetClientClock * self, (1e-3 / (1 - MIN (r_squared, 0.99999))) * GST_SECOND; self->current_timeout = MIN (self->current_timeout, clock->timeout); } - GST_OBJECT_UNLOCK (clock); + GST_CLOCK_SLAVE_UNLOCK (clock); return; @@ -520,7 +520,7 @@ gst_net_client_clock_new (gchar * name, const gchar * remote_address, /* update our internal time so get_time() give something around base_time. assume that the rate is 1 in the beginning. */ internal = gst_clock_get_internal_time (GST_CLOCK (ret)); - gst_clock_set_calibration (GST_CLOCK (ret), internal, base_time, 1.0); + gst_clock_set_calibration (GST_CLOCK (ret), internal, base_time, 1, 1); { GstClockTime now = gst_clock_get_time (GST_CLOCK (ret)); diff --git a/libs/gst/net/gstnetclientclock.c b/libs/gst/net/gstnetclientclock.c index faf01ec..f508f94 100644 --- a/libs/gst/net/gstnetclientclock.c +++ b/libs/gst/net/gstnetclientclock.c @@ -216,9 +216,9 @@ gst_net_client_clock_observe_times (GstNetClientClock * self, clock = GST_CLOCK_CAST (self); - GST_OBJECT_LOCK (self); gst_clock_add_observation (GST_CLOCK (self), local_avg, remote, &r_squared); + GST_CLOCK_SLAVE_LOCK (self); if (clock->filling) { self->current_timeout = 0; } else { @@ -227,7 +227,7 @@ gst_net_client_clock_observe_times (GstNetClientClock * self, (1e-3 / (1 - MIN (r_squared, 0.99999))) * GST_SECOND; self->current_timeout = MIN (self->current_timeout, clock->timeout); } - GST_OBJECT_UNLOCK (clock); + GST_CLOCK_SLAVE_UNLOCK (clock); return; @@ -520,7 +520,7 @@ gst_net_client_clock_new (gchar * name, const gchar * remote_address, /* update our internal time so get_time() give something around base_time. assume that the rate is 1 in the beginning. */ internal = gst_clock_get_internal_time (GST_CLOCK (ret)); - gst_clock_set_calibration (GST_CLOCK (ret), internal, base_time, 1.0); + gst_clock_set_calibration (GST_CLOCK (ret), internal, base_time, 1, 1); { GstClockTime now = gst_clock_get_time (GST_CLOCK (ret)); diff --git a/tests/check/libs/gstnetclientclock.c b/tests/check/libs/gstnetclientclock.c index e62a01e..8ff6bc4 100644 --- a/tests/check/libs/gstnetclientclock.c +++ b/tests/check/libs/gstnetclientclock.c @@ -50,18 +50,17 @@ GST_START_TEST (test_functioning) { GstNetTimeProvider *ntp; GstClock *client, *server; - GstClockTime basex, basey; + GstClockTime basex, basey, rate_num, rate_denom; GstClockTime servtime, clienttime; gint port; - gdouble rate; server = gst_system_clock_obtain (); fail_unless (server != NULL, "failed to get system clock"); /* move the clock ahead 100 seconds */ - gst_clock_get_calibration (server, &basex, &basey, &rate); + gst_clock_get_calibration (server, &basex, &basey, &rate_num, &rate_denom); basey += 100 * GST_SECOND; - gst_clock_set_calibration (server, basex, basey, rate); + gst_clock_set_calibration (server, basex, basey, rate_num, rate_denom); ntp = gst_net_time_provider_new (server, "127.0.0.1", 0); fail_unless (ntp != NULL, "failed to create network time provider"); -- 2.7.4