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}