// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

#include "vm/thread_pool.h"

#include "vm/dart.h"
#include "vm/flags.h"
#include "vm/lockers.h"

namespace dart {

DEFINE_FLAG(int,
            worker_timeout_millis,
            5000,
            "Free workers when they have been idle for this amount of time.");

static int64_t ComputeTimeout(int64_t idle_start) {
  int64_t worker_timeout_micros =
      FLAG_worker_timeout_millis * kMicrosecondsPerMillisecond;
  if (worker_timeout_micros <= 0) {
    // No timeout.
    return 0;
  } else {
    int64_t waited = OS::GetCurrentMonotonicMicros() - idle_start;
    if (waited >= worker_timeout_micros) {
      // We must have gotten a spurious wakeup just before we timed
      // out.  Give the worker one last desperate chance to live.  We
      // are merciful.
      return 1;
    } else {
      return worker_timeout_micros - waited;
    }
  }
}

ThreadPool::ThreadPool(uintptr_t max_pool_size)
    : all_workers_dead_(false), max_pool_size_(max_pool_size) {}

ThreadPool::~ThreadPool() {
  Shutdown();
}

void ThreadPool::RequestWorkersToShutdown() {
  MutexLocker ml(&pool_mutex_);

  // If we are just starting to shutdown threads then this should be done
  // before OSThread::DisableOSThreadCreation is called. If |OSThread| creation
  // is disabled after |Worker::StartThread| is called but before
  // |ThreadPool::Worker::Main| is called then a worker will be stuck in the
  // state idle but will never properly start and thus will never transition to
  // dead - leading to a deadlock.
  RELEASE_ASSERT(shutting_down_ || OSThread::CanCreateOSThreads());

  // Prevent scheduling of new tasks.
  shutting_down_ = true;

  if (running_workers_.IsEmpty() && idle_workers_.IsEmpty()) {
    // All workers have already died.
    all_workers_dead_ = true;
  } else {
    // Tell all idling workers to drain remaining work and then shut down.
    for (auto worker : idle_workers_) {
      worker->Wakeup();
    }
  }
}

void ThreadPool::RequestShutdown(
    ThreadPool* pool,
    std::function<void(void)>&& shutdown_complete) {
  pool->RequestWorkersToShutdown();

  {
    MonitorLocker eml(&pool->exit_monitor_);
    if (!pool->all_workers_dead_) {
      // Workers are still doing some work. Mark this pool for asynchronous
      // deletion. When the last worker finishes it will delete itself and
      // call shutdown_complete.
      pool->shutdown_complete_callback_ = std::move(shutdown_complete);
      return;
    }

    // Threads are in the process of exiting already and there is no way to ask
    // them to do additional cleanup asynchronously. We will just join the
    // last dead worker and delete it synchronously.
  }
  pool->DeleteLastDeadWorker();
  shutdown_complete();
}

void ThreadPool::Shutdown() {
  // Should not combine |Shutdown| and |RequestShutdown| on the same pool.
  ASSERT(shutdown_complete_callback_ == nullptr);

  RequestWorkersToShutdown();

  // Wait until all workers are dead. Any new death will notify the exit
  // monitor.
  {
    MonitorLocker eml(&exit_monitor_);
    while (!all_workers_dead_) {
      eml.Wait();
    }
  }

  DeleteLastDeadWorker();
}

void ThreadPool::DeleteLastDeadWorker() {
  ASSERT(all_workers_dead_);
  ASSERT(count_idle_ == 0);
  ASSERT(count_running_ == 0);
  ASSERT(idle_workers_.IsEmpty());
  ASSERT(running_workers_.IsEmpty());
  JoinDeadWorker(last_dead_worker_);
  last_dead_worker_ = nullptr;
}

bool ThreadPool::RunImpl(std::unique_ptr<Task> task) {
  Worker* new_worker = nullptr;
  {
    MutexLocker ml(&pool_mutex_);
    if (shutting_down_) {
      return false;
    }
    new_worker = ScheduleTaskLocked(std::move(task));
  }
  if (new_worker != nullptr) {
    new_worker->StartThread();
  }
  return true;
}

bool ThreadPool::CurrentThreadIsWorker() {
  auto worker =
      static_cast<Worker*>(OSThread::Current()->owning_thread_pool_worker_);
  return worker != nullptr && worker->pool_ == this;
}

void ThreadPool::MarkCurrentWorkerAsBlocked() {
  MarkWorkerAsBlocked(OSThread::Current());
}

void ThreadPool::MarkWorkerAsBlocked(OSThread* thread) {
  auto worker = static_cast<Worker*>(thread->owning_thread_pool_worker_);
  Worker* new_worker = nullptr;
  if (worker != nullptr) {
    MutexLocker ml(&pool_mutex_);
    ASSERT(!worker->is_blocked_);
    worker->is_blocked_ = true;
    if (max_pool_size_ > 0) {
      ++max_pool_size_;
      // This thread is blocked and therefore no longer usable as a worker.
      // If we have pending tasks and there are no idle workers, we will spawn a
      // new thread (temporarily allow exceeding the maximum pool size) to
      // handle the pending tasks.
      if (pending_tasks_ > count_idle_) {
        new_worker = new Worker(this);
        idle_workers_.Append(new_worker);
        count_idle_++;
      }
    }
  }
  if (new_worker != nullptr) {
    new_worker->StartThread();
  }
}

void ThreadPool::MarkCurrentWorkerAsUnBlocked() {
  auto worker =
      static_cast<Worker*>(OSThread::Current()->owning_thread_pool_worker_);
  if (worker != nullptr) {
    MutexLocker ml(&pool_mutex_);
    ASSERT(worker->is_blocked_);
    if (worker->is_blocked_) {
      worker->is_blocked_ = false;
      if (max_pool_size_ > 0) {
        --max_pool_size_;
        ASSERT(max_pool_size_ > 0);
      }
    }
  }
}

std::unique_ptr<ThreadPool::Task> ThreadPool::TakeNextAvailableTaskLocked() {
  std::unique_ptr<Task> task(tasks_.RemoveFirst());
  pending_tasks_--;
  if (pending_tasks_ > 0 && !idle_workers_.IsEmpty()) {
    // Wake up one more worker if more tasks are left.
    idle_workers_.Last()->Wakeup();
  }
  return task;
}

void ThreadPool::WorkerLoop(Worker* worker) {
  Worker* previous_dead_worker = nullptr;

  while (true) {
    MutexLocker ml(&pool_mutex_);

    if (!tasks_.IsEmpty()) {
      IdleToRunningLocked(worker);
      while (!tasks_.IsEmpty()) {
        auto task = TakeNextAvailableTaskLocked();
        MutexUnlocker mls(&ml);
        task->Run();
        ASSERT(Isolate::Current() == nullptr);
        task.reset();  // Delete the task while unlocked.
      }
      RunningToIdleLocked(worker);
    }

    if (running_workers_.IsEmpty()) {
      ASSERT(tasks_.IsEmpty());
      OnEnterIdleLocked(&ml, worker);
      if (!tasks_.IsEmpty()) {
        continue;
      }
    }

    if (shutting_down_) {
      previous_dead_worker = IdleToDeadLocked(worker);
      break;
    }

    // Sleep until we get a new task, we time out or we're shutdown.
    const int64_t idle_start = OS::GetCurrentMonotonicMicros();
    bool done = false;
    while (!done) {
      const auto result = worker->Sleep(ComputeTimeout(idle_start));

      // We have to drain all pending tasks.
      if (!tasks_.IsEmpty()) break;

      if (shutting_down_ || result == ConditionVariable::kTimedOut) {
        done = true;
        break;
      }
    }
    if (done) {
      previous_dead_worker = IdleToDeadLocked(worker);
      break;
    }
  }

  // |IdleToDeadLocked| obtained the worker which died before us, which we will
  // join here. Since every dead worker will join the previous one, all dead
  // workers effectively form a chain and it is enough to join the worker which
  // died last to join all workers which died before it.
  JoinDeadWorker(previous_dead_worker);
}

void ThreadPool::IdleToRunningLocked(Worker* worker) {
  ASSERT(idle_workers_.ContainsForDebugging(worker));
  idle_workers_.Remove(worker);
  running_workers_.Append(worker);
  count_idle_--;
  count_running_++;
}

void ThreadPool::RunningToIdleLocked(Worker* worker) {
  ASSERT(tasks_.IsEmpty());

  ASSERT(running_workers_.ContainsForDebugging(worker));
  running_workers_.Remove(worker);
  idle_workers_.Append(worker);
  count_running_--;
  count_idle_++;
}

ThreadPool::Worker* ThreadPool::IdleToDeadLocked(Worker* worker) {
  ASSERT(tasks_.IsEmpty());
  Worker* previous_dead = last_dead_worker_;

  ASSERT(idle_workers_.ContainsForDebugging(worker));
  idle_workers_.Remove(worker);
  last_dead_worker_ = worker;
  count_idle_--;

  // Notify shutdown thread that the worker thread is about to finish.
  if (shutting_down_) {
    if (running_workers_.IsEmpty() && idle_workers_.IsEmpty()) {
      all_workers_dead_ = true;
      MonitorLocker eml(&exit_monitor_);
      eml.Notify();
    }
  }

  return previous_dead;
}

void ThreadPool::JoinDeadWorker(Worker* worker) {
  if (worker != nullptr) {
    OSThread::Join(worker->join_id_);
    delete worker;
  }
}

ThreadPool::Worker* ThreadPool::ScheduleTaskLocked(std::unique_ptr<Task> task) {
  // Enqueue the new task.
  tasks_.Append(task.release());
  pending_tasks_++;
  ASSERT(pending_tasks_ >= 1);

  // Notify existing idle worker (if available).
  if (count_idle_ >= pending_tasks_) {
    ASSERT(!idle_workers_.IsEmpty());
    // We always notify only the last worker which became idle. It will wake up
    // more workers if needed.
    idle_workers_.Last()->Wakeup();
    return nullptr;
  }

  // If we have maxed out the number of threads running, we will not start a
  // new one.
  if (max_pool_size_ > 0 && (count_idle_ + count_running_) >= max_pool_size_) {
    if (!idle_workers_.IsEmpty()) {
      // We always notify only the last worker which became idle. It will
      // wake up more workers if needed.
      idle_workers_.Last()->Wakeup();
    }
    return nullptr;
  }

  // Otherwise start a new worker.
  auto new_worker = new Worker(this);
  idle_workers_.Append(new_worker);
  count_idle_++;
  return new_worker;
}

ThreadPool::Worker::Worker(ThreadPool* pool)
    : pool_(pool), join_id_(OSThread::kInvalidThreadJoinId) {}

void ThreadPool::Worker::StartThread() {
  OSThread::Start("DartWorker", &Worker::Main, reinterpret_cast<uword>(this));
}

void ThreadPool::Worker::Main(uword args) {
  // Call the thread start hook here to notify the embedder that the
  // thread pool thread has started.
  Dart_ThreadStartCallback start_cb = Dart::thread_start_callback();
  if (start_cb != nullptr) {
    start_cb();
  }

  OSThread* os_thread = OSThread::Current();
  ASSERT(os_thread != nullptr);

  Worker* worker = reinterpret_cast<Worker*>(args);
  ThreadPool* pool = worker->pool_;

  os_thread->owning_thread_pool_worker_ = worker;
  worker->os_thread_ = os_thread;

  // Once the worker quits it needs to be joined.
  worker->join_id_ = OSThread::GetCurrentThreadJoinId(os_thread);

#if defined(DEBUG)
  {
    MutexLocker ml(&pool->pool_mutex_);
    ASSERT(pool->idle_workers_.ContainsForDebugging(worker));
  }
#endif

  pool->WorkerLoop(worker);

  worker->os_thread_ = nullptr;
  os_thread->owning_thread_pool_worker_ = nullptr;

  // Call the thread exit hook here to notify the embedder that the
  // thread pool thread is exiting.
  Dart_ThreadExitCallback exit_cb = Dart::thread_exit_callback();
  if (exit_cb != nullptr) {
    exit_cb();
  }

  ThreadPool::WorkerThreadExit(pool, worker);
}

void ThreadPool::WorkerThreadExit(ThreadPool* pool, Worker* worker) {
  if (pool->shutdown_complete_callback_ != nullptr && pool->all_workers_dead_ &&
      pool->last_dead_worker_ == worker) {
    // Asynchronous shutdown was requested and this is the last exiting worker.
    // It needs to delete itself and notify the code which requested the
    // shutdown that we are done.
    // Start by detaching the thread (because nobody is going to join it) so
    // that we don't keep any thread related data structures behind.
    OSThread::Detach(worker->join_id_);
    delete worker;
    pool->last_dead_worker_ = nullptr;

    // Run the callback. It might (and most likely will) delete |pool| so this
    // should be the last time we touch the |pool| pointer.
    auto callback = pool->shutdown_complete_callback_;
    pool->shutdown_complete_callback_ = nullptr;
    callback();
  }
}

}  // namespace dart
