//
//
// Copyright 2015 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//

#include <grpc/support/alloc.h>
#include <grpc/support/cpu.h>
#include <grpc/support/port_platform.h>
#include <grpc/support/sync.h>
#include <inttypes.h>

#include <string>

#include "absl/log/check.h"
#include "absl/log/log.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "src/core/lib/debug/trace.h"
#include "src/core/lib/iomgr/exec_ctx.h"
#include "src/core/lib/iomgr/port.h"
#include "src/core/lib/iomgr/timer.h"
#include "src/core/lib/iomgr/timer_heap.h"
#include "src/core/util/crash.h"
#include "src/core/util/manual_constructor.h"
#include "src/core/util/spinlock.h"
#include "src/core/util/time.h"
#include "src/core/util/time_averaged_stats.h"
#include "src/core/util/useful.h"

#define INVALID_HEAP_INDEX 0xffffffffu

#define ADD_DEADLINE_SCALE 0.33
#define MIN_QUEUE_WINDOW_DURATION 0.01
#define MAX_QUEUE_WINDOW_DURATION 1.0

// A "timer shard". Contains a 'heap' and a 'list' of timers. All timers with
// deadlines earlier than 'queue_deadline_cap' are maintained in the heap and
// others are maintained in the list (unordered). This helps to keep the number
// of elements in the heap low.
//
// The 'queue_deadline_cap' gets recomputed periodically based on the timer
// stats maintained in 'stats' and the relevant timers are then moved from the
// 'list' to 'heap'.
//
struct timer_shard {
  gpr_mu mu;
  grpc_core::ManualConstructor<grpc_core::TimeAveragedStats> stats;
  // All and only timers with deadlines < this will be in the heap.
  grpc_core::Timestamp queue_deadline_cap;
  // The deadline of the next timer due in this shard.
  grpc_core::Timestamp min_deadline;
  // Index of this timer_shard in the g_shard_queue.
  uint32_t shard_queue_index;
  // This holds all timers with deadlines < queue_deadline_cap. Timers in this
  // list have the top bit of their deadline set to 0.
  grpc_timer_heap heap;
  // This holds timers whose deadline is >= queue_deadline_cap.
  grpc_timer list;
};
static size_t g_num_shards;

// Array of timer shards. Whenever a timer (grpc_timer *) is added, its address
// is hashed to select the timer shard to add the timer to
static timer_shard* g_shards;

// Maintains a sorted list of timer shards (sorted by their min_deadline, i.e
// the deadline of the next timer in each shard).
// Access to this is protected by g_shared_mutables.mu
static timer_shard** g_shard_queue;

#ifndef NDEBUG

// == DEBUG ONLY: hash table for duplicate timer detection ==

#define NUM_HASH_BUCKETS 1009  // Prime number close to 1000

static gpr_mu g_hash_mu[NUM_HASH_BUCKETS];  // One mutex per bucket
static grpc_timer* g_timer_ht[NUM_HASH_BUCKETS] = {nullptr};

static void init_timer_ht() {
  for (int i = 0; i < NUM_HASH_BUCKETS; i++) {
    gpr_mu_init(&g_hash_mu[i]);
  }
}

static void destroy_timer_ht() {
  for (int i = 0; i < NUM_HASH_BUCKETS; i++) {
    gpr_mu_destroy(&g_hash_mu[i]);
  }
}

static bool is_in_ht(grpc_timer* t) {
  size_t i = grpc_core::HashPointer(t, NUM_HASH_BUCKETS);

  gpr_mu_lock(&g_hash_mu[i]);
  grpc_timer* p = g_timer_ht[i];
  while (p != nullptr && p != t) {
    p = p->hash_table_next;
  }
  gpr_mu_unlock(&g_hash_mu[i]);

  return (p == t);
}

