From 79285bf008bcff28206ba8f48816304c076fffb1 Mon Sep 17 00:00:00 2001 From: Stephen Cheng Date: Fri, 12 Sep 2025 16:36:16 +0800 Subject: [PATCH 1/5] CP-54393: VNC console idle timeout This commit adds idle timeout feature for vnc console connections. Key changes: - Add idle timeout detection by monitoring RFB keyEvent and pointerEvent. - Add callback function to `proxy` to parse the RFB messages and determine if the connection is idle or not. Signed-off-by: Stephen Cheng --- .../lib/xapi-stdext-unix/unixext.ml | 35 +++++++- .../lib/xapi-stdext-unix/unixext.mli | 7 +- ocaml/xapi/console.ml | 90 ++++++++++++++++++- ocaml/xapi/xapi_globs.ml | 13 +++ 4 files changed, 137 insertions(+), 8 deletions(-) diff --git a/ocaml/libs/xapi-stdext/lib/xapi-stdext-unix/unixext.ml b/ocaml/libs/xapi-stdext/lib/xapi-stdext-unix/unixext.ml index 893a7e4d9bc..35e91d602e2 100644 --- a/ocaml/libs/xapi-stdext/lib/xapi-stdext-unix/unixext.ml +++ b/ocaml/libs/xapi-stdext/lib/xapi-stdext-unix/unixext.ml @@ -338,7 +338,8 @@ module CBuf = struct in let read = Unix.read fd x.buffer next len in if read = 0 then x.r_closed <- true ; - x.len <- x.len + read + x.len <- x.len + read ; + (x.buffer, read, next) end exception Process_still_alive @@ -381,11 +382,21 @@ let with_polly f = let finally () = Polly.close polly in Xapi_stdext_pervasives.Pervasiveext.finally (fun () -> f polly) finally -let proxy (a : Unix.file_descr) (b : Unix.file_descr) = +exception Close_proxy + +let proxy ?(should_close : (bytes * int * int -> bool) option = None) + ?(poll_timeout = -1) (a : Unix.file_descr) (b : Unix.file_descr) = let size = 64 * 1024 in (* [a'] is read from [a] and will be written to [b] *) (* [b'] is read from [b] and will be written to [a] *) let a' = CBuf.empty size and b' = CBuf.empty size in + + let close_proxy () = + Unix.shutdown a Unix.SHUTDOWN_ALL ; + Unix.shutdown b Unix.SHUTDOWN_ALL ; + raise Close_proxy + in + Unix.set_nonblock a ; Unix.set_nonblock b ; with_polly @@ fun polly -> @@ -413,12 +424,28 @@ let proxy (a : Unix.file_descr) (b : Unix.file_descr) = Polly.upd polly a a_events ; if Polly.Events.(b_events <> empty) then Polly.upd polly b b_events ; - Polly.wait_fold polly 4 (-1) () (fun _polly fd events () -> + Polly.wait_fold polly 4 poll_timeout () (fun _polly fd events () -> (* Do the writing before the reading *) if Polly.Events.(test out events) then if a = fd then CBuf.write b' a else CBuf.write a' b ; if Polly.Events.(test inp events) then - if a = fd then CBuf.read a' a else CBuf.read b' b + if a = fd then + ignore (CBuf.read a' a) + else + match should_close with + | Some cb -> + let buf, read_len, offset = CBuf.read b' b in + if cb (buf, read_len, offset) then + close_proxy () + | None -> + ignore (CBuf.read b' b) + ) ; + ( match should_close with + | Some cb -> + if cb (Bytes.empty, 0, 0) then + close_proxy () + | None -> + () ) ; (* If there's nothing else to read or write then signal the other end *) List.iter diff --git a/ocaml/libs/xapi-stdext/lib/xapi-stdext-unix/unixext.mli b/ocaml/libs/xapi-stdext/lib/xapi-stdext-unix/unixext.mli index 3db652bd2a3..35beb12f801 100644 --- a/ocaml/libs/xapi-stdext/lib/xapi-stdext-unix/unixext.mli +++ b/ocaml/libs/xapi-stdext/lib/xapi-stdext-unix/unixext.mli @@ -126,7 +126,12 @@ exception Process_still_alive val kill_and_wait : ?signal:int -> ?timeout:float -> int -> unit -val proxy : Unix.file_descr -> Unix.file_descr -> unit +val proxy : + ?should_close:(bytes * int * int -> bool) option + -> ?poll_timeout:int + -> Unix.file_descr + -> Unix.file_descr + -> unit val really_read : Unix.file_descr -> bytes -> int -> int -> unit diff --git a/ocaml/xapi/console.ml b/ocaml/xapi/console.ml index dcf95ef6106..3f657aa5b26 100644 --- a/ocaml/xapi/console.ml +++ b/ocaml/xapi/console.ml @@ -126,7 +126,86 @@ let address_of_console __context console : address option = ) ; address_option -let real_proxy' vnc_port s = +module Console_idle_monitor = struct + let get_console_idle_timeout_value ~__context ~vm = + try + let idle_timeout = + if Db.VM.get_is_control_domain ~__context ~self:vm then + let host = Helpers.get_localhost ~__context in + Db.Host.get_console_idle_timeout ~__context ~self:host + else + let pool = Helpers.get_pool ~__context in + Db.Pool.get_vm_console_idle_timeout ~__context ~self:pool + in + idle_timeout + with _ -> 0L (* Default: no timeout *) + + let has_user_activity messages = + List.exists + (function + | Rfb_client_msgtype_parser.KeyEvent + | Rfb_client_msgtype_parser.PointerEvent + | Rfb_client_msgtype_parser.QEMUClientMessage -> + true + | _ -> + false + ) + messages + + let is_idle_timeout ~idle_timeout_seconds ~last_activity = + idle_timeout_seconds > 0. + && + let elapsed = Mtime_clock.count !last_activity in + Mtime.Span.to_float_ns elapsed /. 1e9 > idle_timeout_seconds + + (* Create an idle timeout callback for the console, + if idle_timeout, then close the proxy *) + let create_idle_timeout_callback ~__context ~vm () = + let rfb_parser = ref (Some (Rfb_client_msgtype_parser.create ())) in + let last_activity = ref (Mtime_clock.counter ()) in + (* Return true for idle timeout to close the proxy, otherwise keep the proxy open *) + fun (buf, read_len, offset) -> + match !rfb_parser with + | None -> + false + | Some parser -> ( + try + let data = Bytes.sub_string buf offset read_len in + match parser data with + | Ok messages -> + if has_user_activity messages then + last_activity := Mtime_clock.counter () ; + + (* The idle_timeout config may change during running the callback, + so need to get the latest *) + let idle_timeout_seconds = + Int64.to_float (get_console_idle_timeout_value ~__context ~vm) + in + if is_idle_timeout ~idle_timeout_seconds ~last_activity then ( + debug "Idle timeout exceeded! (timeout: %.1fs)" + idle_timeout_seconds ; + rfb_parser := None ; + true + ) else + false + | Error error_msg -> + debug "RFB parse error: %s" error_msg ; + rfb_parser := None ; + false + with e -> + debug "Exception in RFB parser: %s" (Printexc.to_string e) ; + rfb_parser := None ; + false + ) +end + +let get_poll_timeout = + if !Xapi_globs.poll_timeout_sec < 0 then + -1 + else + !Xapi_globs.poll_timeout_sec * 1000 (* convert to milliseconds *) + +let real_proxy' ~__context ~vm vnc_port s = try Http_svr.headers s (Http.http_200_ok ()) ; let vnc_sock = @@ -141,7 +220,12 @@ let real_proxy' vnc_port s = debug "Connected; running proxy (between fds: %d and %d)" (Unixext.int_of_file_descr vnc_sock) (Unixext.int_of_file_descr s') ; - Unixext.proxy vnc_sock s' ; + + let poll_timeout = get_poll_timeout in + let idle_timeout_callback = + Some (Console_idle_monitor.create_idle_timeout_callback ~__context ~vm ()) + in + Unixext.proxy ~should_close:idle_timeout_callback ~poll_timeout vnc_sock s' ; debug "Proxy exited" with exn -> debug "error: %s" (ExnHelper.string_of_exn exn) @@ -153,7 +237,7 @@ let real_proxy __context vm _ _ vnc_port s = in if Connection_limit.try_add vm_id is_limit_enabled then finally (* Ensure we drop the vm connection count if exceptions occur *) - (fun () -> real_proxy' vnc_port s) + (fun () -> real_proxy' ~__context ~vm vnc_port s) (fun () -> Connection_limit.drop vm_id) else Http_svr.headers s (Http.http_503_service_unavailable ()) diff --git a/ocaml/xapi/xapi_globs.ml b/ocaml/xapi/xapi_globs.ml index 3688478dce8..0e37fa348c2 100644 --- a/ocaml/xapi/xapi_globs.ml +++ b/ocaml/xapi/xapi_globs.ml @@ -450,6 +450,11 @@ let xha_timeout = "timeout" let message_limit = ref 10000 +(* The timeout (in seconds) for event polling in the proxy loop. + If set to a positive value, the poll will wake up periodically, + which is useful for implementing features like idle timeout or periodic inspection of proxy buffers. *) +let poll_timeout_sec = ref 5 + let xapi_message_script = ref "mail-alarm" (* Emit a warning if more than this amount of clock skew detected *) @@ -1783,6 +1788,14 @@ let other_options = , (fun () -> string_of_float !vm_sysprep_wait) , "Time in seconds to wait for VM to recognise inserted CD" ) + ; ( "poll_timeout_sec" + , Arg.Set_int poll_timeout_sec + , (fun () -> string_of_int !poll_timeout_sec) + , "Timeout (in seconds) for event polling in network proxy loops. When \ + positive, the proxy will wake up periodically to check tasks like vnc \ + idle timeouts or perform other maintenance tasks. Set to -1 to wait \ + indefinitely for network events without periodic wake-ups." + ) ] (* The options can be set with the variable xapiflags in /etc/sysconfig/xapi. From ba365732f914d32822a3d3779b9f2b4002d7d692 Mon Sep 17 00:00:00 2001 From: Stephen Cheng Date: Thu, 9 Oct 2025 16:19:27 +0800 Subject: [PATCH 2/5] fix Signed-off-by: Stephen Cheng --- .../lib/xapi-stdext-unix/unixext.ml | 35 +++++------ .../lib/xapi-stdext-unix/unixext.mli | 2 +- ocaml/xapi/console.ml | 59 ++++++++++++------- 3 files changed, 54 insertions(+), 42 deletions(-) diff --git a/ocaml/libs/xapi-stdext/lib/xapi-stdext-unix/unixext.ml b/ocaml/libs/xapi-stdext/lib/xapi-stdext-unix/unixext.ml index 35e91d602e2..8f29e8f1082 100644 --- a/ocaml/libs/xapi-stdext/lib/xapi-stdext-unix/unixext.ml +++ b/ocaml/libs/xapi-stdext/lib/xapi-stdext-unix/unixext.ml @@ -384,8 +384,8 @@ let with_polly f = exception Close_proxy -let proxy ?(should_close : (bytes * int * int -> bool) option = None) - ?(poll_timeout = -1) (a : Unix.file_descr) (b : Unix.file_descr) = +let proxy ?should_close ?(poll_timeout = -1) (a : Unix.file_descr) + (b : Unix.file_descr) = let size = 64 * 1024 in (* [a'] is read from [a] and will be written to [b] *) (* [b'] is read from [b] and will be written to [a] *) @@ -424,29 +424,22 @@ let proxy ?(should_close : (bytes * int * int -> bool) option = None) Polly.upd polly a a_events ; if Polly.Events.(b_events <> empty) then Polly.upd polly b b_events ; - Polly.wait_fold polly 4 poll_timeout () (fun _polly fd events () -> + Polly.wait_fold polly 4 poll_timeout (Bytes.empty, 0, 0) + (fun _polly fd events acc -> (* Do the writing before the reading *) if Polly.Events.(test out events) then if a = fd then CBuf.write b' a else CBuf.write a' b ; if Polly.Events.(test inp events) then - if a = fd then - ignore (CBuf.read a' a) - else - match should_close with - | Some cb -> - let buf, read_len, offset = CBuf.read b' b in - if cb (buf, read_len, offset) then - close_proxy () - | None -> - ignore (CBuf.read b' b) - ) ; - ( match should_close with - | Some cb -> - if cb (Bytes.empty, 0, 0) then - close_proxy () - | None -> - () - ) ; + if a = fd then ( + ignore (CBuf.read a' a) ; + acc + ) else + CBuf.read b' b + else + acc + ) + |> fun data -> + Option.iter (fun cb -> if cb data then close_proxy ()) should_close ; (* If there's nothing else to read or write then signal the other end *) List.iter (fun (buf, fd) -> diff --git a/ocaml/libs/xapi-stdext/lib/xapi-stdext-unix/unixext.mli b/ocaml/libs/xapi-stdext/lib/xapi-stdext-unix/unixext.mli index 35beb12f801..19a5a32bdfb 100644 --- a/ocaml/libs/xapi-stdext/lib/xapi-stdext-unix/unixext.mli +++ b/ocaml/libs/xapi-stdext/lib/xapi-stdext-unix/unixext.mli @@ -127,7 +127,7 @@ exception Process_still_alive val kill_and_wait : ?signal:int -> ?timeout:float -> int -> unit val proxy : - ?should_close:(bytes * int * int -> bool) option + ?should_close:(bytes * int * int -> bool) -> ?poll_timeout:int -> Unix.file_descr -> Unix.file_descr diff --git a/ocaml/xapi/console.ml b/ocaml/xapi/console.ml index 3f657aa5b26..ba1e72f25c5 100644 --- a/ocaml/xapi/console.ml +++ b/ocaml/xapi/console.ml @@ -140,7 +140,7 @@ module Console_idle_monitor = struct idle_timeout with _ -> 0L (* Default: no timeout *) - let has_user_activity messages = + let is_active messages = List.exists (function | Rfb_client_msgtype_parser.KeyEvent @@ -152,17 +152,26 @@ module Console_idle_monitor = struct ) messages - let is_idle_timeout ~idle_timeout_seconds ~last_activity = - idle_timeout_seconds > 0. - && - let elapsed = Mtime_clock.count !last_activity in - Mtime.Span.to_float_ns elapsed /. 1e9 > idle_timeout_seconds + let timed_out ~idle_timeout_seconds ~last_activity = + match idle_timeout_seconds with + | Some timeout when timeout > 0. -> + let elapsed = Mtime_clock.count !last_activity in + Mtime.Span.to_float_ns elapsed /. 1e9 > timeout + | _ -> + false (* Create an idle timeout callback for the console, if idle_timeout, then close the proxy *) - let create_idle_timeout_callback ~__context ~vm () = + let create_idle_timeout_callback ~__context ~vm = let rfb_parser = ref (Some (Rfb_client_msgtype_parser.create ())) in let last_activity = ref (Mtime_clock.counter ()) in + let is_control_domain = Db.VM.get_is_control_domain ~__context ~self:vm in + let timeout_source = + if is_control_domain then + `Host (Helpers.get_localhost ~__context) + else + `Pool (Helpers.get_pool ~__context) + in (* Return true for idle timeout to close the proxy, otherwise keep the proxy open *) fun (buf, read_len, offset) -> match !rfb_parser with @@ -173,17 +182,30 @@ module Console_idle_monitor = struct let data = Bytes.sub_string buf offset read_len in match parser data with | Ok messages -> - if has_user_activity messages then + if is_active messages then last_activity := Mtime_clock.counter () ; (* The idle_timeout config may change during running the callback, so need to get the latest *) let idle_timeout_seconds = - Int64.to_float (get_console_idle_timeout_value ~__context ~vm) + try + let timeout_value = + match timeout_source with + | `Host host -> + Db.Host.get_console_idle_timeout ~__context ~self:host + | `Pool pool -> + Db.Pool.get_vm_console_idle_timeout ~__context + ~self:pool + in + Some (Int64.to_float timeout_value) + with _ -> None in - if is_idle_timeout ~idle_timeout_seconds ~last_activity then ( - debug "Idle timeout exceeded! (timeout: %.1fs)" - idle_timeout_seconds ; + if timed_out ~idle_timeout_seconds ~last_activity then ( + debug + "Console connection idle timeout exceeded for VM %s \ + (timeout: %.1fs)" + (Ref.string_of vm) + (Option.get idle_timeout_seconds) ; rfb_parser := None ; true ) else @@ -199,11 +221,8 @@ module Console_idle_monitor = struct ) end -let get_poll_timeout = - if !Xapi_globs.poll_timeout_sec < 0 then - -1 - else - !Xapi_globs.poll_timeout_sec * 1000 (* convert to milliseconds *) +let get_poll_timeout = max (-1) (!Xapi_globs.poll_timeout_sec * 1000) +(* convert to milliseconds *) let real_proxy' ~__context ~vm vnc_port s = try @@ -222,10 +241,10 @@ let real_proxy' ~__context ~vm vnc_port s = (Unixext.int_of_file_descr s') ; let poll_timeout = get_poll_timeout in - let idle_timeout_callback = - Some (Console_idle_monitor.create_idle_timeout_callback ~__context ~vm ()) + let should_close = + Console_idle_monitor.create_idle_timeout_callback ~__context ~vm in - Unixext.proxy ~should_close:idle_timeout_callback ~poll_timeout vnc_sock s' ; + Unixext.proxy ~should_close ~poll_timeout vnc_sock s' ; debug "Proxy exited" with exn -> debug "error: %s" (ExnHelper.string_of_exn exn) From d71b6a37c9050c52126957486ac4172835255c8d Mon Sep 17 00:00:00 2001 From: Stephen Cheng Date: Fri, 10 Oct 2025 10:15:42 +0800 Subject: [PATCH 3/5] Changed poll_timeout to float Signed-off-by: Stephen Cheng --- ocaml/xapi/console.ml | 7 ++++++- ocaml/xapi/xapi_globs.ml | 8 ++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/ocaml/xapi/console.ml b/ocaml/xapi/console.ml index ba1e72f25c5..5a1a7f8cba5 100644 --- a/ocaml/xapi/console.ml +++ b/ocaml/xapi/console.ml @@ -221,7 +221,12 @@ module Console_idle_monitor = struct ) end -let get_poll_timeout = max (-1) (!Xapi_globs.poll_timeout_sec * 1000) +let get_poll_timeout = + let poll_period_timeout = !Xapi_globs.proxy_poll_period_timeout in + if poll_period_timeout < 0. then + -1 + else + Float.to_int (poll_period_timeout *. 1000.) (* convert to milliseconds *) let real_proxy' ~__context ~vm vnc_port s = diff --git a/ocaml/xapi/xapi_globs.ml b/ocaml/xapi/xapi_globs.ml index 0e37fa348c2..f84d72149da 100644 --- a/ocaml/xapi/xapi_globs.ml +++ b/ocaml/xapi/xapi_globs.ml @@ -453,7 +453,7 @@ let message_limit = ref 10000 (* The timeout (in seconds) for event polling in the proxy loop. If set to a positive value, the poll will wake up periodically, which is useful for implementing features like idle timeout or periodic inspection of proxy buffers. *) -let poll_timeout_sec = ref 5 +let proxy_poll_period_timeout = ref 5.0 let xapi_message_script = ref "mail-alarm" @@ -1788,9 +1788,9 @@ let other_options = , (fun () -> string_of_float !vm_sysprep_wait) , "Time in seconds to wait for VM to recognise inserted CD" ) - ; ( "poll_timeout_sec" - , Arg.Set_int poll_timeout_sec - , (fun () -> string_of_int !poll_timeout_sec) + ; ( "proxy_poll_period_timeout" + , Arg.Set_float proxy_poll_period_timeout + , (fun () -> string_of_float !proxy_poll_period_timeout) , "Timeout (in seconds) for event polling in network proxy loops. When \ positive, the proxy will wake up periodically to check tasks like vnc \ idle timeouts or perform other maintenance tasks. Set to -1 to wait \ From 9860a69a8889af3077ce9d249f08de59b60ed44a Mon Sep 17 00:00:00 2001 From: Stephen Cheng Date: Fri, 10 Oct 2025 16:27:23 +0800 Subject: [PATCH 4/5] Refine create_idle_timeout_callback Signed-off-by: Stephen Cheng --- ocaml/xapi/console.ml | 112 ++++++++++++++++++------------------------ 1 file changed, 47 insertions(+), 65 deletions(-) diff --git a/ocaml/xapi/console.ml b/ocaml/xapi/console.ml index 5a1a7f8cba5..5fab0d73060 100644 --- a/ocaml/xapi/console.ml +++ b/ocaml/xapi/console.ml @@ -127,7 +127,7 @@ let address_of_console __context console : address option = address_option module Console_idle_monitor = struct - let get_console_idle_timeout_value ~__context ~vm = + let get_idle_timeout_config ~__context ~vm = try let idle_timeout = if Db.VM.get_is_control_domain ~__context ~self:vm then @@ -137,8 +137,8 @@ module Console_idle_monitor = struct let pool = Helpers.get_pool ~__context in Db.Pool.get_vm_console_idle_timeout ~__context ~self:pool in - idle_timeout - with _ -> 0L (* Default: no timeout *) + Some idle_timeout + with _ -> None let is_active messages = List.exists @@ -153,72 +153,54 @@ module Console_idle_monitor = struct messages let timed_out ~idle_timeout_seconds ~last_activity = - match idle_timeout_seconds with - | Some timeout when timeout > 0. -> - let elapsed = Mtime_clock.count !last_activity in - Mtime.Span.to_float_ns elapsed /. 1e9 > timeout - | _ -> - false + idle_timeout_seconds > 0. + && + let elapsed = Mtime_clock.count last_activity in + Mtime.Span.to_float_ns elapsed /. 1e9 > idle_timeout_seconds (* Create an idle timeout callback for the console, - if idle_timeout, then close the proxy *) + if idle timeout, then close the proxy *) let create_idle_timeout_callback ~__context ~vm = - let rfb_parser = ref (Some (Rfb_client_msgtype_parser.create ())) in - let last_activity = ref (Mtime_clock.counter ()) in - let is_control_domain = Db.VM.get_is_control_domain ~__context ~self:vm in - let timeout_source = - if is_control_domain then - `Host (Helpers.get_localhost ~__context) - else - `Pool (Helpers.get_pool ~__context) - in - (* Return true for idle timeout to close the proxy, otherwise keep the proxy open *) - fun (buf, read_len, offset) -> - match !rfb_parser with - | None -> - false - | Some parser -> ( - try - let data = Bytes.sub_string buf offset read_len in - match parser data with - | Ok messages -> - if is_active messages then - last_activity := Mtime_clock.counter () ; - - (* The idle_timeout config may change during running the callback, - so need to get the latest *) - let idle_timeout_seconds = - try - let timeout_value = - match timeout_source with - | `Host host -> - Db.Host.get_console_idle_timeout ~__context ~self:host - | `Pool pool -> - Db.Pool.get_vm_console_idle_timeout ~__context - ~self:pool - in - Some (Int64.to_float timeout_value) - with _ -> None - in - if timed_out ~idle_timeout_seconds ~last_activity then ( - debug - "Console connection idle timeout exceeded for VM %s \ - (timeout: %.1fs)" - (Ref.string_of vm) - (Option.get idle_timeout_seconds) ; - rfb_parser := None ; - true - ) else + match get_idle_timeout_config ~__context ~vm with + | Some timeout_config -> + let idle_timeout_seconds = Int64.to_float timeout_config in + if idle_timeout_seconds > 0. then + let module P = Rfb_client_msgtype_parser in + let state = ref (Some (P.create (), Mtime_clock.counter ())) in + (* Return true for idle timeout to close the proxy, + otherwise return false to keep the proxy open *) + fun (buf, read_len, offset) -> + match !state with + | None -> false - | Error error_msg -> - debug "RFB parse error: %s" error_msg ; - rfb_parser := None ; - false - with e -> - debug "Exception in RFB parser: %s" (Printexc.to_string e) ; - rfb_parser := None ; - false - ) + | Some (rfb_parser, last_activity) -> + let ok msgs = + if is_active msgs then ( + state := Some (rfb_parser, Mtime_clock.counter ()) ; + false + ) else + let timeout_result = + timed_out ~idle_timeout_seconds ~last_activity + in + if timeout_result then + debug + "Console connection idle timeout exceeded for VM %s \ + (timeout: %.1fs)" + (Ref.string_of vm) idle_timeout_seconds ; + timeout_result + in + let error msg = + debug "RFB parse error: %s" msg ; + state := None ; + false + in + Bytes.sub_string buf offset read_len + |> rfb_parser + |> Result.fold ~ok ~error + else + Fun.const false + | None -> + Fun.const false end let get_poll_timeout = From b238556792db9220c405a7304853655b560f83bc Mon Sep 17 00:00:00 2001 From: Stephen Cheng Date: Fri, 10 Oct 2025 19:09:48 +0800 Subject: [PATCH 5/5] fix2 Signed-off-by: Stephen Cheng --- ocaml/xapi/console.ml | 73 ++++++++++++++++++++----------------------- 1 file changed, 34 insertions(+), 39 deletions(-) diff --git a/ocaml/xapi/console.ml b/ocaml/xapi/console.ml index 5fab0d73060..08f4c863e87 100644 --- a/ocaml/xapi/console.ml +++ b/ocaml/xapi/console.ml @@ -137,7 +137,7 @@ module Console_idle_monitor = struct let pool = Helpers.get_pool ~__context in Db.Pool.get_vm_console_idle_timeout ~__context ~self:pool in - Some idle_timeout + if idle_timeout > 0L then Some (Int64.to_float idle_timeout) else None with _ -> None let is_active messages = @@ -153,8 +153,6 @@ module Console_idle_monitor = struct messages let timed_out ~idle_timeout_seconds ~last_activity = - idle_timeout_seconds > 0. - && let elapsed = Mtime_clock.count last_activity in Mtime.Span.to_float_ns elapsed /. 1e9 > idle_timeout_seconds @@ -162,43 +160,40 @@ module Console_idle_monitor = struct if idle timeout, then close the proxy *) let create_idle_timeout_callback ~__context ~vm = match get_idle_timeout_config ~__context ~vm with - | Some timeout_config -> - let idle_timeout_seconds = Int64.to_float timeout_config in - if idle_timeout_seconds > 0. then - let module P = Rfb_client_msgtype_parser in - let state = ref (Some (P.create (), Mtime_clock.counter ())) in - (* Return true for idle timeout to close the proxy, - otherwise return false to keep the proxy open *) - fun (buf, read_len, offset) -> - match !state with - | None -> - false - | Some (rfb_parser, last_activity) -> - let ok msgs = - if is_active msgs then ( - state := Some (rfb_parser, Mtime_clock.counter ()) ; - false - ) else - let timeout_result = - timed_out ~idle_timeout_seconds ~last_activity - in - if timeout_result then - debug - "Console connection idle timeout exceeded for VM %s \ - (timeout: %.1fs)" - (Ref.string_of vm) idle_timeout_seconds ; - timeout_result - in - let error msg = - debug "RFB parse error: %s" msg ; - state := None ; + | Some idle_timeout_seconds -> ( + let module P = Rfb_client_msgtype_parser in + let state = ref (Some (P.create (), Mtime_clock.counter ())) in + (* Return true for idle timeout to close the proxy, + otherwise return false to keep the proxy open *) + fun (buf, read_len, offset) -> + match !state with + | None -> + false + | Some (rfb_parser, last_activity) -> + let ok msgs = + if is_active msgs then ( + state := Some (rfb_parser, Mtime_clock.counter ()) ; false - in - Bytes.sub_string buf offset read_len - |> rfb_parser - |> Result.fold ~ok ~error - else - Fun.const false + ) else + let timeout_result = + timed_out ~idle_timeout_seconds ~last_activity + in + if timeout_result then + debug + "Console connection idle timeout exceeded for VM %s \ + (timeout: %.1fs)" + (Ref.string_of vm) idle_timeout_seconds ; + timeout_result + in + let error msg = + debug "RFB parse error: %s" msg ; + state := None ; + false + in + Bytes.sub_string buf offset read_len + |> rfb_parser + |> Result.fold ~ok ~error + ) | None -> Fun.const false end