diff options
Diffstat (limited to 'base/threading')
-rw-r--r-- | base/threading/post_task_and_reply_impl.cc | 26 | ||||
-rw-r--r-- | base/threading/post_task_and_reply_impl.h | 6 | ||||
-rw-r--r-- | base/threading/sequenced_worker_pool.cc | 75 | ||||
-rw-r--r-- | base/threading/sequenced_worker_pool.h | 17 | ||||
-rw-r--r-- | base/threading/thread.cc | 2 | ||||
-rw-r--r-- | base/threading/thread_unittest.cc | 4 | ||||
-rw-r--r-- | base/threading/worker_pool.cc | 14 | ||||
-rw-r--r-- | base/threading/worker_pool.h | 6 | ||||
-rw-r--r-- | base/threading/worker_pool_posix.cc | 10 | ||||
-rw-r--r-- | base/threading/worker_pool_posix.h | 2 |
10 files changed, 92 insertions, 70 deletions
diff --git a/base/threading/post_task_and_reply_impl.cc b/base/threading/post_task_and_reply_impl.cc index d16f8bd225..cddb8981ad 100644 --- a/base/threading/post_task_and_reply_impl.cc +++ b/base/threading/post_task_and_reply_impl.cc @@ -29,8 +29,8 @@ namespace { class PostTaskAndReplyRelay { public: PostTaskAndReplyRelay(const tracked_objects::Location& from_here, - Closure task, - Closure reply) + OnceClosure task, + OnceClosure reply) : sequence_checker_(), from_here_(from_here), origin_task_runner_(SequencedTaskRunnerHandle::Get()), @@ -39,12 +39,10 @@ class PostTaskAndReplyRelay { ~PostTaskAndReplyRelay() { DCHECK(sequence_checker_.CalledOnValidSequence()); - task_.Reset(); - reply_.Reset(); } void RunTaskAndPostReply() { - task_.Run(); + std::move(task_).Run(); origin_task_runner_->PostTask( from_here_, Bind(&PostTaskAndReplyRelay::RunReplyAndSelfDestruct, base::Unretained(this))); @@ -54,12 +52,12 @@ class PostTaskAndReplyRelay { void RunReplyAndSelfDestruct() { DCHECK(sequence_checker_.CalledOnValidSequence()); - // Force |task_| to be released before |reply_| is to ensure that no one - // accidentally depends on |task_| keeping one of its arguments alive while - // |reply_| is executing. - task_.Reset(); + // Ensure |task_| has already been released before |reply_| to ensure that + // no one accidentally depends on |task_| keeping one of its arguments alive + // while |reply_| is executing. + DCHECK(!task_); - reply_.Run(); + std::move(reply_).Run(); // Cue mission impossible theme. delete this; @@ -68,8 +66,8 @@ class PostTaskAndReplyRelay { const SequenceChecker sequence_checker_; const tracked_objects::Location from_here_; const scoped_refptr<SequencedTaskRunner> origin_task_runner_; - Closure reply_; - Closure task_; + OnceClosure reply_; + OnceClosure task_; }; } // namespace @@ -78,8 +76,8 @@ namespace internal { bool PostTaskAndReplyImpl::PostTaskAndReply( const tracked_objects::Location& from_here, - Closure task, - Closure reply) { + OnceClosure task, + OnceClosure reply) { DCHECK(!task.is_null()) << from_here.ToString(); DCHECK(!reply.is_null()) << from_here.ToString(); PostTaskAndReplyRelay* relay = diff --git a/base/threading/post_task_and_reply_impl.h b/base/threading/post_task_and_reply_impl.h index a02c32ec8c..00aee6d0ed 100644 --- a/base/threading/post_task_and_reply_impl.h +++ b/base/threading/post_task_and_reply_impl.h @@ -29,12 +29,12 @@ class BASE_EXPORT PostTaskAndReplyImpl { // SequencedTaskRunnerHandle::IsSet(). Both |task| and |reply| are guaranteed // to be deleted on the sequence or thread that called this. bool PostTaskAndReply(const tracked_objects::Location& from_here, - Closure task, - Closure reply); + OnceClosure task, + OnceClosure reply); private: virtual bool PostTask(const tracked_objects::Location& from_here, - Closure task) = 0; + OnceClosure task) = 0; }; } // namespace internal diff --git a/base/threading/sequenced_worker_pool.cc b/base/threading/sequenced_worker_pool.cc index ecf6e2c8e0..1d8a67c2e0 100644 --- a/base/threading/sequenced_worker_pool.cc +++ b/base/threading/sequenced_worker_pool.cc @@ -97,12 +97,15 @@ struct SequencedTask : public TrackingInfo { ~SequencedTask() {} + SequencedTask(SequencedTask&&) = default; + SequencedTask& operator=(SequencedTask&&) = default; + int sequence_token_id; int trace_id; int64_t sequence_task_number; SequencedWorkerPool::WorkerShutdown shutdown_behavior; tracked_objects::Location posted_from; - Closure task; + OnceClosure task; // Non-delayed tasks and delayed tasks are managed together by time-to-run // order. We calculate the time by adding the posted time and the given delay. @@ -144,7 +147,7 @@ class SequencedWorkerPoolTaskRunner : public TaskRunner { // TaskRunner implementation bool PostDelayedTask(const tracked_objects::Location& from_here, - Closure task, + OnceClosure task, TimeDelta delay) override; bool RunsTasksOnCurrentThread() const override; @@ -168,7 +171,7 @@ SequencedWorkerPoolTaskRunner::~SequencedWorkerPoolTaskRunner() { bool SequencedWorkerPoolTaskRunner::PostDelayedTask( const tracked_objects::Location& from_here, - Closure task, + OnceClosure task, TimeDelta delay) { if (delay.is_zero()) { return pool_->PostWorkerTaskWithShutdownBehavior(from_here, std::move(task), @@ -198,13 +201,13 @@ class SequencedWorkerPool::PoolSequencedTaskRunner // TaskRunner implementation bool PostDelayedTask(const tracked_objects::Location& from_here, - Closure task, + OnceClosure task, TimeDelta delay) override; bool RunsTasksOnCurrentThread() const override; // SequencedTaskRunner implementation bool PostNonNestableDelayedTask(const tracked_objects::Location& from_here, - Closure task, + OnceClosure task, TimeDelta delay) override; private: @@ -233,7 +236,7 @@ SequencedWorkerPool::PoolSequencedTaskRunner:: bool SequencedWorkerPool::PoolSequencedTaskRunner::PostDelayedTask( const tracked_objects::Location& from_here, - Closure task, + OnceClosure task, TimeDelta delay) { if (delay.is_zero()) { return pool_->PostSequencedWorkerTaskWithShutdownBehavior( @@ -250,7 +253,7 @@ bool SequencedWorkerPool::PoolSequencedTaskRunner:: bool SequencedWorkerPool::PoolSequencedTaskRunner::PostNonNestableDelayedTask( const tracked_objects::Location& from_here, - Closure task, + OnceClosure task, TimeDelta delay) { // There's no way to run nested tasks, so simply forward to // PostDelayedTask. @@ -353,7 +356,7 @@ class SequencedWorkerPool::Inner { SequenceToken sequence_token, WorkerShutdown shutdown_behavior, const tracked_objects::Location& from_here, - Closure task, + OnceClosure task, TimeDelta delay); bool RunsTasksOnCurrentThread() const; @@ -398,8 +401,7 @@ class SequencedWorkerPool::Inner { // Returns true if the task may run at some point in the future and false if // it will definitely not run. // Coalesce upon resolution of http://crbug.com/622400. - bool PostTaskToTaskScheduler(const SequencedTask& sequenced, - const TimeDelta& delay); + bool PostTaskToTaskScheduler(SequencedTask sequenced, const TimeDelta& delay); // Returns the TaskScheduler TaskRunner for the specified |sequence_token_id| // and |traits|. @@ -697,8 +699,10 @@ bool SequencedWorkerPool::Inner::PostTask( SequenceToken sequence_token, WorkerShutdown shutdown_behavior, const tracked_objects::Location& from_here, - Closure task, + OnceClosure task, TimeDelta delay) { + DCHECK(task); + // TODO(fdoray): Uncomment this DCHECK. It is initially commented to avoid a // revert of the CL that adds debug::DumpWithoutCrashing() if it fails on the // waterfall. https://crbug.com/622400 @@ -758,20 +762,22 @@ bool SequencedWorkerPool::Inner::PostTask( // See on top of the file why we don't compile this on Arc++. #if 0 if (g_all_pools_state == AllPoolsState::REDIRECTED_TO_TASK_SCHEDULER) { - if (!PostTaskToTaskScheduler(sequenced, delay)) + if (!PostTaskToTaskScheduler(std::move(sequenced), delay)) return false; } else { #endif - pending_tasks_.insert(sequenced); + SequencedWorkerPool::WorkerShutdown shutdown_behavior = + sequenced.shutdown_behavior; + pending_tasks_.insert(std::move(sequenced)); - if (sequenced.shutdown_behavior == BLOCK_SHUTDOWN) + if (shutdown_behavior == BLOCK_SHUTDOWN) blocking_shutdown_pending_task_count_++; create_thread_id = PrepareToStartAdditionalThreadIfHelpful(); - } #if 0 - } + } #endif + } // Use != REDIRECTED_TO_TASK_SCHEDULER instead of == USE_WORKER_POOL to ensure // correct behavior if a task is posted to a SequencedWorkerPool before @@ -803,7 +809,7 @@ bool SequencedWorkerPool::Inner::PostTask( } bool SequencedWorkerPool::Inner::PostTaskToTaskScheduler( - const SequencedTask& sequenced, + SequencedTask sequenced, const TimeDelta& delay) { #if 1 NOTREACHED(); @@ -837,7 +843,8 @@ bool SequencedWorkerPool::Inner::PostTaskToTaskScheduler( .WithPriority(task_priority_) .WithShutdownBehavior(task_shutdown_behavior); return GetTaskSchedulerTaskRunner(sequenced.sequence_token_id, traits) - ->PostDelayedTask(sequenced.posted_from, sequenced.task, delay); + ->PostDelayedTask(sequenced.posted_from, std::move(sequenced.task), + delay); #endif } @@ -1263,7 +1270,11 @@ SequencedWorkerPool::Inner::GetWorkStatus SequencedWorkerPool::Inner::GetWork( // refcounted, so we just need to keep a copy of them alive until the lock // is exited. The calling code can just clear() the vector they passed to // us once the lock is exited to make this happen. - delete_these_outside_lock->push_back(*i); + // + // The const_cast here is safe since the object is erased from + // |pending_tasks_| soon after the move. + delete_these_outside_lock->push_back( + std::move(const_cast<SequencedTask&>(*i))); pending_tasks_.erase(i++); continue; } @@ -1274,14 +1285,18 @@ SequencedWorkerPool::Inner::GetWorkStatus SequencedWorkerPool::Inner::GetWork( status = GET_WORK_WAIT; if (cleanup_state_ == CLEANUP_RUNNING) { // Deferred tasks are deleted when cleaning up, see Inner::ThreadLoop. - delete_these_outside_lock->push_back(*i); + // The const_cast here is safe since the object is erased from + // |pending_tasks_| soon after the move. + delete_these_outside_lock->push_back( + std::move(const_cast<SequencedTask&>(*i))); pending_tasks_.erase(i); } break; } - // Found a runnable task. - *task = *i; + // Found a runnable task. The const_cast is safe here since the object is + // erased from |pending_tasks_| soon after the move. + *task = std::move(const_cast<SequencedTask&>(*i)); pending_tasks_.erase(i); if (task->shutdown_behavior == BLOCK_SHUTDOWN) { blocking_shutdown_pending_task_count_--; @@ -1558,14 +1573,14 @@ SequencedWorkerPool::GetTaskRunnerWithShutdownBehavior( bool SequencedWorkerPool::PostWorkerTask( const tracked_objects::Location& from_here, - Closure task) { + OnceClosure task) { return inner_->PostTask(NULL, SequenceToken(), BLOCK_SHUTDOWN, from_here, std::move(task), TimeDelta()); } bool SequencedWorkerPool::PostDelayedWorkerTask( const tracked_objects::Location& from_here, - Closure task, + OnceClosure task, TimeDelta delay) { WorkerShutdown shutdown_behavior = delay.is_zero() ? BLOCK_SHUTDOWN : SKIP_ON_SHUTDOWN; @@ -1575,7 +1590,7 @@ bool SequencedWorkerPool::PostDelayedWorkerTask( bool SequencedWorkerPool::PostWorkerTaskWithShutdownBehavior( const tracked_objects::Location& from_here, - Closure task, + OnceClosure task, WorkerShutdown shutdown_behavior) { return inner_->PostTask(NULL, SequenceToken(), shutdown_behavior, from_here, std::move(task), TimeDelta()); @@ -1584,7 +1599,7 @@ bool SequencedWorkerPool::PostWorkerTaskWithShutdownBehavior( bool SequencedWorkerPool::PostSequencedWorkerTask( SequenceToken sequence_token, const tracked_objects::Location& from_here, - Closure task) { + OnceClosure task) { return inner_->PostTask(NULL, sequence_token, BLOCK_SHUTDOWN, from_here, std::move(task), TimeDelta()); } @@ -1592,7 +1607,7 @@ bool SequencedWorkerPool::PostSequencedWorkerTask( bool SequencedWorkerPool::PostDelayedSequencedWorkerTask( SequenceToken sequence_token, const tracked_objects::Location& from_here, - Closure task, + OnceClosure task, TimeDelta delay) { WorkerShutdown shutdown_behavior = delay.is_zero() ? BLOCK_SHUTDOWN : SKIP_ON_SHUTDOWN; @@ -1603,7 +1618,7 @@ bool SequencedWorkerPool::PostDelayedSequencedWorkerTask( bool SequencedWorkerPool::PostNamedSequencedWorkerTask( const std::string& token_name, const tracked_objects::Location& from_here, - Closure task) { + OnceClosure task) { DCHECK(!token_name.empty()); return inner_->PostTask(&token_name, SequenceToken(), BLOCK_SHUTDOWN, from_here, std::move(task), TimeDelta()); @@ -1612,7 +1627,7 @@ bool SequencedWorkerPool::PostNamedSequencedWorkerTask( bool SequencedWorkerPool::PostSequencedWorkerTaskWithShutdownBehavior( SequenceToken sequence_token, const tracked_objects::Location& from_here, - Closure task, + OnceClosure task, WorkerShutdown shutdown_behavior) { return inner_->PostTask(NULL, sequence_token, shutdown_behavior, from_here, std::move(task), TimeDelta()); @@ -1620,7 +1635,7 @@ bool SequencedWorkerPool::PostSequencedWorkerTaskWithShutdownBehavior( bool SequencedWorkerPool::PostDelayedTask( const tracked_objects::Location& from_here, - Closure task, + OnceClosure task, TimeDelta delay) { return PostDelayedWorkerTask(from_here, std::move(task), delay); } diff --git a/base/threading/sequenced_worker_pool.h b/base/threading/sequenced_worker_pool.h index 8cdeb0b5db..e577e1be11 100644 --- a/base/threading/sequenced_worker_pool.h +++ b/base/threading/sequenced_worker_pool.h @@ -275,7 +275,8 @@ class BASE_EXPORT SequencedWorkerPool : public TaskRunner { // // Returns true if the task was posted successfully. This may fail during // shutdown regardless of the specified ShutdownBehavior. - bool PostWorkerTask(const tracked_objects::Location& from_here, Closure task); + bool PostWorkerTask(const tracked_objects::Location& from_here, + OnceClosure task); // Same as PostWorkerTask but allows a delay to be specified (although doing // so changes the shutdown behavior). The task will be run after the given @@ -287,13 +288,13 @@ class BASE_EXPORT SequencedWorkerPool : public TaskRunner { // task will be guaranteed to run to completion before shutdown // (BLOCK_SHUTDOWN semantics). bool PostDelayedWorkerTask(const tracked_objects::Location& from_here, - Closure task, + OnceClosure task, TimeDelta delay); // Same as PostWorkerTask but allows specification of the shutdown behavior. bool PostWorkerTaskWithShutdownBehavior( const tracked_objects::Location& from_here, - Closure task, + OnceClosure task, WorkerShutdown shutdown_behavior); // Like PostWorkerTask above, but provides sequencing semantics. This means @@ -309,13 +310,13 @@ class BASE_EXPORT SequencedWorkerPool : public TaskRunner { // shutdown regardless of the specified ShutdownBehavior. bool PostSequencedWorkerTask(SequenceToken sequence_token, const tracked_objects::Location& from_here, - Closure task); + OnceClosure task); // Like PostSequencedWorkerTask above, but allows you to specify a named // token, which saves an extra call to GetNamedSequenceToken. bool PostNamedSequencedWorkerTask(const std::string& token_name, const tracked_objects::Location& from_here, - Closure task); + OnceClosure task); // Same as PostSequencedWorkerTask but allows a delay to be specified // (although doing so changes the shutdown behavior). The task will be run @@ -329,7 +330,7 @@ class BASE_EXPORT SequencedWorkerPool : public TaskRunner { bool PostDelayedSequencedWorkerTask( SequenceToken sequence_token, const tracked_objects::Location& from_here, - Closure task, + OnceClosure task, TimeDelta delay); // Same as PostSequencedWorkerTask but allows specification of the shutdown @@ -337,12 +338,12 @@ class BASE_EXPORT SequencedWorkerPool : public TaskRunner { bool PostSequencedWorkerTaskWithShutdownBehavior( SequenceToken sequence_token, const tracked_objects::Location& from_here, - Closure task, + OnceClosure task, WorkerShutdown shutdown_behavior); // TaskRunner implementation. Forwards to PostDelayedWorkerTask(). bool PostDelayedTask(const tracked_objects::Location& from_here, - Closure task, + OnceClosure task, TimeDelta delay) override; bool RunsTasksOnCurrentThread() const override; diff --git a/base/threading/thread.cc b/base/threading/thread.cc index c30320f0dc..0aeed2a9e4 100644 --- a/base/threading/thread.cc +++ b/base/threading/thread.cc @@ -11,6 +11,7 @@ #include "base/logging.h" #include "base/run_loop.h" #include "base/synchronization/waitable_event.h" +#include "base/third_party/dynamic_annotations/dynamic_annotations.h" #include "base/threading/thread_id_name_manager.h" #include "base/threading/thread_local.h" #include "base/threading/thread_restrictions.h" @@ -290,6 +291,7 @@ void Thread::ThreadMain() { // Complete the initialization of our Thread object. PlatformThread::SetName(name_.c_str()); + ANNOTATE_THREAD_NAME(name_.c_str()); // Tell the name to race detector. // Lazily initialize the |message_loop| so that it can run on this thread. DCHECK(message_loop_); diff --git a/base/threading/thread_unittest.cc b/base/threading/thread_unittest.cc index af8347432b..0cb964e8f7 100644 --- a/base/threading/thread_unittest.cc +++ b/base/threading/thread_unittest.cc @@ -32,6 +32,8 @@ typedef PlatformTest ThreadTest; namespace { void ToggleValue(bool* value) { + ANNOTATE_BENIGN_RACE(value, "Test-only data race on boolean " + "in base/thread_unittest"); *value = !*value; } @@ -39,6 +41,8 @@ class SleepInsideInitThread : public Thread { public: SleepInsideInitThread() : Thread("none") { init_called_ = false; + ANNOTATE_BENIGN_RACE( + this, "Benign test-only data race on vptr - http://crbug.com/98219"); } ~SleepInsideInitThread() override { Stop(); } diff --git a/base/threading/worker_pool.cc b/base/threading/worker_pool.cc index bc313ce25b..26ff10f1f5 100644 --- a/base/threading/worker_pool.cc +++ b/base/threading/worker_pool.cc @@ -27,7 +27,7 @@ class PostTaskAndReplyWorkerPool : public internal::PostTaskAndReplyImpl { private: bool PostTask(const tracked_objects::Location& from_here, - Closure task) override { + OnceClosure task) override { return WorkerPool::PostTask(from_here, std::move(task), task_is_slow_); } @@ -45,7 +45,7 @@ class WorkerPoolTaskRunner : public TaskRunner { // TaskRunner implementation bool PostDelayedTask(const tracked_objects::Location& from_here, - Closure task, + OnceClosure task, TimeDelta delay) override; bool RunsTasksOnCurrentThread() const override; @@ -56,7 +56,7 @@ class WorkerPoolTaskRunner : public TaskRunner { // zero because non-zero delays are not supported. bool PostDelayedTaskAssertZeroDelay( const tracked_objects::Location& from_here, - Closure task, + OnceClosure task, base::TimeDelta delay); const bool tasks_are_slow_; @@ -73,7 +73,7 @@ WorkerPoolTaskRunner::~WorkerPoolTaskRunner() { bool WorkerPoolTaskRunner::PostDelayedTask( const tracked_objects::Location& from_here, - Closure task, + OnceClosure task, TimeDelta delay) { return PostDelayedTaskAssertZeroDelay(from_here, std::move(task), delay); } @@ -84,7 +84,7 @@ bool WorkerPoolTaskRunner::RunsTasksOnCurrentThread() const { bool WorkerPoolTaskRunner::PostDelayedTaskAssertZeroDelay( const tracked_objects::Location& from_here, - Closure task, + OnceClosure task, base::TimeDelta delay) { DCHECK_EQ(delay.InMillisecondsRoundedUp(), 0) << "WorkerPoolTaskRunner does not support non-zero delays"; @@ -102,8 +102,8 @@ struct TaskRunnerHolder { } // namespace bool WorkerPool::PostTaskAndReply(const tracked_objects::Location& from_here, - Closure task, - Closure reply, + OnceClosure task, + OnceClosure reply, bool task_is_slow) { // Do not report PostTaskAndReplyRelay leaks in tests. There's nothing we can // do about them because WorkerPool doesn't have a flushing API. diff --git a/base/threading/worker_pool.h b/base/threading/worker_pool.h index d97dbd6a69..d1c666d2f9 100644 --- a/base/threading/worker_pool.h +++ b/base/threading/worker_pool.h @@ -32,15 +32,15 @@ class BASE_EXPORT WorkerPool { // false if |task| could not be posted to a worker thread. Regardless of // return value, ownership of |task| is transferred to the worker pool. static bool PostTask(const tracked_objects::Location& from_here, - Closure task, + OnceClosure task, bool task_is_slow); // Just like TaskRunner::PostTaskAndReply, except the destination // for |task| is a worker thread and you can specify |task_is_slow| just // like you can for PostTask above. static bool PostTaskAndReply(const tracked_objects::Location& from_here, - Closure task, - Closure reply, + OnceClosure task, + OnceClosure reply, bool task_is_slow); // Return true if the current thread is one that this WorkerPool runs tasks diff --git a/base/threading/worker_pool_posix.cc b/base/threading/worker_pool_posix.cc index e0efa463f7..851480adda 100644 --- a/base/threading/worker_pool_posix.cc +++ b/base/threading/worker_pool_posix.cc @@ -49,7 +49,7 @@ class WorkerPoolImpl { ~WorkerPoolImpl() = delete; void PostTask(const tracked_objects::Location& from_here, - base::Closure task, + base::OnceClosure task, bool task_is_slow); private: @@ -61,7 +61,7 @@ WorkerPoolImpl::WorkerPoolImpl() kIdleSecondsBeforeExit)) {} void WorkerPoolImpl::PostTask(const tracked_objects::Location& from_here, - base::Closure task, + base::OnceClosure task, bool task_is_slow) { pool_->PostTask(from_here, std::move(task)); } @@ -114,7 +114,7 @@ void WorkerThread::ThreadMain() { // static bool WorkerPool::PostTask(const tracked_objects::Location& from_here, - base::Closure task, + base::OnceClosure task, bool task_is_slow) { g_lazy_worker_pool.Pointer()->PostTask(from_here, std::move(task), task_is_slow); @@ -140,12 +140,14 @@ PosixDynamicThreadPool::~PosixDynamicThreadPool() { void PosixDynamicThreadPool::PostTask( const tracked_objects::Location& from_here, - base::Closure task) { + base::OnceClosure task) { PendingTask pending_task(from_here, std::move(task)); AddTask(&pending_task); } void PosixDynamicThreadPool::AddTask(PendingTask* pending_task) { + DCHECK(pending_task); + DCHECK(pending_task->task); AutoLock locked(lock_); pending_tasks_.push(std::move(*pending_task)); diff --git a/base/threading/worker_pool_posix.h b/base/threading/worker_pool_posix.h index cfa50c21dd..0b10adf8f3 100644 --- a/base/threading/worker_pool_posix.h +++ b/base/threading/worker_pool_posix.h @@ -51,7 +51,7 @@ class BASE_EXPORT PosixDynamicThreadPool int idle_seconds_before_exit); // Adds |task| to the thread pool. - void PostTask(const tracked_objects::Location& from_here, Closure task); + void PostTask(const tracked_objects::Location& from_here, OnceClosure task); // Worker thread method to wait for up to |idle_seconds_before_exit| for more // work from the thread pool. Returns NULL if no work is available. |