1017dd89624a0ae1c856441575cc0ee7080bde10
[guestfs-browser.git] / slave.ml
1 (* Guestfs Browser.
2  * Copyright (C) 2010 Red Hat Inc.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17  *)
18
19 open Utils
20
21 module C = Libvirt.Connect
22 module Cond = Condition
23 module D = Libvirt.Domain
24 module G = Guestfs
25 module M = Mutex
26 module Q = Queue
27
28 type 'a callback = 'a -> unit
29
30 (* The commands. *)
31 type command =
32   | Exit_thread
33   | Connect of string option * unit callback
34   | Get_domains of domain list callback
35
36 and domain = {
37   dom_id : int;
38   dom_name : string;
39   dom_state : D.state;
40 }
41
42 let no_callback _ = ()
43
44 let failure_hook = ref (fun _ -> ())
45 let busy_hook = ref (fun _ -> ())
46 let idle_hook = ref (fun _ -> ())
47
48 let set_failure_hook cb = failure_hook := cb
49 let set_busy_hook cb = busy_hook := cb
50 let set_idle_hook cb = idle_hook := cb
51
52 (* Execute a function, while holding a mutex.  If the function
53  * fails, ensure we release the mutex before rethrowing the
54  * exception.
55  *)
56 let with_lock m f =
57   M.lock m;
58   let r = try Left (f ()) with exn -> Right exn in
59   M.unlock m;
60   match r with
61   | Left r -> r
62   | Right exn -> raise exn
63
64 (* The queue of commands, and a lock and condition to protect it. *)
65 let q = Q.create ()
66 let q_lock = M.create ()
67 let q_cond = Cond.create ()
68
69 (* Send a command message to the slave thread. *)
70 let send_to_slave cmd =
71   debug "sending message %s to slave thread ..." (string_of_command cmd)
72   with_lock q_lock (
73     fun () ->
74       Q.push cmd q;
75       Cond.signal q_cond
76   )
77
78 let discard_command_queue () = with_lock q_lock (fun () -> Q.clear q)
79
80 let connect uri cb = send_to_slave (Connect (uri, cb))
81 let get_domains cb = send_to_slave (Get_domains cb)
82
83 (*----- Slave thread starts here -----*)
84
85 (* Set this to true to exit the thread. *)
86 let quit = ref false
87
88 let rec loop () =
89   (* Get the next command. *)
90   let cmd =
91     with_lock q_lock (
92       fun () ->
93         while Q.is_empty q do
94           Cond.wait q_cond q_lock
95         done;
96         Q.pop q
97     ) in
98
99   debug "slave thread processing command %s ..." (string_of_command cmd);
100
101   (try
102      call_callback !busy_hook ();
103      execute_command cmd;
104      call_callback !idle_hook ();
105    with exn ->
106      (* If a command fails, clear the command queue and run the
107       * failure hook in the main thread.
108       *)
109      call_callback !idle_hook ();
110      discard_command_queue ();
111      call_callback !failure_hook exn
112   );
113
114   if !quit then Thread.exit ();
115   loop ()
116
117 and execute_command = function
118   | Exit_thread ->
119       quit := true;
120       disconnect_all ()
121
122   | Connect (uri, cb) ->
123       disconnect_all ();
124       conn := Some (C.connect_readonly ?uri ());
125       call_callback cb ()
126
127   | Get_domains cb ->
128       let conn = get_conn () in
129       let doms = D.get_domains conn [D.ListAll] in
130       let doms = List.map (
131         fun d ->
132           D.get_id d, D.get_name d, (D.get_info d).D.state
133       ) doms in
134       call_callback cb doms
135
136 (* Call a callback function or hook in the main thread. *)
137 and call_callback cb arg =
138   GtkThread.async cb arg
139
140 (* Expect to be connected, and return the current libvirt connection. *)
141 let get_conn () =
142   match !conn with
143   | Some conn -> conn
144   | None -> failwith "not connected to libvirt"
145
146 (* Close all libvirt and libguestfs handles. *)
147 and disconnect_all () =
148   (match !conn with Some conn -> C.close conn | None -> ());
149   conn := None
150
151 (* Start up one slave thread. *)
152 let slave_thread = Thread.create loop ()
153
154 (* Note the following function is called from the main thread. *)
155 let exit_thread () =
156   discard_command_queue ();
157   send_to_slave Exit_thread;
158   Thread.join slave_thread