/* * Copyright (c) 2002-2017 "Neo Technology," * Network Engine for Objects in Lund AB [http://neotechnology.com] * * This file is part of Neo4j. * * 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. */ package org.neo4j.driver.internal.util; import org.hamcrest.Description; import org.hamcrest.Matcher; import org.hamcrest.TypeSafeMatcher; import java.util.concurrent.PriorityBlockingQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLongFieldUpdater; import java.util.concurrent.locks.LockSupport; import org.neo4j.driver.internal.EventHandler; import static java.util.concurrent.atomic.AtomicLongFieldUpdater.newUpdater; import static org.hamcrest.Matchers.any; import static org.hamcrest.Matchers.equalTo; public class FakeClock implements Clock { public interface EventSink { EventSink VOID = new Adapter(); void sleep( long timestamp, long millis ); void progress( long millis ); class Adapter implements EventSink { @Override public void sleep( long timestamp, long millis ) { } @Override public void progress( long millis ) { } } } private final EventSink events; @SuppressWarnings( "unused"/*assigned through AtomicLongFieldUpdater*/ ) private volatile long timestamp; private static final AtomicLongFieldUpdater<FakeClock> TIMESTAMP = newUpdater( FakeClock.class, "timestamp" ); private PriorityBlockingQueue<WaitingThread> threads; public FakeClock() { this( (EventHandler) null, false ); } public FakeClock( final EventHandler events, boolean progressOnSleep ) { this( events == null ? null : new EventSink() { @Override public void sleep( long timestamp, long duration ) { events.add( new Event.Sleep( Thread.currentThread(), timestamp, duration ) ); } @Override public void progress( long timestamp ) { events.add( new Event.Progress( Thread.currentThread(), timestamp ) ); } }, progressOnSleep ); } public FakeClock( EventSink events, boolean progressOnSleep ) { this.events = events == null ? EventSink.VOID : events; this.threads = progressOnSleep ? null : new PriorityBlockingQueue<WaitingThread>(); } @Override public long millis() { return timestamp; } @Override public void sleep( long millis ) { if ( millis <= 0 ) { return; } long target = timestamp + millis; events.sleep( target - millis, millis ); if ( threads == null ) { progress( millis ); } else { // park until the target time has been reached WaitingThread token = new WaitingThread( Thread.currentThread(), target ); threads.add( token ); for ( ; ; ) { if ( timestamp >= target ) { threads.remove( token ); return; } // park with a timeout to guarantee that we make progress even if something goes wrong LockSupport.parkNanos( this, TimeUnit.MILLISECONDS.toNanos( millis ) ); } } } public void progress( long millis ) { if ( millis < 0 ) { throw new IllegalArgumentException( "time can only progress forwards" ); } events.progress( TIMESTAMP.addAndGet( this, millis ) ); if ( threads != null ) { // wake up the threads that are sleeping awaiting the current time for ( WaitingThread thread; (thread = threads.peek()) != null; ) { if ( thread.timestamp < timestamp ) { threads.remove( thread ); LockSupport.unpark( thread.thread ); } } } } public static abstract class Event extends org.neo4j.driver.internal.Event<EventSink> { final Thread thread; private Event( Thread thread ) { this.thread = thread; } public static Matcher<? extends Event> sleep( long duration ) { return sleep( any( Thread.class ), any( Long.class ), equalTo( duration ) ); } public static Matcher<? extends Event> sleep( final Matcher<Thread> thread, final Matcher<Long> timestamp, final Matcher<Long> duration ) { return new TypeSafeMatcher<Sleep>() { @Override public void describeTo( Description description ) { description.appendText( "Sleep Event on thread <" ) .appendDescriptionOf( thread ) .appendText( "> at timestamp " ) .appendDescriptionOf( timestamp ) .appendText( " for duration " ) .appendDescriptionOf( timestamp ) .appendText( " (in milliseconds)" ); } @Override protected boolean matchesSafely( Sleep event ) { return thread.matches( event.thread ) && timestamp.matches( event.timestamp ) && duration.matches( event.duration ); } }; } public static Matcher<? extends Event> progress( final Matcher<Thread> thread, final Matcher<Long> timestamp ) { return new TypeSafeMatcher<Progress>() { @Override public void describeTo( Description description ) { description.appendText( "Time progresses to timestamp " ) .appendDescriptionOf( timestamp ) .appendText( " by thread <" ) .appendDescriptionOf( thread ) .appendText( ">" ); } @Override protected boolean matchesSafely( Progress event ) { return thread.matches( event.thread ) && timestamp.matches( event.timestamp ); } }; } private static class Sleep extends Event { private final long timestamp, duration; Sleep( Thread thread, long timestamp, long duration ) { super( thread ); this.timestamp = timestamp; this.duration = duration; } @Override public void dispatch( EventSink sink ) { sink.sleep( timestamp, duration ); } } private static class Progress extends Event { private final long timestamp; Progress( Thread thread, long timestamp ) { super( thread ); this.timestamp = timestamp; } @Override public void dispatch( EventSink sink ) { sink.progress( timestamp ); } } } private static class WaitingThread { final Thread thread; final long timestamp; private WaitingThread( Thread thread, long timestamp ) { this.thread = thread; this.timestamp = timestamp; } } }