[Spice-devel] [PATCH 2/2] Avoid a race when receiving monitors config at startup

Jonathon Jongsma jjongsma at redhat.com
Wed Apr 1 08:41:52 PDT 2015


When a new DisplayChannelClient is connected, we send out a monitors
config message so that the client knows the current state of the display
configuration. In some circumstances (e.g. virt-viewer fullscreen
auto-conf), the client will send a monitors config message as soon as
the vdagent is connected. This can happen before the display channel
client is connected. So we can get into a situation where the
DisplayChannelClient is sending out a stale monitors configuration
message while the guest is in the process of re-configuring its
displays. This can cause misbehavior on the guest. To avoid this race,
we keep track of whether we have received a MonitorsConfig message from
the client prior to sending out our initial MonitorsConfig message from
the server. If so, we delay our initial message to give the server a
chance to handle the re-configuration requested by the client.
---
 server/main_channel.c | 13 +++++++++++++
 server/main_channel.h |  3 +++
 server/red_worker.c   | 38 ++++++++++++++++++++++++++++++++++++--
 server/reds.c         |  1 +
 4 files changed, 53 insertions(+), 2 deletions(-)

diff --git a/server/main_channel.c b/server/main_channel.c
index 54718ba..111e139 100644
--- a/server/main_channel.c
+++ b/server/main_channel.c
@@ -160,6 +160,7 @@ struct MainChannelClient {
     int mig_wait_prev_try_seamless;
     int init_sent;
     int seamless_mig_dst;
+    int monitors_config_recvd;
 };
 
 enum NetTestStage {
@@ -1112,6 +1113,7 @@ static MainChannelClient *main_channel_client_create(MainChannel *main_chan, Red
     spice_assert(mcc != NULL);
     mcc->connection_id = connection_id;
     mcc->bitrate_per_sec = ~0;
+    mcc->monitors_config_recvd = FALSE;
 #ifdef RED_STATISTICS
     if (!(mcc->ping_timer = core->timer_add(ping_timer_cb, NULL))) {
         spice_error("ping timer create failed");
@@ -1364,3 +1366,14 @@ int main_channel_migrate_src_complete(MainChannel *main_chan, int success)
    }
    return semi_seamless_count;
 }
+
+void main_channel_client_received_monitors_config(MainChannelClient *mcc)
+{
+    mcc->monitors_config_recvd = TRUE;
+}
+
+int main_channel_client_did_receive_monitors_config(MainChannelClient *mcc)
+{
+    return mcc->monitors_config_recvd;
+}
+
diff --git a/server/main_channel.h b/server/main_channel.h
index c8e9ade..267bcc1 100644
--- a/server/main_channel.h
+++ b/server/main_channel.h
@@ -95,4 +95,7 @@ void main_channel_migrate_dst_complete(MainChannelClient *mcc);
 void main_channel_push_name(MainChannelClient *mcc, const char *name);
 void main_channel_push_uuid(MainChannelClient *mcc, const uint8_t uuid[16]);
 
+void main_channel_client_received_monitors_config(MainChannelClient *mcc);
+int main_channel_client_did_receive_monitors_config(MainChannelClient *mcc);
+
 #endif
diff --git a/server/red_worker.c b/server/red_worker.c
index 5deb30b..dfe8e04 100644
--- a/server/red_worker.c
+++ b/server/red_worker.c
@@ -726,6 +726,7 @@ struct DisplayChannelClient {
     int use_mjpeg_encoder_rate_control;
     uint32_t streams_max_latency;
     uint64_t streams_max_bit_rate;
+    SpiceTimer *monitors_config_timer;
 };
 
 struct DisplayChannel {
@@ -9831,11 +9832,24 @@ static int display_channel_client_wait_for_init(DisplayChannelClient *dcc)
     return FALSE;
 }
 
+static void red_push_monitors_config_internal(DisplayChannelClient *dcc);
+static void red_schedule_push_monitors_config(DisplayChannelClient *dcc)
+{
+    spice_debug("Scheduling push_monitors_config for later to avoid race...");
+    if (!dcc->monitors_config_timer)
+        dcc->monitors_config_timer = spice_timer_queue_add((SpiceTimerFunc)red_push_monitors_config_internal, dcc);
+
+    spice_assert(dcc->monitors_config_timer);
+    spice_timer_set(dcc->monitors_config_timer, 1000);
+}
+
 static void on_new_display_channel_client(DisplayChannelClient *dcc)
 {
     DisplayChannel *display_channel = DCC_TO_DC(dcc);
     RedWorker *worker = display_channel->common.worker;
     RedChannelClient *rcc = &dcc->common.base;
+    RedClient *client = red_channel_client_get_client(rcc);
+    MainChannelClient *mcc = red_client_get_main(client);
 
     red_channel_client_push_set_ack(&dcc->common.base);
 
@@ -9851,7 +9865,16 @@ static void on_new_display_channel_client(DisplayChannelClient *dcc)
         red_current_flush(worker, 0);
         push_new_primary_surface(dcc);
         red_push_surface_image(dcc, 0);
-        red_push_monitors_config(dcc);
+        if(main_channel_client_did_receive_monitors_config(mcc)) {
+            /* if we received a monitors config message from the client before
+             * we could send out a monitors config message from the server,
+             * there is a potential for a race where we send a stale monitors
+             * configuration to the client. Schedule this message for later to
+             * avoid this race*/
+            red_schedule_push_monitors_config(dcc);
+        } else {
+            red_push_monitors_config(dcc);
+        }
         red_pipe_add_verb(rcc, SPICE_MSG_DISPLAY_MARK);
         red_disply_start_streams(dcc);
     }
@@ -10464,6 +10487,7 @@ DisplayChannelClient *display_channel_client_create(CommonChannel *common,
     }
     ring_init(&dcc->palette_cache_lru);
     dcc->palette_cache_available = CLIENT_PALETTE_CACHE_SIZE;
+    dcc->monitors_config_timer = NULL;
     return dcc;
 }
 
