/* * Copyright © 2014 Cask Data, Inc. * * 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 co.cask.cdap.internal.app.runtime.flow; import co.cask.cdap.api.flow.flowlet.Callback; import co.cask.cdap.api.flow.flowlet.Flowlet; import co.cask.cdap.common.lang.ClassLoaders; import co.cask.cdap.common.lang.CombineClassLoader; import co.cask.cdap.common.logging.LoggingContextAccessor; import co.cask.cdap.internal.app.runtime.DataFabricFacade; import co.cask.tephra.TransactionExecutor; import co.cask.tephra.TransactionFailureException; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; import com.google.common.util.concurrent.AbstractIdleService; import com.google.common.util.concurrent.Service; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Collection; /** * This class represents lifecycle of a {@link Flowlet}, Start, Stop, Suspend and Resume. */ final class FlowletRuntimeService extends AbstractIdleService { private static final Logger LOG = LoggerFactory.getLogger(FlowletRuntimeService.class); private final Flowlet flowlet; private final BasicFlowletContext flowletContext; private final Collection<? extends ProcessSpecification<?>> processSpecs; private final Callback txCallback; private final DataFabricFacade dataFabricFacade; private final Service serviceHook; private FlowletProcessDriver flowletProcessDriver; FlowletRuntimeService(Flowlet flowlet, BasicFlowletContext flowletContext, Collection<? extends ProcessSpecification<?>> processSpecs, Callback txCallback, DataFabricFacade dataFabricFacade, Service serviceHook) { this.flowlet = flowlet; this.flowletContext = flowletContext; this.processSpecs = processSpecs; this.txCallback = txCallback; this.dataFabricFacade = dataFabricFacade; this.serviceHook = serviceHook; } @Override protected void startUp() throws Exception { LoggingContextAccessor.setLoggingContext(flowletContext.getLoggingContext()); flowletContext.getProgramMetrics().increment("process.instance", 1); flowletProcessDriver = new FlowletProcessDriver(flowletContext, dataFabricFacade, txCallback, processSpecs); serviceHook.startAndWait(); initFlowlet(); flowletProcessDriver.startAndWait(); } @Override protected void shutDown() throws Exception { LoggingContextAccessor.setLoggingContext(flowletContext.getLoggingContext()); if (flowletProcessDriver != null) { stopService(flowletProcessDriver); } destroyFlowlet(); stopService(serviceHook); } /** * Suspend the running of flowlet. This method will block until the flowlet process is stopped. * This method only get called from FlowletProgramController, and controller handled state transition and * make sure thread safety. */ void suspend() { flowletProcessDriver.stopAndWait(); // After a FlowletProcessDriver stopped, it cannot be started again // Hence copying all states to a new instance and start it again on resuming. flowletProcessDriver = new FlowletProcessDriver(flowletProcessDriver); } /** * Resume the running of flowlet. * This method only get called from FlowletProgramController, and controller handled state transition and * make sure thread safety. */ void resume() { flowletProcessDriver.startAndWait(); } private void initFlowlet() throws InterruptedException { try { dataFabricFacade.createTransactionExecutor().execute(new TransactionExecutor.Subroutine() { @Override public void apply() throws Exception { LOG.info("Initializing flowlet: " + flowletContext); ClassLoader classLoader = setContextCombinedClassLoader(); try { flowlet.initialize(flowletContext); } finally { ClassLoaders.setContextClassLoader(classLoader); } LOG.info("Flowlet initialized: " + flowletContext); } }); } catch (TransactionFailureException e) { Throwable cause = e.getCause() == null ? e : e.getCause(); LOG.error("Flowlet throws exception during flowlet initialize: " + flowletContext, cause); throw Throwables.propagate(cause); } } private void destroyFlowlet() { try { dataFabricFacade.createTransactionExecutor().execute(new TransactionExecutor.Subroutine() { @Override public void apply() throws Exception { LOG.info("Destroying flowlet: " + flowletContext); ClassLoader classLoader = setContextCombinedClassLoader(); try { flowlet.destroy(); } finally { ClassLoaders.setContextClassLoader(classLoader); } LOG.info("Flowlet destroyed: " + flowletContext); } }); } catch (TransactionFailureException e) { Throwable cause = e.getCause() == null ? e : e.getCause(); LOG.error("Flowlet throws exception during flowlet destroy: " + flowletContext, cause); // No need to propagate, as it is shutting down. } catch (InterruptedException e) { // No need to propagate, as it is shutting down. } } /** * Stops the given service and wait for the completion. If there is exception, just log. */ private void stopService(Service service) { try { service.stopAndWait(); } catch (Throwable t) { LOG.warn("Exception when stopping service {}", service); } } private ClassLoader setContextCombinedClassLoader() { return ClassLoaders.setContextClassLoader(new CombineClassLoader( null, ImmutableList.of(flowletContext.getProgram().getClassLoader(), getClass().getClassLoader()))); } }