summaryrefslogtreecommitdiffstats
path: root/ceee/ie/broker/executors_manager.cc
diff options
context:
space:
mode:
Diffstat (limited to 'ceee/ie/broker/executors_manager.cc')
-rw-r--r--ceee/ie/broker/executors_manager.cc453
1 files changed, 453 insertions, 0 deletions
diff --git a/ceee/ie/broker/executors_manager.cc b/ceee/ie/broker/executors_manager.cc
new file mode 100644
index 0000000..5d87467
--- /dev/null
+++ b/ceee/ie/broker/executors_manager.cc
@@ -0,0 +1,453 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// ExecutorsManager implementation.
+
+#include "ceee/ie/broker/executors_manager.h"
+
+#include "base/logging.h"
+#include "ceee/ie/broker/broker_module_util.h"
+#include "ceee/common/com_utils.h"
+
+namespace {
+
+// The timeout we set before accepting a failure when we wait for events.
+const DWORD kTimeOut = 20000;
+
+// Utility class to ensure the setting of an event before we exit a method.
+class AutoSetEvent {
+ public:
+ explicit AutoSetEvent(HANDLE event_handle) : event_handle_(event_handle) {}
+ ~AutoSetEvent() { ::SetEvent(event_handle_); }
+ private:
+ HANDLE event_handle_;
+};
+
+// A cached handle to our current process which we use when we call
+// DuplicateHandle. GetCurrentProcess returns a pseudo HANDLE that doesn't
+// need to be closed.
+const HANDLE kProcessHandle = ::GetCurrentProcess();
+
+} // namespace
+
+
+const size_t ExecutorsManager::kTerminationHandleIndexOffset = 0;
+const size_t ExecutorsManager::kUpdateHandleIndexOffset = 1;
+const size_t ExecutorsManager::kLastHandleIndexOffset =
+ kUpdateHandleIndexOffset;
+const size_t ExecutorsManager::kExtraHandles = kLastHandleIndexOffset + 1;
+
+ExecutorsManager::ExecutorsManager(bool no_thread)
+ : update_threads_list_gate_(::CreateEvent(NULL, FALSE, FALSE, NULL)),
+ // Termination is manual reset. When we're terminated... We're terminated!
+ termination_gate_(::CreateEvent(NULL, TRUE, FALSE, NULL)) {
+ DCHECK(update_threads_list_gate_ != NULL);
+ DCHECK(termination_gate_ != NULL);
+
+ if (!no_thread) {
+ ThreadStartData thread_start_data;
+ thread_start_data.me = this;
+ // Again, manual reset, because when we are started... WE ARE STARTED!!! :-)
+ thread_start_data.thread_started_gate.Attach(::CreateEvent(NULL, TRUE,
+ FALSE, NULL));
+ DCHECK(thread_start_data.thread_started_gate != NULL);
+
+ // Since we provide the this pointer to the thread, we must make sure to
+ // keep ALL INITIALIZATION CODE ABOVE THIS LINE!!!
+ thread_.Attach(::CreateThread(NULL, 0, ThreadProc, &thread_start_data,
+ 0, 0));
+ DCHECK(thread_ != NULL);
+
+ // Make sure the thread is ready before continuing
+ DWORD result = WaitForSingleObject(thread_start_data.thread_started_gate,
+ kTimeOut);
+ DCHECK(result == WAIT_OBJECT_0);
+ }
+}
+
+HRESULT ExecutorsManager::RegisterTabExecutor(ThreadId thread_id,
+ IUnknown* executor) {
+ // We will need to know outside of the lock if the map was empty or not.
+ // This way we can add a ref to the module for the existence of the map.
+ bool map_was_empty = false;
+ {
+ AutoLock lock(lock_);
+ map_was_empty = executors_.empty();
+ if (!map_was_empty && executors_.find(thread_id) != executors_.end()) {
+ return S_OK;
+ }
+ CHandle thread_handle(::OpenThread(SYNCHRONIZE, FALSE, thread_id));
+ if (thread_handle == NULL) {
+ DCHECK(false) << "Can't Open thread: " << thread_id;
+ return E_UNEXPECTED;
+ }
+ ExecutorInfo& new_executor_info = executors_[thread_id];
+ new_executor_info.executor = executor;
+ new_executor_info.thread_handle = thread_handle;
+ } // End of lock.
+
+ if (map_was_empty) {
+ // We go from empty to not empty,
+ // so lock the module to make sure we stay alive.
+ ceee_module_util::LockModule();
+ }
+ return S_OK;
+}
+
+HRESULT ExecutorsManager::RegisterWindowExecutor(ThreadId thread_id,
+ IUnknown* executor) {
+ // We need to fetch the event handle associated to this thread ID from
+ // our map in a thread safe way...
+ CHandle executor_registration_gate;
+ {
+ AutoLock lock(lock_);
+ if (executors_.find(thread_id) != executors_.end()) {
+ DCHECK(false) << "Unexpected registered thread_id: " << thread_id;
+ return E_UNEXPECTED;
+ }
+
+ Tid2Event::iterator iter = pending_registrations_.find(thread_id);
+ if (iter == pending_registrations_.end() || executor == NULL) {
+ DCHECK(false) << "Invalid thread_id: " << thread_id <<
+ ", or NULL executor.";
+ return E_INVALIDARG;
+ }
+ // Make sure we use a duplicate handle so that we don't get caught setting
+ // a dead handle when we exit, in case the other thread wakes up because
+ // of a (unlikely) double registration.
+ BOOL success = ::DuplicateHandle(
+ kProcessHandle, iter->second.m_h, kProcessHandle,
+ &executor_registration_gate.m_h, 0, FALSE, DUPLICATE_SAME_ACCESS);
+ DCHECK(success) << com::LogWe();
+ } // End of lock.
+
+ // We must make sure to wake up the thread(s) that might be waiting on us.
+ // But only when we are done.
+ AutoSetEvent auto_set_event(executor_registration_gate);
+
+ // Try to get a handle to this thread right away so that we can do the rest
+ // atomically. We need it to wake us up when it dies.
+ CHandle thread_handle(::OpenThread(SYNCHRONIZE, FALSE, thread_id));
+ if (thread_handle == NULL) {
+ DCHECK(false) << "Can't Open thread: " << thread_id;
+ return S_FALSE;
+ }
+
+ // We will need to know outside of the lock if the map was empty or not.
+ // This way we can add a ref to the module for the existence of the map.
+ bool map_was_empty = false;
+ {
+ AutoLock lock(lock_);
+ map_was_empty = executors_.empty();
+ // We should not get here if we already have an executor for that thread.
+ DCHECK(executors_.find(thread_id) == executors_.end());
+ ExecutorInfo& new_executor_info = executors_[thread_id];
+ new_executor_info.executor = executor;
+ new_executor_info.thread_handle = thread_handle;
+ } // End of lock.
+
+ if (map_was_empty) {
+ // We go from empty to not empty,
+ // so lock the module to make sure we stay alive.
+ ceee_module_util::LockModule();
+ }
+
+ // Update the list of handles that our thread is waiting on.
+ BOOL success = ::SetEvent(update_threads_list_gate_);
+ DCHECK(success);
+ return S_OK;
+}
+
+HRESULT ExecutorsManager::GetExecutor(ThreadId thread_id, HWND window,
+ REFIID riid, void** executor) {
+ DCHECK(executor != NULL);
+ // We may need to wait for either a currently pending
+ // or own newly created registration of a new executor.
+ CHandle executor_registration_gate;
+
+ // We need to remember if we must create a new one or not.
+ // But we must create the executor creator outside of the lock.
+ bool create_executor = false;
+ {
+ AutoLock lock(lock_);
+ ExecutorsMap::iterator exec_iter = executors_.find(thread_id);
+ if (exec_iter != executors_.end()) {
+ // Found it... We're done... That was quick!!! :-)
+ DCHECK(exec_iter->second.executor != NULL);
+ return exec_iter->second.executor->QueryInterface(riid, executor);
+ }
+
+ // Check if we need to wait for a pending registration.
+ Tid2Event::iterator event_iter = pending_registrations_.find(thread_id);
+ if (event_iter == pending_registrations_.end()) {
+ // No pending registration, so we will need to create a new executor.
+ create_executor = true;
+
+ // Use the thread id as a cookie to only allow known threads to register.
+ // Also use it to map to a new event we will use to signal the end of this
+ // registration. We use a manual reset event so that more than one thread
+ // can wait for it, and once we're done... we're done... period! :-)
+ executor_registration_gate.Attach(::CreateEvent(NULL, TRUE, FALSE, NULL));
+ DCHECK(executor_registration_gate != NULL);
+ CHandle& new_registration_handle = pending_registrations_[thread_id];
+ // Make sure we use a duplicate handle so that we don't get caught waiting
+ // on a dead handle later, in case other threads wake up before we do and
+ // close the handle before we wake up.
+ BOOL success = ::DuplicateHandle(
+ kProcessHandle, executor_registration_gate, kProcessHandle,
+ &new_registration_handle.m_h, 0, FALSE, DUPLICATE_SAME_ACCESS);
+ DCHECK(success) << com::LogWe();
+ } else {
+ // Same comment as above...
+ BOOL success = ::DuplicateHandle(
+ kProcessHandle, event_iter->second.m_h, kProcessHandle,
+ &executor_registration_gate.m_h, 0, FALSE, DUPLICATE_SAME_ACCESS);
+ DCHECK(success) << com::LogWe();
+ }
+ } // End of lock.
+
+ CComPtr<ICeeeExecutorCreator> executor_creator;
+ if (create_executor) {
+ // We need to create an executor creator so that the code setting up
+ // a Windows Hook in the other process, runs from a DLL that can be
+ // injected in that other process... WE are running in an executable.
+ HRESULT hr = GetExecutorCreator(&executor_creator);
+ DCHECK(SUCCEEDED(hr) && executor_creator != NULL) <<
+ "CoCreating Executor Creator. " << com::LogHr(hr);
+ hr = executor_creator->CreateWindowExecutor(thread_id,
+ reinterpret_cast<CeeeWindowHandle>(window));
+ if (FAILED(hr)) {
+ // This could happen if the thread we want to hook to died prematurely.
+ AutoLock lock(lock_);
+ pending_registrations_.erase(thread_id);
+ return hr;
+ }
+ }
+
+ // Wait for the registration to complete.
+ DWORD result = WaitForSingleObject(executor_registration_gate, kTimeOut);
+ LOG_IF(INFO, result != WAIT_OBJECT_0) << "Registration problem? " <<
+ "Wait Result: " << com::LogWe(result);
+
+ // Let the executor creator know that we got the registration
+ // and it can tear down what was needed to trigger it.
+ if (executor_creator != NULL) {
+ HRESULT hr = executor_creator->Teardown(thread_id);
+ DCHECK(SUCCEEDED(hr)) << "Tearing down executor creator" << com::LogHr(hr);
+ }
+
+ // Do our own cleanup and return a reference thread safely...
+ AutoLock lock(lock_);
+ pending_registrations_.erase(thread_id);
+ ExecutorsMap::iterator iter = executors_.find(thread_id);
+ if (iter == executors_.end()) {
+ DCHECK(false) << "New executor registration failed.";
+ return E_UNEXPECTED;
+ }
+
+ DCHECK(iter->second.executor != NULL);
+ return iter->second.executor->QueryInterface(riid, executor);
+}
+
+HRESULT ExecutorsManager::RemoveExecutor(ThreadId thread_id) {
+ // Make sure to Release the executor outside the lock.
+ CComPtr<IUnknown> dead_executor;
+ bool map_is_empty = false;
+ {
+ AutoLock lock(lock_);
+ ExecutorsMap::iterator iter = executors_.find(thread_id);
+ if (iter == executors_.end()) {
+ return S_FALSE;
+ }
+
+ dead_executor.Attach(iter->second.executor.Detach());
+ executors_.erase(iter);
+ map_is_empty = executors_.empty();
+ } // End of lock.
+
+ if (map_is_empty) {
+ // We go from not empty to empty,
+ // so unlock the module it can leave in peace.
+ ceee_module_util::UnlockModule();
+ }
+ return S_OK;
+}
+
+HRESULT ExecutorsManager::Terminate() {
+ if (thread_ != NULL) {
+ // Ask our thread to quit and wait for it to be done.
+ DWORD result = ::SignalObjectAndWait(termination_gate_, thread_, kTimeOut,
+ FALSE);
+ DCHECK(result == WAIT_OBJECT_0);
+ thread_.Close();
+ }
+ if (!executors_.empty()) {
+ // TODO(mad@chromium.org): Can this happen???
+ NOTREACHED();
+ ceee_module_util::UnlockModule();
+ }
+
+ executors_.clear();
+ update_threads_list_gate_.Close();
+ termination_gate_.Close();
+
+ return S_OK;
+}
+
+void ExecutorsManager::SetTabIdForHandle(long tab_id, HWND handle) {
+ AutoLock lock(lock_);
+ DCHECK(tab_id_map_.end() == tab_id_map_.find(tab_id));
+ DCHECK(handle_map_.end() == handle_map_.find(handle));
+ if (handle == reinterpret_cast<HWND>(INVALID_HANDLE_VALUE) ||
+ tab_id == kInvalidChromeSessionId) {
+ NOTREACHED();
+ return;
+ }
+
+ tab_id_map_[tab_id] = handle;
+ handle_map_[handle] = tab_id;
+}
+
+void ExecutorsManager::DeleteTabHandle(HWND handle) {
+ AutoLock lock(lock_);
+ HandleMap::iterator handle_it = handle_map_.find(handle);
+ if(handle_map_.end() != handle_it) {
+ DCHECK(false);
+ return;
+ }
+
+ TabIdMap::iterator tab_id_it = tab_id_map_.find(handle_it->second);
+ if(tab_id_map_.end() != tab_id_it) {
+ DCHECK(false);
+ return;
+ }
+
+#ifdef DEBUG
+ tab_id_map_[handle_it->second] = reinterpret_cast<HWND>(INVALID_HANDLE_VALUE);
+ handle_map_[handle] = kInvalidChromeSessionId;
+#else
+ tab_id_map_.erase(handle_it->second);
+ handle_map_.erase(handle);
+#endif // DEBUG
+}
+
+HWND ExecutorsManager::GetTabHandleFromId(int tab_id) {
+ AutoLock lock(lock_);
+ TabIdMap::const_iterator it = tab_id_map_.find(tab_id);
+ DCHECK(it != tab_id_map_.end());
+
+ if (it == tab_id_map_.end())
+ return reinterpret_cast<HWND>(INVALID_HANDLE_VALUE);
+
+ // Deleted? I hope not.
+ DCHECK(it->second != reinterpret_cast<HWND>(INVALID_HANDLE_VALUE));
+ return it->second;
+}
+
+int ExecutorsManager::GetTabIdFromHandle(HWND tab_handle) {
+ AutoLock lock(lock_);
+ HandleMap::const_iterator it = handle_map_.find(tab_handle);
+ DCHECK(it != handle_map_.end());
+ if (it == handle_map_.end())
+ return kInvalidChromeSessionId;
+ DCHECK(it->second != kInvalidChromeSessionId); // Deleted? I hope not.
+ return it->second;
+}
+
+HRESULT ExecutorsManager::GetExecutorCreator(
+ ICeeeExecutorCreator** executor_creator) {
+ return ::CoCreateInstance(CLSID_CeeeExecutorCreator, NULL,
+ CLSCTX_INPROC_SERVER, IID_ICeeeExecutorCreator,
+ reinterpret_cast<void**>(executor_creator));
+}
+
+size_t ExecutorsManager::GetThreadHandles(
+ CHandle thread_handles[], ThreadId thread_ids[], size_t num_threads) {
+ AutoLock lock(lock_);
+ ExecutorsMap::iterator iter = executors_.begin();
+ size_t index = 0;
+ for (; index < num_threads && iter != executors_.end(); ++index, ++iter) {
+ DCHECK(thread_handles[index].m_h == NULL);
+ // We need to duplicate the handle to make sure the caller will not wait
+ // on a closed handle.
+ BOOL success = ::DuplicateHandle(
+ kProcessHandle, iter->second.thread_handle, kProcessHandle,
+ &thread_handles[index].m_h, 0, FALSE, DUPLICATE_SAME_ACCESS);
+ DCHECK(success) << com::LogWe();
+ thread_ids[index] = iter->first;
+ }
+
+ return index;
+}
+
+DWORD ExecutorsManager::WaitForSingleObject(HANDLE wait_handle, DWORD timeout) {
+ return ::WaitForSingleObject(wait_handle, timeout);
+}
+
+DWORD ExecutorsManager::WaitForMultipleObjects(DWORD num_handles,
+ const HANDLE* wait_handles, BOOL wait_all, DWORD timeout) {
+ return ::WaitForMultipleObjects(num_handles, wait_handles, wait_all, timeout);
+}
+
+
+DWORD ExecutorsManager::ThreadProc(LPVOID parameter) {
+ // We must make sure to join the multi thread apartment so that the executors
+ // get released properly in the same apartment they were acquired from.
+ ::CoInitializeEx(NULL, COINIT_MULTITHREADED);
+
+ ThreadStartData* thread_start_data =
+ reinterpret_cast<ThreadStartData*>(parameter);
+ DCHECK(thread_start_data != NULL);
+ ExecutorsManager* me = thread_start_data->me;
+ DCHECK(me != NULL);
+
+ // Let our parent know that we are old enough now!
+ ::SetEvent(thread_start_data->thread_started_gate);
+ // Setting the event will destroy the thread start data living on the stack
+ // so make sure we don't use it anymore.
+ thread_start_data = NULL;
+
+ while (true) {
+ CHandle smart_handles[MAXIMUM_WAIT_OBJECTS];
+ HANDLE handles[MAXIMUM_WAIT_OBJECTS];
+ ThreadId thread_ids[MAXIMUM_WAIT_OBJECTS];
+ // Get as many handles as we can, leaving room for kExtraHandles.
+ size_t num_threads = me->GetThreadHandles(
+ smart_handles, thread_ids, MAXIMUM_WAIT_OBJECTS - kExtraHandles);
+ // Wait function needs an array of raw handles, not smart ones.
+ for (size_t index = 0; index < num_threads; ++index)
+ handles[index] = smart_handles[index];
+
+ // We also need to wait for our termination signal.
+ handles[num_threads + kTerminationHandleIndexOffset] =
+ me->termination_gate_;
+ // As well as a signal warning us to go fetch more thread handles.
+ handles[num_threads + kUpdateHandleIndexOffset] =
+ me->update_threads_list_gate_;
+
+ size_t num_handles = num_threads + kExtraHandles;
+ DWORD result = me->WaitForMultipleObjects(num_handles, handles, FALSE,
+ INFINITE);
+ if (result == WAIT_OBJECT_0 + num_threads +
+ kUpdateHandleIndexOffset) {
+ // We got a new thread added,
+ // simply let the loop turn to add it to our watch list.
+ } else if (result >= WAIT_OBJECT_0 &&
+ result < WAIT_OBJECT_0 + num_threads) {
+ // One of our threads have died, cleanup time.
+ me->RemoveExecutor(thread_ids[result - WAIT_OBJECT_0]);
+ } else if (result == WAIT_OBJECT_0 + num_threads +
+ kTerminationHandleIndexOffset) {
+ // we are being terminated, break the cycle.
+ break;
+ } else {
+ DCHECK(result == WAIT_FAILED);
+ LOG(ERROR) << "ExecutorsManager::ThreadProc " << com::LogWe();
+ break;
+ }
+ }
+ // Merci... Bonsoir...
+ ::CoUninitialize();
+ return 1;
+}