diff options
Diffstat (limited to 'mojo/shell/runner')
-rw-r--r-- | mojo/shell/runner/child/runner_connection.cc | 16 | ||||
-rw-r--r-- | mojo/shell/runner/host/child_process.cc | 53 | ||||
-rw-r--r-- | mojo/shell/runner/host/child_process_host.cc | 111 | ||||
-rw-r--r-- | mojo/shell/runner/host/child_process_host.h | 62 |
4 files changed, 212 insertions, 30 deletions
diff --git a/mojo/shell/runner/child/runner_connection.cc b/mojo/shell/runner/child/runner_connection.cc index 313c00a..75b4648 100644 --- a/mojo/shell/runner/child/runner_connection.cc +++ b/mojo/shell/runner/child/runner_connection.cc @@ -74,6 +74,13 @@ class Blocker { using GotApplicationRequestCallback = base::Callback<void(InterfaceRequest<mojom::ShellClient>)>; +void OnCreateMessagePipe(ScopedMessagePipeHandle* result, + Blocker::Unblocker unblocker, + ScopedMessagePipeHandle pipe) { + *result = std::move(pipe); + unblocker.Unblock(base::Bind(&base::DoNothing)); +} + void OnGotApplicationRequest(InterfaceRequest<mojom::ShellClient>* out_request, InterfaceRequest<mojom::ShellClient> request) { *out_request = std::move(request); @@ -222,11 +229,14 @@ bool RunnerConnectionImpl::WaitForApplicationRequest( std::string primordial_pipe_token = base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( switches::kPrimordialPipeToken); - handle = edk::CreateChildMessagePipe(primordial_pipe_token); + Blocker blocker; + edk::CreateChildMessagePipe( + primordial_pipe_token, + base::Bind(&OnCreateMessagePipe, base::Unretained(&handle), + blocker.GetUnblocker())); + blocker.Block(); } - DCHECK(handle.is_valid()); - Blocker blocker; controller_runner_->PostTask( FROM_HERE, diff --git a/mojo/shell/runner/host/child_process.cc b/mojo/shell/runner/host/child_process.cc index 75230f0..0208b84 100644 --- a/mojo/shell/runner/host/child_process.cc +++ b/mojo/shell/runner/host/child_process.cc @@ -51,6 +51,10 @@ namespace shell { namespace { +void DidCreateChannel(embedder::ChannelInfo* channel_info) { + DVLOG(2) << "ChildControllerImpl::DidCreateChannel()"; +} + // Blocker --------------------------------------------------------------------- // Blocks a thread until another thread unblocks it, at which point it unblocks @@ -296,14 +300,34 @@ scoped_ptr<mojo::shell::LinuxSandbox> InitializeSandbox() { } #endif -ScopedMessagePipeHandle InitializeHostMessagePipe( - edk::ScopedPlatformHandle platform_channel, - scoped_refptr<base::TaskRunner> io_task_runner) { - edk::SetParentPipeHandle(std::move(platform_channel)); - std::string primordial_pipe_token = - base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( - switches::kPrimordialPipeToken); - return edk::CreateChildMessagePipe(primordial_pipe_token); +void InitializeHostMessagePipe( + embedder::ScopedPlatformHandle platform_channel, + scoped_refptr<base::TaskRunner> io_task_runner, + const base::Callback<void(ScopedMessagePipeHandle)>& callback) { + if (base::CommandLine::ForCurrentProcess()->HasSwitch("use-new-edk")) { + embedder::SetParentPipeHandle(std::move(platform_channel)); + std::string primordial_pipe_token = + base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( + switches::kPrimordialPipeToken); + edk::CreateChildMessagePipe(primordial_pipe_token, callback); + } else { + ScopedMessagePipeHandle host_message_pipe; + host_message_pipe = + embedder::CreateChannel(std::move(platform_channel), + base::Bind(&DidCreateChannel), io_task_runner); + callback.Run(std::move(host_message_pipe)); + } +} + +void OnHostMessagePipeCreated(AppContext* app_context, + base::NativeLibrary app_library, + const Blocker::Unblocker& unblocker, + ScopedMessagePipeHandle pipe) { + app_context->controller_runner()->PostTask( + FROM_HERE, + base::Bind(&ChildControllerImpl::Init, base::Unretained(app_context), + base::Unretained(app_library), base::Passed(&pipe), + unblocker)); } } // namespace @@ -336,8 +360,8 @@ int ChildProcessMain() { sandbox = InitializeSandbox(); #endif - edk::ScopedPlatformHandle platform_channel = - edk::PlatformChannelPair::PassClientHandleFromParentProcess( + embedder::ScopedPlatformHandle platform_channel = + embedder::PlatformChannelPair::PassClientHandleFromParentProcess( command_line); CHECK(platform_channel.is_valid()); @@ -348,12 +372,13 @@ int ChildProcessMain() { app_context.Init(); app_context.StartControllerThread(); - ScopedMessagePipeHandle host_pipe = InitializeHostMessagePipe( - std::move(platform_channel), app_context.io_runner()); app_context.controller_runner()->PostTask( FROM_HERE, - base::Bind(&ChildControllerImpl::Init, &app_context, app_library, - base::Passed(&host_pipe), blocker.GetUnblocker())); + base::Bind( + &InitializeHostMessagePipe, base::Passed(&platform_channel), + make_scoped_refptr(app_context.io_runner()), + base::Bind(&OnHostMessagePipeCreated, base::Unretained(&app_context), + base::Unretained(app_library), blocker.GetUnblocker()))); // This will block, then run whatever the controller wants. blocker.Block(); diff --git a/mojo/shell/runner/host/child_process_host.cc b/mojo/shell/runner/host/child_process_host.cc index 2b3cfc9..9a5b439 100644 --- a/mojo/shell/runner/host/child_process_host.cc +++ b/mojo/shell/runner/host/child_process_host.cc @@ -35,24 +35,55 @@ namespace mojo { namespace shell { +ChildProcessHost::PipeHolder::PipeHolder() {} + +void ChildProcessHost::PipeHolder::Reject() { + base::AutoLock lock(lock_); + reject_pipe_ = true; + pipe_.reset(); +} + +void ChildProcessHost::PipeHolder::SetPipe(ScopedMessagePipeHandle pipe) { + base::AutoLock lock(lock_); + DCHECK(!pipe_.is_valid()); + if (!reject_pipe_) + pipe_ = std::move(pipe); +} + +ScopedMessagePipeHandle ChildProcessHost::PipeHolder::PassPipe() { + base::AutoLock lock(lock_); + DCHECK(pipe_.is_valid()); + return std::move(pipe_); +} + +ChildProcessHost::PipeHolder::~PipeHolder() {} + ChildProcessHost::ChildProcessHost(base::TaskRunner* launch_process_runner, bool start_sandboxed, const base::FilePath& app_path) : launch_process_runner_(launch_process_runner), start_sandboxed_(start_sandboxed), app_path_(app_path), + channel_info_(nullptr), start_child_process_event_(false, false), weak_factory_(this) { - node_channel_.reset(new edk::PlatformChannelPair); - primordial_pipe_token_ = edk::GenerateRandomToken(); - controller_.Bind( - InterfacePtrInfo<mojom::ChildController>( - edk::CreateParentMessagePipe(primordial_pipe_token_), 0u)); + pipe_holder_ = new PipeHolder(); + if (base::CommandLine::ForCurrentProcess()->HasSwitch("use-new-edk")) { + node_channel_.reset(new edk::PlatformChannelPair); + primordial_pipe_token_ = edk::GenerateRandomToken(); + } else { + pipe_holder_->SetPipe(embedder::CreateChannel( + platform_channel_pair_.PassServerHandle(), + base::Bind(&ChildProcessHost::DidCreateChannel, base::Unretained(this)), + base::ThreadTaskRunnerHandle::Get())); + OnMessagePipeCreated(); + } } ChildProcessHost::ChildProcessHost(ScopedHandle channel) : launch_process_runner_(nullptr), start_sandboxed_(false), + channel_info_(nullptr), start_child_process_event_(false, false), weak_factory_(this) { CHECK(channel.is_valid()); @@ -68,11 +99,40 @@ ChildProcessHost::~ChildProcessHost() { void ChildProcessHost::Start(const ProcessReadyCallback& callback) { DCHECK(!child_process_.IsValid()); + DCHECK(process_ready_callback_.is_null()); + + process_ready_callback_ = callback; + if (base::CommandLine::ForCurrentProcess()->HasSwitch("use-new-edk")) { + // With the new EDK, bootstrap message pipes are created asynchronously. + // We recieve the bound pipe (if successful) on an arbitrary thread, + // stash it in the thread-safe |pipe_holder_|, and then try to call + // OnMessagePipeCreated() on the host's main thread. + // + // Because of the way the launcher process shuts down, it's possible for + // the main thread's MessageLoop to stop running (but not yet be destroyed!) + // while this boostrap is pending, resulting in OnMessagePipeCreated() never + // being called. + // + // A typical child process (i.e. one using ShellConnection to bind the other + // end of this pipe) may hang forever waiting for an Initialize() message + // unless the pipe is closed. This in turn means that Join() could hang + // waiting for the process to exit. Deadlock! + // + // |pipe_holder_| exists for this reason. If it's still holding onto the + // pipe when Join() is called, the pipe will be closed. + DCHECK(!primordial_pipe_token_.empty()); + edk::CreateParentMessagePipe( + primordial_pipe_token_, + base::Bind(&OnParentMessagePipeCreated, pipe_holder_, + base::ThreadTaskRunnerHandle::Get(), + base::Bind(&ChildProcessHost::OnMessagePipeCreated, + weak_factory_.GetWeakPtr()))); + } + launch_process_runner_->PostTaskAndReply( FROM_HERE, base::Bind(&ChildProcessHost::DoLaunch, base::Unretained(this)), - base::Bind(&ChildProcessHost::DidStart, - weak_factory_.GetWeakPtr(), callback)); + base::Bind(&ChildProcessHost::DidStart, weak_factory_.GetWeakPtr())); } int ChildProcessHost::Join() { @@ -82,6 +142,10 @@ int ChildProcessHost::Join() { controller_ = mojom::ChildControllerPtr(); DCHECK(child_process_.IsValid()); + // Ensure the child pipe is closed even if it wasn't yet connected to the + // controller. + pipe_holder_->Reject(); + int rv = -1; LOG_IF(ERROR, !child_process_.WaitForExit(&rv)) << "Failed to wait for child process"; @@ -108,11 +172,11 @@ void ChildProcessHost::ExitNow(int32_t exit_code) { controller_->ExitNow(exit_code); } -void ChildProcessHost::DidStart(const ProcessReadyCallback& callback) { +void ChildProcessHost::DidStart() { DVLOG(2) << "ChildProcessHost::DidStart()"; if (child_process_.IsValid()) { - callback.Run(child_process_.Pid()); + MaybeNotifyProcessReady(); } else { LOG(ERROR) << "Failed to start child process"; AppCompleted(MOJO_RESULT_UNKNOWN); @@ -214,5 +278,34 @@ void ChildProcessHost::AppCompleted(int32_t result) { } } +void ChildProcessHost::DidCreateChannel(embedder::ChannelInfo* channel_info) { + DVLOG(2) << "AppChildProcessHost::DidCreateChannel()"; + + DCHECK(channel_info || + base::CommandLine::ForCurrentProcess()->HasSwitch("use-new-edk")); + channel_info_ = channel_info; +} + +void ChildProcessHost::OnMessagePipeCreated() { + controller_.Bind( + InterfacePtrInfo<mojom::ChildController>(pipe_holder_->PassPipe(), 0u)); + MaybeNotifyProcessReady(); +} + +void ChildProcessHost::MaybeNotifyProcessReady() { + if (controller_.is_bound() && child_process_.IsValid()) + process_ready_callback_.Run(child_process_.Pid()); +} + +// static +void ChildProcessHost::OnParentMessagePipeCreated( + scoped_refptr<PipeHolder> holder, + scoped_refptr<base::TaskRunner> callback_task_runner, + const base::Closure& callback, + ScopedMessagePipeHandle pipe) { + holder->SetPipe(std::move(pipe)); + callback_task_runner->PostTask(FROM_HERE, callback); +} + } // namespace shell } // namespace mojo diff --git a/mojo/shell/runner/host/child_process_host.h b/mojo/shell/runner/host/child_process_host.h index 2664d30..906a1a2 100644 --- a/mojo/shell/runner/host/child_process_host.h +++ b/mojo/shell/runner/host/child_process_host.h @@ -71,24 +71,70 @@ class ChildProcessHost { void ExitNow(int32_t exit_code); protected: - void DidStart(const ProcessReadyCallback& callback); + void DidStart(); private: + // A thread-safe holder for the bootstrap message pipe to this child process. + // The pipe is established on an arbitrary thread and may not be connected + // until the host's message loop has stopped running. + class PipeHolder : public base::RefCountedThreadSafe<PipeHolder> { + public: + PipeHolder(); + + void Reject(); + void SetPipe(ScopedMessagePipeHandle pipe); + ScopedMessagePipeHandle PassPipe(); + + private: + friend class base::RefCountedThreadSafe<PipeHolder>; + + ~PipeHolder(); + + base::Lock lock_; + bool reject_pipe_ = false; + ScopedMessagePipeHandle pipe_; + + DISALLOW_COPY_AND_ASSIGN(PipeHolder); + }; + void DoLaunch(); void AppCompleted(int32_t result); + // Callback for |embedder::CreateChannel()|. + void DidCreateChannel(embedder::ChannelInfo* channel_info); + + // Called once |pipe_holder_| is bound to a pipe. + void OnMessagePipeCreated(); + + // Called when the child process is launched and when the bootstrap + // message pipe is created. Once both things have happened (which may happen + // in either order), |process_ready_callback_| is invoked. + void MaybeNotifyProcessReady(); + + // Callback used to receive the child message pipe from the ports EDK. + // This may be called on any thread. It will always stash the pipe in + // |holder|, and it will then attempt to call |callback| on + // |callback_task_runner| (which may or may not still be running tasks.) + static void OnParentMessagePipeCreated( + scoped_refptr<PipeHolder> holder, + scoped_refptr<base::TaskRunner> callback_task_runner, + const base::Closure& callback, + ScopedMessagePipeHandle pipe); + scoped_refptr<base::TaskRunner> launch_process_runner_; bool start_sandboxed_; const base::FilePath app_path_; base::Process child_process_; // Used for the ChildController binding. - edk::PlatformChannelPair platform_channel_pair_; + embedder::PlatformChannelPair platform_channel_pair_; mojom::ChildControllerPtr controller_; + embedder::ChannelInfo* channel_info_; mojom::ChildController::StartAppCallback on_app_complete_; - edk::HandlePassingInformation handle_passing_info_; + embedder::HandlePassingInformation handle_passing_info_; - // Used to back the NodeChannel between the parent and child node. + // Used only when --use-new-edk is specified. Used to back the NodeChannel + // between the parent and child node. scoped_ptr<edk::PlatformChannelPair> node_channel_; // Since Start() calls a method on another thread, we use an event to block @@ -98,6 +144,14 @@ class ChildProcessHost { // A token the child can use to connect a primordial pipe to the host. std::string primordial_pipe_token_; + // Holds the message pipe to the child process until it is either closed or + // bound to the controller interface. + scoped_refptr<PipeHolder> pipe_holder_; + + // Invoked exactly once, as soon as the child process's ID is known and + // a pipe to the child has been established. + ProcessReadyCallback process_ready_callback_; + base::WeakPtrFactory<ChildProcessHost> weak_factory_; DISALLOW_COPY_AND_ASSIGN(ChildProcessHost); |