diff --git a/src/developer/debug/debug_agent/debug_agent.cc b/src/developer/debug/debug_agent/debug_agent.cc
index 401068519731f34e0e335fa6950e2b19a68e9b9d..66c5ee5027e79c269ec6efbac9f04a9cfa8bd66f 100644
--- a/src/developer/debug/debug_agent/debug_agent.cc
+++ b/src/developer/debug/debug_agent/debug_agent.cc
@@ -536,11 +536,6 @@ void DebugAgent::LaunchComponent(const debug_ipc::LaunchRequest& request,
   *reply = {};
   reply->inferior_type = debug_ipc::InferiorType::kComponent;
 
-  if (!debug_ipc::MessageLoopTarget::Current()->SupportsFidl()) {
-    reply->status = ZX_ERR_NOT_SUPPORTED;
-    return;
-  }
-
   ComponentLauncher component_launcher(services_);
 
   ComponentDescription description;
diff --git a/src/developer/debug/debug_agent/integration_tests/message_loop_wrapper.cc b/src/developer/debug/debug_agent/integration_tests/message_loop_wrapper.cc
index a171d98d4311ee78f6df88f7f9d7c1eb55c26f5f..aedfd114eea158dabb166bc43af7ae7336de5cf6 100644
--- a/src/developer/debug/debug_agent/integration_tests/message_loop_wrapper.cc
+++ b/src/developer/debug/debug_agent/integration_tests/message_loop_wrapper.cc
@@ -4,12 +4,12 @@
 
 #include "src/developer/debug/debug_agent/integration_tests/message_loop_wrapper.h"
 
-#include "src/developer/debug/shared/message_loop_async.h"
+#include "src/developer/debug/shared/message_loop_target.h"
 
 namespace debug_agent {
 
 MessageLoopWrapper::MessageLoopWrapper() {
-  loop_ = std::make_unique<debug_ipc::MessageLoopAsync>();
+  loop_ = std::make_unique<debug_ipc::MessageLoopTarget>();
   loop_->Init();
 }
 
diff --git a/src/developer/debug/debug_agent/main.cc b/src/developer/debug/debug_agent/main.cc
index 98e1d567dba572786d1a4496b44d0a1dc75b6959..63115b1bbfef97d014b613c272475d69882a7637 100644
--- a/src/developer/debug/debug_agent/main.cc
+++ b/src/developer/debug/debug_agent/main.cc
@@ -20,9 +20,7 @@
 #include "src/developer/debug/debug_agent/unwind.h"
 #include "src/developer/debug/shared/buffered_fd.h"
 #include "src/developer/debug/shared/logging/logging.h"
-#include "src/developer/debug/shared/message_loop_async.h"
 #include "src/developer/debug/shared/message_loop_target.h"
-#include "src/developer/debug/shared/message_loop_zircon.h"
 #include "src/developer/debug/shared/zx_status.h"
 #include "src/lib/files/unique_fd.h"
 
@@ -31,21 +29,6 @@ using namespace debug_ipc;
 namespace debug_agent {
 namespace {
 
-std::unique_ptr<debug_ipc::MessageLoopTarget> GetMessageLoop(
-    MessageLoopTarget::Type type) {
-  switch (type) {
-    case MessageLoopTarget::Type::kAsync:
-      return std::make_unique<debug_ipc::MessageLoopAsync>();
-    case MessageLoopTarget::Type::kZircon:
-      return std::make_unique<debug_ipc::MessageLoopZircon>();
-    case MessageLoopTarget::Type::kLast:
-      break;
-  }
-
-  FXL_NOTREACHED();
-  return nullptr;
-}
-
 // SocketConnection ------------------------------------------------------------
 
 // Represents one connection to a client.
@@ -173,9 +156,6 @@ const std::map<std::string, std::string>& GetOptions() {
   static std::map<std::string, std::string> options = {
       {"auwind", R"(
       [Experimental] Use the unwinder from AOSP.)"},
-      {"legacy-message-loop", R"(
-      [DEPRECATED] Use the originalbackend message loop.
-                   Will be removed eventually.)"},
       {"debug-mode", R"(
       Run the agent on debug mode. This will enable conditional logging messages
       and timing profiling. Mainly useful for people developing zxdb.)"},
@@ -241,13 +221,6 @@ int main(int argc, char* argv[]) {
     debug_agent::SetUnwinderType(debug_agent::UnwinderType::kAndroid);
   }
 
-  // By default use the async agent message loop.
-  auto message_loop_type = MessageLoopTarget::Type::kAsync;
-  if (cmdline.HasOption("legacy-message-loop")) {
-    // Use new async loop.
-    message_loop_type = MessageLoopTarget::Type::kZircon;
-  }
-
   // TODO(donosoc): Do correct category setup.
   debug_ipc::SetLogCategories({LogCategory::kAll});
   if (cmdline.HasOption("debug-mode")) {
@@ -267,14 +240,11 @@ int main(int argc, char* argv[]) {
 
     auto services = sys::ServiceDirectory::CreateFromNamespace();
 
-    printf("Using %s message loop.\n",
-           MessageLoopTarget::TypeToString(message_loop_type));
-    auto message_loop = debug_agent::GetMessageLoop(message_loop_type);
+    auto message_loop = std::make_unique<MessageLoopTarget>();
     zx_status_t status = message_loop->InitTarget();
     if (status != ZX_OK) {
-      const char* type = MessageLoopTarget::TypeToString(message_loop_type);
-      FXL_LOG(ERROR) << "Could not initialize message loop (type: " << type
-                     << "): " << debug_ipc::ZxStatusToString(status);
+      FXL_LOG(ERROR) << "Could not initialize message loop: "
+                     << debug_ipc::ZxStatusToString(status);
     }
 
     // The scope ensures the objects are destroyed before calling Cleanup on the
diff --git a/src/developer/debug/shared/BUILD.gn b/src/developer/debug/shared/BUILD.gn
index f65d5b2b3df56367f37e418d42486889cc79545e..28a077c3073ae27174cd880bbd211707bcc41ee3 100644
--- a/src/developer/debug/shared/BUILD.gn
+++ b/src/developer/debug/shared/BUILD.gn
@@ -49,18 +49,8 @@ static_library("shared") {
       "buffered_zx_socket.h",
       "event_handlers.cc",
       "event_handlers.h",
-
-      # Implementation of message loop using async-loop.
-      "message_loop_async.cc",
-      "message_loop_async.h",
-
-      # New interface to abstract message loop from the debug agent.
       "message_loop_target.cc",
       "message_loop_target.h",
-
-      # Original message loop (previously MessageLoopZircon).
-      "message_loop_zircon.cc",
-      "message_loop_zircon.h",
       "zircon_utils.cc",
       "zircon_utils.h",
     ]
diff --git a/src/developer/debug/shared/event_handlers.cc b/src/developer/debug/shared/event_handlers.cc
index ed27c47ee0ccac05774dbe0451cd013ffe7f6c00..7088e4d77734d7b58a94948df19a1533e1eaea54 100644
--- a/src/developer/debug/shared/event_handlers.cc
+++ b/src/developer/debug/shared/event_handlers.cc
@@ -7,7 +7,7 @@
 #include <lib/async-loop/loop.h>
 #include <lib/async/default.h>
 
-#include "src/developer/debug/shared/message_loop_async.h"
+#include "src/developer/debug/shared/message_loop_target.h"
 #include "src/developer/debug/shared/logging/logging.h"
 #include "src/developer/debug/shared/zircon_utils.h"
 #include "src/developer/debug/shared/zx_status.h"
@@ -53,7 +53,7 @@ void SignalHandler::Handler(async_dispatcher_t*, async_wait_t* wait,
                             const zx_packet_signal_t* signal) {
   FXL_DCHECK(status == ZX_OK);
 
-  auto* loop = MessageLoopAsync::Current();
+  auto* loop = MessageLoopTarget::Current();
   FXL_DCHECK(loop);
 
   // Search for the AsyncHandle that triggered this signal.
@@ -140,7 +140,7 @@ void ExceptionHandler::Handler(async_dispatcher_t*,
                            << ExceptionTypeToString(packet->type);
   }
 
-  auto* loop = MessageLoopAsync::Current();
+  auto* loop = MessageLoopTarget::Current();
   FXL_DCHECK(loop);
 
   auto handler_it = loop->exception_handlers().find(exception);
diff --git a/src/developer/debug/shared/message_loop_async.cc b/src/developer/debug/shared/message_loop_async.cc
deleted file mode 100644
index 8a99a64dd891c4d24d018b652cc41ca68a364d5f..0000000000000000000000000000000000000000
--- a/src/developer/debug/shared/message_loop_async.cc
+++ /dev/null
@@ -1,580 +0,0 @@
-// Copyright 2018 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "src/developer/debug/shared/message_loop_async.h"
-
-#include <lib/fdio/io.h>
-#include <lib/zx/handle.h>
-#include <lib/zx/job.h>
-#include <lib/zx/process.h>
-#include <stdio.h>
-#include <zircon/syscalls/exception.h>
-
-#include "src/developer/debug/shared/event_handlers.h"
-#include "src/developer/debug/shared/fd_watcher.h"
-#include "src/developer/debug/shared/logging/logging.h"
-#include "src/developer/debug/shared/socket_watcher.h"
-#include "src/developer/debug/shared/zircon_exception_watcher.h"
-#include "src/developer/debug/shared/zx_status.h"
-#include "src/lib/fxl/compiler_specific.h"
-#include "src/lib/fxl/logging.h"
-
-namespace debug_ipc {
-
-namespace {
-
-thread_local MessageLoopAsync* current_message_loop_async = nullptr;
-
-}  // namespace
-
-// Exception ------------------------------------------------------------
-
-struct MessageLoopAsync::Exception {
-  zx_koid_t thread_koid = 0;
-  // Not-owning. Must outlive.
-  async_exception_t* exception_token = nullptr;
-};
-
-// MessageLoopAsync -----------------------------------------------------------
-
-MessageLoopAsync::MessageLoopAsync() : loop_(&kAsyncLoopConfigAttachToThread) {}
-
-MessageLoopAsync::~MessageLoopAsync() {
-  FXL_DCHECK(Current() != this);  // Cleanup should have been called.
-}
-
-void MessageLoopAsync::Init() { InitTarget(); }
-
-zx_status_t MessageLoopAsync::InitTarget() {
-  MessageLoop::Init();
-
-  FXL_DCHECK(!current_message_loop_async);
-  current_message_loop_async = this;
-  MessageLoopTarget::current_message_loop_type =
-      MessageLoopTarget::Type::kAsync;
-
-  zx::event::create(0, &task_event_);
-
-  WatchInfo info;
-  info.type = WatchType::kTask;
-  zx_status_t status =
-      AddSignalHandler(kTaskSignalKey, task_event_.get(), kTaskSignal, &info);
-
-  if (status != ZX_OK)
-    return status;
-
-  watches_[kTaskSignalKey] = std::move(info);
-  return ZX_OK;
-}
-
-void MessageLoopAsync::Cleanup() {
-  // We need to remove the signal/exception handlers before the message loop
-  // goes away.
-  signal_handlers_.clear();
-  exception_handlers_.clear();
-
-  FXL_DCHECK(current_message_loop_async == this);
-  current_message_loop_async = nullptr;
-  MessageLoopTarget::current_message_loop_type = MessageLoopTarget::Type::kLast;
-
-  MessageLoop::Cleanup();
-}
-
-// static
-MessageLoopAsync* MessageLoopAsync::Current() {
-  return current_message_loop_async;
-}
-
-const MessageLoopAsync::WatchInfo* MessageLoopAsync::FindWatchInfo(
-    int id) const {
-  auto it = watches_.find(id);
-  if (it == watches_.end())
-    return nullptr;
-  return &it->second;
-}
-
-zx_status_t MessageLoopAsync::AddSignalHandler(int id, zx_handle_t object,
-                                               zx_signals_t signals,
-                                               WatchInfo* associated_info) {
-  SignalHandler handler;
-  zx_status_t status = handler.Init(id, object, signals);
-  if (status != ZX_OK)
-    return status;
-
-  // The handler should not be there already.
-  FXL_DCHECK(signal_handlers_.find(handler.handle()) == signal_handlers_.end());
-
-  associated_info->signal_handler_key = handler.handle();
-  signal_handlers_[handler.handle()] = std::move(handler);
-
-  return ZX_OK;
-}
-
-zx_status_t MessageLoopAsync::AddExceptionHandler(int id, zx_handle_t object,
-                                                  uint32_t options,
-                                                  WatchInfo* associated_info) {
-  ExceptionHandler handler;
-  zx_status_t status = handler.Init(id, object, options);
-  if (status != ZX_OK)
-    return status;
-
-  // The handler should not be there already.
-  FXL_DCHECK(exception_handlers_.find(handler.handle()) ==
-             exception_handlers_.end());
-
-  associated_info->exception_handler_key = handler.handle();
-  exception_handlers_[handler.handle()] = std::move(handler);
-
-  return ZX_OK;
-}
-
-MessageLoop::WatchHandle MessageLoopAsync::WatchFD(WatchMode mode, int fd,
-                                                   FDWatcher* watcher) {
-  WatchInfo info;
-  info.type = WatchType::kFdio;
-  info.fd_watcher = watcher;
-  info.fd = fd;
-  info.fdio = fdio_unsafe_fd_to_io(fd);
-  if (!info.fdio)
-    return WatchHandle();
-
-  uint32_t events = 0;
-  switch (mode) {
-    case WatchMode::kRead:
-      events = POLLIN;
-      break;
-    case WatchMode::kWrite:
-      events = POLLOUT;
-      break;
-    case WatchMode::kReadWrite:
-      events = POLLIN | POLLOUT;
-      break;
-  }
-
-  zx_signals_t signals = ZX_SIGNAL_NONE;
-  fdio_unsafe_wait_begin(info.fdio, events, &info.fd_handle, &signals);
-  if (info.fd_handle == ZX_HANDLE_INVALID)
-    return WatchHandle();
-
-  int watch_id;
-  {
-    std::lock_guard<std::mutex> guard(mutex_);
-
-    watch_id = next_watch_id_;
-    next_watch_id_++;
-  }
-
-  zx_status_t status =
-      AddSignalHandler(watch_id, info.fd_handle, signals, &info);
-  if (status != ZX_OK)
-    return WatchHandle();
-
-  watches_[watch_id] = info;
-  return WatchHandle(this, watch_id);
-}
-
-zx_status_t MessageLoopAsync::WatchSocket(WatchMode mode,
-                                          zx_handle_t socket_handle,
-                                          SocketWatcher* watcher,
-                                          MessageLoop::WatchHandle* out) {
-  WatchInfo info;
-  info.type = WatchType::kSocket;
-  info.socket_watcher = watcher;
-  info.socket_handle = socket_handle;
-
-  int watch_id;
-  {
-    std::lock_guard<std::mutex> guard(mutex_);
-
-    watch_id = next_watch_id_;
-    next_watch_id_++;
-  }
-
-  zx_signals_t signals = 0;
-  if (mode == WatchMode::kRead || mode == WatchMode::kReadWrite)
-    signals |= ZX_SOCKET_READABLE;
-
-  if (mode == WatchMode::kWrite || mode == WatchMode::kReadWrite)
-    signals |= ZX_SOCKET_WRITABLE;
-
-  zx_status_t status =
-      AddSignalHandler(watch_id, socket_handle, ZX_SOCKET_WRITABLE, &info);
-  if (status != ZX_OK)
-    return status;
-
-  watches_[watch_id] = info;
-  *out = WatchHandle(this, watch_id);
-  return ZX_OK;
-}
-
-zx_status_t MessageLoopAsync::WatchProcessExceptions(
-    WatchProcessConfig config, MessageLoop::WatchHandle* out) {
-  WatchInfo info;
-  info.resource_name = config.process_name;
-  info.type = WatchType::kProcessExceptions;
-  info.exception_watcher = config.watcher;
-  info.task_koid = config.process_koid;
-  info.task_handle = config.process_handle;
-
-  int watch_id;
-  {
-    std::lock_guard<std::mutex> guard(mutex_);
-
-    watch_id = next_watch_id_;
-    next_watch_id_++;
-  }
-
-  // Watch all exceptions for the process.
-  zx_status_t status;
-  status = AddExceptionHandler(watch_id, config.process_handle,
-                               ZX_EXCEPTION_PORT_DEBUGGER, &info);
-  if (status != ZX_OK)
-    return status;
-
-  // Watch for the process terminated signal.
-  status = AddSignalHandler(watch_id, config.process_handle,
-                            ZX_PROCESS_TERMINATED, &info);
-  if (status != ZX_OK)
-    return status;
-
-  DEBUG_LOG(MessageLoop) << "Watching process " << info.resource_name;
-
-  watches_[watch_id] = info;
-  *out = WatchHandle(this, watch_id);
-  return ZX_OK;
-}
-
-zx_status_t MessageLoopAsync::WatchJobExceptions(
-    WatchJobConfig config, MessageLoop::WatchHandle* out) {
-  WatchInfo info;
-  info.resource_name = config.job_name;
-  info.type = WatchType::kJobExceptions;
-  info.exception_watcher = config.watcher;
-  info.task_koid = config.job_koid;
-  info.task_handle = config.job_handle;
-
-  int watch_id;
-  {
-    std::lock_guard<std::mutex> guard(mutex_);
-
-    watch_id = next_watch_id_;
-    next_watch_id_++;
-  }
-
-  // Create and track the exception handle.
-  zx_status_t status = AddExceptionHandler(watch_id, config.job_handle,
-                                           ZX_EXCEPTION_PORT_DEBUGGER, &info);
-  if (status != ZX_OK)
-    return status;
-
-  DEBUG_LOG(MessageLoop) << "Watching job " << info.resource_name;
-
-  watches_[watch_id] = info;
-  *out = WatchHandle(this, watch_id);
-  return ZX_OK;
-}
-
-zx_status_t MessageLoopAsync::ResumeFromException(zx_koid_t thread_koid,
-                                                  zx::thread& thread,
-                                                  uint32_t options) {
-  auto it = thread_exception_map_.find(thread_koid);
-  FXL_DCHECK(it != thread_exception_map_.end());
-  zx_status_t res = async_resume_from_exception(async_get_default_dispatcher(),
-                                                it->second.exception_token,
-                                                thread.get(), options);
-  thread_exception_map_.erase(thread_koid);
-  return res;
-}
-
-bool MessageLoopAsync::CheckAndProcessPendingTasks() {
-  std::lock_guard<std::mutex> guard(mutex_);
-  // Do a C++ task.
-  if (ProcessPendingTask()) {
-    SetHasTasks();  // Enqueue another task signal.
-    return true;
-  }
-  return false;
-}
-
-void MessageLoopAsync::HandleException(const ExceptionHandler& handler,
-                                       zx_port_packet_t packet) {
-  WatchInfo* watch_info = nullptr;
-  {
-    // Some event being watched.
-    std::lock_guard<std::mutex> guard(mutex_);
-    auto found = watches_.find(packet.key);
-    if (found == watches_.end()) {
-      // It is possible to get an exception that doesn't have a watch handle.
-      // A case is a race between detaching from a process and getting an
-      // exception on that process.
-      //
-      // The normal process looks like this:
-      //
-      // 1. In order to correctly detach, the debug agent has to resume threads
-      //    from their exceptions. Otherwise that exception will be treated as
-      //    unhandled when the agent detaches and will bubble up.
-      // 2. The agent detaches from the exception port. This means that the
-      //    watch handle is no longer listening.
-      //
-      // It is possible between (1) and (2) to get an exception, which will be
-      // queued in the exception port of the thread. Now, the agent won't read
-      // from the port until *after* it has detached from the exception port.
-      // This means that this new exception is not handled and will be bubbled
-      // up, which is correct as the debug agent stated that it has nothing more
-      // to do with the process.
-      //
-      // Now the problem is that zircon does not clean stale packets from a
-      // queue, meaning that the next time the message loop waits on the port,
-      // it will find a stale packet. In this context a stale packet means one
-      // that does not have a watch handle, as it was deleted in (1). Hence we
-      // get into this case and we simply log it for posperity.
-      //
-      // TODO(ZX-2623): zircon is going to clean up stale packets from ports
-      //                in the future. When that is done, this case should not
-      //                happen and we should go back into asserting it.
-      FXL_LOG(WARNING) << "Got stale port packet. This is most probably due to "
-                          "a race between detaching from a process and an "
-                          "exception ocurring.";
-      return;
-    }
-    watch_info = &found->second;
-  }
-
-  // Dispatch the watch callback outside of the lock. This depends on the
-  // stability of the WatchInfo pointer in the map (std::map is stable across
-  // updates) and the watch not getting unregistered from another thread
-  // asynchronously (which the API requires and is enforced by a DCHECK in
-  // the StopWatching impl).
-  switch (watch_info->type) {
-    case WatchType::kProcessExceptions:
-      OnProcessException(handler, *watch_info, packet);
-      break;
-    case WatchType::kJobExceptions:
-      OnJobException(handler, *watch_info, packet);
-      break;
-    case WatchType::kTask:
-    case WatchType::kFdio:
-    case WatchType::kSocket:
-      FXL_NOTREACHED();
-  }
-}
-
-uint64_t MessageLoopAsync::GetMonotonicNowNS() const {
-  zx::time ret;
-  zx::clock::get(&ret);
-
-  return ret.get();
-}
-
-// Previously, the approach was to first look for C++ tasks and when handled
-// look for WatchHandle work and finally wait for an event. This worked because
-// handle events didn't post C++ tasks.
-//
-// But some tests do post tasks on handle events. Because C++ tasks are signaled
-// by explicitly signaling an zx::event, without manually checking, the C++
-// tasks will never be checked and we would get blocked until a watch handled
-// is triggered.
-//
-// In order to handle the events properly, we need to check for C++ tasks before
-// and *after* handling watch handle events. This way we always process C++
-// tasks before handle events and will get signaled if one of them posted a new
-// task.
-void MessageLoopAsync::RunImpl() {
-  // Init should have been called.
-  FXL_DCHECK(Current() == this);
-  zx_status_t status;
-
-  zx::time time;
-  uint64_t delay = DelayNS();
-  if (delay == MessageLoop::kMaxDelay) {
-    time = zx::time::infinite();
-  } else {
-    time = zx::deadline_after(zx::nsec(delay));
-  }
-
-  for (;;) {
-    status = loop_.ResetQuit();
-    FXL_DCHECK(status != ZX_ERR_BAD_STATE);
-    status = loop_.Run(time);
-    FXL_DCHECK(status == ZX_OK || status == ZX_ERR_CANCELED ||
-               status == ZX_ERR_TIMED_OUT)
-        << "Expected ZX_OK || ZX_ERR_CANCELED || ZX_ERR_TIMED_OUT, got "
-        << ZxStatusToString(status);
-
-    if (status != ZX_ERR_TIMED_OUT) {
-      return;
-    }
-
-    std::lock_guard<std::mutex> guard(mutex_);
-    if (ProcessPendingTask())
-      SetHasTasks();
-  }
-}
-
-void MessageLoopAsync::QuitNow() {
-  MessageLoop::QuitNow();
-  loop_.Quit();
-}
-
-void MessageLoopAsync::StopWatching(int id) {
-  // The dispatch code for watch callbacks requires this be called on the
-  // same thread as the message loop is.
-  FXL_DCHECK(Current() == this);
-
-  std::lock_guard<std::mutex> guard(mutex_);
-
-  auto found = watches_.find(id);
-  FXL_DCHECK(found != watches_.end());
-
-  WatchInfo& info = found->second;
-  // BufferedFD constantly creates and destroys FD handles, flooding the log
-  // with non-helpful logging statements.
-  if (info.type != WatchType::kFdio) {
-    DEBUG_LOG(MessageLoop) << "Stop watching " << WatchTypeToString(info.type)
-                           << " " << info.resource_name;
-  }
-
-  switch (info.type) {
-    case WatchType::kProcessExceptions: {
-      RemoveExceptionHandler(info.exception_handler_key);
-      RemoveSignalHandler(info.signal_handler_key);
-      break;
-    }
-    case WatchType::kJobExceptions: {
-      RemoveExceptionHandler(info.exception_handler_key);
-      break;
-    }
-    case WatchType::kTask:
-    case WatchType::kFdio:
-    case WatchType::kSocket:
-      RemoveSignalHandler(info.signal_handler_key);
-      break;
-  }
-  watches_.erase(found);
-}
-
-void MessageLoopAsync::SetHasTasks() { task_event_.signal(0, kTaskSignal); }
-
-void MessageLoopAsync::OnFdioSignal(int watch_id, const WatchInfo& info,
-                                    zx_signals_t observed) {
-  uint32_t events = 0;
-  fdio_unsafe_wait_end(info.fdio, observed, &events);
-
-  if ((events & POLLERR) || (events & POLLHUP) || (events & POLLNVAL) ||
-      (events & POLLRDHUP)) {
-    info.fd_watcher->OnFDReady(info.fd, false, false, true);
-
-    // Don't dispatch any other notifications when there's an error. Zircon
-    // seems to set readable and writable on error even if there's nothing
-    // there.
-    return;
-  }
-
-  bool readable = !!(events & POLLIN);
-  bool writable = !!(events & POLLOUT);
-  info.fd_watcher->OnFDReady(info.fd, readable, writable, false);
-}
-
-void MessageLoopAsync::RemoveSignalHandler(const async_wait_t* key) {
-  FXL_DCHECK(key);
-  size_t erase_count = signal_handlers_.erase(key);
-  FXL_DCHECK(erase_count == 1u);
-}
-
-void MessageLoopAsync::RemoveExceptionHandler(const async_exception_t* key) {
-  FXL_DCHECK(key);
-  size_t erase_count = exception_handlers_.erase(key);
-  FXL_DCHECK(erase_count == 1u);
-}
-
-void MessageLoopAsync::AddException(const ExceptionHandler& handler,
-                                    zx_koid_t thread_koid) {
-  FXL_DCHECK(thread_exception_map_.find(thread_koid) ==
-             thread_exception_map_.end());
-
-  Exception exception;
-  exception.thread_koid = thread_koid;
-  exception.exception_token = const_cast<async_exception_t*>(handler.handle());
-  thread_exception_map_[thread_koid] = std::move(exception);
-}
-
-void MessageLoopAsync::OnProcessException(const ExceptionHandler& handler,
-                                          const WatchInfo& info,
-                                          const zx_port_packet_t& packet) {
-  if (ZX_PKT_IS_EXCEPTION(packet.type)) {
-    // All debug exceptions.
-    switch (packet.type) {
-      case ZX_EXCP_THREAD_STARTING:
-        AddException(handler, packet.exception.tid);
-        info.exception_watcher->OnThreadStarting(info.task_koid,
-                                                 packet.exception.tid);
-        break;
-      case ZX_EXCP_THREAD_EXITING:
-        AddException(handler, packet.exception.tid);
-        info.exception_watcher->OnThreadExiting(info.task_koid,
-                                                packet.exception.tid);
-        break;
-      case ZX_EXCP_GENERAL:
-      case ZX_EXCP_FATAL_PAGE_FAULT:
-      case ZX_EXCP_UNDEFINED_INSTRUCTION:
-      case ZX_EXCP_SW_BREAKPOINT:
-      case ZX_EXCP_HW_BREAKPOINT:
-      case ZX_EXCP_UNALIGNED_ACCESS:
-      case ZX_EXCP_POLICY_ERROR:
-        AddException(handler, packet.exception.tid);
-        info.exception_watcher->OnException(info.task_koid,
-                                            packet.exception.tid, packet.type);
-        break;
-      default:
-        FXL_NOTREACHED();
-    }
-  } else {
-    FXL_NOTREACHED();
-  }
-}
-
-void MessageLoopAsync::OnProcessTerminated(const WatchInfo& info,
-                                           zx_signals_t observed) {
-  FXL_DCHECK(observed & ZX_PROCESS_TERMINATED);
-  info.exception_watcher->OnProcessTerminated(info.task_koid);
-}
-
-void MessageLoopAsync::OnJobException(const ExceptionHandler& handler,
-                                      const WatchInfo& info,
-                                      const zx_port_packet_t& packet) {
-  if (ZX_PKT_IS_EXCEPTION(packet.type)) {
-    // All debug exceptions.
-    switch (packet.type) {
-      case ZX_EXCP_PROCESS_STARTING:
-        AddException(handler, packet.exception.tid);
-        info.exception_watcher->OnProcessStarting(
-            info.task_koid, packet.exception.pid, packet.exception.tid);
-        return;
-      default:
-        break;
-    }
-  }
-  FXL_NOTREACHED();
-}
-
-void MessageLoopAsync::OnSocketSignal(int watch_id, const WatchInfo& info,
-                                      zx_signals_t observed) {
-  // Dispatch readable signal.
-  if (observed & ZX_SOCKET_READABLE)
-    info.socket_watcher->OnSocketReadable(info.socket_handle);
-
-  // When signaling both readable and writable, make sure the readable handler
-  // didn't remove the watch.
-  if ((observed & ZX_SOCKET_READABLE) && (observed & ZX_SOCKET_WRITABLE)) {
-    std::lock_guard<std::mutex> guard(mutex_);
-    if (watches_.find(watch_id) == watches_.end())
-      return;
-  }
-
-  // Dispatch writable signal.
-  if (observed & ZX_SOCKET_WRITABLE)
-    info.socket_watcher->OnSocketWritable(info.socket_handle);
-}
-
-}  // namespace debug_ipc
diff --git a/src/developer/debug/shared/message_loop_async.h b/src/developer/debug/shared/message_loop_async.h
deleted file mode 100644
index f25d4909d2c4279c6df7a20c947df8c0689103d9..0000000000000000000000000000000000000000
--- a/src/developer/debug/shared/message_loop_async.h
+++ /dev/null
@@ -1,183 +0,0 @@
-// Copyright 2018 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#pragma once
-
-#include <lib/async-loop/cpp/loop.h>
-#include <lib/fdio/unsafe.h>
-#include <lib/zx/event.h>
-#include <lib/zx/port.h>
-#include <lib/zx/thread.h>
-
-#include <vector>
-
-#include "src/developer/debug/shared/event_handlers.h"
-#include "src/developer/debug/shared/message_loop_target.h"
-
-namespace debug_ipc {
-
-class ExceptionHandler;
-class SignalHandler;
-class SocketWatcher;
-class ZirconExceptionWatcher;
-
-class MessageLoopAsync : public MessageLoopTarget {
- public:
-  // Associated struct to track information about what type of resource a watch
-  // handle is following.
-  // EventHandlers need access to the WatchInfo implementation, hence the reason
-  // for it to be public.
-  // Definition at the end of the header.
-  struct WatchInfo;
-
-  using SignalHandlerMap = std::map<const async_wait_t*, SignalHandler>;
-  using ExceptionHandlerMap =
-      std::map<const async_exception_t*, ExceptionHandler>;
-
-  MessageLoopAsync();
-  ~MessageLoopAsync();
-
-  void Init() override;
-  zx_status_t InitTarget() override;
-
-  Type GetType() const override { return Type::kAsync; }
-  bool SupportsFidl() const override { return true; }
-
-  void Cleanup() override;
-
-  // Returns the current message loop or null if there isn't one.
-  static MessageLoopAsync* Current();
-
-  // MessageLoop implementation.
-  WatchHandle WatchFD(WatchMode mode, int fd, FDWatcher* watcher) override;
-
-  zx_status_t WatchSocket(WatchMode mode, zx_handle_t socket_handle,
-                          SocketWatcher* watcher, WatchHandle* out) override;
-
-  // Attaches to the exception port of the given process and issues callbacks
-  // on the given watcher. The watcher must outlive the returned WatchHandle.
-  // Must only be called on the message loop thread.
-  zx_status_t WatchProcessExceptions(WatchProcessConfig config,
-                                     WatchHandle* out) override;
-
-  // Attaches to the exception port of the given job and issues callbacks
-  // on the given watcher. The watcher must outlive the returned WatchHandle.
-  // Must only be called on the message loop thread.
-  zx_status_t WatchJobExceptions(WatchJobConfig config,
-                                 WatchHandle* out) override;
-
-  // When this class issues an exception notification, the code should call
-  // this function to resume the thread from the exception. This is a wrapper
-  // for zx_task_resume_from_exception.
-  /* zx_status_t ResumeFromException(zx::thread& thread, uint32_t options); */
-  zx_status_t ResumeFromException(zx_koid_t thread_koid, zx::thread&,
-                                  uint32_t options) override;
-
-  void QuitNow() override;
-
-  const SignalHandlerMap& signal_handlers() const { return signal_handlers_; }
-
-  const ExceptionHandlerMap& exception_handlers() const {
-    return exception_handlers_;
-  }
-
- private:
-  const WatchInfo* FindWatchInfo(int id) const;
-
-  // MessageLoop protected implementation.
-  uint64_t GetMonotonicNowNS() const override;
-  void RunImpl() override;
-  void StopWatching(int id) override;
-  // Triggers an event signaling that there is a pending event.
-  void SetHasTasks() override;
-
-  // Check for any pending C++ tasks and process them.
-  // Returns true if there was an event pending to be processed.
-  bool CheckAndProcessPendingTasks();
-
-  // Handles WatchHandles event. These are all the events that are not C++ tasks
-  // posted to the message loop.
-  void HandleException(const ExceptionHandler&, zx_port_packet_t packet);
-
-  // Handle an event of the given type.
-  void OnFdioSignal(int watch_id, const WatchInfo& info, zx_signals_t observed);
-  void OnProcessException(const ExceptionHandler&, const WatchInfo& info,
-                          const zx_port_packet_t& packet);
-  void OnProcessTerminated(const WatchInfo&, zx_signals_t observed);
-
-  void OnJobException(const ExceptionHandler&, const WatchInfo& info,
-                      const zx_port_packet_t& packet);
-  void OnSocketSignal(int watch_id, const WatchInfo& info,
-                      zx_signals_t observed);
-
-  using WatchMap = std::map<int, WatchInfo>;
-  WatchMap watches_;
-
-  // ID used as an index into watches_.
-  int next_watch_id_ = 1;
-
-  async::Loop loop_;
-  zx::event task_event_;
-
-  SignalHandlerMap signal_handlers_;
-  // See SignalHandler constructor.
-  // |associated_info| needs to be updated with the fact that it has an
-  // associated SignalHandler.
-  zx_status_t AddSignalHandler(int, zx_handle_t, zx_signals_t, WatchInfo* info);
-  void RemoveSignalHandler(const async_wait_t* id);
-
-  ExceptionHandlerMap exception_handlers_;
-  // See ExceptionHandler constructor.
-  // |associated_info| needs to be updated with the fact that it has an
-  // associated ExceptionHandler.
-  zx_status_t AddExceptionHandler(int, zx_handle_t, uint32_t, WatchInfo* info);
-  void RemoveExceptionHandler(const async_exception_t*);
-
-  // Every exception source (ExceptionHandler) will get an async_exception_t*
-  // that works as a "key" for the async_loop. This async_exception_t* is what
-  // you give back to the loop to return from an exception.
-  //
-  // So, everytime there is an exception, there needs to be a tracking from
-  // thread_koid to this async_exception_t*, so that when a thread is resumed,
-  // we can pass to the loop the correct key by just using the thread koid.
-  struct Exception;
-  std::map<zx_koid_t, Exception> thread_exception_map_;
-  void AddException(const ExceptionHandler&, zx_koid_t thread_koid);
-
-  FXL_DISALLOW_COPY_AND_ASSIGN(MessageLoopAsync);
-
-  friend class SignalHandler;
-  friend class ExceptionHandler;
-};
-
-// EventHandlers need access to the WatchInfo implementation.
-struct MessageLoopAsync::WatchInfo {
-  // Name of the resource being watched.
-  // Mostly tracked for debugging purposes.
-  std::string resource_name;
-
-  WatchType type = WatchType::kFdio;
-
-  // FDIO-specific watcher parameters.
-  int fd = -1;
-  fdio_t* fdio = nullptr;
-  FDWatcher* fd_watcher = nullptr;
-  zx_handle_t fd_handle = ZX_HANDLE_INVALID;
-
-  // Socket-specific parameters.
-  SocketWatcher* socket_watcher = nullptr;
-  zx_handle_t socket_handle = ZX_HANDLE_INVALID;
-
-  // Task-exception-specific parameters, can be of job or process type.
-  ZirconExceptionWatcher* exception_watcher = nullptr;
-  zx_koid_t task_koid = 0;
-  zx_handle_t task_handle = ZX_HANDLE_INVALID;
-
-  // This makes easier the lookup of the associated ExceptionHandler with this
-  // watch id.
-  const async_wait_t* signal_handler_key = nullptr;
-  const async_exception_t* exception_handler_key = nullptr;
-};
-
-}  // namespace debug_ipc
diff --git a/src/developer/debug/shared/message_loop_target.cc b/src/developer/debug/shared/message_loop_target.cc
index 7295eb06d2740cb93d57db083169727c060ec7e7..6dafa464a8c0391afe85f77e80d847edb6feff2e 100644
--- a/src/developer/debug/shared/message_loop_target.cc
+++ b/src/developer/debug/shared/message_loop_target.cc
@@ -1,48 +1,577 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
 #include "src/developer/debug/shared/message_loop_target.h"
 
-#include "src/developer/debug/shared/message_loop_async.h"
-#include "src/developer/debug/shared/message_loop_zircon.h"
+#include <lib/fdio/io.h>
+#include <lib/zx/handle.h>
+#include <lib/zx/job.h>
+#include <lib/zx/process.h>
+#include <stdio.h>
+#include <zircon/syscalls/exception.h>
 
+#include "src/developer/debug/shared/event_handlers.h"
+#include "src/developer/debug/shared/fd_watcher.h"
+#include "src/developer/debug/shared/logging/logging.h"
+#include "src/developer/debug/shared/socket_watcher.h"
+#include "src/developer/debug/shared/zircon_exception_watcher.h"
+#include "src/developer/debug/shared/zx_status.h"
+#include "src/lib/fxl/compiler_specific.h"
 #include "src/lib/fxl/logging.h"
 
 namespace debug_ipc {
 
-MessageLoopTarget::~MessageLoopTarget() = default;
+namespace {
 
-MessageLoopTarget::Type MessageLoopTarget::current_message_loop_type =
-    MessageLoopTarget::Type::kLast;
+thread_local MessageLoopTarget* current_message_loop = nullptr;
 
+}  // namespace
+
+// Exception ------------------------------------------------------------
+
+struct MessageLoopTarget::Exception {
+  zx_koid_t thread_koid = 0;
+  // Not-owning. Must outlive.
+  async_exception_t* exception_token = nullptr;
+};
+
+// MessageLoopTarget -----------------------------------------------------------
+
+MessageLoopTarget::MessageLoopTarget() : loop_(&kAsyncLoopConfigAttachToThread) {}
+
+MessageLoopTarget::~MessageLoopTarget() {
+  FXL_DCHECK(Current() != this);  // Cleanup should have been called.
+}
+
+void MessageLoopTarget::Init() { InitTarget(); }
+
+zx_status_t MessageLoopTarget::InitTarget() {
+  MessageLoop::Init();
+
+  FXL_DCHECK(!current_message_loop);
+  current_message_loop = this;
+
+  zx::event::create(0, &task_event_);
+
+  WatchInfo info;
+  info.type = WatchType::kTask;
+  zx_status_t status =
+      AddSignalHandler(kTaskSignalKey, task_event_.get(), kTaskSignal, &info);
+
+  if (status != ZX_OK)
+    return status;
+
+  watches_[kTaskSignalKey] = std::move(info);
+  return ZX_OK;
+}
+
+void MessageLoopTarget::Cleanup() {
+  // We need to remove the signal/exception handlers before the message loop
+  // goes away.
+  signal_handlers_.clear();
+  exception_handlers_.clear();
+
+  FXL_DCHECK(current_message_loop == this);
+  current_message_loop = nullptr;
+
+  MessageLoop::Cleanup();
+}
+
+// static
 MessageLoopTarget* MessageLoopTarget::Current() {
-  FXL_DCHECK(current_message_loop_type != MessageLoopTarget::Type::kLast);
-  switch (current_message_loop_type) {
-    case MessageLoopTarget::Type::kAsync:
-      return MessageLoopAsync::Current();
-    case MessageLoopTarget::Type::kZircon:
-      return MessageLoopZircon::Current();
-    case MessageLoopTarget::Type::kLast:
+  return current_message_loop;
+}
+
+const MessageLoopTarget::WatchInfo* MessageLoopTarget::FindWatchInfo(
+    int id) const {
+  auto it = watches_.find(id);
+  if (it == watches_.end())
+    return nullptr;
+  return &it->second;
+}
+
+zx_status_t MessageLoopTarget::AddSignalHandler(int id, zx_handle_t object,
+                                               zx_signals_t signals,
+                                               WatchInfo* associated_info) {
+  SignalHandler handler;
+  zx_status_t status = handler.Init(id, object, signals);
+  if (status != ZX_OK)
+    return status;
+
+  // The handler should not be there already.
+  FXL_DCHECK(signal_handlers_.find(handler.handle()) == signal_handlers_.end());
+
+  associated_info->signal_handler_key = handler.handle();
+  signal_handlers_[handler.handle()] = std::move(handler);
+
+  return ZX_OK;
+}
+
+zx_status_t MessageLoopTarget::AddExceptionHandler(int id, zx_handle_t object,
+                                                  uint32_t options,
+                                                  WatchInfo* associated_info) {
+  ExceptionHandler handler;
+  zx_status_t status = handler.Init(id, object, options);
+  if (status != ZX_OK)
+    return status;
+
+  // The handler should not be there already.
+  FXL_DCHECK(exception_handlers_.find(handler.handle()) ==
+             exception_handlers_.end());
+
+  associated_info->exception_handler_key = handler.handle();
+  exception_handlers_[handler.handle()] = std::move(handler);
+
+  return ZX_OK;
+}
+
+MessageLoop::WatchHandle MessageLoopTarget::WatchFD(WatchMode mode, int fd,
+                                                   FDWatcher* watcher) {
+  WatchInfo info;
+  info.type = WatchType::kFdio;
+  info.fd_watcher = watcher;
+  info.fd = fd;
+  info.fdio = fdio_unsafe_fd_to_io(fd);
+  if (!info.fdio)
+    return WatchHandle();
+
+  uint32_t events = 0;
+  switch (mode) {
+    case WatchMode::kRead:
+      events = POLLIN;
+      break;
+    case WatchMode::kWrite:
+      events = POLLOUT;
+      break;
+    case WatchMode::kReadWrite:
+      events = POLLIN | POLLOUT;
       break;
   }
 
-  FXL_NOTREACHED();
-  return nullptr;
+  zx_signals_t signals = ZX_SIGNAL_NONE;
+  fdio_unsafe_wait_begin(info.fdio, events, &info.fd_handle, &signals);
+  if (info.fd_handle == ZX_HANDLE_INVALID)
+    return WatchHandle();
+
+  int watch_id;
+  {
+    std::lock_guard<std::mutex> guard(mutex_);
+
+    watch_id = next_watch_id_;
+    next_watch_id_++;
+  }
+
+  zx_status_t status =
+      AddSignalHandler(watch_id, info.fd_handle, signals, &info);
+  if (status != ZX_OK)
+    return WatchHandle();
+
+  watches_[watch_id] = info;
+  return WatchHandle(this, watch_id);
 }
 
-const char* MessageLoopTarget::TypeToString(Type type) {
-  switch (type) {
-    case MessageLoopTarget::Type::kAsync:
-      return "Async";
-    case MessageLoopTarget::Type::kZircon:
-      return "Zircon";
-    case MessageLoopTarget::Type::kLast:
-      return "Last";
+zx_status_t MessageLoopTarget::WatchSocket(WatchMode mode,
+                                          zx_handle_t socket_handle,
+                                          SocketWatcher* watcher,
+                                          MessageLoop::WatchHandle* out) {
+  WatchInfo info;
+  info.type = WatchType::kSocket;
+  info.socket_watcher = watcher;
+  info.socket_handle = socket_handle;
+
+  int watch_id;
+  {
+    std::lock_guard<std::mutex> guard(mutex_);
+
+    watch_id = next_watch_id_;
+    next_watch_id_++;
+  }
+
+  zx_signals_t signals = 0;
+  if (mode == WatchMode::kRead || mode == WatchMode::kReadWrite)
+    signals |= ZX_SOCKET_READABLE;
+
+  if (mode == WatchMode::kWrite || mode == WatchMode::kReadWrite)
+    signals |= ZX_SOCKET_WRITABLE;
+
+  zx_status_t status =
+      AddSignalHandler(watch_id, socket_handle, ZX_SOCKET_WRITABLE, &info);
+  if (status != ZX_OK)
+    return status;
+
+  watches_[watch_id] = info;
+  *out = WatchHandle(this, watch_id);
+  return ZX_OK;
+}
+
+zx_status_t MessageLoopTarget::WatchProcessExceptions(
+    WatchProcessConfig config, MessageLoop::WatchHandle* out) {
+  WatchInfo info;
+  info.resource_name = config.process_name;
+  info.type = WatchType::kProcessExceptions;
+  info.exception_watcher = config.watcher;
+  info.task_koid = config.process_koid;
+  info.task_handle = config.process_handle;
+
+  int watch_id;
+  {
+    std::lock_guard<std::mutex> guard(mutex_);
+
+    watch_id = next_watch_id_;
+    next_watch_id_++;
+  }
+
+  // Watch all exceptions for the process.
+  zx_status_t status;
+  status = AddExceptionHandler(watch_id, config.process_handle,
+                               ZX_EXCEPTION_PORT_DEBUGGER, &info);
+  if (status != ZX_OK)
+    return status;
+
+  // Watch for the process terminated signal.
+  status = AddSignalHandler(watch_id, config.process_handle,
+                            ZX_PROCESS_TERMINATED, &info);
+  if (status != ZX_OK)
+    return status;
+
+  DEBUG_LOG(MessageLoop) << "Watching process " << info.resource_name;
+
+  watches_[watch_id] = info;
+  *out = WatchHandle(this, watch_id);
+  return ZX_OK;
+}
+
+zx_status_t MessageLoopTarget::WatchJobExceptions(
+    WatchJobConfig config, MessageLoop::WatchHandle* out) {
+  WatchInfo info;
+  info.resource_name = config.job_name;
+  info.type = WatchType::kJobExceptions;
+  info.exception_watcher = config.watcher;
+  info.task_koid = config.job_koid;
+  info.task_handle = config.job_handle;
+
+  int watch_id;
+  {
+    std::lock_guard<std::mutex> guard(mutex_);
+
+    watch_id = next_watch_id_;
+    next_watch_id_++;
+  }
+
+  // Create and track the exception handle.
+  zx_status_t status = AddExceptionHandler(watch_id, config.job_handle,
+                                           ZX_EXCEPTION_PORT_DEBUGGER, &info);
+  if (status != ZX_OK)
+    return status;
+
+  DEBUG_LOG(MessageLoop) << "Watching job " << info.resource_name;
+
+  watches_[watch_id] = info;
+  *out = WatchHandle(this, watch_id);
+  return ZX_OK;
+}
+
+zx_status_t MessageLoopTarget::ResumeFromException(zx_koid_t thread_koid,
+                                                  zx::thread& thread,
+                                                  uint32_t options) {
+  auto it = thread_exception_map_.find(thread_koid);
+  FXL_DCHECK(it != thread_exception_map_.end());
+  zx_status_t res = async_resume_from_exception(async_get_default_dispatcher(),
+                                                it->second.exception_token,
+                                                thread.get(), options);
+  thread_exception_map_.erase(thread_koid);
+  return res;
+}
+
+bool MessageLoopTarget::CheckAndProcessPendingTasks() {
+  std::lock_guard<std::mutex> guard(mutex_);
+  // Do a C++ task.
+  if (ProcessPendingTask()) {
+    SetHasTasks();  // Enqueue another task signal.
+    return true;
+  }
+  return false;
+}
+
+void MessageLoopTarget::HandleException(const ExceptionHandler& handler,
+                                       zx_port_packet_t packet) {
+  WatchInfo* watch_info = nullptr;
+  {
+    // Some event being watched.
+    std::lock_guard<std::mutex> guard(mutex_);
+    auto found = watches_.find(packet.key);
+    if (found == watches_.end()) {
+      // It is possible to get an exception that doesn't have a watch handle.
+      // A case is a race between detaching from a process and getting an
+      // exception on that process.
+      //
+      // The normal process looks like this:
+      //
+      // 1. In order to correctly detach, the debug agent has to resume threads
+      //    from their exceptions. Otherwise that exception will be treated as
+      //    unhandled when the agent detaches and will bubble up.
+      // 2. The agent detaches from the exception port. This means that the
+      //    watch handle is no longer listening.
+      //
+      // It is possible between (1) and (2) to get an exception, which will be
+      // queued in the exception port of the thread. Now, the agent won't read
+      // from the port until *after* it has detached from the exception port.
+      // This means that this new exception is not handled and will be bubbled
+      // up, which is correct as the debug agent stated that it has nothing more
+      // to do with the process.
+      //
+      // Now the problem is that zircon does not clean stale packets from a
+      // queue, meaning that the next time the message loop waits on the port,
+      // it will find a stale packet. In this context a stale packet means one
+      // that does not have a watch handle, as it was deleted in (1). Hence we
+      // get into this case and we simply log it for posperity.
+      //
+      // TODO(ZX-2623): zircon is going to clean up stale packets from ports
+      //                in the future. When that is done, this case should not
+      //                happen and we should go back into asserting it.
+      FXL_LOG(WARNING) << "Got stale port packet. This is most probably due to "
+                          "a race between detaching from a process and an "
+                          "exception ocurring.";
+      return;
+    }
+    watch_info = &found->second;
+  }
+
+  // Dispatch the watch callback outside of the lock. This depends on the
+  // stability of the WatchInfo pointer in the map (std::map is stable across
+  // updates) and the watch not getting unregistered from another thread
+  // asynchronously (which the API requires and is enforced by a DCHECK in
+  // the StopWatching impl).
+  switch (watch_info->type) {
+    case WatchType::kProcessExceptions:
+      OnProcessException(handler, *watch_info, packet);
+      break;
+    case WatchType::kJobExceptions:
+      OnJobException(handler, *watch_info, packet);
+      break;
+    case WatchType::kTask:
+    case WatchType::kFdio:
+    case WatchType::kSocket:
+      FXL_NOTREACHED();
+  }
+}
+
+uint64_t MessageLoopTarget::GetMonotonicNowNS() const {
+  zx::time ret;
+  zx::clock::get(&ret);
+
+  return ret.get();
+}
+
+// Previously, the approach was to first look for C++ tasks and when handled
+// look for WatchHandle work and finally wait for an event. This worked because
+// handle events didn't post C++ tasks.
+//
+// But some tests do post tasks on handle events. Because C++ tasks are signaled
+// by explicitly signaling an zx::event, without manually checking, the C++
+// tasks will never be checked and we would get blocked until a watch handled
+// is triggered.
+//
+// In order to handle the events properly, we need to check for C++ tasks before
+// and *after* handling watch handle events. This way we always process C++
+// tasks before handle events and will get signaled if one of them posted a new
+// task.
+void MessageLoopTarget::RunImpl() {
+  // Init should have been called.
+  FXL_DCHECK(Current() == this);
+  zx_status_t status;
+
+  zx::time time;
+  uint64_t delay = DelayNS();
+  if (delay == MessageLoop::kMaxDelay) {
+    time = zx::time::infinite();
+  } else {
+    time = zx::deadline_after(zx::nsec(delay));
+  }
+
+  for (;;) {
+    status = loop_.ResetQuit();
+    FXL_DCHECK(status != ZX_ERR_BAD_STATE);
+    status = loop_.Run(time);
+    FXL_DCHECK(status == ZX_OK || status == ZX_ERR_CANCELED ||
+               status == ZX_ERR_TIMED_OUT)
+        << "Expected ZX_OK || ZX_ERR_CANCELED || ZX_ERR_TIMED_OUT, got "
+        << ZxStatusToString(status);
+
+    if (status != ZX_ERR_TIMED_OUT) {
+      return;
+    }
+
+    std::lock_guard<std::mutex> guard(mutex_);
+    if (ProcessPendingTask())
+      SetHasTasks();
+  }
+}
+
+void MessageLoopTarget::QuitNow() {
+  MessageLoop::QuitNow();
+  loop_.Quit();
+}
+
+void MessageLoopTarget::StopWatching(int id) {
+  // The dispatch code for watch callbacks requires this be called on the
+  // same thread as the message loop is.
+  FXL_DCHECK(Current() == this);
+
+  std::lock_guard<std::mutex> guard(mutex_);
+
+  auto found = watches_.find(id);
+  FXL_DCHECK(found != watches_.end());
+
+  WatchInfo& info = found->second;
+  // BufferedFD constantly creates and destroys FD handles, flooding the log
+  // with non-helpful logging statements.
+  if (info.type != WatchType::kFdio) {
+    DEBUG_LOG(MessageLoop) << "Stop watching " << WatchTypeToString(info.type)
+                           << " " << info.resource_name;
   }
 
+  switch (info.type) {
+    case WatchType::kProcessExceptions: {
+      RemoveExceptionHandler(info.exception_handler_key);
+      RemoveSignalHandler(info.signal_handler_key);
+      break;
+    }
+    case WatchType::kJobExceptions: {
+      RemoveExceptionHandler(info.exception_handler_key);
+      break;
+    }
+    case WatchType::kTask:
+    case WatchType::kFdio:
+    case WatchType::kSocket:
+      RemoveSignalHandler(info.signal_handler_key);
+      break;
+  }
+  watches_.erase(found);
+}
+
+void MessageLoopTarget::SetHasTasks() { task_event_.signal(0, kTaskSignal); }
+
+void MessageLoopTarget::OnFdioSignal(int watch_id, const WatchInfo& info,
+                                    zx_signals_t observed) {
+  uint32_t events = 0;
+  fdio_unsafe_wait_end(info.fdio, observed, &events);
+
+  if ((events & POLLERR) || (events & POLLHUP) || (events & POLLNVAL) ||
+      (events & POLLRDHUP)) {
+    info.fd_watcher->OnFDReady(info.fd, false, false, true);
+
+    // Don't dispatch any other notifications when there's an error. Zircon
+    // seems to set readable and writable on error even if there's nothing
+    // there.
+    return;
+  }
+
+  bool readable = !!(events & POLLIN);
+  bool writable = !!(events & POLLOUT);
+  info.fd_watcher->OnFDReady(info.fd, readable, writable, false);
+}
+
+void MessageLoopTarget::RemoveSignalHandler(const async_wait_t* key) {
+  FXL_DCHECK(key);
+  size_t erase_count = signal_handlers_.erase(key);
+  FXL_DCHECK(erase_count == 1u);
+}
+
+void MessageLoopTarget::RemoveExceptionHandler(const async_exception_t* key) {
+  FXL_DCHECK(key);
+  size_t erase_count = exception_handlers_.erase(key);
+  FXL_DCHECK(erase_count == 1u);
+}
+
+void MessageLoopTarget::AddException(const ExceptionHandler& handler,
+                                    zx_koid_t thread_koid) {
+  FXL_DCHECK(thread_exception_map_.find(thread_koid) ==
+             thread_exception_map_.end());
+
+  Exception exception;
+  exception.thread_koid = thread_koid;
+  exception.exception_token = const_cast<async_exception_t*>(handler.handle());
+  thread_exception_map_[thread_koid] = std::move(exception);
+}
+
+void MessageLoopTarget::OnProcessException(const ExceptionHandler& handler,
+                                          const WatchInfo& info,
+                                          const zx_port_packet_t& packet) {
+  if (ZX_PKT_IS_EXCEPTION(packet.type)) {
+    // All debug exceptions.
+    switch (packet.type) {
+      case ZX_EXCP_THREAD_STARTING:
+        AddException(handler, packet.exception.tid);
+        info.exception_watcher->OnThreadStarting(info.task_koid,
+                                                 packet.exception.tid);
+        break;
+      case ZX_EXCP_THREAD_EXITING:
+        AddException(handler, packet.exception.tid);
+        info.exception_watcher->OnThreadExiting(info.task_koid,
+                                                packet.exception.tid);
+        break;
+      case ZX_EXCP_GENERAL:
+      case ZX_EXCP_FATAL_PAGE_FAULT:
+      case ZX_EXCP_UNDEFINED_INSTRUCTION:
+      case ZX_EXCP_SW_BREAKPOINT:
+      case ZX_EXCP_HW_BREAKPOINT:
+      case ZX_EXCP_UNALIGNED_ACCESS:
+      case ZX_EXCP_POLICY_ERROR:
+        AddException(handler, packet.exception.tid);
+        info.exception_watcher->OnException(info.task_koid,
+                                            packet.exception.tid, packet.type);
+        break;
+      default:
+        FXL_NOTREACHED();
+    }
+  } else {
+    FXL_NOTREACHED();
+  }
+}
+
+void MessageLoopTarget::OnProcessTerminated(const WatchInfo& info,
+                                           zx_signals_t observed) {
+  FXL_DCHECK(observed & ZX_PROCESS_TERMINATED);
+  info.exception_watcher->OnProcessTerminated(info.task_koid);
+}
+
+void MessageLoopTarget::OnJobException(const ExceptionHandler& handler,
+                                      const WatchInfo& info,
+                                      const zx_port_packet_t& packet) {
+  if (ZX_PKT_IS_EXCEPTION(packet.type)) {
+    // All debug exceptions.
+    switch (packet.type) {
+      case ZX_EXCP_PROCESS_STARTING:
+        AddException(handler, packet.exception.tid);
+        info.exception_watcher->OnProcessStarting(
+            info.task_koid, packet.exception.pid, packet.exception.tid);
+        return;
+      default:
+        break;
+    }
+  }
   FXL_NOTREACHED();
-  return nullptr;
+}
+
+void MessageLoopTarget::OnSocketSignal(int watch_id, const WatchInfo& info,
+                                      zx_signals_t observed) {
+  // Dispatch readable signal.
+  if (observed & ZX_SOCKET_READABLE)
+    info.socket_watcher->OnSocketReadable(info.socket_handle);
+
+  // When signaling both readable and writable, make sure the readable handler
+  // didn't remove the watch.
+  if ((observed & ZX_SOCKET_READABLE) && (observed & ZX_SOCKET_WRITABLE)) {
+    std::lock_guard<std::mutex> guard(mutex_);
+    if (watches_.find(watch_id) == watches_.end())
+      return;
+  }
+
+  // Dispatch writable signal.
+  if (observed & ZX_SOCKET_WRITABLE)
+    info.socket_watcher->OnSocketWritable(info.socket_handle);
 }
 
 const char* WatchTypeToString(WatchType type) {
diff --git a/src/developer/debug/shared/message_loop_target.h b/src/developer/debug/shared/message_loop_target.h
index 2ca33804bc67d6407be22911b1ed987a9f27ccaa..0d738971d4313a26c97f8a963f1f6c88476c9e39 100644
--- a/src/developer/debug/shared/message_loop_target.h
+++ b/src/developer/debug/shared/message_loop_target.h
@@ -1,17 +1,26 @@
-// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
 #pragma once
 
+#include <lib/async-loop/cpp/loop.h>
+#include <lib/fdio/unsafe.h>
+#include <lib/zx/event.h>
+#include <lib/zx/port.h>
 #include <lib/zx/thread.h>
 
+#include <vector>
+
+#include "src/developer/debug/shared/event_handlers.h"
 #include "src/developer/debug/shared/message_loop.h"
 
 namespace debug_ipc {
 
-class ZirconExceptionWatcher;
+class ExceptionHandler;
+class SignalHandler;
 class SocketWatcher;
+class ZirconExceptionWatcher;
 
 enum class WatchType : uint32_t {
   kTask,
@@ -22,39 +31,32 @@ enum class WatchType : uint32_t {
 };
 const char* WatchTypeToString(WatchType);
 
-// MessageLoopTarget is an abstract interface for all message loops that can
-// be used in the debug agent.
-class MessageLoopTarget : public MessageLoop {
+class MessageLoopTarget final : public MessageLoop {
  public:
-  // New message loops must be suscribed here.
-  enum class Type {
-    kAsync,
-    kZircon,
-    kLast,
-  };
-  static const char* TypeToString(Type);
-
-  // Set by a message loop at InitTarget();
-  static Type current_message_loop_type;
+  // Associated struct to track information about what type of resource a watch
+  // handle is following.
+  // EventHandlers need access to the WatchInfo implementation, hence the reason
+  // for it to be public.
+  // Definition at the end of the header.
+  struct WatchInfo;
 
-  // Target message loops can call wither Init or InitTarget. The difference is
-  // that InitTarget will return a status about what happened, whether Init
-  // will silently ignore it.
-  virtual zx_status_t InitTarget() = 0;
+  using SignalHandlerMap = std::map<const async_wait_t*, SignalHandler>;
+  using ExceptionHandlerMap =
+      std::map<const async_exception_t*, ExceptionHandler>;
 
-  virtual ~MessageLoopTarget();
+  MessageLoopTarget();
+  ~MessageLoopTarget();
 
-  virtual Type GetType() const = 0;
+  void Init() override;
+  zx_status_t InitTarget();
 
-  // Fidl requires a special dispatcher to be setup. Not all message loops
-  // support it.
-  virtual bool SupportsFidl() const = 0;
+  void Cleanup() override;
 
   // Returns the current message loop or null if there isn't one.
   static MessageLoopTarget* Current();
 
   // MessageLoop implementation.
-  WatchHandle WatchFD(WatchMode mode, int fd, FDWatcher* watcher) override = 0;
+  WatchHandle WatchFD(WatchMode mode, int fd, FDWatcher* watcher) override;
 
   // Watches the given socket for read/write status. The watcher must outlive
   // the returned WatchHandle. Must only be called on the message loop thread.
@@ -63,8 +65,8 @@ class MessageLoopTarget : public MessageLoop {
   // become both readable and writable at the same time which will necessitate
   // calling both callbacks. The code does not expect the FDWatcher to
   // disappear in between these callbacks.
-  virtual zx_status_t WatchSocket(WatchMode mode, zx_handle_t socket_handle,
-                                  SocketWatcher* watcher, WatchHandle* out) = 0;
+  zx_status_t WatchSocket(WatchMode mode, zx_handle_t socket_handle,
+                          SocketWatcher* watcher, WatchHandle* out);
 
   // Attaches to the exception port of the given process and issues callbacks
   // on the given watcher. The watcher must outlive the returned WatchHandle.
@@ -75,8 +77,8 @@ class MessageLoopTarget : public MessageLoop {
     zx_koid_t process_koid;
     ZirconExceptionWatcher* watcher = nullptr;
   };
-  virtual zx_status_t WatchProcessExceptions(WatchProcessConfig config,
-                                             WatchHandle* out) = 0;
+  zx_status_t WatchProcessExceptions(WatchProcessConfig config,
+                                     WatchHandle* out);
 
   // Attaches to the exception port of the given job and issues callbacks
   // on the given watcher. The watcher must outlive the returned WatchHandle.
@@ -87,17 +89,120 @@ class MessageLoopTarget : public MessageLoop {
     zx_koid_t job_koid;
     ZirconExceptionWatcher* watcher;
   };
-  virtual zx_status_t WatchJobExceptions(WatchJobConfig config,
-                                         WatchHandle* out) = 0;
+  zx_status_t WatchJobExceptions(WatchJobConfig config, WatchHandle* out);
 
   // When this class issues an exception notification, the code should call
   // this function to resume the thread from the exception. This is a wrapper
   // for zx_task_resume_from_exception or it's async-loop equivalent.
   // |thread_koid| is needed to identify the exception in some message loop
   // implementations.
-  virtual zx_status_t ResumeFromException(zx_koid_t thread_koid,
-                                          zx::thread& thread,
-                                          uint32_t options) = 0;
+  zx_status_t ResumeFromException(zx_koid_t thread_koid, zx::thread& thread,
+                                  uint32_t options);
+
+  void QuitNow() override;
+
+  const SignalHandlerMap& signal_handlers() const { return signal_handlers_; }
+
+  const ExceptionHandlerMap& exception_handlers() const {
+    return exception_handlers_;
+  }
+
+ private:
+  const WatchInfo* FindWatchInfo(int id) const;
+
+  // MessageLoop protected implementation.
+  uint64_t GetMonotonicNowNS() const override;
+  void RunImpl() override;
+  void StopWatching(int id) override;
+  // Triggers an event signaling that there is a pending event.
+  void SetHasTasks() override;
+
+  // Check for any pending C++ tasks and process them.
+  // Returns true if there was an event pending to be processed.
+  bool CheckAndProcessPendingTasks();
+
+  // Handles WatchHandles event. These are all the events that are not C++ tasks
+  // posted to the message loop.
+  void HandleException(const ExceptionHandler&, zx_port_packet_t packet);
+
+  // Handle an event of the given type.
+  void OnFdioSignal(int watch_id, const WatchInfo& info, zx_signals_t observed);
+  void OnProcessException(const ExceptionHandler&, const WatchInfo& info,
+                          const zx_port_packet_t& packet);
+  void OnProcessTerminated(const WatchInfo&, zx_signals_t observed);
+
+  void OnJobException(const ExceptionHandler&, const WatchInfo& info,
+                      const zx_port_packet_t& packet);
+  void OnSocketSignal(int watch_id, const WatchInfo& info,
+                      zx_signals_t observed);
+
+  using WatchMap = std::map<int, WatchInfo>;
+  WatchMap watches_;
+
+  // ID used as an index into watches_.
+  int next_watch_id_ = 1;
+
+  async::Loop loop_;
+  zx::event task_event_;
+
+  SignalHandlerMap signal_handlers_;
+  // See SignalHandler constructor.
+  // |associated_info| needs to be updated with the fact that it has an
+  // associated SignalHandler.
+  zx_status_t AddSignalHandler(int, zx_handle_t, zx_signals_t, WatchInfo* info);
+  void RemoveSignalHandler(const async_wait_t* id);
+
+  ExceptionHandlerMap exception_handlers_;
+  // See ExceptionHandler constructor.
+  // |associated_info| needs to be updated with the fact that it has an
+  // associated ExceptionHandler.
+  zx_status_t AddExceptionHandler(int, zx_handle_t, uint32_t, WatchInfo* info);
+  void RemoveExceptionHandler(const async_exception_t*);
+
+  // Every exception source (ExceptionHandler) will get an async_exception_t*
+  // that works as a "key" for the async_loop. This async_exception_t* is what
+  // you give back to the loop to return from an exception.
+  //
+  // So, everytime there is an exception, there needs to be a tracking from
+  // thread_koid to this async_exception_t*, so that when a thread is resumed,
+  // we can pass to the loop the correct key by just using the thread koid.
+  struct Exception;
+  std::map<zx_koid_t, Exception> thread_exception_map_;
+  void AddException(const ExceptionHandler&, zx_koid_t thread_koid);
+
+  FXL_DISALLOW_COPY_AND_ASSIGN(MessageLoopTarget);
+
+  friend class SignalHandler;
+  friend class ExceptionHandler;
+};
+
+// EventHandlers need access to the WatchInfo implementation.
+struct MessageLoopTarget::WatchInfo {
+  // Name of the resource being watched.
+  // Mostly tracked for debugging purposes.
+  std::string resource_name;
+
+  WatchType type = WatchType::kFdio;
+
+  // FDIO-specific watcher parameters.
+  int fd = -1;
+  fdio_t* fdio = nullptr;
+  FDWatcher* fd_watcher = nullptr;
+  zx_handle_t fd_handle = ZX_HANDLE_INVALID;
+
+  // Socket-specific parameters.
+  SocketWatcher* socket_watcher = nullptr;
+  zx_handle_t socket_handle = ZX_HANDLE_INVALID;
+
+  // Task-exception-specific parameters, can be of job or process type.
+  ZirconExceptionWatcher* exception_watcher = nullptr;
+  zx_koid_t task_koid = 0;
+  zx_handle_t task_handle = ZX_HANDLE_INVALID;
+
+  // This makes easier the lookup of the associated ExceptionHandler with this
+  // watch id.
+  const async_wait_t* signal_handler_key = nullptr;
+  const async_exception_t* exception_handler_key = nullptr;
 };
 
 }  // namespace debug_ipc
diff --git a/src/developer/debug/shared/message_loop_zircon.cc b/src/developer/debug/shared/message_loop_zircon.cc
deleted file mode 100644
index c78ecd3e41266d473e60a1c281998fe83b7f422d..0000000000000000000000000000000000000000
--- a/src/developer/debug/shared/message_loop_zircon.cc
+++ /dev/null
@@ -1,534 +0,0 @@
-// Copyright 2018 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "src/developer/debug/shared/message_loop_zircon.h"
-
-#include <lib/fdio/io.h>
-#include <lib/fdio/unsafe.h>
-#include <lib/zx/handle.h>
-#include <lib/zx/job.h>
-#include <lib/zx/process.h>
-#include <zircon/syscalls/exception.h>
-
-#include "src/developer/debug/shared/fd_watcher.h"
-#include "src/developer/debug/shared/socket_watcher.h"
-#include "src/developer/debug/shared/zircon_exception_watcher.h"
-#include "src/lib/fxl/logging.h"
-
-namespace debug_ipc {
-
-namespace {
-
-// This signal on the task_event_ indicates there is work to do.
-constexpr uint32_t kTaskSignal = ZX_USER_SIGNAL_0;
-
-// 0 is an invalid ID for watchers, so is safe to use here.
-constexpr uint64_t kTaskSignalKey = 0;
-
-thread_local MessageLoopZircon* current_message_loop_zircon = nullptr;
-
-}  // namespace
-
-// Everything in this class must be simple and copyable since we copy this
-// structure for every call (to avoid locking problems).
-struct MessageLoopZircon::WatchInfo {
-  // Mostly for debugging purposes.
-  std::string resource_name;
-
-  WatchType type = WatchType::kFdio;
-
-  // FDIO-specific watcher parameters.
-  int fd = -1;
-  fdio_t* fdio = nullptr;
-  FDWatcher* fd_watcher = nullptr;
-  zx_handle_t fd_handle = ZX_HANDLE_INVALID;
-
-  // Socket-specific parameters.
-  SocketWatcher* socket_watcher = nullptr;
-  zx_handle_t socket_handle = ZX_HANDLE_INVALID;
-
-  // Task-exception-specific parameters, can be of job or process type.
-  ZirconExceptionWatcher* exception_watcher = nullptr;
-  zx_koid_t task_koid = 0;
-  zx_handle_t task_handle = ZX_HANDLE_INVALID;
-};
-
-MessageLoopZircon::MessageLoopZircon() {
-  zx::port::create(0, &port_);
-
-  zx::event::create(0, &task_event_);
-  task_event_.wait_async(port_, kTaskSignalKey, kTaskSignal,
-                         ZX_WAIT_ASYNC_REPEATING);
-}
-
-MessageLoopZircon::~MessageLoopZircon() {
-  FXL_DCHECK(Current() != this);  // Cleanup should have been called.
-}
-
-void MessageLoopZircon::Init() { InitTarget(); }
-
-zx_status_t MessageLoopZircon::InitTarget() {
-  MessageLoop::Init();
-
-  FXL_DCHECK(!current_message_loop_zircon);
-  current_message_loop_zircon = this;
-  MessageLoopTarget::current_message_loop_type =
-      MessageLoopTarget::Type::kZircon;
-
-  return ZX_OK;
-}
-
-void MessageLoopZircon::Cleanup() {
-  FXL_DCHECK(current_message_loop_zircon == this);
-  current_message_loop_zircon = nullptr;
-  MessageLoopTarget::current_message_loop_type = MessageLoopTarget::Type::kLast;
-
-  MessageLoop::Cleanup();
-}
-
-void MessageLoopZircon::QuitNow() { MessageLoop::QuitNow(); }
-
-// static
-MessageLoopZircon* MessageLoopZircon::Current() {
-  return current_message_loop_zircon;
-}
-
-MessageLoop::WatchHandle MessageLoopZircon::WatchFD(WatchMode mode, int fd,
-                                                    FDWatcher* watcher) {
-  WatchInfo info;
-  info.type = WatchType::kFdio;
-  info.fd_watcher = watcher;
-  info.fd = fd;
-  info.fdio = fdio_unsafe_fd_to_io(fd);
-  if (!info.fdio)
-    return WatchHandle();
-
-  uint32_t events = 0;
-  switch (mode) {
-    case WatchMode::kRead:
-      events = POLLIN;
-      break;
-    case WatchMode::kWrite:
-      events = POLLOUT;
-      break;
-    case WatchMode::kReadWrite:
-      events = POLLIN | POLLOUT;
-      break;
-  }
-
-  zx_signals_t signals = ZX_SIGNAL_NONE;
-  fdio_unsafe_wait_begin(info.fdio, events, &info.fd_handle, &signals);
-  if (info.fd_handle == ZX_HANDLE_INVALID)
-    return WatchHandle();
-
-  int watch_id;
-  {
-    std::lock_guard<std::mutex> guard(mutex_);
-
-    watch_id = next_watch_id_;
-    next_watch_id_++;
-    if (zx_object_wait_async(info.fd_handle, port_.get(),
-                             static_cast<uint64_t>(watch_id), signals,
-                             ZX_WAIT_ASYNC_REPEATING) != ZX_OK)
-      return WatchHandle();
-
-    watches_[watch_id] = info;
-  }
-
-  return WatchHandle(this, watch_id);
-}
-
-zx_status_t MessageLoopZircon::WatchSocket(WatchMode mode,
-                                           zx_handle_t socket_handle,
-                                           SocketWatcher* watcher,
-                                           MessageLoop::WatchHandle* out) {
-  WatchInfo info;
-  info.type = WatchType::kSocket;
-  info.socket_watcher = watcher;
-  info.socket_handle = socket_handle;
-
-  int watch_id;
-  {
-    std::lock_guard<std::mutex> guard(mutex_);
-
-    watch_id = next_watch_id_;
-    next_watch_id_++;
-
-    if (mode == WatchMode::kRead || mode == WatchMode::kReadWrite) {
-      zx_status_t status =
-          zx_object_wait_async(socket_handle, port_.get(), watch_id,
-                               ZX_SOCKET_READABLE, ZX_WAIT_ASYNC_REPEATING);
-      if (status != ZX_OK)
-        return status;
-    }
-
-    if (mode == WatchMode::kWrite || mode == WatchMode::kReadWrite) {
-      zx_status_t status =
-          zx_object_wait_async(socket_handle, port_.get(), watch_id,
-                               ZX_SOCKET_WRITABLE, ZX_WAIT_ASYNC_REPEATING);
-      if (status != ZX_OK)
-        return status;
-    }
-
-    watches_[watch_id] = info;
-  }
-
-  *out = WatchHandle(this, watch_id);
-  return ZX_OK;
-}
-
-zx_status_t MessageLoopZircon::WatchProcessExceptions(
-    WatchProcessConfig config, MessageLoop::WatchHandle* out) {
-  WatchInfo info;
-  info.type = WatchType::kProcessExceptions;
-  info.resource_name = config.process_name;
-  info.exception_watcher = config.watcher;
-  info.task_koid = config.process_koid;
-  info.task_handle = config.process_handle;
-
-  int watch_id;
-  {
-    std::lock_guard<std::mutex> guard(mutex_);
-
-    watch_id = next_watch_id_;
-    next_watch_id_++;
-
-    // Bind to the exception port.
-    zx_status_t status =
-        zx_task_bind_exception_port(config.process_handle, port_.get(),
-                                    watch_id, ZX_EXCEPTION_PORT_DEBUGGER);
-    if (status != ZX_OK)
-      return status;
-
-    // Also watch for process termination.
-    status =
-        zx_object_wait_async(config.process_handle, port_.get(), watch_id,
-                             ZX_PROCESS_TERMINATED, ZX_WAIT_ASYNC_REPEATING);
-    if (status != ZX_OK)
-      return status;
-
-    watches_[watch_id] = info;
-  }
-
-  *out = WatchHandle(this, watch_id);
-  return ZX_OK;
-}
-
-zx_status_t MessageLoopZircon::WatchJobExceptions(
-    WatchJobConfig config, MessageLoop::WatchHandle* out) {
-  WatchInfo info;
-  info.type = WatchType::kJobExceptions;
-  info.resource_name = config.job_name;
-  info.exception_watcher = config.watcher;
-  info.task_koid = config.job_koid;
-  info.task_handle = config.job_handle;
-
-  int watch_id;
-  {
-    std::lock_guard<std::mutex> guard(mutex_);
-
-    watch_id = next_watch_id_;
-    next_watch_id_++;
-
-    // Bind to the exception port.
-    zx_status_t status = zx_task_bind_exception_port(
-        config.job_handle, port_.get(), watch_id, ZX_EXCEPTION_PORT_DEBUGGER);
-    if (status != ZX_OK)
-      return status;
-
-    watches_[watch_id] = info;
-  }
-
-  *out = WatchHandle(this, watch_id);
-  return ZX_OK;
-}
-
-// |thread_koid| is unused in this message loop.
-zx_status_t MessageLoopZircon::ResumeFromException(zx_koid_t,
-                                                   zx::thread& thread,
-                                                   uint32_t options) {
-  return thread.resume_from_exception(port_, options);
-}
-
-bool MessageLoopZircon::CheckAndProcessPendingTasks() {
-  std::lock_guard<std::mutex> guard(mutex_);
-  // Do a C++ task.
-  if (ProcessPendingTask()) {
-    SetHasTasks();  // Enqueue another task signal.
-    return true;
-  }
-  return false;
-}
-
-void MessageLoopZircon::HandleException(zx_port_packet_t packet) {
-  WatchInfo* watch_info = nullptr;
-  {
-    // Some event being watched.
-    std::lock_guard<std::mutex> guard(mutex_);
-    auto found = watches_.find(packet.key);
-    if (found == watches_.end()) {
-      // It is possible to get an exception that doesn't have a watch handle.
-      // A case is a race between detaching from a process and getting an
-      // exception on that process.
-      //
-      // The normal process looks like this:
-      //
-      // 1. In order to correctly detach, the debug agent has to resume threads
-      //    from their exceptions. Otherwise that exception will be treated as
-      //    unhandled when the agent detaches and will bubble up.
-      // 2. The agent detaches from the exception port. This means that the
-      //    watch handle is no longer listening.
-      //
-      // It is possible between (1) and (2) to get an exception, which will be
-      // queued in the exception port of the thread. Now, the agent won't read
-      // from the port until *after* it has detached from the exception port.
-      // This means that this new exception is not handled and will be bubbled
-      // up, which is correct as the debug agent stated that it has nothing more
-      // to do with the process.
-      //
-      // Now the problem is that zircon does not clean stale packets from a
-      // queue, meaning that the next time the message loop waits on the port,
-      // it will find a stale packet. In this context a stale packet means one
-      // that does not have a watch handle, as it was deleted in (1). Hence we
-      // get into this case and we simply log it for posperity.
-      //
-      // TODO(zX-2623): zircon is going to clean up stale packets from ports
-      //                in the future. When that is done, this case should not
-      //                happen and we should go back into asserting it.
-      FXL_LOG(WARNING) << "Got stale port packet. This is most probably due to "
-                          "a race between detaching from a process and an "
-                          "exception ocurring.";
-      return;
-    }
-    watch_info = &found->second;
-  }
-
-  // Dispatch the watch callback outside of the lock. This depends on the
-  // stability of the WatchInfo pointer in the map (std::map is stable across
-  // updates) and the watch not getting unregistered from another thread
-  // asynchronously (which the API requires and is enforced by a DCHECK in
-  // the StopWatching impl).
-  switch (watch_info->type) {
-    case WatchType::kFdio:
-      OnFdioSignal(packet.key, *watch_info, packet);
-      break;
-    case WatchType::kProcessExceptions:
-      OnProcessException(*watch_info, packet);
-      break;
-    case WatchType::kJobExceptions:
-      OnJobException(*watch_info, packet);
-      break;
-    case WatchType::kSocket:
-      OnSocketSignal(packet.key, *watch_info, packet);
-      break;
-    default:
-      FXL_NOTREACHED();
-  }
-}
-
-uint64_t MessageLoopZircon::GetMonotonicNowNS() const {
-  zx::time ret;
-  zx::clock::get(&ret);
-
-  return ret.get();
-}
-
-// Previously, the approach was to first look for C++ tasks and when handled
-// look for WatchHandle work and finally wait for an event. This worked because
-// handle events didn't post C++ tasks.
-//
-// But some tests do post tasks on handle events. Because C++ tasks are signaled
-// by explicitly signaling an zx::event, without manually checking, the C++
-// tasks will never be checked and we would get blocked until a watch handled
-// is triggered.
-//
-// In order to handle the events properly, we need to check for C++ tasks before
-// and *after* handling watch handle events. This way we always process C++
-// tasks before handle events and will get signaled if one of them posted a new
-// task.
-void MessageLoopZircon::RunImpl() {
-  // Init should have been called.
-  FXL_DCHECK(Current() == this);
-
-  zx::time time;
-  uint64_t delay = DelayNS();
-  if (delay == MessageLoop::kMaxDelay) {
-    time = zx::time::infinite();
-  } else {
-    time = zx::deadline_after(zx::nsec(delay));
-  }
-
-  zx_port_packet_t packet;
-  while (!should_quit() && port_.wait(time, &packet) == ZX_OK) {
-    // We check first for pending C++ tasks. If an event was handled, it will
-    // signal the associated zx::event in order to trigger the port once more
-    // (this is the way we process an enqueued event). If there is no enqueued
-    // event, we won't trigger the event and go back to wait on the port.
-    if (packet.key == kTaskSignalKey) {
-      CheckAndProcessPendingTasks();
-      continue;
-    }
-
-    // If it wasn't a task, we check for what kind of exception it was and
-    // handle it.
-    HandleException(packet);
-
-    // The exception handling could have added more pending work, so we have to
-    // re-check in order to correctly signal for new work.
-    CheckAndProcessPendingTasks();
-  }
-}
-
-void MessageLoopZircon::StopWatching(int id) {
-  // The dispatch code for watch callbacks requires this be called on the
-  // same thread as the message loop is.
-  FXL_DCHECK(Current() == this);
-
-  std::lock_guard<std::mutex> guard(mutex_);
-
-  auto found = watches_.find(id);
-  if (found == watches_.end()) {
-    FXL_NOTREACHED();
-    return;
-  }
-
-  WatchInfo& info = found->second;
-  switch (info.type) {
-    case WatchType::kFdio:
-      port_.cancel(*zx::unowned_handle(info.fd_handle),
-                   static_cast<uint64_t>(id));
-      break;
-    case WatchType::kProcessExceptions: {
-      zx::unowned_process process(info.task_handle);
-
-      // Binding an invalid port will detach from the exception port.
-      process->bind_exception_port(zx::port(), 0, ZX_EXCEPTION_PORT_DEBUGGER);
-      // Stop watching for process events.
-      port_.cancel(*process, id);
-      break;
-    }
-    case WatchType::kJobExceptions: {
-      zx::unowned_job job(info.task_handle);
-      // Binding an invalid port will detach from the exception port.
-      job->bind_exception_port(zx::port(), 0, ZX_EXCEPTION_PORT_DEBUGGER);
-      // Stop watching for job events.
-      port_.cancel(*job, id);
-      break;
-    }
-    case WatchType::kSocket:
-      port_.cancel(*zx::unowned_handle(info.socket_handle), id);
-      break;
-    default:
-      FXL_NOTREACHED();
-      break;
-  }
-  watches_.erase(found);
-}
-
-void MessageLoopZircon::SetHasTasks() { task_event_.signal(0, kTaskSignal); }
-
-void MessageLoopZircon::OnFdioSignal(int watch_id, const WatchInfo& info,
-                                     const zx_port_packet_t& packet) {
-  uint32_t events = 0;
-  fdio_unsafe_wait_end(info.fdio, packet.signal.observed, &events);
-
-  if ((events & POLLERR) || (events & POLLHUP) || (events & POLLNVAL) ||
-      (events & POLLRDHUP)) {
-    info.fd_watcher->OnFDReady(info.fd, false, false, true);
-
-    // Don't dispatch any other notifications when there's an error. Zircon
-    // seems to set readable and writable on error even if there's nothing
-    // there.
-    return;
-  }
-
-  info.fd_watcher->OnFDReady(info.fd, !!(events & POLLIN), !!(events & POLLOUT),
-                             false);
-}
-
-void MessageLoopZircon::OnProcessException(const WatchInfo& info,
-                                           const zx_port_packet_t& packet) {
-  if (ZX_PKT_IS_EXCEPTION(packet.type)) {
-    // All debug exceptions.
-    switch (packet.type) {
-      case ZX_EXCP_THREAD_STARTING:
-        info.exception_watcher->OnThreadStarting(info.task_koid,
-                                                 packet.exception.tid);
-        break;
-      case ZX_EXCP_THREAD_EXITING:
-        info.exception_watcher->OnThreadExiting(info.task_koid,
-                                                packet.exception.tid);
-        break;
-      case ZX_EXCP_GENERAL:
-      case ZX_EXCP_FATAL_PAGE_FAULT:
-      case ZX_EXCP_UNDEFINED_INSTRUCTION:
-      case ZX_EXCP_SW_BREAKPOINT:
-      case ZX_EXCP_HW_BREAKPOINT:
-      case ZX_EXCP_UNALIGNED_ACCESS:
-      case ZX_EXCP_POLICY_ERROR:
-        info.exception_watcher->OnException(info.task_koid,
-                                            packet.exception.tid, packet.type);
-        break;
-      default:
-        FXL_NOTREACHED();
-    }
-  } else if (ZX_PKT_IS_SIGNAL_REP(packet.type) &&
-             packet.signal.observed & ZX_PROCESS_TERMINATED) {
-    // This type of watcher also gets process terminated signals.
-    info.exception_watcher->OnProcessTerminated(info.task_koid);
-  } else {
-    FXL_NOTREACHED();
-  }
-}
-
-void MessageLoopZircon::OnJobException(const WatchInfo& info,
-                                       const zx_port_packet_t& packet) {
-  if (ZX_PKT_IS_EXCEPTION(packet.type)) {
-    // All debug exceptions.
-    switch (packet.type) {
-      case ZX_EXCP_PROCESS_STARTING:
-        info.exception_watcher->OnProcessStarting(
-            info.task_koid, packet.exception.pid, packet.exception.tid);
-        break;
-      default:
-        FXL_NOTREACHED();
-    }
-  } else {
-    FXL_NOTREACHED();
-  }
-}
-
-void MessageLoopZircon::OnSocketSignal(int watch_id, const WatchInfo& info,
-                                       const zx_port_packet_t& packet) {
-  if (!ZX_PKT_IS_SIGNAL_REP(packet.type))
-    return;
-
-  auto observed = packet.signal.observed;
-
-  // See if the socket was closed.
-  if ((observed & ZX_SOCKET_PEER_CLOSED) ||
-      (observed & ZX_SIGNAL_HANDLE_CLOSED)) {
-    info.socket_watcher->OnSocketError(info.socket_handle);
-    // |info| is can be deleted at this point, so don't use it anymore.
-    return;
-  }
-
-  // Dispatch readable signal.
-  if (observed & ZX_SOCKET_READABLE)
-    info.socket_watcher->OnSocketReadable(info.socket_handle);
-
-  // When signaling both readable and writable, make sure the readable handler
-  // didn't remove the watch.
-  if ((observed & ZX_SOCKET_READABLE) && (observed & ZX_SOCKET_WRITABLE)) {
-    std::lock_guard<std::mutex> guard(mutex_);
-    if (watches_.find(packet.key) == watches_.end())
-      return;
-  }
-
-  // Dispatch writable signal.
-  if (observed & ZX_SOCKET_WRITABLE)
-    info.socket_watcher->OnSocketWritable(info.socket_handle);
-}
-
-}  // namespace debug_ipc
diff --git a/src/developer/debug/shared/message_loop_zircon.h b/src/developer/debug/shared/message_loop_zircon.h
deleted file mode 100644
index e0704060968ccfcd5eadd066c247311b2422b38a..0000000000000000000000000000000000000000
--- a/src/developer/debug/shared/message_loop_zircon.h
+++ /dev/null
@@ -1,94 +0,0 @@
-// Copyright 2018 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#pragma once
-
-#include <lib/fdio/unsafe.h>
-#include <lib/zx/event.h>
-#include <lib/zx/port.h>
-#include <lib/zx/thread.h>
-
-#include "src/developer/debug/shared/message_loop_target.h"
-
-namespace debug_ipc {
-
-class ZirconExceptionWatcher;
-class SocketWatcher;
-
-class MessageLoopZircon : public MessageLoopTarget {
- public:
-  MessageLoopZircon();
-  ~MessageLoopZircon();
-
-  void Init() override;
-  zx_status_t InitTarget() override;
-
-  void Cleanup() override;
-  void QuitNow() override;
-
-  Type GetType() const override { return Type::kZircon; }
-  bool SupportsFidl() const override { return false; }
-
-  // Returns the current message loop or null if there isn't one.
-  static MessageLoopZircon* Current();
-
-  // MessageLoop implementation.
-  WatchHandle WatchFD(WatchMode mode, int fd, FDWatcher* watcher) override;
-
-  zx_status_t WatchSocket(WatchMode mode, zx_handle_t socket_handle,
-                          SocketWatcher* watcher, WatchHandle* out) override;
-
-  zx_status_t WatchProcessExceptions(WatchProcessConfig config,
-                                     WatchHandle* out) override;
-
-  zx_status_t WatchJobExceptions(WatchJobConfig config,
-                                 WatchHandle* out) override;
-
-  zx_status_t ResumeFromException(zx_koid_t thread_koid, zx::thread& thread,
-                                  uint32_t options) override;
-
- private:
-  // Associated struct to track information about what type of resource a watch
-  // handle is following.
-  struct WatchInfo;
-
-  // MessageLoop protected implementation.
-  uint64_t GetMonotonicNowNS() const override;
-  void RunImpl() override;
-  void StopWatching(int id) override;
-  // Triggers an event signaling that there is a pending event.
-  void SetHasTasks() override;
-
-  // Check for any pending C++ tasks and process them.
-  // Returns true if there was an event pending to be processed.
-  bool CheckAndProcessPendingTasks();
-
-  // Handles WatchHandles event. These are all the events that are not C++
-  // tasks posted to the message loop.
-  void HandleException(zx_port_packet_t packet);
-
-  // Handle an event of the given type.
-  void OnFdioSignal(int watch_id, const WatchInfo& info,
-                    const zx_port_packet_t& packet);
-  void OnProcessException(const WatchInfo& info,
-                          const zx_port_packet_t& packet);
-  void OnJobException(const WatchInfo& info, const zx_port_packet_t& packet);
-  void OnSocketSignal(int watch_id, const WatchInfo& info,
-                      const zx_port_packet_t& packet);
-
-  using WatchMap = std::map<int, WatchInfo>;
-  WatchMap watches_;
-
-  // ID used as an index into watches_.
-  int next_watch_id_ = 1;
-
-  zx::port port_;
-
-  // This event is signaled when there are tasks to process.
-  zx::event task_event_;
-
-  FXL_DISALLOW_COPY_AND_ASSIGN(MessageLoopZircon);
-};
-
-}  // namespace debug_ipc