// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/browser_process_io_thread.h"

#include "base/clang_profiling_buildflags.h"
#include "base/compiler_specific.h"
#include "base/debug/alias.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/threading/hang_watcher.h"
#include "base/threading/thread_restrictions.h"
#include "base/trace_event/memory_dump_manager.h"
#include "build/build_config.h"
#include "content/browser/browser_child_process_host_impl.h"
#include "content/browser/browser_thread_impl.h"
#include "content/browser/child_process_host_impl.h"
#include "content/browser/service_host/utility_process_host.h"
#include "content/public/browser/browser_child_process_host_iterator.h"
#include "content/public/common/process_type.h"
#include "services/network/public/mojom/network_service.mojom.h"

#if BUILDFLAG(IS_ANDROID)
#include "base/android/jni_android.h"
#endif

#if BUILDFLAG(IS_WIN)
#include "base/win/scoped_com_initializer.h"
#endif

namespace content {

BrowserProcessIOThread::BrowserProcessIOThread()
    : base::Thread(BrowserThreadImpl::GetThreadName(BrowserThread::IO)) {
  // Not bound to creation thread.
  DETACH_FROM_THREAD(browser_thread_checker_);
}

BrowserProcessIOThread::~BrowserProcessIOThread() {
  Stop();
}

void BrowserProcessIOThread::RegisterAsBrowserThread() {
  DCHECK(IsRunning());

  DCHECK(!browser_thread_);
  browser_thread_.reset(
      new BrowserThreadImpl(BrowserThread::IO, task_runner()));
}

void BrowserProcessIOThread::AllowBlockingForTesting() {
  DCHECK(!IsRunning());
  is_blocking_allowed_for_testing_ = true;
}

void BrowserProcessIOThread::Init() {
  DCHECK_CALLED_ON_VALID_THREAD(browser_thread_checker_);

#if BUILDFLAG(IS_WIN)
  com_initializer_ = std::make_unique<base::win::ScopedCOMInitializer>();
#endif

  if (!is_blocking_allowed_for_testing_) {
    base::DisallowUnresponsiveTasks();
  }
}

void BrowserProcessIOThread::Run(base::RunLoop* run_loop) {
  DCHECK_CALLED_ON_VALID_THREAD(browser_thread_checker_);

#if BUILDFLAG(IS_ANDROID)
  // Not to reset thread name to "Thread-???" by VM, attach VM with thread name.
  // Though it may create unnecessary VM thread objects, keeping thread name
  // gives more benefit in debugging in the platform.
  if (!thread_name().empty()) {
    base::android::AttachCurrentThreadWithName(thread_name());
  }
#endif

  IOThreadRun(run_loop);
}

void BrowserProcessIOThread::CleanUp() {
  DCHECK_CALLED_ON_VALID_THREAD(browser_thread_checker_);

#if BUILDFLAG(IS_WIN)
  com_initializer_.reset();
#endif
}

void BrowserProcessIOThread::IOThreadRun(base::RunLoop* run_loop) {
  // Register the IO thread for hang watching before it starts running and set
  // up a closure to automatically unregister it when Run() returns.
  base::ScopedClosureRunner unregister_thread_closure;
  if (base::HangWatcher::IsIOThreadHangWatchingEnabled()) {
    unregister_thread_closure = base::HangWatcher::RegisterThread(
        base::HangWatcher::ThreadType::kIOThread);
  }

  Thread::Run(run_loop);
  NO_CODE_FOLDING();
}

void BrowserProcessIOThread::ProcessHostCleanUp() {
  for (BrowserChildProcessHostIterator it(PROCESS_TYPE_UTILITY); !it.Done();
       ++it) {
    if (it.GetDelegate()->GetServiceName() ==
        network::mojom::NetworkService::Name_) {
      // This ensures that cookies and cache are flushed to disk on shutdown.
      // https://crbug.com/841001
#if BUILDFLAG(CLANG_PROFILING)
      // On profiling build, browser_tests runs 10x slower.
      const int kMaxSecondsToWaitForNetworkProcess = 100;
#elif BUILDFLAG(IS_CHROMEOS)
      // ChromeOS will kill the browser process if it doesn't shut down within
      // 3 seconds, so make sure we wait for less than that.
      const int kMaxSecondsToWaitForNetworkProcess = 1;
#else
      const int kMaxSecondsToWaitForNetworkProcess = 10;
#endif

      ChildProcessHostImpl* child_process =
          static_cast<ChildProcessHostImpl*>(it.GetHost());
      auto& process = child_process->GetPeerProcess();
      if (!process.IsValid())
        continue;
      base::ScopedAllowBaseSyncPrimitives scoped_allow_base_sync_primitives;
      const base::TimeTicks start_time = base::TimeTicks::Now();
      process.WaitForExitWithTimeout(
          base::Seconds(kMaxSecondsToWaitForNetworkProcess), nullptr);
      // Record time spent for the method call.
      base::TimeDelta network_wait_time = base::TimeTicks::Now() - start_time;
      DVLOG(1) << "Waited " << network_wait_time.InMilliseconds()
               << " ms for network service";
    }
  }

  // If any child processes are still running, terminate them and
  // and delete the BrowserChildProcessHost instances to release whatever
  // IO thread only resources they are referencing.
  BrowserChildProcessHostImpl::TerminateAll();
}

}  // namespace content