Implement Simulated Thread#sleep()

Implement Simulated Thread#sleep()



Background



I am designing my software so that I can easily perform unit tests. I have an IClock interface which, among other methods, has
IClock#wait(TimeUnit timeUnit, long duration). This method will halt the current thread for timeUnit duration (i.e. 1 second).


IClock


IClock#wait(TimeUnit timeUnit, long duration)



There are two implementations of the IClock interface:


IClock


SimulatedClock


RealClock


System.currentTimeMillis()



This is the default method for IClock#wait(...):


IClock#wait(...)


/**
* Locks current thread for specified time
*
* @param timeUnit
* @param dt
*/
default void wait(TimeUnit timeUnit, long dt)

Lock lock = new ReentrantLock();
scheduleIn(timeUnit, dt, lock::unlock);
lock.lock();



Problem



The current way I want simulated unit tests to work is


IClock#wait(...)


SimulatedClock



However, what is really happening is:


IClock#wait()



So, what I need to do is to be able to determine when all threads are done or blocked. Although this can be done with Thread#getState(), I would rather employ a solution which is more elegant and works with ForkJoinPool.


Thread#getState()


ForkJoinPool



Full Code



GitHub


package com.team2502.ezauton.utils;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

public class SimulatedClock implements IClock


private long time = 0;

private List<Job> jobs = new ArrayList<>();

public SimulatedClock()

public void init()

init(System.currentTimeMillis());


public void init(long time)

setTime(time);


/**
* Add time in milliseconds
*
* @param dt millisecond increase
* @return The new time
*/
public long addTime(long dt)

setTime(getTime() + dt);
return getTime();


/**
* Adds time with units
*
* @param timeUnit
* @param value
*/
public void addTime(TimeUnit timeUnit, long value)

addTime(timeUnit.toMillis(value));


/**
* Add one millisecond and returns new value
*
* @return The new time
*/
public long incAndGet()

return addTime(1);


/**
* Increment a certain amount of times
*
* @param times
*/
public void incTimes(long times, long dt)

long init = getTime();
long totalDt = times * dt;
for(int i = 0; i < times; i++)

if(!jobs.isEmpty())

addTime(dt);

else

break;


setTime(init + totalDt);


/**
* Increment a certain amount of times
*
* @param times
* @return
*/
public void incTimes(long times)

incTimes(times, 1);


@Override
public long getTime()

return time;


public void setTime(long time)

jobs.removeIf(job ->
if(job.getMillis() < time)

job.getRunnable().run();
return true;

return false;
);

this.time = time;


@Override
public void scheduleAt(long millis, Runnable runnable)

if(millis < getTime())

throw new IllegalArgumentException("You are scheduling a task for before the current time!");

jobs.add(new Job(millis, runnable));


private static class Job

private final long millis;
private final Runnable runnable;

public Job(long millis, Runnable runnable)

this.millis = millis;
this.runnable = runnable;


public long getMillis()

return millis;


public Runnable getRunnable()

return runnable;




package com.team2502.ezauton.command;

import com.team2502.ezauton.utils.SimulatedClock;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;

public class Simulation


private final SimulatedClock simulatedClock;
private List<IAction> actions = new ArrayList<>();

public Simulation()

simulatedClock = new SimulatedClock();


public SimulatedClock getSimulatedClock()

return simulatedClock;


public Simulation add(IAction action)

actions.add(action);
return this;


/**
* @param timeoutMillis Max millis
*/
public void run(long timeoutMillis)

simulatedClock.init();

actions.forEach(action -> new ThreadBuilder(action, simulatedClock).buildAndRun());

simulatedClock.incTimes(timeoutMillis);

// Need to wait until all threads are finished
if(!ForkJoinPool.commonPool().awaitQuiescence(1, TimeUnit.SECONDS))

throw new RuntimeException("Simulator did not finish in a second.");




public void run(TimeUnit timeUnit, long value)

run(timeUnit.toMillis(value));



@Test
public void testSimpleAction()

AtomicBoolean atomicBoolean = new AtomicBoolean(false);
Simulation simulation = new Simulation();
simulation.add(new DealyedAction((TimeUnit.SECONDS, 5) -> atomicBoolean.set(true)));
simulation.run(TimeUnit.SECONDS, 100);
Assert.assertTrue(atomicBoolean.get());