static void add_to_ht(grpc_timer* t) {
  CHECK(!t->hash_table_next);
  size_t i = grpc_core::HashPointer(t, NUM_HASH_BUCKETS);

  gpr_mu_lock(&g_hash_mu[i]);
  grpc_timer* p = g_timer_ht[i];
  while (p != nullptr && p != t) {
    p = p->hash_table_next;
  }

  if (p == t) {
    grpc_closure* c = t->closure;
    grpc_core::Crash(absl::StrFormat(
        "** Duplicate timer (%p) being added. Closure: (%p), created at: "
        "(%s:%d), scheduled at: (%s:%d) **",
        t, c, c->file_created, c->line_created, c->file_initiated,
        c->line_initiated));
  }

  // Timer not present in the bucket. Insert at head of the list
  t->hash_table_next = g_timer_ht[i];
  g_timer_ht[i] = t;
  gpr_mu_unlock(&g_hash_mu[i]);
}

static void remove_from_ht(grpc_timer* t) {
  size_t i = grpc_core::HashPointer(t, NUM_HASH_BUCKETS);
  bool removed = false;

  gpr_mu_lock(&g_hash_mu[i]);
  if (g_timer_ht[i] == t) {
    g_timer_ht[i] = g_timer_ht[i]->hash_table_next;
    removed = true;
  } else if (g_timer_ht[i] != nullptr) {
    grpc_timer* p = g_timer_ht[i];
    while (p->hash_table_next != nullptr && p->hash_table_next != t) {
      p = p->hash_table_next;
    }

    if (p->hash_table_next == t) {
      p->hash_table_next = t->hash_table_next;
      removed = true;
    }
  }
  gpr_mu_unlock(&g_hash_mu[i]);

  if (!removed) {
    grpc_closure* c = t->closure;
    grpc_core::Crash(absl::StrFormat(
        "** Removing timer (%p) that is not added to hash table. Closure "
        "(%p), created at: (%s:%d), scheduled at: (%s:%d) **",
        t, c, c->file_created, c->line_created, c->file_initiated,
        c->line_initiated));
  }

  t->hash_table_next = nullptr;
}

// If a timer is added to a timer shard (either heap or a list), it must
// be pending. A timer is added to hash table only-if it is added to the
// timer shard.
// Therefore, if timer->pending is false, it cannot be in hash table
static void validate_non_pending_timer(grpc_timer* t) {
  if (!t->pending && is_in_ht(t)) {
    grpc_closure* c = t->closure;
    grpc_core::Crash(absl::StrFormat(
        "** gpr_timer_cancel() called on a non-pending timer (%p) which "
        "is in the hash table. Closure: (%p), created at: (%s:%d), "
        "scheduled at: (%s:%d) **",
        t, c, c->file_created, c->line_created, c->file_initiated,
        c->line_initiated));
  }
}

#define INIT_TIMER_HASH_TABLE() init_timer_ht()
#define DESTROY_TIMER_HASH_TABLE() destroy_timer_ht()
#define ADD_TO_HASH_TABLE(t) add_to_ht((t))
#define REMOVE_FROM_HASH_TABLE(t) remove_from_ht((t))
#define VALIDATE_NON_PENDING_TIMER(t) validate_non_pending_timer((t))

#else

#define INIT_TIMER_HASH_TABLE()
#define DESTROY_TIMER_HASH_TABLE()
#define ADD_TO_HASH_TABLE(t)
#define REMOVE_FROM_HASH_TABLE(t)
#define VALIDATE_NON_PENDING_TIMER(t)

#endif

// Thread local variable that stores the deadline of the next timer the thread
// has last-seen. This is an optimization to prevent the thread from checking
// shared_mutables.min_timer (which requires acquiring shared_mutables.mu lock,
// an expensive operation)
static thread_local int64_t g_last_seen_min_timer;

struct shared_mutables {
  // The deadline of the next timer due across all timer shards
  grpc_core::Timestamp min_timer;
  // Allow only one run_some_expired_timers at once
  gpr_spinlock checker_mu;
  bool initialized;
  // Protects g_shard_queue (and the shared_mutables struct itself)
  gpr_mu mu;
} GPR_ALIGN_STRUCT(GPR_CACHELINE_SIZE);

static struct shared_mutables g_shared_mutables;

static grpc_timer_check_result run_some_expired_timers(
    grpc_core::Timestamp now, grpc_core::Timestamp* next,
    grpc_error_handle error);

