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(@Parameter(StageHandler.class) final EventHandler<T> handler,
060                         @Parameter(NumberOfThreads.class) final 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(@Parameter(StageName.class) final String name,
075                         @Parameter(StageHandler.class) final EventHandler<T> handler,
076                         @Parameter(NumberOfThreads.class) final int numThreads,
077                         @Parameter(ErrorHandler.class) final 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    }
084    this.numThreads = numThreads;
085    this.executor = Executors.newFixedThreadPool(numThreads, new DefaultThreadFactory(name));
086    StageManager.instance().register(this);
087  }
088
089  /**
090   * Constructs a thread-pool stage.
091   *
092   * @param name       the stage name
093   * @param handler    the event handler to execute
094   * @param numThreads the number of threads to use
095   * @throws WakeRuntimeException
096   */
097  @Inject
098  public ThreadPoolStage(@Parameter(StageName.class) final String name,
099                         @Parameter(StageHandler.class) final EventHandler<T> handler,
100                         @Parameter(NumberOfThreads.class) final int numThreads) {
101    this(name, handler, numThreads, null);
102  }
103
104  /**
105   * Constructs a thread-pool stage.
106   *
107   * @param handler  the event handler to execute
108   * @param executor the external executor service provided
109   */
110  @Inject
111  public ThreadPoolStage(@Parameter(StageHandler.class) final EventHandler<T> handler,
112                         @Parameter(StageExecutorService.class) final ExecutorService executor) {
113    this(handler.getClass().getName(), handler, executor);
114  }
115
116
117  /**
118   * Constructs a thread-pool stage.
119   *
120   * @param handler      the event handler to execute
121   * @param executor     the external executor service provided
122   * @param errorHandler the error handler
123   */
124  @Inject
125  public ThreadPoolStage(@Parameter(StageHandler.class) final EventHandler<T> handler,
126                         @Parameter(StageExecutorService.class) final ExecutorService executor,
127                         @Parameter(ErrorHandler.class) final EventHandler<Throwable> errorHandler) {
128    this(handler.getClass().getName(), handler, executor, errorHandler);
129  }
130
131  /**
132   * Constructs a thread-pool stage.
133   *
134   * @param name     the stage name
135   * @param handler  the event handler to execute
136   * @param executor the external executor service provided
137   *                 for consistent tracking, it is recommended to create executor with {@link DefaultThreadFactory}
138   */
139  @Inject
140  public ThreadPoolStage(@Parameter(StageName.class) final String name,
141                         @Parameter(StageHandler.class) final EventHandler<T> handler,
142                         @Parameter(StageExecutorService.class) final ExecutorService executor) {
143    this(name, handler, executor, null);
144  }
145
146  /**
147   * Constructs a thread-pool stage.
148   *
149   * @param name         the stage name
150   * @param handler      the event handler to execute
151   * @param executor     the external executor service provided
152   *                     for consistent tracking, it is recommended to create executor with {@link DefaultThreadFactory}
153   * @param errorHandler the error handler
154   */
155  @Inject
156  public ThreadPoolStage(@Parameter(StageName.class) final String name,
157                         @Parameter(StageHandler.class) final EventHandler<T> handler,
158                         @Parameter(StageExecutorService.class) final ExecutorService executor,
159                         @Parameter(ErrorHandler.class) final EventHandler<Throwable> errorHandler) {
160    super(name);
161    this.handler = handler;
162    this.errorHandler = errorHandler;
163    this.numThreads = 0;
164    this.executor = executor;
165    StageManager.instance().register(this);
166  }
167
168  /**
169   * Handles the event using a thread in the thread pool.
170   *
171   * @param value the event
172   */
173  @Override
174  public void onNext(final T value) {
175    beforeOnNext();
176    executor.submit(new Runnable() {
177
178      @Override
179      public void run() {
180        try {
181          handler.onNext(value);
182          afterOnNext();
183        } catch (final Throwable t) {
184          if (errorHandler != null) {
185            errorHandler.onNext(t);
186          } else {
187            LOG.log(Level.SEVERE, name + " Exception from event handler", t);
188            throw t;
189          }
190        }
191      }
192
193    });
194  }
195
196  /**
197   * Closes resources.
198   *
199   * @return Exception
200   */
201  @Override
202  public void close() throws Exception {
203    if (closed.compareAndSet(false, true)) {
204      if (numThreads > 0) {
205        executor.shutdown();
206        if (!executor.awaitTermination(shutdownTimeout, TimeUnit.MILLISECONDS)) {
207          LOG.log(Level.WARNING, "Executor did not terminate in " + shutdownTimeout + "ms.");
208          final List<Runnable> droppedRunnables = executor.shutdownNow();
209          LOG.log(Level.WARNING, "Executor dropped " + droppedRunnables.size() + " tasks.");
210        }
211      }
212    }
213  }
214
215  /**
216   * Gets the queue length of this stage.
217   *
218   * @return the queue length
219   */
220  public int getQueueLength() {
221    return ((ThreadPoolExecutor) executor).getQueue().size();
222  }
223
224}