@@ -11262,7 +11286,8 @@ static void worker_update_monitors_config(RedWorker *worker,
     memcpy(monitors_config->heads, dev_monitors_config->heads, heads_size);
 }
 
-static void red_push_monitors_config(DisplayChannelClient *dcc)
+/* don't remove the timer */
+static void red_push_monitors_config_internal(DisplayChannelClient *dcc)
 {
     MonitorsConfig *monitors_config = DCC_TO_WORKER(dcc)->monitors_config;
 
@@ -11279,6 +11304,15 @@ static void red_push_monitors_config(DisplayChannelClient *dcc)
     red_channel_client_push(&dcc->common.base);
 }
 
+static void red_push_monitors_config(DisplayChannelClient *dcc)
+{
+    if (dcc->monitors_config_timer) {
+        spice_timer_remove(dcc->monitors_config_timer);
+        dcc->monitors_config_timer = NULL;
+    }
+    red_push_monitors_config_internal(dcc);
+}
+
 static void red_worker_push_monitors_config(RedWorker *worker)
 {
     DisplayChannelClient *dcc;
diff --git a/server/reds.c b/server/reds.c
index 4e36961..206702d 100644
--- a/server/reds.c
+++ b/server/reds.c
@@ -1033,6 +1033,7 @@ void reds_on_main_agent_data(MainChannelClient *mcc, void *message, size_t size)
     case AGENT_MSG_FILTER_DISCARD:
         return;
     case AGENT_MSG_FILTER_MONITORS_CONFIG:
+        main_channel_client_received_monitors_config(mcc);
         if (red_dispatcher_use_client_monitors_config()) {
             reds_on_main_agent_monitors_config(mcc, message, size);
             return;
-- 
2.1.0



More information about the Spice-devel mailing list