| Index: remoting/host/win/worker_process_launcher.cc
|
| diff --git a/remoting/host/win/worker_process_launcher.cc b/remoting/host/win/worker_process_launcher.cc
|
| index 834b8bb6338a885c573c5b6b158db8a66115a027..b06b4a39813fb99382aa80d0fe005389ae003a4e 100644
|
| --- a/remoting/host/win/worker_process_launcher.cc
|
| +++ b/remoting/host/win/worker_process_launcher.cc
|
| @@ -4,11 +4,9 @@
|
|
|
| #include "remoting/host/win/worker_process_launcher.h"
|
|
|
| -#include <windows.h>
|
| #include <sddl.h>
|
| #include <limits>
|
|
|
| -#include "base/base_switches.h"
|
| #include "base/bind.h"
|
| #include "base/bind_helpers.h"
|
| #include "base/logging.h"
|
| @@ -17,77 +15,219 @@
|
| #include "base/rand_util.h"
|
| #include "base/stringprintf.h"
|
| #include "base/time.h"
|
| +#include "base/timer.h"
|
| #include "base/utf_string_conversions.h"
|
| +#include "base/win/object_watcher.h"
|
| #include "base/win/scoped_handle.h"
|
| +#include "ipc/ipc_channel.h"
|
| #include "ipc/ipc_channel_proxy.h"
|
| #include "ipc/ipc_message.h"
|
| +#include "net/base/backoff_entry.h"
|
| +#include "remoting/host/host_exit_codes.h"
|
| #include "remoting/host/worker_process_ipc_delegate.h"
|
|
|
| using base::win::ScopedHandle;
|
| -
|
| -namespace {
|
| +using base::TimeDelta;
|
|
|
| // Match the pipe name prefix used by Chrome IPC channels so that the client
|
| // could use Chrome IPC APIs instead of connecting manually.
|
| const char kChromePipeNamePrefix[] = "\\\\.\\pipe\\chrome.";
|
|
|
| -} // namespace
|
| +// The minimum and maximum delays between attempts to inject host process into
|
| +// a session.
|
| +const int kMaxLaunchDelaySeconds = 60;
|
| +const int kMinLaunchDelaySeconds = 1;
|
| +
|
| +const net::BackoffEntry::Policy kDefaultBackoffPolicy = {
|
| + // Number of initial errors (in sequence) to ignore before applying
|
| + // exponential back-off rules.
|
| + 0,
|
| +
|
| + // Initial delay for exponential back-off in ms.
|
| + 1000,
|
| +
|
| + // Factor by which the waiting time will be multiplied.
|
| + 2,
|
| +
|
| + // Fuzzing percentage. ex: 10% will spread requests randomly
|
| + // between 90%-100% of the calculated time.
|
| + 0,
|
| +
|
| + // Maximum amount of time we are willing to delay our request in ms.
|
| + 60000,
|
| +
|
| + // Time to keep an entry from being discarded even when it
|
| + // has no significant state, -1 to never discard.
|
| + -1,
|
| +
|
| + // Don't use initial delay unless the last request was an error.
|
| + false,
|
| +};
|
| +
|
|
|
| namespace remoting {
|
|
|
| +// Launches a worker process that is controlled via an IPC channel. All
|
| +// interaction with the spawned process is through the IPC::Listener and Send()
|
| +// method. In case of error the channel is closed and the worker process is
|
| +// terminated.
|
| +class WorkerProcessLauncher::Core
|
| + : public base::RefCountedThreadSafe<WorkerProcessLauncher::Core>,
|
| + public base::win::ObjectWatcher::Delegate,
|
| + public IPC::Listener {
|
| + public:
|
| + // Creates the launcher that will use |launcher_delegate| to manage the worker
|
| + // process and |worker_delegate| to handle IPCs. The caller must ensure that
|
| + // |worker_delegate| remains valid until Stoppable::Stop() method has been
|
| + // called.
|
| + //
|
| + // The caller should call all the methods on this class on
|
| + // the |caller_task_runner| thread. Methods of both delegate interfaces are
|
| + // called on the |caller_task_runner| thread as well. |io_task_runner| is used
|
| + // to perform background IPC I/O.
|
| + Core(scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
|
| + scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
|
| + scoped_ptr<WorkerProcessLauncher::Delegate> launcher_delegate,
|
| + WorkerProcessIpcDelegate* worker_delegate,
|
| + const std::string& pipe_security_descriptor);
|
| +
|
| + // Launches the worker process.
|
| + void Start();
|
| +
|
| + // Stops the worker process asynchronously. The caller can drop the reference
|
| + // to |this| as soon as Stop() returns.
|
| + void Stop();
|
| +
|
| + // Sends an IPC message to the worker process. The message will be silently
|
| + // dropped if Send() is called before Start() or after stutdown has been
|
| + // initiated.
|
| + void Send(IPC::Message* message);
|
| +
|
| + // base::win::ObjectWatcher::Delegate implementation.
|
| + virtual void OnObjectSignaled(HANDLE object) OVERRIDE;
|
| +
|
| + // IPC::Listener implementation.
|
| + virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE;
|
| + virtual void OnChannelConnected(int32 peer_pid) OVERRIDE;
|
| + virtual void OnChannelError() OVERRIDE;
|
| +
|
| + private:
|
| + friend class base::RefCountedThreadSafe<Core>;
|
| + virtual ~Core();
|
| +
|
| + // Creates the server end of the Chromoting IPC channel.
|
| + bool CreatePipeForIpcChannel(const std::string& channel_name,
|
| + const std::string& pipe_security_descriptor,
|
| + base::win::ScopedHandle* pipe_out);
|
| +
|
| + // Generates random channel ID.
|
| + std::string GenerateRandomChannelId();
|
| +
|
| + // Attempts to launch the worker process. Schedules next launch attempt if
|
| + // creation of the process fails.
|
| + void LaunchWorker();
|
| +
|
| + // Records a successfull launch attempt.
|
| + void RecordSuccessfullLaunch();
|
| +
|
| + // Stops the worker process asynchronously and schedules next launch attempt
|
| + // unless Stop() has been called already.
|
| + void StopWorker();
|
| +
|
| + // All public methods are called on the |caller_task_runner| thread.
|
| + scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner_;
|
| +
|
| + // The task runner is used perform background IPC I/O.
|
| + scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_;
|
| +
|
| + // Implements specifics of launching a worker process.
|
| + scoped_ptr<WorkerProcessLauncher::Delegate> launcher_delegate_;
|
| +
|
| + // Handles IPC messages sent by the worker process.
|
| + WorkerProcessIpcDelegate* worker_delegate_;
|
| +
|
| + // The IPC channel connecting to the launched process.
|
| + scoped_ptr<IPC::ChannelProxy> ipc_channel_;
|
| +
|
| + // The timer used to delay termination of the process in the case of an IPC
|
| + // error.
|
| + scoped_ptr<base::OneShotTimer<Core> > ipc_error_timer_;
|
| +
|
| + // Launch backoff state.
|
| + net::BackoffEntry launch_backoff_;
|
| +
|
| + // Timer used to delay recording a successfull launch.
|
| + scoped_ptr<base::OneShotTimer<Core> > launch_success_timer_;
|
| +
|
| + // Timer used to schedule the next attempt to launch the process.
|
| + scoped_ptr<base::OneShotTimer<Core> > launch_timer_;
|
| +
|
| + // Used to determine when the launched process terminates.
|
| + base::win::ObjectWatcher process_watcher_;
|
| +
|
| + // A waiting handle that becomes signalled once the launched process has
|
| + // been terminated.
|
| + base::win::ScopedHandle process_exit_event_;
|
| +
|
| + // The server end of the pipe.
|
| + base::win::ScopedHandle pipe_;
|
| +
|
| + // The security descriptor (as SDDL) of the server end of the pipe.
|
| + std::string pipe_security_descriptor_;
|
| +
|
| + // Self reference to keep the object alive while the worker process is being
|
| + // terminated.
|
| + scoped_refptr<Core> self_;
|
| +
|
| + // True when Stop() has been called.
|
| + bool stopping_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(Core);
|
| +};
|
| +
|
| WorkerProcessLauncher::Delegate::~Delegate() {
|
| }
|
|
|
| -WorkerProcessLauncher::WorkerProcessLauncher(
|
| - Delegate* launcher_delegate,
|
| +WorkerProcessLauncher::Core::Core(
|
| + scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
|
| + scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
|
| + scoped_ptr<WorkerProcessLauncher::Delegate> launcher_delegate,
|
| WorkerProcessIpcDelegate* worker_delegate,
|
| - const base::Closure& stopped_callback,
|
| - scoped_refptr<base::SingleThreadTaskRunner> main_task_runner,
|
| - scoped_refptr<base::SingleThreadTaskRunner> ipc_task_runner)
|
| - : Stoppable(main_task_runner, stopped_callback),
|
| - launcher_delegate_(launcher_delegate),
|
| + const std::string& pipe_security_descriptor)
|
| + : caller_task_runner_(caller_task_runner),
|
| + io_task_runner_(io_task_runner),
|
| + launcher_delegate_(launcher_delegate.Pass()),
|
| worker_delegate_(worker_delegate),
|
| - main_task_runner_(main_task_runner),
|
| - ipc_task_runner_(ipc_task_runner) {
|
| + launch_backoff_(&kDefaultBackoffPolicy),
|
| + pipe_security_descriptor_(pipe_security_descriptor),
|
| + stopping_(false) {
|
| + DCHECK(caller_task_runner_->BelongsToCurrentThread());
|
| +
|
| + // base::OneShotTimer must be destroyed on the same thread it was created on.
|
| + ipc_error_timer_.reset(new base::OneShotTimer<Core>());
|
| + launch_success_timer_.reset(new base::OneShotTimer<Core>());
|
| + launch_timer_.reset(new base::OneShotTimer<Core>());
|
| }
|
|
|
| -WorkerProcessLauncher::~WorkerProcessLauncher() {
|
| - DCHECK(main_task_runner_->BelongsToCurrentThread());
|
| -}
|
| +void WorkerProcessLauncher::Core::Start() {
|
| + DCHECK(caller_task_runner_->BelongsToCurrentThread());
|
| + DCHECK(!stopping_);
|
|
|
| -void WorkerProcessLauncher::Start(const std::string& pipe_sddl) {
|
| - DCHECK(main_task_runner_->BelongsToCurrentThread());
|
| - DCHECK(ipc_channel_.get() == NULL);
|
| - DCHECK(!pipe_.IsValid());
|
| - DCHECK(!process_exit_event_.IsValid());
|
| - DCHECK(process_watcher_.GetWatchedObject() == NULL);
|
| + LaunchWorker();
|
| +}
|
|
|
| - std::string channel_name = GenerateRandomChannelId();
|
| - if (CreatePipeForIpcChannel(channel_name, pipe_sddl, &pipe_)) {
|
| - // Wrap the pipe into an IPC channel.
|
| - ipc_channel_.reset(new IPC::ChannelProxy(
|
| - IPC::ChannelHandle(pipe_),
|
| - IPC::Channel::MODE_SERVER,
|
| - this,
|
| - ipc_task_runner_));
|
| +void WorkerProcessLauncher::Core::Stop() {
|
| + DCHECK(caller_task_runner_->BelongsToCurrentThread());
|
|
|
| - // Launch the process and attach an object watcher to the returned process
|
| - // handle so that we get notified if the process terminates.
|
| - if (launcher_delegate_->DoLaunchProcess(channel_name,
|
| - &process_exit_event_)) {
|
| - if (process_watcher_.StartWatching(process_exit_event_, this)) {
|
| - return;
|
| - }
|
| -
|
| - launcher_delegate_->DoKillProcess(CONTROL_C_EXIT);
|
| - }
|
| + if (!stopping_) {
|
| + stopping_ = true;
|
| + worker_delegate_ = NULL;
|
| + StopWorker();
|
| }
|
| -
|
| - Stop();
|
| }
|
|
|
| -void WorkerProcessLauncher::Send(IPC::Message* message) {
|
| - DCHECK(main_task_runner_->BelongsToCurrentThread());
|
| +void WorkerProcessLauncher::Core::Send(IPC::Message* message) {
|
| + DCHECK(caller_task_runner_->BelongsToCurrentThread());
|
|
|
| if (ipc_channel_.get()) {
|
| ipc_channel_->Send(message);
|
| @@ -96,27 +236,24 @@ void WorkerProcessLauncher::Send(IPC::Message* message) {
|
| }
|
| }
|
|
|
| -void WorkerProcessLauncher::OnObjectSignaled(HANDLE object) {
|
| - DCHECK(main_task_runner_->BelongsToCurrentThread());
|
| +void WorkerProcessLauncher::Core::OnObjectSignaled(HANDLE object) {
|
| + DCHECK(caller_task_runner_->BelongsToCurrentThread());
|
| DCHECK(process_watcher_.GetWatchedObject() == NULL);
|
|
|
| - Stop();
|
| + StopWorker();
|
| }
|
|
|
| -bool WorkerProcessLauncher::OnMessageReceived(const IPC::Message& message) {
|
| - DCHECK(main_task_runner_->BelongsToCurrentThread());
|
| +bool WorkerProcessLauncher::Core::OnMessageReceived(
|
| + const IPC::Message& message) {
|
| + DCHECK(caller_task_runner_->BelongsToCurrentThread());
|
| DCHECK(ipc_channel_.get() != NULL);
|
| - DCHECK(pipe_.IsValid());
|
| - DCHECK(process_exit_event_.IsValid());
|
|
|
| return worker_delegate_->OnMessageReceived(message);
|
| }
|
|
|
| -void WorkerProcessLauncher::OnChannelConnected(int32 peer_pid) {
|
| - DCHECK(main_task_runner_->BelongsToCurrentThread());
|
| +void WorkerProcessLauncher::Core::OnChannelConnected(int32 peer_pid) {
|
| + DCHECK(caller_task_runner_->BelongsToCurrentThread());
|
| DCHECK(ipc_channel_.get() != NULL);
|
| - DCHECK(pipe_.IsValid());
|
| - DCHECK(process_exit_event_.IsValid());
|
|
|
| // |peer_pid| is send by the client and cannot be trusted.
|
| // GetNamedPipeClientProcessId() is not available on XP. The pipe's security
|
| @@ -129,47 +266,27 @@ void WorkerProcessLauncher::OnChannelConnected(int32 peer_pid) {
|
| worker_delegate_->OnChannelConnected();
|
| }
|
|
|
| -void WorkerProcessLauncher::OnChannelError() {
|
| - DCHECK(main_task_runner_->BelongsToCurrentThread());
|
| +void WorkerProcessLauncher::Core::OnChannelError() {
|
| + DCHECK(caller_task_runner_->BelongsToCurrentThread());
|
| DCHECK(ipc_channel_.get() != NULL);
|
| - DCHECK(pipe_.IsValid());
|
| - DCHECK(process_exit_event_.IsValid());
|
|
|
| // Schedule a delayed termination of the worker process. Usually, the pipe is
|
| // disconnected when the worker process is about to exit. Waiting a little bit
|
| // here allows the worker to exit completely and so, notify
|
| - // |process_watcher_|. As the result DoKillProcess() will not be called and
|
| + // |process_watcher_|. As the result KillProcess() will not be called and
|
| // the original exit code reported by the worker process will be retrieved.
|
| - ipc_error_timer_.Start(FROM_HERE, base::TimeDelta::FromSeconds(5),
|
| - this, &WorkerProcessLauncher::Stop);
|
| + ipc_error_timer_->Start(FROM_HERE, base::TimeDelta::FromSeconds(5),
|
| + this, &Core::StopWorker);
|
| }
|
|
|
| -void WorkerProcessLauncher::DoStop() {
|
| - DCHECK(main_task_runner_->BelongsToCurrentThread());
|
| -
|
| - ipc_channel_.reset();
|
| - pipe_.Close();
|
| -
|
| - // Kill the process if it has been started already.
|
| - if (process_watcher_.GetWatchedObject() != NULL) {
|
| - launcher_delegate_->DoKillProcess(CONTROL_C_EXIT);
|
| - return;
|
| - }
|
| -
|
| - ipc_error_timer_.Stop();
|
| -
|
| - DCHECK(ipc_channel_.get() == NULL);
|
| - DCHECK(!pipe_.IsValid());
|
| - DCHECK(process_watcher_.GetWatchedObject() == NULL);
|
| -
|
| - process_exit_event_.Close();
|
| - CompleteStopping();
|
| +WorkerProcessLauncher::Core::~Core() {
|
| + DCHECK(stopping_);
|
| }
|
|
|
| // Creates the server end of the Chromoting IPC channel.
|
| -bool WorkerProcessLauncher::CreatePipeForIpcChannel(
|
| +bool WorkerProcessLauncher::Core::CreatePipeForIpcChannel(
|
| const std::string& channel_name,
|
| - const std::string& pipe_sddl,
|
| + const std::string& pipe_security_descriptor,
|
| ScopedHandle* pipe_out) {
|
| // Create security descriptor for the channel.
|
| SECURITY_ATTRIBUTES security_attributes;
|
| @@ -178,7 +295,7 @@ bool WorkerProcessLauncher::CreatePipeForIpcChannel(
|
|
|
| ULONG security_descriptor_length = 0;
|
| if (!ConvertStringSecurityDescriptorToSecurityDescriptor(
|
| - UTF8ToUTF16(pipe_sddl).c_str(),
|
| + UTF8ToUTF16(pipe_security_descriptor).c_str(),
|
| SDDL_REVISION_1,
|
| reinterpret_cast<PSECURITY_DESCRIPTOR*>(
|
| &security_attributes.lpSecurityDescriptor),
|
| @@ -218,10 +335,130 @@ bool WorkerProcessLauncher::CreatePipeForIpcChannel(
|
| }
|
|
|
| // N.B. Copied from src/content/common/child_process_host_impl.cc
|
| -std::string WorkerProcessLauncher::GenerateRandomChannelId() {
|
| +std::string WorkerProcessLauncher::Core::GenerateRandomChannelId() {
|
| return base::StringPrintf("%d.%p.%d",
|
| base::GetCurrentProcId(), this,
|
| base::RandInt(0, std::numeric_limits<int>::max()));
|
| }
|
|
|
| +void WorkerProcessLauncher::Core::LaunchWorker() {
|
| + DCHECK(caller_task_runner_->BelongsToCurrentThread());
|
| + DCHECK(ipc_channel_.get() == NULL);
|
| + DCHECK(!launch_success_timer_->IsRunning());
|
| + DCHECK(!launch_timer_->IsRunning());
|
| + DCHECK(!pipe_.IsValid());
|
| + DCHECK(!process_exit_event_.IsValid());
|
| + DCHECK(process_watcher_.GetWatchedObject() == NULL);
|
| +
|
| + std::string channel_name = GenerateRandomChannelId();
|
| + if (CreatePipeForIpcChannel(channel_name, pipe_security_descriptor_,
|
| + &pipe_)) {
|
| + // Wrap the pipe into an IPC channel.
|
| + ipc_channel_.reset(new IPC::ChannelProxy(
|
| + IPC::ChannelHandle(pipe_),
|
| + IPC::Channel::MODE_SERVER,
|
| + this,
|
| + io_task_runner_));
|
| +
|
| + // Launch the process and attach an object watcher to the returned process
|
| + // handle so that we get notified if the process terminates.
|
| + if (launcher_delegate_->LaunchProcess(channel_name, &process_exit_event_)) {
|
| + if (process_watcher_.StartWatching(process_exit_event_, this)) {
|
| + // Record a successful launch once the process has been running for at
|
| + // least two seconds.
|
| + launch_success_timer_->Start(FROM_HERE, base::TimeDelta::FromSeconds(2),
|
| + this, &Core::RecordSuccessfullLaunch);
|
| + return;
|
| + }
|
| +
|
| + launcher_delegate_->KillProcess(CONTROL_C_EXIT);
|
| + }
|
| + }
|
| +
|
| + launch_backoff_.InformOfRequest(false);
|
| + StopWorker();
|
| +}
|
| +
|
| +void WorkerProcessLauncher::Core::RecordSuccessfullLaunch() {
|
| + DCHECK(caller_task_runner_->BelongsToCurrentThread());
|
| +
|
| + launch_backoff_.InformOfRequest(true);
|
| +}
|
| +
|
| +void WorkerProcessLauncher::Core::StopWorker() {
|
| + DCHECK(caller_task_runner_->BelongsToCurrentThread());
|
| +
|
| + // Record a launch failure if the process exited too soon.
|
| + if (launch_success_timer_->IsRunning()) {
|
| + launch_success_timer_->Stop();
|
| + launch_backoff_.InformOfRequest(false);
|
| + }
|
| +
|
| + // Keep |this| alive until the worker process is terminated.
|
| + self_ = this;
|
| +
|
| + ipc_channel_.reset();
|
| + pipe_.Close();
|
| +
|
| + // Kill the process if it has been started already.
|
| + if (process_watcher_.GetWatchedObject() != NULL) {
|
| + launcher_delegate_->KillProcess(CONTROL_C_EXIT);
|
| + return;
|
| + }
|
| +
|
| + ipc_error_timer_->Stop();
|
| +
|
| + DCHECK(ipc_channel_.get() == NULL);
|
| + DCHECK(!pipe_.IsValid());
|
| + DCHECK(process_watcher_.GetWatchedObject() == NULL);
|
| +
|
| + process_exit_event_.Close();
|
| +
|
| + // Do not relaunch the worker process if the caller has asked us to stop.
|
| + if (stopping_) {
|
| + ipc_error_timer_.reset();
|
| + launch_timer_.reset();
|
| + self_ = NULL;
|
| + return;
|
| + }
|
| +
|
| + self_ = NULL;
|
| +
|
| + // Stop trying to restart the worker process if it exited due to
|
| + // misconfiguration.
|
| + DWORD exit_code = launcher_delegate_->GetExitCode();
|
| + if (kMinPermanentErrorExitCode <= exit_code &&
|
| + exit_code <= kMaxPermanentErrorExitCode) {
|
| + // |delegate_| must be valid because Stop() hasn't been called yet and
|
| + // |running_| is true. |worker_delegate_| is valid here because Stop()
|
| + // hasn't been called yet (|stopping_| is false).
|
| + worker_delegate_->OnPermanentError();
|
| + return;
|
| + }
|
| +
|
| + // Schedule the next attempt to launch the worker process.
|
| + launch_timer_->Start(FROM_HERE, launch_backoff_.GetTimeUntilRelease(),
|
| + this, &Core::LaunchWorker);
|
| +}
|
| +
|
| +WorkerProcessLauncher::WorkerProcessLauncher(
|
| + scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
|
| + scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
|
| + scoped_ptr<Delegate> launcher_delegate,
|
| + WorkerProcessIpcDelegate* worker_delegate,
|
| + const std::string& pipe_security_descriptor) {
|
| + core_ = new Core(caller_task_runner, io_task_runner, launcher_delegate.Pass(),
|
| + worker_delegate, pipe_security_descriptor);
|
| + core_->Start();
|
| +}
|
| +
|
| +WorkerProcessLauncher::~WorkerProcessLauncher() {
|
| + core_->Stop();
|
| + core_ = NULL;
|
| +}
|
| +
|
| +void WorkerProcessLauncher::Send(IPC::Message* message) {
|
| + core_->Send(message);
|
| +}
|
| +
|
| } // namespace remoting
|
|
|