Merge pull request #8873 from google/benvanik-join-workers

Splitting worker shutdown into request+join+deinit.
diff --git a/iree/task/executor.c b/iree/task/executor.c
index 506b7fa..dc68595 100644
--- a/iree/task/executor.c
+++ b/iree/task/executor.c
@@ -182,19 +182,26 @@
     iree_task_worker_request_exit(worker);
   }
 
-  // Also ask the poller to exit - it'll wake from any wait it's in and abort
-  // all the remaining waits.
+  // Also ask the poller to exit - it'll wake from any system waits it's in and
+  // abort all the remaining waits.
   iree_task_poller_request_exit(&executor->poller);
 
-  // Now that all workers should be in the process of exiting we can join with
-  // them. Some may take longer than others to exit but that's fine as we can't
-  // return from here until they do anyway.
+  // Now that all workers and the poller should be in the process of exiting we
+  // can join with them. Some may take longer than others to exit but that's
+  // fine as we can't return from here until they exit anyway.
+  for (iree_host_size_t i = 0; i < executor->worker_count; ++i) {
+    iree_task_worker_t* worker = &executor->workers[i];
+    iree_task_worker_await_exit(worker);
+  }
+  iree_task_poller_await_exit(&executor->poller);
+
+  // Tear down all workers and the poller now that no more threads are live.
+  // Any live threads may still be touching their own data structures or those
+  // of others (for example when trying to steal work).
   for (iree_host_size_t i = 0; i < executor->worker_count; ++i) {
     iree_task_worker_t* worker = &executor->workers[i];
     iree_task_worker_deinitialize(worker);
   }
-
-  // Once no more workers can possibly put work on the poller we can kill it.
   iree_task_poller_deinitialize(&executor->poller);
 
   iree_event_pool_free(executor->event_pool);
diff --git a/iree/task/poller.c b/iree/task/poller.c
index 5d21ed3..ee6465b 100644
--- a/iree/task/poller.c
+++ b/iree/task/poller.c
@@ -116,18 +116,24 @@
          IREE_TASK_POLLER_STATE_ZOMBIE;
 }
 
+void iree_task_poller_await_exit(iree_task_poller_t* poller) {
+  if (!poller->thread) return;
+  IREE_TRACE_ZONE_BEGIN(z0);
+
+  iree_task_poller_request_exit(poller);
+  iree_notification_await(&poller->state_notification,
+                          (iree_condition_fn_t)iree_task_poller_is_zombie,
+                          poller, iree_infinite_timeout());
+
+  IREE_TRACE_ZONE_END(z0);
+}
+
 void iree_task_poller_deinitialize(iree_task_poller_t* poller) {
   IREE_TRACE_ZONE_BEGIN(z0);
 
-  // Wait for the thread to enter the zombie state indicating it has exited our
-  // main function - it may still be live in the OS, but it'll not be touching
-  // any of our data structures again so it's fine to blast away.
-  iree_task_poller_request_exit(poller);
-  if (poller->thread) {
-    iree_notification_await(&poller->state_notification,
-                            (iree_condition_fn_t)iree_task_poller_is_zombie,
-                            poller, iree_infinite_timeout());
-  }
+  // Must have called request_exit/await_exit.
+  IREE_ASSERT_TRUE(iree_task_poller_is_zombie(poller));
+
   iree_thread_release(poller->thread);
   poller->thread = NULL;
 
diff --git a/iree/task/poller.h b/iree/task/poller.h
index 4d4e42a..8618682 100644
--- a/iree/task/poller.h
+++ b/iree/task/poller.h
@@ -111,18 +111,26 @@
     iree_thread_affinity_t ideal_thread_affinity,
     iree_task_poller_t* out_poller);
 
-// Deinitializes |poller| and joins with the wait thread.
-// Any active waits will be aborted. May block.
-// To reduce the potential for blocking issue an iree_task_poller_request_exit
-// prior to deinitializing.
-void iree_task_poller_deinitialize(iree_task_poller_t* poller);
-
 // Requests that the poller wait thread begin exiting (if it hasn't already).
 // If the wait thread is in a syscall it will be woken as soon as possible.
 //
 // May be called from any thread. Any active waits will be aborted as possible.
 void iree_task_poller_request_exit(iree_task_poller_t* poller);
 