static grpc_core::Timestamp compute_min_deadline(timer_shard* shard) {
  return grpc_timer_heap_is_empty(&shard->heap)
             ? shard->queue_deadline_cap + grpc_core::Duration::Epsilon()
             : grpc_core::Timestamp::FromMillisecondsAfterProcessEpoch(
                   grpc_timer_heap_top(&shard->heap)->deadline);
}

static void timer_list_init() {
  uint32_t i;

  g_num_shards = grpc_core::Clamp(2 * gpr_cpu_num_cores(), 1u, 32u);
  g_shards =
      static_cast<timer_shard*>(gpr_zalloc(g_num_shards * sizeof(*g_shards)));
  g_shard_queue = static_cast<timer_shard**>(
      gpr_zalloc(g_num_shards * sizeof(*g_shard_queue)));

  g_shared_mutables.initialized = true;
  g_shared_mutables.checker_mu = GPR_SPINLOCK_INITIALIZER;
  gpr_mu_init(&g_shared_mutables.mu);
  g_shared_mutables.min_timer = grpc_core::Timestamp::Now();

  g_last_seen_min_timer = 0;

  for (i = 0; i < g_num_shards; i++) {
    timer_shard* shard = &g_shards[i];
    gpr_mu_init(&shard->mu);
    shard->stats.Init(1.0 / ADD_DEADLINE_SCALE, 0.1, 0.5);
    shard->queue_deadline_cap = g_shared_mutables.min_timer;
    shard->shard_queue_index = i;
    grpc_timer_heap_init(&shard->heap);
    shard->list.next = shard->list.prev = &shard->list;
    shard->min_deadline = compute_min_deadline(shard);
    g_shard_queue[i] = shard;
  }

  INIT_TIMER_HASH_TABLE();
}

static void timer_list_shutdown() {
  size_t i;
  run_some_expired_timers(grpc_core::Timestamp::InfFuture(), nullptr,
                          GRPC_ERROR_CREATE("Timer list shutdown"));
  for (i = 0; i < g_num_shards; i++) {
    timer_shard* shard = &g_shards[i];
    gpr_mu_destroy(&shard->mu);
    grpc_timer_heap_destroy(&shard->heap);
  }
  gpr_mu_destroy(&g_shared_mutables.mu);
  gpr_free(g_shards);
  gpr_free(g_shard_queue);
  g_shared_mutables.initialized = false;

  DESTROY_TIMER_HASH_TABLE();
}

// returns true if the first element in the list
static void list_join(grpc_timer* head, grpc_timer* timer) {
  timer->next = head;
  timer->prev = head->prev;
  timer->next->prev = timer->prev->next = timer;
}

static void list_remove(grpc_timer* timer) {
  timer->next->prev = timer->prev;
  timer->prev->next = timer->next;
}

static void swap_adjacent_shards_in_queue(uint32_t first_shard_queue_index) {
  timer_shard* temp;
  temp = g_shard_queue[first_shard_queue_index];
  g_shard_queue[first_shard_queue_index] =
      g_shard_queue[first_shard_queue_index + 1];
  g_shard_queue[first_shard_queue_index + 1] = temp;
  g_shard_queue[first_shard_queue_index]->shard_queue_index =
      first_shard_queue_index;
  g_shard_queue[first_shard_queue_index + 1]->shard_queue_index =
      first_shard_queue_index + 1;
}

static void note_deadline_change(timer_shard* shard) {
  while (shard->shard_queue_index > 0 &&
         shard->min_deadline <
             g_shard_queue[shard->shard_queue_index - 1]->min_deadline) {
    swap_adjacent_shards_in_queue(shard->shard_queue_index - 1);
  }
  while (shard->shard_queue_index < g_num_shards - 1 &&
         shard->min_deadline >
             g_shard_queue[shard->shard_queue_index + 1]->min_deadline) {
    swap_adjacent_shards_in_queue(shard->shard_queue_index);
  }
}

void grpc_timer_init_unset(grpc_timer* timer) { timer->pending = false; }

