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.impl;
020
021import org.apache.reef.tang.annotations.Parameter;
022import org.apache.reef.wake.AbstractEStage;
023import org.apache.reef.wake.EventHandler;
024import org.apache.reef.wake.StageConfiguration.*;
025import org.apache.reef.wake.WakeParameters;
026import org.apache.reef.wake.exception.WakeRuntimeException;
027
028import javax.inject.Inject;
029import java.util.List;
030import java.util.concurrent.ExecutorService;
031import java.util.concurrent.Executors;
032import java.util.concurrent.ThreadPoolExecutor;
033import java.util.concurrent.TimeUnit;
034import java.util.logging.Level;
035import java.util.logging.Logger;
036
037/**
038 * Stage that executes an event handler with a thread pool
039 *
040 * @param <T> type
041 */
042public final class ThreadPoolStage<T> extends AbstractEStage<T> {
043  private static final Logger LOG = Logger.getLogger(ThreadPoolStage.class.getName());
044
045  private final EventHandler<T> handler;
046  private final ExecutorService executor;
047  private final int numThreads;
048  private final long shutdownTimeout = WakeParameters.EXECUTOR_SHUTDOWN_TIMEOUT;
049  private final EventHandler<Throwable> errorHandler;
050
051  /**
052   * Constructs a thread-pool stage
053   *
054   * @param handler    the event handler to execute
055   * @param numThreads the number of threads to use
056   * @throws WakeRuntimeException
057   */
058  @Inject
059  public ThreadPoolStage(final @Parameter(StageHandler.class) EventHandler<T> handler,
060                         final @Parameter(NumberOfThreads.class) int numThreads) {
061    this(handler.getClass().getName(), handler, numThreads, null);
062  }
063
064  /**
065   * Constructs a thread-pool stage
066   *
067   * @param name         the stage name
068   * @param handler      the event handler to execute
069   * @param numThreads   the number of threads to use
070   * @param errorHandler the error handler
071   * @throws WakeRuntimeException
072   */
073  @Inject
074  public ThreadPoolStage(final @Parameter(StageName.class) String name,
075                         final @Parameter(StageHandler.class) EventHandler<T> handler,
076                         final @Parameter(NumberOfThreads.class) int numThreads,
077                         final @Parameter(ErrorHandler.class) EventHandler<Throwable> errorHandler) {
078    super(name);
079    this.handler = handler;
080    this.errorHandler = errorHandler;
081    if (numThreads <= 0)
082      throw new WakeRuntimeException(name + " numThreads " + numThreads + " is less than or equal to 0");
083    this.numThreads = numThreads;
084    this.executor = Executors.newFixedThreadPool(numThreads, new DefaultThreadFactory(name));
085    StageManager.instance().register(this);
086  }
087
088  /**
089   * Constructs a thread-pool stage
090   *
091   * @param name       the stage name
092   * @param handler    the event handler to execute
093   * @param numThreads the number of threads to use
094   * @throws WakeRuntimeException
095   */
096  @Inject
097  public ThreadPoolStage(final @Parameter(StageName.class) String name,
098                         final @Parameter(StageHandler.class) EventHandler<T> handler,
099                         final @Parameter(NumberOfThreads.class) int numThreads) {
100    this(name, handler, numThreads, null);
101  }
102
103  /**
104   * Constructs a thread-pool stage
105   *
106   * @param handler  the event handler to execute
107   * @param executor the external executor service provided
108   */
109  @Inject
110  public ThreadPoolStage(final @Parameter(StageHandler.class) EventHandler<T> handler,
111                         final @Parameter(StageExecutorService.class) ExecutorService executor) {
112    this(handler.getClass().getName(), handler, executor);
113  }
114
115
116  /**
117   * Constructs a thread-pool stage
118   *
119   * @param handler      the event handler to execute
120   * @param executor     the external executor service provided
121   * @param errorHandler the error handler
122   */
123  @Inject
124  public ThreadPoolStage(final @Parameter(StageHandler.class) EventHandler<T> handler,
125                         final @Parameter(StageExecutorService.class) ExecutorService executor,
126                         final @Parameter(ErrorHandler.class) EventHandler<Throwable> errorHandler) {
127    this(handler.getClass().getName(), handler, executor, errorHandler);
128  }
129
130  /**
131   * Constructs a thread-pool stage
132   *
133   * @param name     the stage name
134   * @param handler  the event handler to execute
135   * @param executor the external executor service provided
136   *                 for consistent tracking, it is recommended to create executor with {@link DefaultThreadFactory}
137   */
138  @Inject
139  public ThreadPoolStage(final @Parameter(StageName.class) String name,
140                         final @Parameter(StageHandler.class) EventHandler<T> handler,
141                         final @Parameter(StageExecutorService.class) ExecutorService executor) {
142    this(name, handler, executor, null);
143  }
144
145  /**
146   * Constructs a thread-pool stage
147   *
148   * @param name         the stage name
149   * @param handler      the event handler to execute
150   * @param executor     the external executor service provided
151   *                     for consistent tracking, it is recommended to create executor with {@link DefaultThreadFactory}
152   * @param errorHandler the error handler
153   */
154  @Inject
155  public ThreadPoolStage(final @Parameter(StageName.class) String name,
156                         final @Parameter(StageHandler.class) EventHandler<T> handler,
157                         final @Parameter(StageExecutorService.class) ExecutorService executor,
158                         final @Parameter(ErrorHandler.class) EventHandler<Throwable> errorHandler) {
159    super(name);
160    this.handler = handler;
161    this.errorHandler = errorHandler;
162    this.numThreads = 0;
163    this.executor = executor;
164    StageManager.instance().register(this);
165  }
166
167  /**
168   * Handles the event using a thread in the thread pool
169   *
170   * @param value the event
171   */
172  @Override
173  public void onNext(final T value) {
174    beforeOnNext();
175    executor.submit(new Runnable() {
176
177      @Override
178      public void run() {
179        try {
180          handler.onNext(value);
181          afterOnNext();
182        } catch (Throwable t) {
183          if (errorHandler != null) {
184            errorHandler.onNext(t);
185          } else {
186            LOG.log(Level.SEVERE, name + " Exception from event handler", t);
187            throw t;
188          }
189        }
190      }
191
192    });
193  }
194
195  /**
196   * Closes resources
197   *
198   * @return Exception
199   */
200  @Override
201  public void close() throws Exception {
202    if (closed.compareAndSet(false, true)) {
203      if (numThreads > 0) {
204        executor.shutdown();
205        if (!executor.awaitTermination(shutdownTimeout, TimeUnit.MILLISECONDS)) {
206          LOG.log(Level.WARNING, "Executor did not terminate in " + shutdownTimeout + "ms.");
207          List<Runnable> droppedRunnables = executor.shutdownNow();
208          LOG.log(Level.WARNING, "Executor dropped " + droppedRunnables.size() + " tasks.");
209        }
210      }
211    }
212  }
213
214  /**
215   * Gets the queue length of this stage
216   *
217   * @return the queue length
218   */
219  public int getQueueLength() {
220    return ((ThreadPoolExecutor) executor).getQueue().size();
221  }
222
223}