org.neo4j.test.FakeClockJobScheduler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of test-utils Show documentation
Show all versions of test-utils Show documentation
A set of utilities used by tests.
/*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [https://neo4j.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
package org.neo4j.test;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Stream;
import org.neo4j.scheduler.ActiveGroup;
import org.neo4j.scheduler.CallableExecutor;
import org.neo4j.scheduler.FailedJobRun;
import org.neo4j.scheduler.Group;
import org.neo4j.scheduler.JobHandle;
import org.neo4j.scheduler.JobMonitoringParams;
import org.neo4j.scheduler.JobScheduler;
import org.neo4j.scheduler.MonitoredJobExecutor;
import org.neo4j.scheduler.MonitoredJobInfo;
import org.neo4j.scheduler.SchedulerThreadFactoryFactory;
import org.neo4j.time.FakeClock;
/**
* N.B - Do not use this with time resolutions of less than 1 ms!
*/
public class FakeClockJobScheduler extends FakeClock implements JobScheduler {
private final AtomicLong jobIdGen = new AtomicLong();
private final Collection jobs = new CopyOnWriteArrayList<>();
public FakeClockJobScheduler() {
super();
}
private JobTrigger schedule(Callable job, long firstDeadline) {
JobTrigger jobTrigger = new JobTrigger<>(job, firstDeadline, 0);
jobs.add(jobTrigger);
return jobTrigger;
}
private JobTrigger schedule(Runnable job, long firstDeadline) {
JobTrigger jobTrigger = new JobTrigger(job, firstDeadline, 0);
jobs.add(jobTrigger);
return jobTrigger;
}
private JobTrigger scheduleRecurring(Runnable job, long firstDeadline, long period) {
JobTrigger jobTrigger = new JobTrigger(job, firstDeadline, period);
jobs.add(jobTrigger);
return jobTrigger;
}
@Override
public FakeClock forward(long delta, TimeUnit unit) {
super.forward(delta, unit);
processSchedule();
return this;
}
private void processSchedule() {
boolean anyTriggered;
do {
anyTriggered = false;
for (JobTrigger job : jobs) {
if (job.tryTrigger()) {
anyTriggered = true;
}
}
} while (anyTriggered);
}
private long now() {
return instant().toEpochMilli();
}
@Override
public void setTopLevelGroupName(String name) {}
@Override
public void setParallelism(Group group, int parallelism) {}
@Override
public void setThreadFactory(Group group, SchedulerThreadFactoryFactory threadFactory) {}
@Override
public CallableExecutor executor(Group group) {
return new FakeClockExecutor();
}
@Override
public MonitoredJobExecutor monitoredJobExecutor(Group group) {
var executor = new FakeClockExecutor();
return (monitoringParams, command) -> executor.execute(command);
}
@Override
public ThreadFactory threadFactory(Group group) {
throw new UnsupportedOperationException();
}
@Override
public JobHandle schedule(Group group, JobMonitoringParams jobMonitoringParams, Callable job) {
JobTrigger handle = schedule(job, now());
processSchedule();
return handle;
}
@Override
public JobHandle> schedule(Group group, Runnable job) {
JobTrigger handle = schedule(job, now());
processSchedule();
return handle;
}
@Override
public JobHandle> schedule(Group group, JobMonitoringParams monitoredJobParams, Runnable job) {
return schedule(group, job);
}
@Override
public JobHandle> schedule(Group group, Runnable job, long initialDelay, TimeUnit timeUnit) {
JobTrigger handle = schedule(job, now() + timeUnit.toMillis(initialDelay));
if (initialDelay <= 0) {
processSchedule();
}
return handle;
}
@Override
public JobHandle> schedule(
Group group, JobMonitoringParams monitoredJobParams, Runnable job, long initialDelay, TimeUnit timeUnit) {
return schedule(group, job, initialDelay, timeUnit);
}
@Override
public JobHandle> scheduleRecurring(Group group, Runnable job, long period, TimeUnit timeUnit) {
JobTrigger handle = scheduleRecurring(job, now(), timeUnit.toMillis(period));
processSchedule();
return handle;
}
@Override
public JobHandle> scheduleRecurring(
Group group, JobMonitoringParams monitoredJobParams, Runnable job, long period, TimeUnit timeUnit) {
return scheduleRecurring(group, job, period, timeUnit);
}
@Override
public JobHandle> scheduleRecurring(
Group group, Runnable job, long initialDelay, long period, TimeUnit timeUnit) {
JobTrigger handle = scheduleRecurring(job, now() + timeUnit.toMillis(initialDelay), timeUnit.toMillis(period));
if (initialDelay <= 0) {
processSchedule();
}
return handle;
}
@Override
public JobHandle> scheduleRecurring(
Group group,
JobMonitoringParams monitoredJobParams,
Runnable job,
long initialDelay,
long period,
TimeUnit timeUnit) {
return scheduleRecurring(group, job, initialDelay, period, timeUnit);
}
@Override
public Stream activeGroups() {
return Stream.empty();
}
@Override
public List getMonitoredJobs() {
return List.of();
}
@Override
public List getFailedJobRuns() {
return List.of();
}
@Override
public void init() {
throw new UnsupportedOperationException();
}
@Override
public void start() {
throw new UnsupportedOperationException();
}
@Override
public void stop() {
throw new UnsupportedOperationException();
}
@Override
public void shutdown() {
throw new UnsupportedOperationException();
}
@Override
public void close() {
shutdown();
}
class JobTrigger implements JobHandle, Future {
private final long id = jobIdGen.incrementAndGet();
private final Runnable runnable;
private final Callable callable;
private final long period;
private long deadline;
JobTrigger(Callable callable, long firstDeadline, long period) {
this.runnable = null;
this.callable = callable;
this.deadline = firstDeadline;
this.period = period;
}
JobTrigger(Runnable runnable, long firstDeadline, long period) {
this.runnable = runnable;
this.callable = null;
this.deadline = firstDeadline;
this.period = period;
}
boolean tryTrigger() {
if (now() >= deadline) {
if (runnable != null) {
runnable.run();
}
if (callable != null) {
try {
callable.call();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
if (period != 0) {
deadline += period;
} else {
jobs.remove(this);
}
return true;
}
return false;
}
@Override
public void cancel() {
cancel(false);
}
@Override
public void waitTermination() {
throw new UnsupportedOperationException();
}
@Override
public void waitTermination(long timeout, TimeUnit unit) {
throw new UnsupportedOperationException();
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
return jobs.remove(this);
}
@Override
public boolean isCancelled() {
return !jobs.contains(this);
}
@Override
public boolean isDone() {
throw new UnsupportedOperationException();
}
@Override
public T get() {
throw new UnsupportedOperationException();
}
@Override
public T get(long timeout, TimeUnit unit) {
throw new UnsupportedOperationException();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
JobTrigger jobTrigger = (JobTrigger) o;
return id == jobTrigger.id;
}
@Override
public int hashCode() {
return Objects.hash(id);
}
}
private class FakeClockExecutor implements CallableExecutor {
@Override
public Future submit(Callable callable) {
return schedule(callable, now());
}
@Override
public void execute(Runnable command) {
schedule(command, now());
}
}
}