001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.reef.wake.time.runtime;
020
021import org.apache.reef.tang.InjectionFuture;
022import org.apache.reef.tang.annotations.Parameter;
023import org.apache.reef.wake.EventHandler;
024import org.apache.reef.wake.impl.PubSubEventHandler;
025import org.apache.reef.wake.time.Clock;
026import org.apache.reef.wake.time.Time;
027import org.apache.reef.wake.time.event.Alarm;
028import org.apache.reef.wake.time.event.StartTime;
029import org.apache.reef.wake.time.event.StopTime;
030import org.apache.reef.wake.time.runtime.event.*;
031
032import javax.inject.Inject;
033import java.util.Set;
034import java.util.TreeSet;
035import java.util.logging.Level;
036import java.util.logging.Logger;
037
038public final class RuntimeClock implements Clock {
039
040  private static final Logger LOG = Logger.getLogger(Clock.class.toString());
041
042  private final Timer timer;
043
044  private final TreeSet<Time> schedule;
045
046  private final PubSubEventHandler<Time> handlers;
047
048  private final InjectionFuture<Set<EventHandler<StartTime>>> startHandler;
049  private final InjectionFuture<Set<EventHandler<StopTime>>> stopHandler;
050  private final InjectionFuture<Set<EventHandler<RuntimeStart>>> runtimeStartHandler;
051  private final InjectionFuture<Set<EventHandler<RuntimeStop>>> runtimeStopHandler;
052  private final InjectionFuture<Set<EventHandler<IdleClock>>> idleHandler;
053
054  private Throwable stoppedOnException;
055  private boolean closed = false;
056
057  @Inject
058  RuntimeClock(final Timer timer,
059               @Parameter(Clock.StartHandler.class) final InjectionFuture<Set<EventHandler<StartTime>>> startHandler,
060               @Parameter(StopHandler.class) final InjectionFuture<Set<EventHandler<StopTime>>> stopHandler,
061               @Parameter(Clock.RuntimeStartHandler.class)
062               final InjectionFuture<Set<EventHandler<RuntimeStart>>> runtimeStartHandler,
063               @Parameter(Clock.RuntimeStopHandler.class)
064               final InjectionFuture<Set<EventHandler<RuntimeStop>>> runtimeStopHandler,
065               @Parameter(IdleHandler.class) final InjectionFuture<Set<EventHandler<IdleClock>>> idleHandler) {
066    this.timer = timer;
067    this.schedule = new TreeSet<>();
068    this.handlers = new PubSubEventHandler<>();
069
070    this.startHandler = startHandler;
071    this.stopHandler = stopHandler;
072    this.runtimeStartHandler = runtimeStartHandler;
073    this.runtimeStopHandler = runtimeStopHandler;
074    this.idleHandler = idleHandler;
075
076    this.stoppedOnException = null;
077
078    LOG.log(Level.FINE, "RuntimeClock instantiated.");
079  }
080
081  @Override
082  public void scheduleAlarm(final int offset, final EventHandler<Alarm> handler) {
083    synchronized (this.schedule) {
084      if (this.closed) {
085        throw new IllegalStateException("Scheduling alarm on a closed clock");
086      }
087
088      this.schedule.add(new ClientAlarm(this.timer.getCurrent() + offset, handler));
089      this.schedule.notifyAll();
090    }
091  }
092
093  public void registerEventHandler(final Class<? extends Time> clazz, final EventHandler<Time> handler) {
094    this.handlers.subscribe(clazz, handler);
095  }
096
097  public void scheduleRuntimeAlarm(final int offset, final EventHandler<Alarm> handler) {
098    synchronized (this.schedule) {
099      this.schedule.add(new RuntimeAlarm(this.timer.getCurrent() + offset, handler));
100      this.schedule.notifyAll();
101    }
102  }
103
104  @Override
105  public void stop() {
106    this.stop(null);
107  }
108
109  @Override
110  public void stop(final Throwable stopOnException) {
111    LOG.entering(RuntimeClock.class.getCanonicalName(), "stop");
112    synchronized (this.schedule) {
113      this.schedule.clear();
114      this.schedule.add(new StopTime(timer.getCurrent()));
115      this.schedule.notifyAll();
116      this.closed = true;
117      if (this.stoppedOnException != null) {
118        this.stoppedOnException = stopOnException;
119      }
120    }
121    LOG.exiting(RuntimeClock.class.getCanonicalName(), "stop");
122  }
123
124  @Override
125  public void close() {
126    LOG.entering(RuntimeClock.class.getCanonicalName(), "close");
127    synchronized (this.schedule) {
128      if (this.closed) {
129        LOG.log(Level.INFO, "Clock is already closed");
130        return;
131      }
132      this.schedule.clear();
133      this.schedule.add(new StopTime(findAcceptableStopTime()));
134      this.schedule.notifyAll();
135      this.closed = true;
136      LOG.log(Level.INFO, "Clock.close()");
137    }
138    LOG.exiting(RuntimeClock.class.getCanonicalName(), "close");
139  }
140
141  /**
142   * Finds an acceptable stop time, which is the
143   * a time beyond that of any client alarm.
144   *
145   * @return an acceptable stop time
146   */
147  private long findAcceptableStopTime() {
148    long time = timer.getCurrent();
149    for (final Time t : this.schedule) {
150      if (t instanceof ClientAlarm) {
151        assert (time <= t.getTimeStamp());
152        time = t.getTimeStamp();
153      }
154    }
155    return time + 1;
156  }
157
158
159  @Override
160  public boolean isIdle() {
161    synchronized (this.schedule) {
162      for (final Time t : this.schedule) {
163        if (t instanceof ClientAlarm) {
164          return false;
165        }
166      }
167      return true;
168    }
169  }
170
171  @SuppressWarnings("checkstyle:hiddenfield")
172  private <T extends Time> void subscribe(final Class<T> eventClass, final Set<EventHandler<T>> handlers) {
173    for (final EventHandler<T> handler : handlers) {
174      this.handlers.subscribe(eventClass, handler);
175    }
176  }
177
178  /**
179   * Logs the currently running threads.
180   *
181   * @param level  the level used for the log entry
182   * @param prefix put before the comma-separated list of threads
183   */
184  private void logThreads(final Level level, final String prefix) {
185    final StringBuilder sb = new StringBuilder(prefix);
186    for (final Thread t : Thread.getAllStackTraces().keySet()) {
187      sb.append(t.getName());
188      sb.append(", ");
189    }
190    LOG.log(level, sb.toString());
191  }
192
193  @Override
194  public void run() {
195    LOG.entering(RuntimeClock.class.getCanonicalName(), "run");
196
197    try {
198      LOG.log(Level.FINE, "Subscribe event handlers");
199      subscribe(StartTime.class, this.startHandler.get());
200      subscribe(StopTime.class, this.stopHandler.get());
201      subscribe(RuntimeStart.class, this.runtimeStartHandler.get());
202      subscribe(RuntimeStop.class, this.runtimeStopHandler.get());
203      subscribe(IdleClock.class, this.idleHandler.get());
204
205      LOG.log(Level.FINE, "Initiate runtime start");
206      this.handlers.onNext(new RuntimeStart(this.timer.getCurrent()));
207
208      LOG.log(Level.FINE, "Initiate start time");
209      final StartTime start = new StartTime(this.timer.getCurrent());
210      this.handlers.onNext(start);
211
212      while (true) {
213        LOG.log(Level.FINEST, "Entering clock main loop iteration.");
214        try {
215          if (this.isIdle()) {
216            // Handle an idle clock event, without locking this.schedule
217            this.handlers.onNext(new IdleClock(timer.getCurrent()));
218          }
219
220          Time time = null;
221          synchronized (this.schedule) {
222            while (this.schedule.isEmpty()) {
223              this.schedule.wait();
224            }
225
226            assert (this.schedule.first() != null);
227
228            // Wait until the first scheduled time is ready
229            for (long duration = this.timer.getDuration(this.schedule.first().getTimeStamp());
230                 duration > 0;
231                 duration = this.timer.getDuration(this.schedule.first().getTimeStamp())) {
232              // note: while I'm waiting, another alarm could be scheduled with a shorter duration
233              // so the next time I go around the loop I need to revise my duration
234              this.schedule.wait(duration);
235            }
236            // Remove the event from the schedule and process it:
237            time = this.schedule.pollFirst();
238            assert (time != null);
239          }
240
241          if (time instanceof Alarm) {
242            final Alarm alarm = (Alarm) time;
243            alarm.handle();
244          } else {
245            this.handlers.onNext(time);
246            if (time instanceof StopTime) {
247              break; // we're done.
248            }
249          }
250        } catch (final InterruptedException e) {
251          // waiting interrupted - return to loop
252        }
253      }
254      if (this.stoppedOnException == null) {
255        this.handlers.onNext(new RuntimeStop(this.timer.getCurrent()));
256      } else {
257        this.handlers.onNext(new RuntimeStop(this.timer.getCurrent(), this.stoppedOnException));
258      }
259    } catch (final Exception e) {
260      e.printStackTrace();
261      this.handlers.onNext(new RuntimeStop(this.timer.getCurrent(), e));
262    } finally {
263      logThreads(Level.FINE, "Threads running after exiting the clock main loop: ");
264      LOG.log(Level.FINE, "Runtime clock exit");
265    }
266    LOG.exiting(RuntimeClock.class.getCanonicalName(), "run");
267
268  }
269
270
271}