static void timer_init(grpc_timer* timer, grpc_core::Timestamp deadline,
                       grpc_closure* closure) {
  int is_first_timer = 0;
  timer_shard* shard = &g_shards[grpc_core::HashPointer(timer, g_num_shards)];
  timer->closure = closure;
  timer->deadline = deadline.milliseconds_after_process_epoch();

#ifndef NDEBUG
  timer->hash_table_next = nullptr;
#endif

  GRPC_TRACE_VLOG(timer, 2)
      << "TIMER " << timer << ": SET "
      << deadline.milliseconds_after_process_epoch() << " now "
      << grpc_core::Timestamp::Now().milliseconds_after_process_epoch()
      << " call " << closure << "[" << closure->cb << "]";

  if (!g_shared_mutables.initialized) {
    timer->pending = false;
    grpc_core::ExecCtx::Run(
        DEBUG_LOCATION, timer->closure,
        GRPC_ERROR_CREATE("Attempt to create timer before initialization"));
    return;
  }

  gpr_mu_lock(&shard->mu);
  timer->pending = true;
  grpc_core::Timestamp now = grpc_core::Timestamp::Now();
  if (deadline <= now) {
    timer->pending = false;
    grpc_core::ExecCtx::Run(DEBUG_LOCATION, timer->closure, absl::OkStatus());
    gpr_mu_unlock(&shard->mu);
    // early out
    return;
  }

  shard->stats->AddSample((deadline - now).millis() / 1000.0);

  ADD_TO_HASH_TABLE(timer);

  if (deadline < shard->queue_deadline_cap) {
    is_first_timer = grpc_timer_heap_add(&shard->heap, timer);
  } else {
    timer->heap_index = INVALID_HEAP_INDEX;
    list_join(&shard->list, timer);
  }
  GRPC_TRACE_VLOG(timer, 2)
      << "  .. add to shard " << (shard - g_shards)
      << " with queue_deadline_cap="
      << shard->queue_deadline_cap.milliseconds_after_process_epoch()
      << " => is_first_timer=" << (is_first_timer ? "true" : "false");
  gpr_mu_unlock(&shard->mu);

  // Deadline may have decreased, we need to adjust the main queue.  Note
  // that there is a potential racy unlocked region here.  There could be a
  // reordering of multiple grpc_timer_init calls, at this point, but the < test
  // below should ensure that we err on the side of caution.  There could
  // also be a race with grpc_timer_check, which might beat us to the lock.  In
  // that case, it is possible that the timer that we added will have already
  // run by the time we hold the lock, but that too is a safe error.
  // Finally, it's possible that the grpc_timer_check that intervened failed to
  // trigger the new timer because the min_deadline hadn't yet been reduced.
  // In that case, the timer will simply have to wait for the next
  // grpc_timer_check.
  if (is_first_timer) {
    gpr_mu_lock(&g_shared_mutables.mu);
    GRPC_TRACE_VLOG(timer, 2)
        << "  .. old shard min_deadline="
        << shard->min_deadline.milliseconds_after_process_epoch();
    if (deadline < shard->min_deadline) {
      grpc_core::Timestamp old_min_deadline = g_shard_queue[0]->min_deadline;
      shard->min_deadline = deadline;
      note_deadline_change(shard);
      if (shard->shard_queue_index == 0 && deadline < old_min_deadline) {
#if GPR_ARCH_64
        // TODO(sreek): Using c-style cast here. static_cast<> gives an error
        // (on mac platforms complaining that gpr_atm* is (long *) while
        // (&g_shared_mutables.min_timer) is a (long long *). The cast should be
        // safe since we know that both are pointer types and 64-bit wide.
        gpr_atm_no_barrier_store((gpr_atm*)(&g_shared_mutables.min_timer),
                                 deadline.milliseconds_after_process_epoch());
#else
        // On 32-bit systems, gpr_atm_no_barrier_store does not work on 64-bit
        // types (like grpc_core::Timestamp). So all reads and writes to
        // g_shared_mutables.min_timer variable under g_shared_mutables.mu
        g_shared_mutables.min_timer = deadline;
#endif
        grpc_kick_poller();
      }
    }
    gpr_mu_unlock(&g_shared_mutables.mu);
  }
}

static void timer_consume_kick(void) {
  // Force re-evaluation of last seen min
  g_last_seen_min_timer = 0;
}