+// Blocks the caller until |poller| has exited.
+//
+// May be called from any thread.
+void iree_task_poller_await_exit(iree_task_poller_t* poller);
+
+// Deinitializes |poller| after the thread has exited.
+// The poller must be in the IREE_TASK_POLLER_STATE_ZOMBIE state.
+//
+// Expected shutdown sequence:
+//  - request_exit
+//  - await_exit
+//  - deinitialize
+void iree_task_poller_deinitialize(iree_task_poller_t* poller);
+
 // Enqueues |wait_tasks| on the poller and kicks the wait thread.
 // The task pointers will be retained by the poller and must remain valid.
 //
diff --git a/iree/task/worker.c b/iree/task/worker.c
index e0c4ea6..d12e9a2 100644
--- a/iree/task/worker.c
+++ b/iree/task/worker.c
@@ -114,18 +114,24 @@
          IREE_TASK_WORKER_STATE_ZOMBIE;
 }
 
+void iree_task_worker_await_exit(iree_task_worker_t* worker) {
+  if (!worker->thread) return;
+  IREE_TRACE_ZONE_BEGIN(z0);
+
+  iree_task_worker_request_exit(worker);
+  iree_notification_await(&worker->state_notification,
+                          (iree_condition_fn_t)iree_task_worker_is_zombie,
+                          worker, iree_infinite_timeout());
+
+  IREE_TRACE_ZONE_END(z0);
+}
+
 void iree_task_worker_deinitialize(iree_task_worker_t* worker) {
   IREE_TRACE_ZONE_BEGIN(z0);
 
-  // Wait for the thread to enter the zombie state indicating it has exited our
-  // main function - it may still be live in the OS, but it'll not be touching
-  // any of our data structures again so it's fine to blast away.
-  iree_task_worker_request_exit(worker);
-  if (worker->thread) {
-    iree_notification_await(&worker->state_notification,
-                            (iree_condition_fn_t)iree_task_worker_is_zombie,
-                            worker, iree_infinite_timeout());
-  }
+  // Must have called request_exit/await_exit.
+  IREE_ASSERT_TRUE(iree_task_worker_is_zombie(worker));
+
   iree_thread_release(worker->thread);
   worker->thread = NULL;
 
diff --git a/iree/task/worker.h b/iree/task/worker.h
index 6e6c435..6a7fc31 100644
--- a/iree/task/worker.h
+++ b/iree/task/worker.h
@@ -161,10 +161,6 @@
     iree_byte_span_t local_memory, iree_prng_splitmix64_state_t* seed_prng,
     iree_task_worker_t* out_worker);
 
-// Deinitializes a worker that has successfully exited. The worker must be in
-// the IREE_TASK_WORKER_STATE_ZOMBIE state.
-void iree_task_worker_deinitialize(iree_task_worker_t* worker);
-
 // Requests that the worker begin exiting (if it hasn't already).
 // If the worker is actively processing tasks it will wait until it has
 // completed all it can and is about to go idle prior to exiting.
@@ -172,6 +168,20 @@
 // May be called from any thread (including the worker thread).
 void iree_task_worker_request_exit(iree_task_worker_t* worker);
 
+// Blocks the caller until |worker| has exited.
+//
+// May be called from any thread.
+void iree_task_worker_await_exit(iree_task_worker_t* worker);
+
+// Deinitializes a worker that has successfully exited.
+// The worker must be in the IREE_TASK_WORKER_STATE_ZOMBIE state.
+//
+// Expected shutdown sequence:
+//  - request_exit on all workers
+//  - await_exit on all workers
+//  - deinitialize all workers
+void iree_task_worker_deinitialize(iree_task_worker_t* worker);
+
 // Posts a FIFO list of tasks to the worker mailbox. The target worker takes
 // ownership of the tasks and will be woken if it is currently idle.
 //