11diff --git a/chrome/browser/browseros_server/browseros_server_manager.cc b/chrome/browser/browseros_server/browseros_server_manager.cc
22new file mode 100644
3- index 0000000000000 ..43f1ee391f0a8
3+ index 0000000000000 ..e16b9181f8e3d
44--- /dev/null
55+++ b/chrome/browser/browseros_server/browseros_server_manager.cc
6- @@ -0 ,0 +1 ,967 @@
6+ @@ -0 ,0 +1 ,952 @@
77+// Copyright 2024 The Chromium Authors
88+// Use of this source code is governed by a BSD-style license that can be
99+// found in the LICENSE file.
@@ -55,10 +55,17 @@ index 0000000000000..43f1ee391f0a8
5555+
5656+namespace {
5757+
58- +const int kBackLog = 10 ;
58+ +constexpr int kBackLog = 10 ;
5959+constexpr base::FilePath::CharType kConfigFileName [] =
6060+ FILE_PATH_LITERAL(" server_config.json" );
6161+
62+ +constexpr base::TimeDelta kHealthCheckInterval = base::Seconds(30 );
63+ +constexpr base::TimeDelta kHealthCheckTimeout = base::Seconds(15 );
64+ +constexpr base::TimeDelta kProcessCheckInterval = base::Seconds(10 );
65+ +
66+ +constexpr int kMaxPortAttempts = 100 ;
67+ +constexpr int kMaxPort = 65535 ;
68+ +
6269+// Holds configuration data gathered on UI thread, passed to background thread
6370+struct ServerConfig {
6471+ std::string install_id;
@@ -426,13 +433,10 @@ index 0000000000000..43f1ee391f0a8
426433+
427434+ LOG (INFO) << " browseros: Starting BrowserOS server" ;
428435+
429- +
430436+ // Start servers and process
437+ + // Note: monitoring timers are started in OnProcessLaunched() after successful launch
431438+ StartCDPServer ();
432439+ LaunchBrowserOSProcess ();
433- +
434- + health_check_timer_.Start (FROM_HERE, base::Seconds (60 ), this ,
435- + &BrowserOSServerManager::CheckServerHealth);
436440+}
437441+
438442+void BrowserOSServerManager::Stop () {
@@ -444,8 +448,8 @@ index 0000000000000..43f1ee391f0a8
444448+ health_check_timer_.Stop ();
445449+ process_check_timer_.Stop ();
446450+
447- + TerminateBrowserOSProcess ();
448- + StopCDPServer ( );
451+ + // Use wait=false for shutdown - just send kill signal, don't block UI thread
452+ + TerminateBrowserOSProcess ( /* wait= */ false );
449453+
450454+ // Release lock
451455+ if (lock_file_.IsValid ()) {
@@ -496,7 +500,6 @@ index 0000000000000..43f1ee391f0a8
496500+ base::FilePath execution_dir = GetBrowserOSExecutionDir ();
497501+ if (execution_dir.empty ()) {
498502+ LOG (ERROR) << " browseros: Failed to resolve execution directory" ;
499- + StopCDPServer ();
500503+ return ;
501504+ }
502505+
@@ -545,21 +548,26 @@ index 0000000000000..43f1ee391f0a8
545548+void BrowserOSServerManager::OnProcessLaunched (base::Process process) {
546549+ if (!process.IsValid ()) {
547550+ LOG (ERROR) << " browseros: Failed to launch BrowserOS server" ;
548- + StopCDPServer ();
551+ + // Don't stop CDP server - it's independent and may be used by other things
552+ + // Leave system in degraded state (CDP up, no browseros_server) rather than
553+ + // completely broken state (no CDP, no server)
549554+ is_restarting_ = false ;
550555+ return ;
551556+ }
552557+
553558+ process_ = std::move (process);
554559+ is_running_ = true ;
555560+
556- + LOG (INFO) << " browseros: BrowserOS server started" ;
561+ + LOG (INFO) << " browseros: BrowserOS server started with PID: " << process_. Pid () ;
557562+ LOG (INFO) << " browseros: CDP port: " << cdp_port_;
558563+ LOG (INFO) << " browseros: MCP port: " << mcp_port_;
559564+ LOG (INFO) << " browseros: Agent port: " << agent_port_;
560565+ LOG (INFO) << " browseros: Extension port: " << extension_port_;
561566+
562- + process_check_timer_.Start (FROM_HERE, base::Seconds (5 ), this ,
567+ + // Start/restart monitoring timers
568+ + health_check_timer_.Start (FROM_HERE, kHealthCheckInterval , this ,
569+ + &BrowserOSServerManager::CheckServerHealth);
570+ + process_check_timer_.Start (FROM_HERE, kProcessCheckInterval , this ,
563571+ &BrowserOSServerManager::CheckProcessStatus);
564572+
565573+ // Reset restart flag and pref after successful launch
@@ -573,36 +581,34 @@ index 0000000000000..43f1ee391f0a8
573581+ }
574582+}
575583+
576- +void BrowserOSServerManager::TerminateBrowserOSProcess () {
584+ +void BrowserOSServerManager::TerminateBrowserOSProcess (bool wait ) {
577585+ if (!process_.IsValid ()) {
578586+ return ;
579587+ }
580588+
581- + LOG (INFO) << " browseros: Force killing BrowserOS server process (PID: "
582- + << process_.Pid () << " )" ;
583- +
584- + // sync primitives is needed for process termination.
585- + // NOTE: only run on background threads
586- + base::ScopedAllowBaseSyncPrimitives allow_sync;
587- + base::ScopedAllowBlocking allow_blocking;
589+ + LOG (INFO) << " browseros: Terminating BrowserOS server process (PID: "
590+ + << process_.Pid () << " , wait: " << (wait ? " true" : " false" ) << " )" ;
588591+
589592+#if BUILDFLAG (IS_POSIX)
590- + // POSIX: Send SIGKILL for immediate termination (no graceful shutdown)
591- + // This matches Windows TerminateProcess behavior
592593+ base::ProcessId pid = process_.Pid ();
593- + if (kill (pid, SIGKILL) == 0 ) {
594+ + if (kill (pid, SIGKILL) != 0 ) {
595+ + PLOG (ERROR) << " browseros: Failed to send SIGKILL to PID " << pid;
596+ + } else if (wait) {
597+ + // Blocking wait - must be called from background thread
598+ + base::ScopedAllowBaseSyncPrimitives allow_sync;
599+ + base::ScopedAllowBlocking allow_blocking;
594600+ int exit_code = 0 ;
595601+ if (process_.WaitForExit (&exit_code)) {
596- + LOG (INFO) << " browseros: Process killed successfully with SIGKILL " ;
602+ + LOG (INFO) << " browseros: Process killed successfully" ;
597603+ } else {
598- + LOG (WARNING) << " browseros: SIGKILL sent but WaitForExit failed" ;
604+ + LOG (WARNING) << " browseros: WaitForExit failed" ;
599605+ }
600606+ } else {
601- + PLOG (ERROR ) << " browseros: Failed to send SIGKILL to PID " << pid ;
607+ + LOG (INFO ) << " browseros: SIGKILL sent (not waiting for exit) " ;
602608+ }
603609+#else
604- + // Windows: TerminateProcess is already immediate force kill
605- + bool terminated = process_.Terminate (0 , true );
610+ + // Windows: Terminate with wait parameter
611+ + bool terminated = process_.Terminate (0 , wait );
606612+ if (terminated) {
607613+ LOG (INFO) << " browseros: Process terminated successfully" ;
608614+ } else {
@@ -617,28 +623,21 @@ index 0000000000000..43f1ee391f0a8
617623+ LOG (INFO) << " browseros: BrowserOS server exited with code: " << exit_code;
618624+ is_running_ = false ;
619625+
620- + // Stop CDP server since BrowserOS process is gone
621- + StopCDPServer ();
626+ + // Stop timers during restart to prevent races
627+ + health_check_timer_.Stop ();
628+ + process_check_timer_.Stop ();
622629+
623- + // Restart if it crashed unexpectedly
624- + if (exit_code != 0 ) {
625- + LOG (WARNING) << " browseros: BrowserOS server crashed, restarting..." ;
626- + Start ();
627- + }
630+ + // Always restart - we want the server running
631+ + // Don't call Start() - we already hold the lock and CDP server is running
632+ + LOG (WARNING) << " browseros: BrowserOS server exited, restarting process..." ;
633+ + LaunchBrowserOSProcess ();
628634+}
629635+
630636+void BrowserOSServerManager::CheckServerHealth () {
631637+ if (!is_running_) {
632638+ return ;
633639+ }
634640+
635- + // First check if process is still alive
636- + if (!process_.IsValid ()) {
637- + LOG (WARNING) << " browseros: BrowserOS server process is invalid, restarting..." ;
638- + RestartBrowserOSProcess ();
639- + return ;
640- + }
641- +
642641+ // Build health check URL
643642+ GURL health_url (" http://127.0.0.1:" + base::NumberToString (mcp_port_) + " /health" );
644643+
@@ -667,10 +666,9 @@ index 0000000000000..43f1ee391f0a8
667666+ resource_request->method = " GET" ;
668667+ resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit ;
669668+
670- + // Create URL loader with 10 second timeout
671669+ auto url_loader = network::SimpleURLLoader::Create (
672670+ std::move (resource_request), traffic_annotation);
673- + url_loader->SetTimeoutDuration (base::Seconds ( 10 ) );
671+ + url_loader->SetTimeoutDuration (kHealthCheckTimeout );
674672+
675673+ // Get URL loader factory from default storage partition
676674+ auto * url_loader_factory =
@@ -692,10 +690,13 @@ index 0000000000000..43f1ee391f0a8
692690+ return ;
693691+ }
694692+
695- + // Check if process has exited
696693+ int exit_code = 0 ;
697- + if (process_.WaitForExitWithTimeout (base::TimeDelta (), &exit_code)) {
698- + // Process has exited
694+ + bool exited = process_.WaitForExitWithTimeout (base::TimeDelta (), &exit_code);
695+ + LOG (INFO) << " browseros: CheckProcessStatus PID: " << process_.Pid ()
696+ + << " , WaitForExitWithTimeout returned: " << exited
697+ + << " , exit_code: " << exit_code;
698+ +
699+ + if (exited) {
699700+ OnProcessExited (exit_code);
700701+ }
701702+}
@@ -730,12 +731,36 @@ index 0000000000000..43f1ee391f0a8
730731+void BrowserOSServerManager::RestartBrowserOSProcess () {
731732+ LOG (INFO) << " browseros: Restarting BrowserOS server process" ;
732733+
733- + // Stop the process and monitoring
734+ + // Prevent multiple concurrent restarts
735+ + if (is_restarting_) {
736+ + LOG (INFO) << " browseros: Restart already in progress, ignoring" ;
737+ + return ;
738+ + }
739+ + is_restarting_ = true ;
740+ +
741+ + // Stop all timers during restart to prevent races
742+ + health_check_timer_.Stop ();
734743+ process_check_timer_.Stop ();
735- + TerminateBrowserOSProcess ();
736744+
737- + // Relaunch the process
738- + LaunchBrowserOSProcess ();
745+ + // Capture UI task runner to post back after background work
746+ + auto ui_task_runner = base::SequencedTaskRunner::GetCurrentDefault ();
747+ +
748+ + // Kill process on background thread (wait=true requires background thread),
749+ + // then relaunch on UI thread
750+ + base::ThreadPool::PostTask (
751+ + FROM_HERE, {base::MayBlock (), base::TaskPriority::USER_BLOCKING},
752+ + base::BindOnce (
753+ + [](BrowserOSServerManager* manager,
754+ + scoped_refptr<base::SequencedTaskRunner> ui_runner) {
755+ + manager->TerminateBrowserOSProcess (/* wait=*/ true );
756+ +
757+ + // Post back to UI thread to launch new process
758+ + ui_runner->PostTask (
759+ + FROM_HERE,
760+ + base::BindOnce (&BrowserOSServerManager::LaunchBrowserOSProcess,
761+ + base::Unretained (manager)));
762+ + },
763+ + base::Unretained (this ), ui_task_runner));
739764+}
740765+
741766+void BrowserOSServerManager::OnAllowRemoteInMCPChanged () {
@@ -776,51 +801,11 @@ index 0000000000000..43f1ee391f0a8
776801+ return ;
777802+ }
778803+
779- + // Ignore if already restarting (prevents thrashing from UI spam)
780- + if (is_restarting_) {
781- + LOG (INFO) << " browseros: Restart already in progress, ignoring duplicate request" ;
782- + return ;
783- + }
784- +
785- + // Ignore if not running
786- + if (!is_running_) {
787- + LOG (WARNING) << " browseros: Cannot restart - server is not running" ;
788- + // Reset pref anyway
789- + prefs->SetBoolean (browseros_server::kRestartServerRequested , false );
790- + return ;
791- + }
792- +
793804+ LOG (INFO) << " browseros: Server restart requested via preference" ;
794- + is_restarting_ = true ;
795- +
796- + // Stop timer now (must be on UI thread)
797- + process_check_timer_.Stop ();
798- +
799- + // Capture UI task runner to post back after background work
800- + auto ui_task_runner = base::SequencedTaskRunner::GetCurrentDefault ();
801- +
802- + // Kill process on background thread, then relaunch on UI thread
803- + base::ThreadPool::PostTask (
804- + FROM_HERE, {base::MayBlock (), base::TaskPriority::USER_BLOCKING},
805- + base::BindOnce (
806- + [](BrowserOSServerManager* manager,
807- + scoped_refptr<base::SequencedTaskRunner> ui_runner) {
808- + // Kill old process and wait for exit (blocking, safe on background)
809- + manager->TerminateBrowserOSProcess ();
810- +
811- + // Post back to UI thread to launch new process
812- + ui_runner->PostTask (
813- + FROM_HERE,
814- + base::BindOnce (&BrowserOSServerManager::LaunchBrowserOSProcess,
815- + base::Unretained (manager)));
816- + },
817- + base::Unretained (this ), ui_task_runner));
805+ + RestartBrowserOSProcess ();
818806+}
819807+
820808+int BrowserOSServerManager::FindAvailablePort (int starting_port) {
821- + const int kMaxPortAttempts = 100 ;
822- + const int kMaxPort = 65535 ;
823- +
824809+ LOG (INFO) << " browseros: Finding port starting from "
825810+ << starting_port;
826811+
0 commit comments