static void timer_cancel(grpc_timer* timer) {
  if (!g_shared_mutables.initialized) {
    // must have already been cancelled, also the shard mutex is invalid
    return;
  }

  timer_shard* shard = &g_shards[grpc_core::HashPointer(timer, g_num_shards)];
  gpr_mu_lock(&shard->mu);
  GRPC_TRACE_VLOG(timer, 2)
      << "TIMER " << timer
      << ": CANCEL pending=" << (timer->pending ? "true" : "false");

  if (timer->pending) {
    REMOVE_FROM_HASH_TABLE(timer);

    grpc_core::ExecCtx::Run(DEBUG_LOCATION, timer->closure,
                            absl::CancelledError());
    timer->pending = false;
    if (timer->heap_index == INVALID_HEAP_INDEX) {
      list_remove(timer);
    } else {
      grpc_timer_heap_remove(&shard->heap, timer);
    }
  } else {
    VALIDATE_NON_PENDING_TIMER(timer);
  }
  gpr_mu_unlock(&shard->mu);
}

// Rebalances the timer shard by computing a new 'queue_deadline_cap' and moving
// all relevant timers in shard->list (i.e timers with deadlines earlier than
// 'queue_deadline_cap') into into shard->heap.
// Returns 'true' if shard->heap has at least ONE element
// REQUIRES: shard->mu locked
static bool refill_heap(timer_shard* shard, grpc_core::Timestamp now) {
  // Compute the new queue window width and bound by the limits:
  double computed_deadline_delta =
      shard->stats->UpdateAverage() * ADD_DEADLINE_SCALE;
  double deadline_delta =
      grpc_core::Clamp(computed_deadline_delta, MIN_QUEUE_WINDOW_DURATION,
                       MAX_QUEUE_WINDOW_DURATION);
  grpc_timer *timer, *next;

  // Compute the new cap and put all timers under it into the queue:
  shard->queue_deadline_cap =
      std::max(now, shard->queue_deadline_cap) +
      grpc_core::Duration::FromSecondsAsDouble(deadline_delta);

  GRPC_TRACE_VLOG(timer_check, 2)
      << "  .. shard[" << (shard - g_shards) << "]->queue_deadline_cap --> "
      << shard->queue_deadline_cap.milliseconds_after_process_epoch();
  for (timer = shard->list.next; timer != &shard->list; timer = next) {
    next = timer->next;
    auto timer_deadline =
        grpc_core::Timestamp::FromMillisecondsAfterProcessEpoch(
            timer->deadline);

    if (timer_deadline < shard->queue_deadline_cap) {
      GRPC_TRACE_VLOG(timer_check, 2)
          << "  .. add timer with deadline "
          << timer_deadline.milliseconds_after_process_epoch() << " to heap";
      list_remove(timer);
      grpc_timer_heap_add(&shard->heap, timer);
    }
  }
  return !grpc_timer_heap_is_empty(&shard->heap);
}

// This pops the next non-cancelled timer with deadline <= now from the
// queue, or returns NULL if there isn't one.
// REQUIRES: shard->mu locked
static grpc_timer* pop_one(timer_shard* shard, grpc_core::Timestamp now) {
  grpc_timer* timer;
  for (;;) {
    GRPC_TRACE_VLOG(timer_check, 2)
        << "  .. shard[" << (shard - g_shards) << "]: heap_empty="
        << (grpc_timer_heap_is_empty(&shard->heap) ? "true" : "false");
    if (grpc_timer_heap_is_empty(&shard->heap)) {
      if (now < shard->queue_deadline_cap) return nullptr;
      if (!refill_heap(shard, now)) return nullptr;
    }
    timer = grpc_timer_heap_top(&shard->heap);
    auto timer_deadline =
        grpc_core::Timestamp::FromMillisecondsAfterProcessEpoch(
            timer->deadline);
    GRPC_TRACE_VLOG(timer_check, 2)
        << "  .. check top timer deadline="
        << timer_deadline.milliseconds_after_process_epoch()
        << " now=" << now.milliseconds_after_process_epoch();
    if (timer_deadline > now) return nullptr;
    GRPC_TRACE_VLOG(timer, 2) << "TIMER " << timer << ": FIRE "
                              << (now - timer_deadline).millis() << "ms late";
    timer->pending = false;
    grpc_timer_heap_pop(&shard->heap);
    return timer;
  }
}

