xref: /qemu/net/colo-compare.c (revision 814bb12a)
1 /*
2  * COarse-grain LOck-stepping Virtual Machines for Non-stop Service (COLO)
3  * (a.k.a. Fault Tolerance or Continuous Replication)
4  *
5  * Copyright (c) 2016 HUAWEI TECHNOLOGIES CO., LTD.
6  * Copyright (c) 2016 FUJITSU LIMITED
7  * Copyright (c) 2016 Intel Corporation
8  *
9  * Author: Zhang Chen <zhangchen.fnst@cn.fujitsu.com>
10  *
11  * This work is licensed under the terms of the GNU GPL, version 2 or
12  * later.  See the COPYING file in the top-level directory.
13  */
14 
15 #include "qemu/osdep.h"
16 #include "qemu/error-report.h"
17 #include "trace.h"
18 #include "qemu-common.h"
19 #include "qapi/qmp/qerror.h"
20 #include "qapi/error.h"
21 #include "net/net.h"
22 #include "net/eth.h"
23 #include "qom/object_interfaces.h"
24 #include "qemu/iov.h"
25 #include "qom/object.h"
26 #include "qemu/typedefs.h"
27 #include "net/queue.h"
28 #include "sysemu/char.h"
29 #include "qemu/sockets.h"
30 #include "qapi-visit.h"
31 #include "net/colo.h"
32 
33 #define TYPE_COLO_COMPARE "colo-compare"
34 #define COLO_COMPARE(obj) \
35     OBJECT_CHECK(CompareState, (obj), TYPE_COLO_COMPARE)
36 
37 #define COMPARE_READ_LEN_MAX NET_BUFSIZE
38 #define MAX_QUEUE_SIZE 1024
39 
40 /* TODO: Should be configurable */
41 #define REGULAR_PACKET_CHECK_MS 3000
42 
43 /*
44   + CompareState ++
45   |               |
46   +---------------+   +---------------+         +---------------+
47   |conn list      +--->conn           +--------->conn           |
48   +---------------+   +---------------+         +---------------+
49   |               |     |           |             |          |
50   +---------------+ +---v----+  +---v----+    +---v----+ +---v----+
51                     |primary |  |secondary    |primary | |secondary
52                     |packet  |  |packet  +    |packet  | |packet  +
53                     +--------+  +--------+    +--------+ +--------+
54                         |           |             |          |
55                     +---v----+  +---v----+    +---v----+ +---v----+
56                     |primary |  |secondary    |primary | |secondary
57                     |packet  |  |packet  +    |packet  | |packet  +
58                     +--------+  +--------+    +--------+ +--------+
59                         |           |             |          |
60                     +---v----+  +---v----+    +---v----+ +---v----+
61                     |primary |  |secondary    |primary | |secondary
62                     |packet  |  |packet  +    |packet  | |packet  +
63                     +--------+  +--------+    +--------+ +--------+
64 */
65 typedef struct CompareState {
66     Object parent;
67 
68     char *pri_indev;
69     char *sec_indev;
70     char *outdev;
71     CharBackend chr_pri_in;
72     CharBackend chr_sec_in;
73     CharBackend chr_out;
74     SocketReadState pri_rs;
75     SocketReadState sec_rs;
76 
77     /* connection list: the connections belonged to this NIC could be found
78      * in this list.
79      * element type: Connection
80      */
81     GQueue conn_list;
82     /* hashtable to save connection */
83     GHashTable *connection_track_table;
84     /* compare thread, a thread for each NIC */
85     QemuThread thread;
86     /* Timer used on the primary to find packets that are never matched */
87     QEMUTimer *timer;
88     QemuMutex timer_check_lock;
89 } CompareState;
90 
91 typedef struct CompareClass {
92     ObjectClass parent_class;
93 } CompareClass;
94 
95 enum {
96     PRIMARY_IN = 0,
97     SECONDARY_IN,
98 };
99 
100 static int compare_chr_send(CharBackend *out,
101                             const uint8_t *buf,
102                             uint32_t size);
103 
104 /*
105  * Return 0 on success, if return -1 means the pkt
106  * is unsupported(arp and ipv6) and will be sent later
107  */
108 static int packet_enqueue(CompareState *s, int mode)
109 {
110     ConnectionKey key;
111     Packet *pkt = NULL;
112     Connection *conn;
113 
114     if (mode == PRIMARY_IN) {
115         pkt = packet_new(s->pri_rs.buf, s->pri_rs.packet_len);
116     } else {
117         pkt = packet_new(s->sec_rs.buf, s->sec_rs.packet_len);
118     }
119 
120     if (parse_packet_early(pkt)) {
121         packet_destroy(pkt, NULL);
122         pkt = NULL;
123         return -1;
124     }
125     fill_connection_key(pkt, &key);
126 
127     conn = connection_get(s->connection_track_table,
128                           &key,
129                           &s->conn_list);
130 
131     if (!conn->processing) {
132         g_queue_push_tail(&s->conn_list, conn);
133         conn->processing = true;
134     }
135 
136     if (mode == PRIMARY_IN) {
137         if (g_queue_get_length(&conn->primary_list) <=
138                                MAX_QUEUE_SIZE) {
139             g_queue_push_tail(&conn->primary_list, pkt);
140         } else {
141             error_report("colo compare primary queue size too big,"
142                          "drop packet");
143         }
144     } else {
145         if (g_queue_get_length(&conn->secondary_list) <=
146                                MAX_QUEUE_SIZE) {
147             g_queue_push_tail(&conn->secondary_list, pkt);
148         } else {
149             error_report("colo compare secondary queue size too big,"
150                          "drop packet");
151         }
152     }
153 
154     return 0;
155 }
156 
157 /*
158  * The IP packets sent by primary and secondary
159  * will be compared in here
160  * TODO support ip fragment, Out-Of-Order
161  * return:    0  means packet same
162  *            > 0 || < 0 means packet different
163  */
164 static int colo_packet_compare(Packet *ppkt, Packet *spkt)
165 {
166     trace_colo_compare_ip_info(ppkt->size, inet_ntoa(ppkt->ip->ip_src),
167                                inet_ntoa(ppkt->ip->ip_dst), spkt->size,
168                                inet_ntoa(spkt->ip->ip_src),
169                                inet_ntoa(spkt->ip->ip_dst));
170 
171     if (ppkt->size == spkt->size) {
172         return memcmp(ppkt->data, spkt->data, spkt->size);
173     } else {
174         return -1;
175     }
176 }
177 
178 /*
179  * Called from the compare thread on the primary
180  * for compare tcp packet
181  * compare_tcp copied from Dr. David Alan Gilbert's branch
182  */
183 static int colo_packet_compare_tcp(Packet *spkt, Packet *ppkt)
184 {
185     struct tcphdr *ptcp, *stcp;
186     int res;
187 
188     trace_colo_compare_main("compare tcp");
189     if (ppkt->size != spkt->size) {
190         if (trace_event_get_state(TRACE_COLO_COMPARE_MISCOMPARE)) {
191             trace_colo_compare_main("pkt size not same");
192         }
193         return -1;
194     }
195 
196     ptcp = (struct tcphdr *)ppkt->transport_header;
197     stcp = (struct tcphdr *)spkt->transport_header;
198 
199     /*
200      * The 'identification' field in the IP header is *very* random
201      * it almost never matches.  Fudge this by ignoring differences in
202      * unfragmented packets; they'll normally sort themselves out if different
203      * anyway, and it should recover at the TCP level.
204      * An alternative would be to get both the primary and secondary to rewrite
205      * somehow; but that would need some sync traffic to sync the state
206      */
207     if (ntohs(ppkt->ip->ip_off) & IP_DF) {
208         spkt->ip->ip_id = ppkt->ip->ip_id;
209         /* and the sum will be different if the IDs were different */
210         spkt->ip->ip_sum = ppkt->ip->ip_sum;
211     }
212 
213     res = memcmp(ppkt->data + ETH_HLEN, spkt->data + ETH_HLEN,
214                 (spkt->size - ETH_HLEN));
215 
216     if (res != 0 && trace_event_get_state(TRACE_COLO_COMPARE_MISCOMPARE)) {
217         trace_colo_compare_pkt_info(inet_ntoa(ppkt->ip->ip_src),
218                                     inet_ntoa(ppkt->ip->ip_dst),
219                                     ntohl(ptcp->th_seq),
220                                     ntohl(ptcp->th_ack),
221                                     ntohl(stcp->th_seq),
222                                     ntohl(stcp->th_ack),
223                                     res, ptcp->th_flags,
224                                     stcp->th_flags,
225                                     ppkt->size,
226                                     spkt->size);
227 
228         qemu_hexdump((char *)ppkt->data, stderr,
229                      "colo-compare ppkt", ppkt->size);
230         qemu_hexdump((char *)spkt->data, stderr,
231                      "colo-compare spkt", spkt->size);
232     }
233 
234     return res;
235 }
236 
237 /*
238  * Called from the compare thread on the primary
239  * for compare udp packet
240  */
241 static int colo_packet_compare_udp(Packet *spkt, Packet *ppkt)
242 {
243     int ret;
244 
245     trace_colo_compare_main("compare udp");
246     ret = colo_packet_compare(ppkt, spkt);
247 
248     if (ret) {
249         trace_colo_compare_udp_miscompare("primary pkt size", ppkt->size);
250         qemu_hexdump((char *)ppkt->data, stderr, "colo-compare", ppkt->size);
251         trace_colo_compare_udp_miscompare("Secondary pkt size", spkt->size);
252         qemu_hexdump((char *)spkt->data, stderr, "colo-compare", spkt->size);
253     }
254 
255     return ret;
256 }
257 
258 /*
259  * Called from the compare thread on the primary
260  * for compare icmp packet
261  */
262 static int colo_packet_compare_icmp(Packet *spkt, Packet *ppkt)
263 {
264     int network_length;
265 
266     trace_colo_compare_main("compare icmp");
267     network_length = ppkt->ip->ip_hl * 4;
268     if (ppkt->size != spkt->size ||
269         ppkt->size < network_length + ETH_HLEN) {
270         return -1;
271     }
272 
273     if (colo_packet_compare(ppkt, spkt)) {
274         trace_colo_compare_icmp_miscompare("primary pkt size",
275                                            ppkt->size);
276         qemu_hexdump((char *)ppkt->data, stderr, "colo-compare",
277                      ppkt->size);
278         trace_colo_compare_icmp_miscompare("Secondary pkt size",
279                                            spkt->size);
280         qemu_hexdump((char *)spkt->data, stderr, "colo-compare",
281                      spkt->size);
282         return -1;
283     } else {
284         return 0;
285     }
286 }
287 
288 /*
289  * Called from the compare thread on the primary
290  * for compare other packet
291  */
292 static int colo_packet_compare_other(Packet *spkt, Packet *ppkt)
293 {
294     trace_colo_compare_main("compare other");
295     trace_colo_compare_ip_info(ppkt->size, inet_ntoa(ppkt->ip->ip_src),
296                                inet_ntoa(ppkt->ip->ip_dst), spkt->size,
297                                inet_ntoa(spkt->ip->ip_src),
298                                inet_ntoa(spkt->ip->ip_dst));
299     return colo_packet_compare(ppkt, spkt);
300 }
301 
302 static int colo_old_packet_check_one(Packet *pkt, int64_t *check_time)
303 {
304     int64_t now = qemu_clock_get_ms(QEMU_CLOCK_HOST);
305 
306     if ((now - pkt->creation_ms) > (*check_time)) {
307         trace_colo_old_packet_check_found(pkt->creation_ms);
308         return 0;
309     } else {
310         return 1;
311     }
312 }
313 
314 static void colo_old_packet_check_one_conn(void *opaque,
315                                            void *user_data)
316 {
317     Connection *conn = opaque;
318     GList *result = NULL;
319     int64_t check_time = REGULAR_PACKET_CHECK_MS;
320 
321     result = g_queue_find_custom(&conn->primary_list,
322                                  &check_time,
323                                  (GCompareFunc)colo_old_packet_check_one);
324 
325     if (result) {
326         /* do checkpoint will flush old packet */
327         /* TODO: colo_notify_checkpoint();*/
328     }
329 }
330 
331 /*
332  * Look for old packets that the secondary hasn't matched,
333  * if we have some then we have to checkpoint to wake
334  * the secondary up.
335  */
336 static void colo_old_packet_check(void *opaque)
337 {
338     CompareState *s = opaque;
339 
340     g_queue_foreach(&s->conn_list, colo_old_packet_check_one_conn, NULL);
341 }
342 
343 /*
344  * Called from the compare thread on the primary
345  * for compare connection
346  */
347 static void colo_compare_connection(void *opaque, void *user_data)
348 {
349     CompareState *s = user_data;
350     Connection *conn = opaque;
351     Packet *pkt = NULL;
352     GList *result = NULL;
353     int ret;
354 
355     while (!g_queue_is_empty(&conn->primary_list) &&
356            !g_queue_is_empty(&conn->secondary_list)) {
357         qemu_mutex_lock(&s->timer_check_lock);
358         pkt = g_queue_pop_tail(&conn->primary_list);
359         qemu_mutex_unlock(&s->timer_check_lock);
360         switch (conn->ip_proto) {
361         case IPPROTO_TCP:
362             result = g_queue_find_custom(&conn->secondary_list,
363                      pkt, (GCompareFunc)colo_packet_compare_tcp);
364             break;
365         case IPPROTO_UDP:
366             result = g_queue_find_custom(&conn->secondary_list,
367                      pkt, (GCompareFunc)colo_packet_compare_udp);
368             break;
369         case IPPROTO_ICMP:
370             result = g_queue_find_custom(&conn->secondary_list,
371                      pkt, (GCompareFunc)colo_packet_compare_icmp);
372             break;
373         default:
374             result = g_queue_find_custom(&conn->secondary_list,
375                      pkt, (GCompareFunc)colo_packet_compare_other);
376             break;
377         }
378 
379         if (result) {
380             ret = compare_chr_send(&s->chr_out, pkt->data, pkt->size);
381             if (ret < 0) {
382                 error_report("colo_send_primary_packet failed");
383             }
384             trace_colo_compare_main("packet same and release packet");
385             g_queue_remove(&conn->secondary_list, result->data);
386             packet_destroy(pkt, NULL);
387         } else {
388             /*
389              * If one packet arrive late, the secondary_list or
390              * primary_list will be empty, so we can't compare it
391              * until next comparison.
392              */
393             trace_colo_compare_main("packet different");
394             qemu_mutex_lock(&s->timer_check_lock);
395             g_queue_push_tail(&conn->primary_list, pkt);
396             qemu_mutex_unlock(&s->timer_check_lock);
397             /* TODO: colo_notify_checkpoint();*/
398             break;
399         }
400     }
401 }
402 
403 static int compare_chr_send(CharBackend *out,
404                             const uint8_t *buf,
405                             uint32_t size)
406 {
407     int ret = 0;
408     uint32_t len = htonl(size);
409 
410     if (!size) {
411         return 0;
412     }
413 
414     ret = qemu_chr_fe_write_all(out, (uint8_t *)&len, sizeof(len));
415     if (ret != sizeof(len)) {
416         goto err;
417     }
418 
419     ret = qemu_chr_fe_write_all(out, (uint8_t *)buf, size);
420     if (ret != size) {
421         goto err;
422     }
423 
424     return 0;
425 
426 err:
427     return ret < 0 ? ret : -EIO;
428 }
429 
430 static int compare_chr_can_read(void *opaque)
431 {
432     return COMPARE_READ_LEN_MAX;
433 }
434 
435 /*
436  * Called from the main thread on the primary for packets
437  * arriving over the socket from the primary.
438  */
439 static void compare_pri_chr_in(void *opaque, const uint8_t *buf, int size)
440 {
441     CompareState *s = COLO_COMPARE(opaque);
442     int ret;
443 
444     ret = net_fill_rstate(&s->pri_rs, buf, size);
445     if (ret == -1) {
446         qemu_chr_fe_set_handlers(&s->chr_pri_in, NULL, NULL, NULL,
447                                  NULL, NULL, true);
448         error_report("colo-compare primary_in error");
449     }
450 }
451 
452 /*
453  * Called from the main thread on the primary for packets
454  * arriving over the socket from the secondary.
455  */
456 static void compare_sec_chr_in(void *opaque, const uint8_t *buf, int size)
457 {
458     CompareState *s = COLO_COMPARE(opaque);
459     int ret;
460 
461     ret = net_fill_rstate(&s->sec_rs, buf, size);
462     if (ret == -1) {
463         qemu_chr_fe_set_handlers(&s->chr_sec_in, NULL, NULL, NULL,
464                                  NULL, NULL, true);
465         error_report("colo-compare secondary_in error");
466     }
467 }
468 
469 static void *colo_compare_thread(void *opaque)
470 {
471     GMainContext *worker_context;
472     GMainLoop *compare_loop;
473     CompareState *s = opaque;
474 
475     worker_context = g_main_context_new();
476 
477     qemu_chr_fe_set_handlers(&s->chr_pri_in, compare_chr_can_read,
478                              compare_pri_chr_in, NULL, s, worker_context, true);
479     qemu_chr_fe_set_handlers(&s->chr_sec_in, compare_chr_can_read,
480                              compare_sec_chr_in, NULL, s, worker_context, true);
481 
482     compare_loop = g_main_loop_new(worker_context, FALSE);
483 
484     g_main_loop_run(compare_loop);
485 
486     g_main_loop_unref(compare_loop);
487     g_main_context_unref(worker_context);
488     return NULL;
489 }
490 
491 static char *compare_get_pri_indev(Object *obj, Error **errp)
492 {
493     CompareState *s = COLO_COMPARE(obj);
494 
495     return g_strdup(s->pri_indev);
496 }
497 
498 static void compare_set_pri_indev(Object *obj, const char *value, Error **errp)
499 {
500     CompareState *s = COLO_COMPARE(obj);
501 
502     g_free(s->pri_indev);
503     s->pri_indev = g_strdup(value);
504 }
505 
506 static char *compare_get_sec_indev(Object *obj, Error **errp)
507 {
508     CompareState *s = COLO_COMPARE(obj);
509 
510     return g_strdup(s->sec_indev);
511 }
512 
513 static void compare_set_sec_indev(Object *obj, const char *value, Error **errp)
514 {
515     CompareState *s = COLO_COMPARE(obj);
516 
517     g_free(s->sec_indev);
518     s->sec_indev = g_strdup(value);
519 }
520 
521 static char *compare_get_outdev(Object *obj, Error **errp)
522 {
523     CompareState *s = COLO_COMPARE(obj);
524 
525     return g_strdup(s->outdev);
526 }
527 
528 static void compare_set_outdev(Object *obj, const char *value, Error **errp)
529 {
530     CompareState *s = COLO_COMPARE(obj);
531 
532     g_free(s->outdev);
533     s->outdev = g_strdup(value);
534 }
535 
536 static void compare_pri_rs_finalize(SocketReadState *pri_rs)
537 {
538     CompareState *s = container_of(pri_rs, CompareState, pri_rs);
539 
540     if (packet_enqueue(s, PRIMARY_IN)) {
541         trace_colo_compare_main("primary: unsupported packet in");
542         compare_chr_send(&s->chr_out, pri_rs->buf, pri_rs->packet_len);
543     } else {
544         /* compare connection */
545         g_queue_foreach(&s->conn_list, colo_compare_connection, s);
546     }
547 }
548 
549 static void compare_sec_rs_finalize(SocketReadState *sec_rs)
550 {
551     CompareState *s = container_of(sec_rs, CompareState, sec_rs);
552 
553     if (packet_enqueue(s, SECONDARY_IN)) {
554         trace_colo_compare_main("secondary: unsupported packet in");
555     } else {
556         /* compare connection */
557         g_queue_foreach(&s->conn_list, colo_compare_connection, s);
558     }
559 }
560 
561 
562 /*
563  * Return 0 is success.
564  * Return 1 is failed.
565  */
566 static int find_and_check_chardev(CharDriverState **chr,
567                                   char *chr_name,
568                                   Error **errp)
569 {
570     *chr = qemu_chr_find(chr_name);
571     if (*chr == NULL) {
572         error_setg(errp, "Device '%s' not found",
573                    chr_name);
574         return 1;
575     }
576 
577     if (!qemu_chr_has_feature(*chr, QEMU_CHAR_FEATURE_RECONNECTABLE)) {
578         error_setg(errp, "chardev \"%s\" is not reconnectable",
579                    chr_name);
580         return 1;
581     }
582 
583     return 0;
584 }
585 
586 /*
587  * Check old packet regularly so it can watch for any packets
588  * that the secondary hasn't produced equivalents of.
589  */
590 static void check_old_packet_regular(void *opaque)
591 {
592     CompareState *s = opaque;
593 
594     timer_mod(s->timer, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) +
595               REGULAR_PACKET_CHECK_MS);
596     /* if have old packet we will notify checkpoint */
597     /*
598      * TODO: Make timer handler run in compare thread
599      * like qemu_chr_add_handlers_full.
600      */
601     qemu_mutex_lock(&s->timer_check_lock);
602     colo_old_packet_check(s);
603     qemu_mutex_unlock(&s->timer_check_lock);
604 }
605 
606 /*
607  * Called from the main thread on the primary
608  * to setup colo-compare.
609  */
610 static void colo_compare_complete(UserCreatable *uc, Error **errp)
611 {
612     CompareState *s = COLO_COMPARE(uc);
613     CharDriverState *chr;
614     char thread_name[64];
615     static int compare_id;
616 
617     if (!s->pri_indev || !s->sec_indev || !s->outdev) {
618         error_setg(errp, "colo compare needs 'primary_in' ,"
619                    "'secondary_in','outdev' property set");
620         return;
621     } else if (!strcmp(s->pri_indev, s->outdev) ||
622                !strcmp(s->sec_indev, s->outdev) ||
623                !strcmp(s->pri_indev, s->sec_indev)) {
624         error_setg(errp, "'indev' and 'outdev' could not be same "
625                    "for compare module");
626         return;
627     }
628 
629     if (find_and_check_chardev(&chr, s->pri_indev, errp) ||
630         !qemu_chr_fe_init(&s->chr_pri_in, chr, errp)) {
631         return;
632     }
633 
634     if (find_and_check_chardev(&chr, s->sec_indev, errp) ||
635         !qemu_chr_fe_init(&s->chr_sec_in, chr, errp)) {
636         return;
637     }
638 
639     if (find_and_check_chardev(&chr, s->outdev, errp) ||
640         !qemu_chr_fe_init(&s->chr_out, chr, errp)) {
641         return;
642     }
643 
644     net_socket_rs_init(&s->pri_rs, compare_pri_rs_finalize);
645     net_socket_rs_init(&s->sec_rs, compare_sec_rs_finalize);
646 
647     g_queue_init(&s->conn_list);
648     qemu_mutex_init(&s->timer_check_lock);
649 
650     s->connection_track_table = g_hash_table_new_full(connection_key_hash,
651                                                       connection_key_equal,
652                                                       g_free,
653                                                       connection_destroy);
654 
655     sprintf(thread_name, "colo-compare %d", compare_id);
656     qemu_thread_create(&s->thread, thread_name,
657                        colo_compare_thread, s,
658                        QEMU_THREAD_JOINABLE);
659     compare_id++;
660 
661     /* A regular timer to kick any packets that the secondary doesn't match */
662     s->timer = timer_new_ms(QEMU_CLOCK_VIRTUAL, /* Only when guest runs */
663                             check_old_packet_regular, s);
664     timer_mod(s->timer, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) +
665                         REGULAR_PACKET_CHECK_MS);
666 
667     return;
668 }
669 
670 static void colo_compare_class_init(ObjectClass *oc, void *data)
671 {
672     UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc);
673 
674     ucc->complete = colo_compare_complete;
675 }
676 
677 static void colo_compare_init(Object *obj)
678 {
679     object_property_add_str(obj, "primary_in",
680                             compare_get_pri_indev, compare_set_pri_indev,
681                             NULL);
682     object_property_add_str(obj, "secondary_in",
683                             compare_get_sec_indev, compare_set_sec_indev,
684                             NULL);
685     object_property_add_str(obj, "outdev",
686                             compare_get_outdev, compare_set_outdev,
687                             NULL);
688 }
689 
690 static void colo_compare_finalize(Object *obj)
691 {
692     CompareState *s = COLO_COMPARE(obj);
693 
694     qemu_chr_fe_deinit(&s->chr_pri_in);
695     qemu_chr_fe_deinit(&s->chr_sec_in);
696     qemu_chr_fe_deinit(&s->chr_out);
697 
698     g_queue_free(&s->conn_list);
699 
700     if (qemu_thread_is_self(&s->thread)) {
701         /* compare connection */
702         g_queue_foreach(&s->conn_list, colo_compare_connection, s);
703         qemu_thread_join(&s->thread);
704     }
705 
706     if (s->timer) {
707         timer_del(s->timer);
708     }
709 
710     qemu_mutex_destroy(&s->timer_check_lock);
711 
712     g_free(s->pri_indev);
713     g_free(s->sec_indev);
714     g_free(s->outdev);
715 }
716 
717 static const TypeInfo colo_compare_info = {
718     .name = TYPE_COLO_COMPARE,
719     .parent = TYPE_OBJECT,
720     .instance_size = sizeof(CompareState),
721     .instance_init = colo_compare_init,
722     .instance_finalize = colo_compare_finalize,
723     .class_size = sizeof(CompareClass),
724     .class_init = colo_compare_class_init,
725     .interfaces = (InterfaceInfo[]) {
726         { TYPE_USER_CREATABLE },
727         { }
728     }
729 };
730 
731 static void register_types(void)
732 {
733     type_register_static(&colo_compare_info);
734 }
735 
736 type_init(register_types);
737