<div dir="ltr"><div><div>Hi,<br><br></div>Thanks for tracking this down. First, the patch doesn't compile.<br><br>This is indeed what I imagined, that the mm time is not monotonic over migrations on different machines (iirc, I was told the stream were stopped before migration)<br>

<br></div>Imho, there should be also a base mm time after migration completes, so that the client always has at least a monotonic mm time.<br><br><br><div class="gmail_extra"><div class="gmail_quote">
On Tue, Apr 30, 2013 at 12:19 AM, Yonit Halperin <span dir="ltr"><<a href="mailto:yhalperi@redhat.com" target="_blank">yhalperi@redhat.com</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">

rhbz#951664<br>
<br>
When the src and dst server have different mm-times, we can<br>
hit a case when for a short period of time the session mm-time and<br>
the video mm-time are not synced. If the video mm-time is much<br>
bigger than the session mm-time, the next stream rendering will be<br>
scheduled to (video-mm-time - session-mm-time), and even after<br>
the different mm-times would be synced, the video won't be rendered<br>
till the rendering timeout that was scheduled based on a wrong mm-time,<br>
takes place.<br>
This patch protects from such cases. You can find more details in the<br>
code comments.<br>
---<br>
 gtk/channel-display.c | 104 ++++++++++++++++++++++++++++++++++++++++++++++++--<br>
 1 file changed, 101 insertions(+), 3 deletions(-)<br>
<br>
diff --git a/gtk/channel-display.c b/gtk/channel-display.c<br>
index 9e03727..d8457e9 100644<br>
--- a/gtk/channel-display.c<br>
+++ b/gtk/channel-display.c<br>
@@ -116,6 +116,7 @@ static gboolean display_stream_render(display_stream *st);<br>
 static void spice_display_channel_reset(SpiceChannel *channel, gboolean migrating);<br>
 static void spice_display_channel_reset_capabilities(SpiceChannel *channel);<br>
 static void destroy_canvas(display_surface *surface);<br>
+static void _msg_in_unref_func(gpointer data, gpointer user_data);<br>
<br>
 /* ------------------------------------------------------------------ */<br>
<br>
@@ -1089,20 +1090,27 @@ static gboolean display_stream_schedule(display_stream *st)<br>
     guint32 time, d;<br>
     SpiceStreamDataHeader *op;<br>
     SpiceMsgIn *in;<br>
-    if (st->timeout)<br>
-        return TRUE;<br>
<br>
+    spice_debug("%s", __FUNCTION__);<br></blockquote><div><br></div><div>We use g_log functions or SPICE_DEBUG in spice-gtk.<br> <br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">


+    if (st->timeout) {<br>
+        return TRUE;<br>
+    }<br></blockquote><div><br></div><div>This style change isn't necessary. <br> <br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
     time = spice_session_get_mm_time(spice_channel_get_session(st->channel));<br>
     in = g_queue_peek_head(st->msgq);<br>
     g_return_val_if_fail(in != NULL, TRUE);<br>
<br>
     op = spice_msg_in_parsed(in);<br>
+<br></blockquote><div> <br></div><div>extra line<br><br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
     if (time < op->multi_media_time) {<br>
         d = op->multi_media_time - time;<br>
         SPICE_DEBUG("scheduling next stream render in %u ms", d);<br>
         st->timeout = g_timeout_add(d, (GSourceFunc)display_stream_render, st);<br>
+        st->timeout_monotonic_expiry = g_get_monotonic_time()/1000 + d;<br>
         return TRUE;<br>
     } else {<br>
+        SPICE_DEBUG("%s: rendering too late by %u ms (ts: %u, mmtime: %u), droping ",<br>
+                    __FUNCTION__, time - op->multi_media_time,<br>
+                    op->multi_media_time, time);<br>
         in = g_queue_pop_head(st->msgq);<br>
         spice_msg_in_unref(in);<br>
         st->num_drops_on_playback++;<br>
@@ -1303,7 +1311,95 @@ static void display_update_stream_report(SpiceDisplayChannel *channel, uint32_t<br>
     }<br>
 }<br>