// REQUIRES: shard->mu unlocked
static size_t pop_timers(timer_shard* shard, grpc_core::Timestamp now,
                         grpc_core::Timestamp* new_min_deadline,
                         grpc_error_handle error) {
  size_t n = 0;
  grpc_timer* timer;
  gpr_mu_lock(&shard->mu);
  while ((timer = pop_one(shard, now))) {
    REMOVE_FROM_HASH_TABLE(timer);
    grpc_core::ExecCtx::Run(DEBUG_LOCATION, timer->closure, error);
    n++;
  }
  *new_min_deadline = compute_min_deadline(shard);
  gpr_mu_unlock(&shard->mu);
  GRPC_TRACE_VLOG(timer_check, 2)
      << "  .. shard[" << (shard - g_shards) << "] popped " << n;
  return n;
}

static grpc_timer_check_result run_some_expired_timers(
    grpc_core::Timestamp now, grpc_core::Timestamp* next,
    grpc_error_handle error) {
  grpc_timer_check_result result = GRPC_TIMERS_NOT_CHECKED;

#if GPR_ARCH_64
  // TODO(sreek): Using c-style cast here. static_cast<> gives an error (on
  // mac platforms complaining that gpr_atm* is (long *) while
  // (&g_shared_mutables.min_timer) is a (long long *). The cast should be
  // safe since we know that both are pointer types and 64-bit wide
  grpc_core::Timestamp min_timer =
      grpc_core::Timestamp::FromMillisecondsAfterProcessEpoch(
          gpr_atm_no_barrier_load((gpr_atm*)(&g_shared_mutables.min_timer)));
#else
  // On 32-bit systems, gpr_atm_no_barrier_load does not work on 64-bit types
  // (like grpc_core::Timestamp). So all reads and writes to
  // g_shared_mutables.min_timer are done under g_shared_mutables.mu
  gpr_mu_lock(&g_shared_mutables.mu);
  grpc_core::Timestamp min_timer = g_shared_mutables.min_timer;
  gpr_mu_unlock(&g_shared_mutables.mu);
#endif
  g_last_seen_min_timer = min_timer.milliseconds_after_process_epoch();

  if (now < min_timer) {
    if (next != nullptr) *next = std::min(*next, min_timer);
    return GRPC_TIMERS_CHECKED_AND_EMPTY;
  }

  if (gpr_spinlock_trylock(&g_shared_mutables.checker_mu)) {
    gpr_mu_lock(&g_shared_mutables.mu);
    result = GRPC_TIMERS_CHECKED_AND_EMPTY;

    GRPC_TRACE_VLOG(timer_check, 2)
        << "  .. shard[" << (g_shard_queue[0] - g_shards)
        << "]->min_deadline = "
        << g_shard_queue[0]->min_deadline.milliseconds_after_process_epoch();

    while (g_shard_queue[0]->min_deadline < now ||
           (now != grpc_core::Timestamp::InfFuture() &&
            g_shard_queue[0]->min_deadline == now)) {
      grpc_core::Timestamp new_min_deadline;

      // For efficiency, we pop as many available timers as we can from the
      // shard.  This may violate perfect timer deadline ordering, but that
      // shouldn't be a big deal because we don't make ordering guarantees.
      if (pop_timers(g_shard_queue[0], now, &new_min_deadline, error) > 0) {
        result = GRPC_TIMERS_FIRED;
      }

      GRPC_TRACE_VLOG(timer_check, 2)
          << "  .. result --> " << result << ", shard["
          << (g_shard_queue[0] - g_shards) << "]->min_deadline "
          << g_shard_queue[0]->min_deadline.milliseconds_after_process_epoch()
          << " --> " << new_min_deadline.milliseconds_after_process_epoch()
          << ", now=" << now.milliseconds_after_process_epoch();

      // An grpc_timer_init() on the shard could intervene here, adding a new
      // timer that is earlier than new_min_deadline.  However,
      // grpc_timer_init() will block on the mutex before it can call
      // set_min_deadline, so this one will complete first and then the Addtimer
      // will reduce the min_deadline (perhaps unnecessarily).
      g_shard_queue[0]->min_deadline = new_min_deadline;
      note_deadline_change(g_shard_queue[0]);
    }

    if (next) {
      *next = std::min(*next, g_shard_queue[0]->min_deadline);
    }

#if GPR_ARCH_64
    // TODO(sreek): Using c-style cast here. static_cast<> gives an error (on
    // mac platforms complaining that gpr_atm* is (long *) while
    // (&g_shared_mutables.min_timer) is a (long long *). The cast should be
    // safe since we know that both are pointer types and 64-bit wide
    gpr_atm_no_barrier_store(
        (gpr_atm*)(&g_shared_mutables.min_timer),
        g_shard_queue[0]->min_deadline.milliseconds_after_process_epoch());
#else
    // On 32-bit systems, gpr_atm_no_barrier_store does not work on 64-bit
    // types (like grpc_core::Timestamp). So all reads and writes to
    // g_shared_mutables.min_timer are done under g_shared_mutables.mu
    g_shared_mutables.min_timer = g_shard_queue[0]->min_deadline;
#endif
    gpr_mu_unlock(&g_shared_mutables.mu);
    gpr_spinlock_unlock(&g_shared_mutables.checker_mu);
  }

  return result;
}

