blob: a24598fe77290f44016870c540f9b94dbb602602 [file] [log] [blame]
// Copyright 2010-2015, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "ipc/process_watch_dog.h"
#ifdef OS_WIN
#include <windows.h>
#else
#include <signal.h>
#include <errno.h>
#endif
#include "base/logging.h"
#include "base/mutex.h"
#include "base/port.h"
#include "base/scoped_handle.h"
#include "base/util.h"
namespace mozc {
#ifdef OS_WIN
ProcessWatchDog::ProcessWatchDog()
: event_(::CreateEventW(NULL, TRUE, FALSE, NULL)),
process_id_(UnknownProcessID),
thread_id_(UnknownThreadID),
timeout_(-1),
is_finished_(false),
mutex_(new Mutex) {
if (event_.get() == NULL) {
LOG(ERROR) << "::CreateEvent() failed.";
return;
}
Thread::Start(); // start
}
ProcessWatchDog::~ProcessWatchDog() {
is_finished_ = true; // set the flag to terminate the thread
if (event_.get() != NULL) {
::SetEvent(event_.get()); // wake up WaitForMultipleObjects
}
Join(); // wait for the thread
}
bool ProcessWatchDog::SetID(ProcessID process_id, ThreadID thread_id,
int timeout) {
if (event_.get() == NULL) {
LOG(ERROR) << "event is NULL";
return false;
}
if (process_id_ == process_id && thread_id_ == thread_id &&
timeout_ == timeout) {
// don't repeat if we are checking the same thread/process
return true;
}
// rewrite the valeus
{
scoped_lock l(mutex_.get());
process_id_ = process_id;
thread_id_ = thread_id;
timeout_ = timeout;
}
// wake up WaitForMultipleObjects
::SetEvent(event_.get());
return true;
}
void ProcessWatchDog::Run() {
while (!is_finished_) {
ScopedHandle process_handle;
ScopedHandle thread_handle;
int timeout = -1;
// read the current ids/timeout
{
scoped_lock l(mutex_.get());
if (process_id_ != UnknownProcessID) {
const HANDLE handle = ::OpenProcess(SYNCHRONIZE, FALSE, process_id_);
const DWORD error = ::GetLastError();
process_handle.reset(handle);
if (process_handle.get() == NULL) {
LOG(ERROR) << "OpenProcess failed: " << process_id_ << " " << error;
switch (error) {
case ERROR_ACCESS_DENIED:
Signaled(ProcessWatchDog::PROCESS_ACCESS_DENIED_SIGNALED);
break;
case ERROR_INVALID_PARAMETER:
Signaled(ProcessWatchDog::PROCESS_NOT_FOUND_SIGNALED);
break;
default:
Signaled(ProcessWatchDog::PROCESS_ERROR_SIGNALED);
break;
}
}
}
if (thread_id_ != UnknownThreadID) {
const HANDLE handle = ::OpenThread(SYNCHRONIZE, FALSE, thread_id_);
const DWORD error = ::GetLastError();
thread_handle.reset(handle);
if (thread_handle.get() == NULL) {
LOG(ERROR) << "OpenThread failed: " << thread_id_ << " " << error;
switch (error) {
case ERROR_ACCESS_DENIED:
Signaled(ProcessWatchDog::THREAD_ACCESS_DENIED_SIGNALED);
break;
case ERROR_INVALID_PARAMETER:
Signaled(ProcessWatchDog::THREAD_NOT_FOUND_SIGNALED);
break;
default:
Signaled(ProcessWatchDog::THREAD_ERROR_SIGNALED);
break;
}
}
}
timeout = timeout_;
if (timeout_ < 0) {
timeout = INFINITE;
}
process_id_ = UnknownProcessID;
thread_id_ = UnknownThreadID;
timeout_ = -1;
}
SignalType types[3];
HANDLE handles[3] = {};
// set event
handles[0] = event_.get();
// set handles
DWORD size = 1;
if (process_handle.get() != NULL) {
VLOG(2) << "Inserting process handle";
handles[size] = process_handle.get();
types[size] = ProcessWatchDog::PROCESS_SIGNALED;
++size;
}
if (thread_handle.get() != NULL) {
VLOG(2) << "Inserting thread handle";
handles[size] = thread_handle.get();
types[size] = ProcessWatchDog::THREAD_SIGNALED;
++size;
}
const DWORD result = ::WaitForMultipleObjects(
size, handles, FALSE, timeout);
SignalType result_type = ProcessWatchDog::UNKNOWN_SIGNALED;
switch (result) {
case WAIT_OBJECT_0:
case WAIT_ABANDONED_0:
VLOG(2) << "event is signaled";
::ResetEvent(event_.get()); // reset event to wait for the new request
break;
case WAIT_OBJECT_0 + 1:
case WAIT_ABANDONED_0 + 1:
VLOG(2) << "handle 1 is signaled";
result_type = types[1];
break;
case WAIT_OBJECT_0 + 2:
case WAIT_ABANDONED_0 + 2:
VLOG(2) << "handle 2 is signaled";
result_type = types[2];
break;
case WAIT_TIMEOUT:
VLOG(2) << "timeout is signaled";
result_type = ProcessWatchDog::TIMEOUT_SIGNALED;
break;
default:
LOG(ERROR) << "WaitForMultipleObjects() failed: " << GetLastError();
break;
}
if (result_type != ProcessWatchDog::UNKNOWN_SIGNALED) {
VLOG(1) << "Sending signal: " << static_cast<int>(result_type);
Signaled(result_type); // call signal handler
}
}
}
#else // OS_WIN
ProcessWatchDog::ProcessWatchDog()
: process_id_(UnknownProcessID),
thread_id_(UnknownProcessID),
is_finished_(false),
mutex_(new Mutex) {
Thread::Start();
}
ProcessWatchDog::~ProcessWatchDog() {
is_finished_ = true;
Join();
}
bool ProcessWatchDog::SetID(ProcessWatchDog::ProcessID process_id,
ProcessWatchDog::ThreadID thread_id,
int timeout) {
if (process_id_ == process_id && thread_id_ == thread_id &&
timeout_ == timeout) {
// don't repeat if we are checking the same thread/process
return true;
}
LOG_IF(ERROR, thread_id != UnknownThreadID)
<< "Linux/Mac don't allow to capture ThreadID";
LOG_IF(ERROR, timeout > 0) << "timeout is not supported";
if (::kill(process_id, 0) != 0) {
if (errno == ESRCH) {
// emit PROCESS_NOT_FOUND_SIGNALED immediately,
// if the process id is not found NOW.
Signaled(ProcessWatchDog::PROCESS_NOT_FOUND_SIGNALED);
process_id = UnknownProcessID;
}
}
{
scoped_lock l(mutex_.get());
process_id_ = process_id;
thread_id_ = thread_id;
timeout_ = -1;
}
return true;
}
void ProcessWatchDog::Run() {
// Polling-based watch-dog.
// Unlike WaitForMultipleObjects on Windows, no event-driven API seems to be
// available on Linux.
// NOTE In theory, there may possibility that some other process
// reuse same process id in 250ms or write to is_finished_ stays
// forever in another CPU's local cache.
// TODO(team): use kqueue with EVFILT_PROC/NOTE_EXIT for Mac.
while (!is_finished_) {
Util::Sleep(250);
if (process_id_ == UnknownProcessID) {
continue;
}
if (::kill(process_id_, 0) != 0) {
if (errno == EPERM) {
Signaled(ProcessWatchDog::PROCESS_ACCESS_DENIED_SIGNALED);
} else if (errno == ESRCH) {
// Since we are polling the process by NULL signal,
// it is essentially impossible to tell the process is not found
// or terminated.
Signaled(ProcessWatchDog::PROCESS_SIGNALED);
} else {
Signaled(ProcessWatchDog::PROCESS_ERROR_SIGNALED);
}
scoped_lock l(mutex_.get());
process_id_ = UnknownProcessID;
}
}
}
#endif // OS_WIN
} // namespace mozc