<br>
+#define MM_TIME_RESET_TEST_DELTA_THRESH 200 // milliseconds<br>
+<br>
+/*<br>
+ * Migration can occur between 2 spice-servers with different mm-times.<br>
+ * Then, the following cases can happen after migration completes:<br>
+ * (We refer to src/dst-time as the mm-times on the src/dst servers):<br>
+ *<br>
+ * (case 1) Frames with time ~= dst-time arrive to the client before the<br>
+ *          playback-channel updates the session's mm-time (i.e., the mm_time<br>
+ *          of the session is still based on the src-time).<br>
+ *     (a) If src-time < dst-time:<br>
+ *         display_stream_schedule schedules the next rendering to<br>
+ *         ~(dst-time - src-time) milliseconds from now.<br>
+ *         Since we assume monotonic mm_time, display_stream_schedule,<br>
+ *         returns immediately when a rendering timeout<br>
+ *         has already been set, and doesn't update the timeout,<br>
+ *         even after the mm_time is updated.<br>
+ *         When src-time << dst-time, a significant video frames loss will occur.<br>
+ *     (b) If src-time > dst-time<br>
+ *         Frames will be dropped till the mm-time will be updated.<br>
+ * (case 2) mm-time is synced with dst-time, but frames that were in the command<br>
+ *         ring during migration still arrive (such frames hold src-time).<br>
+ *    (a) If src-time < dst-time<br>
+ *        The frames that hold src-time will be dropped, since their<br>
+ *        mm_time < session-mm_time. But all the new frames that are generated in<br>
+ *        the driver after migration, will be rendered appropriately.<br>
+ *    (b) If src-time > dst-time<br>
+ *        Similar consequences as in 1 (a)<br>
+ * case 2 is less likely, since at takes at least 20 frames till the dst-server re-identifies<br>
+ * the video stream and starts sending stream data<br>
+ *<br>
+ * display_stream_test_mm_time_reset tests for cases (1.a) and (2.b), and when<br>
+ * they are encountered, it resets the stream.<br>
+ */<br>
+static void display_stream_test_mm_time_reset(display_stream *st, SpiceMsgIn *new_frame_msg, guint32 mm_time)<br>
+{<br>
+    SpiceStreamDataHeader *new_op;<br>
+    SpiceMsgIn *tail_msg;<br>
+    SpiceStreamDataHeader *tail_op;<br>
+<br>
+    SPICE_DEBUG("%s", __FUNCTION__);<br>
+    g_return_if_fail(new_frame_msg != NULL);<br>
+    tail_msg = g_queue_peek_head(st->msgq);<br></blockquote><div><br></div><div>Why call "tail" the "head"?<br><br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">


+    if (!tail_msg) {<br>
+        return;<br>
+    }<br>
+    tail_op = spice_msg_in_parsed(tail_msg);<br>
+    new_op = spice_msg_in_parsed(new_frame_msg);<br>
+<br>
+    // case (2.b) (see the comments above).<br>
+    if (new_op->multi_media_time < tail_op->multi_media_time) {<br></blockquote><div><br></div><div>I think this check should be made on the tail, indeed. Perhaps you changed it? Why?<br> <br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">


+        SPICE_DEBUG("new-frame-time < tail-frame-time (%u < %u):"<br>
+                    " reseting stream",<br>
+                    new_op->multi_media_time,<br>
+                    tail_op->multi_media_time);<br>
+        if (st->timeout != 0) {<br>
+            g_source_remove(st->timeout);<br>
+            st->timeout = 0;<br>
+        }<br>
+        g_queue_foreach(st->msgq, _msg_in_unref_func, NULL);<br>
+        g_queue_clear(st->msgq);<br>
+    } else if (st->timeout) {<br>
+        SpiceMsgIn *head_msg;<br>
+        SpiceStreamDataHeader *head_op;<br>
+        guint32 head_render_time;<br>
+<br>
+        head_msg = g_queue_peek_head(st->msgq);<br>
+        g_return_if_fail(head_msg != NULL);<br>
+        head_op = spice_msg_in_parsed(head_msg);<br>
+<br>
+        head_render_time = g_get_monotonic_time()/1000;<br>
+        if (mm_time < head_op->multi_media_time) {<br>
+            head_render_time += head_op->multi_media_time - mm_time;<br>
+        }<br>
+        // case (1.a)<br>
+        if (head_render_time + MM_TIME_RESET_TEST_DELTA_THRESH < st->timeout_monotonic_expiry) {<br></blockquote><div><br></div><div>I don't understand how you check that the mm time has moved forward (by comparing againt current monotonic time + threshold). A naive approach would instead just find large discontinuities in the queued frames.<br>
<br>Since the clock is given by the audio playback, shouldn' t the discontinuity be triggered there, and flush & re-scheduling the frame by some signal handler?<br><br></div><div>
 <br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
+            SPICE_DEBUG("mm-time has moved forward: rescheduling "<br>
+                        "stream rendering (old-timout %u new-timeout %u diff %u",<br>
+                        st->timeout_monotonic_expiry,<br>
+                        head_render_time,<br>
+                        st->timeout_monotonic_expiry - head_render_time);<br>
+            g_source_remove(st->timeout);<br>
+            st->timeout = 0;<br>
+        }<br>
+    }<br>
+}<br>
+<br>
 #define STREAM_PLAYBACK_SYNC_DROP_SEQ_LEN_LIMIT 5<br>
+<br>
 /* coroutine context */<br>
 static void display_handle_stream_data(SpiceChannel *channel, SpiceMsgIn *in)<br>
 {<br>
@@ -1349,8 +1445,10 @@ static void display_handle_stream_data(SpiceChannel *channel, SpiceMsgIn *in)<br>
     } else {<br>
         CHANNEL_DEBUG(channel, "video latency: %d", latency);<br>
         spice_msg_in_ref(in);<br>
+        display_stream_test_mm_time_reset(st, in, mmtime);<br>
         g_queue_push_tail(st->msgq, in);<br>
-        display_stream_schedule(st);<br>
+        while (!display_stream_schedule(st)) {<br>
+        }<br>
         if (st->cur_drops_seq_stats.len) {<br>
             st->cur_drops_seq_stats.duration = op->multi_media_time -<br>
                                                st->cur_drops_seq_stats.start_mm_time;<br>
<span><font color="#888888">--<br>
1.8.1.4<br>
<br>
_______________________________________________<br>
Spice-devel mailing list<br>
<a href="mailto:Spice-devel@lists.freedesktop.org" target="_blank">Spice-devel@lists.freedesktop.org</a><br>
<a href="http://lists.freedesktop.org/mailman/listinfo/spice-devel" target="_blank">http://lists.freedesktop.org/mailman/listinfo/spice-devel</a><br>
</font></span></blockquote></div><br><br clear="all"><br>-- <br>Marc-André Lureau
</div></div>