The question is widely applicable to a large audience. A detailed canonical answer is required to address all the concerns.



Provide a working, simulateable Thread.sleep() implementation.





But why you can't use Thread.sleep? Why you need to simulate it here?
– GotoFinal
Aug 20 at 20:44





I want the unit tests to be run as fast as possible. For example, I might want to simulate an action which would actually run for 100 seconds in the real world but could be run in a matter of milliseconds when simulated.
– Andrew Gazelka
2 days ago





Why do you create Threads manually and manage the scheduling yourself, rather than using the existing ScheduledExecutor infrastructure? And to be clear, you want to make sure that the Runnables under test are finished by n milliseconds, right?
– daniu
yesterday



ScheduledExecutor


Runnable


n





Your use of ReentrantLock() in IClock#wait(...) makes absolutely no sense because each calling thread would get a separate instance of the lock.
– tbsalling
yesterday




2 Answers
2



That looks like the various threads are not getting going in time, before simulatedClock.incTimes() is called.


simulatedClock.incTimes()



Often with a multi-threaded test, there is some kind of 'rendezvous' at the start - to allow all of the threads to check-in once they are safely up and running. If you know how many threads there are going to be up front, a CountDownLatch makes this easy.


CountDownLatch



E.g. in Simulation.run():


Simulation.run()


simulatedClock.init(new CountDownLatch(actions.size()));



Which keeps a reference to the CountDownLatch for later.


CountDownLatch



When each thread arrives in SimulatedClock.scheduleAt(), it can count the latch down by one:


SimulatedClock.scheduleAt()


@Override
public void scheduleAt(long millis, Runnable runnable)

if(millis < getTime())

throw new IllegalArgumentException("You are scheduling a task for before the current time!");

jobs.add(new Job(millis, runnable));

countDownLatch.countDown();



Then incTimes() can wait for all the threads to show up:


incTimes()


public void incTimes(long times, long dt)
{
countDownLatch.await();

long init = getTime();
...



I know this doesn't exactly answer your question, but you shouldn't create Threads manually and use the existing concurrency framework.


Thread



You can just do this:


public static void main(String args)
AtomicBoolean bool = new AtomicBoolean(false);
ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
ScheduledFuture<?> future = executorService.schedule(() -> bool.set(true), 5, TimeUnit.SECONDS);
try
boolean b = future.get(100, TimeUnit.SECONDS);
catch (Exception e)
fail();

assertTrue(b);



You can also integrate it into your framework if your scheduleAt returns the Future.


scheduleAt


Future


interface Clock
Future<?> scheduleAt(long millis, Runnable r);


class SchedulerService implements Clock
ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();

public Future<?> scheduleAt(long millis, Runnable r)
Instant scheduleTime = Instant.ofEpochMilli(millis);
Instant now = Instant.now();
if (scheduleTime.isBefore(now))
throw new IllegalArgumentException("You are scheduling a task for before the current time!");

long delay = scheduleTime.minus(now).toEpochMilli();
return executorService.schedule(r, delay, TimeUnit.MILLISECONDS);







By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Popular posts from this blog

ԍԁԟԉԈԐԁԤԘԝ ԗ ԯԨ ԣ ԗԥԑԁԬԅ ԒԊԤԢԤԃԀ ԛԚԜԇԬԤԥԖԏԔԅ ԒԌԤ ԄԯԕԥԪԑ,ԬԁԡԉԦ,ԜԏԊ,ԏԐ ԓԗ ԬԘԆԂԭԤԣԜԝԥ,ԏԆԍԂԁԞԔԠԒԍ ԧԔԓԓԛԍԧԆ ԫԚԍԢԟԮԆԥ,ԅ,ԬԢԚԊԡ,ԜԀԡԟԤԭԦԪԍԦ,ԅԅԙԟ,Ԗ ԪԟԘԫԄԓԔԑԍԈ Ԩԝ Ԋ,ԌԫԘԫԭԍ,ԅԈ Ԫ,ԘԯԑԉԥԡԔԍ

How to change the default border color of fbox? [duplicate]

Henj