/*
 * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 *
 */

#include "gc/g1/g1ServiceThread.hpp"
#include "runtime/interfaceSupport.inline.hpp"
#include "runtime/os.hpp"
#include "utilities/autoRestore.hpp"
#include "unittest.hpp"

class CheckTask : public G1ServiceTask {
  int _execution_count;
  bool _reschedule;

public:
  CheckTask(const char* name) :
      G1ServiceTask(name),
      _execution_count(0),
      _reschedule(true) { }
  virtual void execute() {
    _execution_count++;
    if (_reschedule) {
      schedule(100);
    }
  }

  int execution_count() { return _execution_count;}
  void set_reschedule(bool reschedule) { _reschedule = reschedule; }
};

static void stop_service_thread(G1ServiceThread* thread) {
  ThreadInVMfromNative tvn(JavaThread::current());
  thread->stop();
}

// Test that a task that is added during runtime gets run.
TEST_VM(G1ServiceThread, test_add) {
  // Create thread and let it start.
  G1ServiceThread* st = new G1ServiceThread();
  os::naked_short_sleep(500);

  CheckTask ct("AddAndRun");
  st->register_task(&ct);

  // Give CheckTask time to run.
  os::naked_short_sleep(500);
  stop_service_thread(st);

  ASSERT_GT(ct.execution_count(), 0);
}

// Test that a task that is added while the service thread is
// waiting gets run in a timely manner.
TEST_VM(G1ServiceThread, test_add_while_waiting) {
  // Make sure default tasks use long intervals so that the service thread
  // is doing a long wait for the next execution.
  AutoModifyRestore<uintx> f1(G1PeriodicGCInterval, 100000);

  // Create thread and let it start.
  G1ServiceThread* st = new G1ServiceThread();
  os::naked_short_sleep(500);

  // Register a new task that should run right away.
  CheckTask ct("AddWhileWaiting");
  st->register_task(&ct);

  // Give CheckTask time to run.
  os::naked_short_sleep(500);
  stop_service_thread(st);

  ASSERT_GT(ct.execution_count(), 0);
}

// Test that a task with negative timeout is not rescheduled.
TEST_VM(G1ServiceThread, test_add_run_once) {
  // Create thread and let it start.
  G1ServiceThread* st = new G1ServiceThread();
  os::naked_short_sleep(500);

  // Set reschedule to false to only run once.
  CheckTask ct("AddRunOnce");
  ct.set_reschedule(false);
  st->register_task(&ct);

  // Give CheckTask time to run.
  os::naked_short_sleep(500);
  stop_service_thread(st);

  // Should be exactly 1 since negative timeout should
  // prevent rescheduling.
  ASSERT_EQ(ct.execution_count(), 1);
}

class TestTask : public G1ServiceTask {
  jlong _delay_ms;
public:
  TestTask(jlong delay) :
      G1ServiceTask("TestTask"),
      _delay_ms(delay) {
    set_time(delay);
  }
  virtual void execute() {}
  void update_time(jlong now, int multiplier) {
    set_time(now + (_delay_ms * multiplier));
  }
};

TEST_VM(G1ServiceTaskQueue, add_ordered) {
  G1ServiceTaskQueue queue;

  int num_test_tasks = 5;
  for (int i = 1; i <= num_test_tasks; i++) {
    // Create tasks with different timeout.
    TestTask* task = new TestTask(100 * i);
    queue.add_ordered(task);
  }

  // Now fake a run-loop, that reschedules the tasks using a
  // random multiplier.
  for (jlong now = 0; now < 1000000; now++) {
    // Random multiplier is at least 1 to ensure progress.
    int multiplier = 1 + os::random() % 10;
    while (queue.front()->time() < now) {
      TestTask* task = (TestTask*) queue.front();
      queue.remove_front();
      // Update delay multiplier.
      task->execute();
      task->update_time(now, multiplier);
      // All additions will verify that the queue is sorted.
      queue.add_ordered(task);
    }
  }

  while (!queue.is_empty()) {
    G1ServiceTask* task = queue.front();
    queue.remove_front();
    delete task;
  }
}

#ifdef ASSERT
TEST_VM_ASSERT_MSG(G1ServiceTaskQueue, remove_from_empty,
    ".*Should never try to verify empty queue") {
  G1ServiceTaskQueue queue;
  queue.remove_front();
}

TEST_VM_ASSERT_MSG(G1ServiceTaskQueue, get_from_empty,
    ".*Should never try to verify empty queue") {
  G1ServiceTaskQueue queue;
  queue.front();
}

TEST_VM_ASSERT_MSG(G1ServiceTaskQueue, set_time_in_queue,
    ".*Not allowed to update time while in queue") {
  G1ServiceTaskQueue queue;
  TestTask a(100);
  queue.add_ordered(&a);
  // Not allowed to update time while in queue.
  a.update_time(500, 1);
}

#endif