static grpc_timer_check_result timer_check(grpc_core::Timestamp* next) {
  // prelude
  grpc_core::Timestamp now = grpc_core::Timestamp::Now();

  // fetch from a thread-local first: this avoids contention on a globally
  // mutable cacheline in the common case
  grpc_core::Timestamp min_timer =
      grpc_core::Timestamp::FromMillisecondsAfterProcessEpoch(
          g_last_seen_min_timer);

  if (now < min_timer) {
    if (next != nullptr) {
      *next = std::min(*next, min_timer);
    }
    GRPC_TRACE_VLOG(timer_check, 2)
        << "TIMER CHECK SKIP: now=" << now.milliseconds_after_process_epoch()
        << " min_timer=" << min_timer.milliseconds_after_process_epoch();
    return GRPC_TIMERS_CHECKED_AND_EMPTY;
  }

  grpc_error_handle shutdown_error =
      now != grpc_core::Timestamp::InfFuture()
          ? absl::OkStatus()
          : GRPC_ERROR_CREATE("Shutting down timer system");

  // tracing
  if (GRPC_TRACE_FLAG_ENABLED(timer_check)) {
    std::string next_str;
    if (next == nullptr) {
      next_str = "NULL";
    } else {
      next_str = absl::StrCat(next->milliseconds_after_process_epoch());
    }
#if GPR_ARCH_64
    VLOG(2) << "TIMER CHECK BEGIN: now="
            << now.milliseconds_after_process_epoch() << " next=" << next_str
            << " tls_min=" << min_timer.milliseconds_after_process_epoch()
            << " glob_min="
            << grpc_core::Timestamp::FromMillisecondsAfterProcessEpoch(
                   gpr_atm_no_barrier_load(
                       (gpr_atm*)(&g_shared_mutables.min_timer)))
                   .milliseconds_after_process_epoch();
#else
    VLOG(2) << "TIMER CHECK BEGIN: now="
            << now.milliseconds_after_process_epoch() << " next=" << next_str
            << " min=" << min_timer.milliseconds_after_process_epoch();
#endif
  }
  // actual code
  grpc_timer_check_result r =
      run_some_expired_timers(now, next, shutdown_error);
  // tracing
  if (GRPC_TRACE_FLAG_ENABLED(timer_check)) {
    std::string next_str;
    if (next == nullptr) {
      next_str = "NULL";
    } else {
      next_str = absl::StrCat(next->milliseconds_after_process_epoch());
    }
    VLOG(2) << "TIMER CHECK END: r=" << r << "; next=" << next_str.c_str();
  }
  return r;
}

grpc_timer_vtable grpc_generic_timer_vtable = {
    timer_init,      timer_cancel,        timer_check,
    timer_list_init, timer_list_shutdown, timer_consume_kick};
