All Downloads are FREE. Search and download functionalities are using the official Maven repository.
Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
io.cdap.cdap.internal.app.runtime.distributed.remote.RemoteExecutionTwillController Maven / Gradle / Ivy
/*
* Copyright © 2018 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 io.cdap.cdap.internal.app.runtime.distributed.remote;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.Service;
import com.google.common.util.concurrent.Uninterruptibles;
import io.cdap.cdap.common.app.RunIds;
import io.cdap.cdap.common.conf.CConfiguration;
import io.cdap.cdap.common.conf.Constants;
import io.cdap.cdap.common.service.Retries;
import io.cdap.cdap.common.service.RetryStrategies;
import io.cdap.cdap.common.service.RetryStrategy;
import io.cdap.cdap.proto.id.ProgramRunId;
import io.cdap.cdap.runtime.spi.runtimejob.RuntimeJobStatus;
import org.apache.twill.api.Command;
import org.apache.twill.api.ResourceReport;
import org.apache.twill.api.RunId;
import org.apache.twill.api.ServiceController;
import org.apache.twill.api.TwillController;
import org.apache.twill.api.logging.LogEntry;
import org.apache.twill.api.logging.LogHandler;
import org.apache.twill.common.Threads;
import org.apache.twill.discovery.ServiceDiscovered;
import org.apache.twill.internal.ServiceListenerAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.annotation.Nullable;
/**
* Implementation of {@link TwillController} that uses {@link RemoteProcessController} to control a running program.
*/
class RemoteExecutionTwillController implements TwillController {
private static final Logger LOG = LoggerFactory.getLogger(RemoteExecutionTwillController.class);
private final ProgramRunId programRunId;
private final RunId runId;
private final CompletionStage started;
private final CompletableFuture completion;
private final ScheduledExecutorService scheduler;
private final RemoteProcessController remoteProcessController;
private final RemoteExecutionService executionService;
private final long pollCompletedMillis;
private final boolean terminateWithController;
private volatile boolean terminateOnServiceStop;
RemoteExecutionTwillController(CConfiguration cConf, ProgramRunId programRunId,
CompletionStage startupCompletionStage,
RemoteProcessController remoteProcessController,
ScheduledExecutorService scheduler, RemoteExecutionService service,
boolean terminateWithController) {
this.programRunId = programRunId;
this.runId = RunIds.fromString(programRunId.getRun());
this.pollCompletedMillis = cConf.getLong(Constants.RuntimeMonitor.POLL_TIME_MS);
// On start up task succeeded, complete the started stage to unblock the onRunning()
// On start up task failure, mark this controller as terminated with exception
CompletableFuture completion = new CompletableFuture<>();
this.started = startupCompletionStage.thenApply(o -> RemoteExecutionTwillController.this);
this.terminateOnServiceStop = true;
this.started.exceptionally(throwable -> {
completion.completeExceptionally(throwable);
return RemoteExecutionTwillController.this;
});
service.addListener(new ServiceListenerAdapter() {
@Override
public void terminated(Service.State from) {
if (terminateOnServiceStop) {
completion.complete(RemoteExecutionTwillController.this);
}
}
@Override
public void failed(Service.State from, Throwable failure) {
if (terminateOnServiceStop) {
completion.completeExceptionally(failure);
}
}
}, Threads.SAME_THREAD_EXECUTOR);
this.completion = completion;
this.scheduler = scheduler;
this.remoteProcessController = remoteProcessController;
this.executionService = service;
this.terminateWithController = terminateWithController;
}
public void release() {
terminateOnServiceStop = false;
executionService.stop();
}
public void complete() {
terminateOnServiceStop = true;
executionService.stop();
try {
RuntimeJobStatus status;
RetryStrategy retryStrategy = RetryStrategies.timeLimit(
5, TimeUnit.SECONDS, RetryStrategies.exponentialDelay(500, 2000, TimeUnit.MILLISECONDS));
// Make sure the remote execution is completed
// Give 5 seconds for the remote process to shutdown. After 5 seconds, issues a kill.
long startTime = System.currentTimeMillis();
while ((status = Retries.callWithRetries(
remoteProcessController::getStatus, retryStrategy, Exception.class::isInstance)) == RuntimeJobStatus.RUNNING) {
if (System.currentTimeMillis() - startTime >= 5000) {
throw new IllegalStateException("Remote process for " + programRunId + " is still running");
}
TimeUnit.SECONDS.sleep(1);
}
remoteProcessController.kill(status);
} catch (Exception e) {
// If there is exception, use the remote execution controller to try killing the remote process
try {
LOG.debug("Force termination of remote process for program run {}", programRunId);
remoteProcessController.kill(RuntimeJobStatus.RUNNING);
} catch (Exception ex) {
LOG.warn("Failed to terminate remote process for program run {}", programRunId, ex);
}
}
}
@Override
public Future terminate() {
if (completion.isDone()) {
return CompletableFuture.completedFuture(this);
}
// graceful stop messages are handled by the RemoteClient.
// It will shut itself down, so this just needs to return a future that completes when the
// remote process is no longer running
CompletableFuture result = completion.thenApply(r -> r);
scheduler.execute(() -> {
try {
if (terminateWithController) {
remoteProcessController.terminate();
}
// Poll for completion
scheduler.schedule(new Runnable() {
@Override
public void run() {
try {
if (!remoteProcessController.isRunning()) {
completion.complete(RemoteExecutionTwillController.this);
return;
}
// Schedule to check again
scheduler.schedule(this, pollCompletedMillis, TimeUnit.MILLISECONDS);
} catch (Exception e) {
result.completeExceptionally(e);
}
}
}, pollCompletedMillis, TimeUnit.MILLISECONDS);
} catch (Exception e) {
// Only fail the result future. We have to keep the terminationFuture to be not completed so that the
// caller can retry termination.
result.completeExceptionally(e);
}
});
return result;
}
@Override
public void kill() {
try {
remoteProcessController.kill(RuntimeJobStatus.RUNNING);
} catch (Exception e) {
throw new RuntimeException("Failed when requesting program " + programRunId + " to stop", e);
}
try {
Uninterruptibles.getUninterruptibly(completion);
} catch (ExecutionException e) {
// We ignore termination error since we only care about killing the program, but not interested in the final state
LOG.debug("Exception raised when terminating program {}", programRunId, e);
}
}
@Override
public void addLogHandler(LogHandler handler) {
LOG.trace("LogHandler is not supported for {}", getClass().getSimpleName());
}
@Override
public ServiceDiscovered discoverService(String serviceName) {
throw new UnsupportedOperationException();
}
@Override
public Future changeInstances(String runnable, int newCount) {
throw new UnsupportedOperationException();
}
@Nullable
@Override
public ResourceReport getResourceReport() {
return null;
}
@Override
public Future restartAllInstances(String runnable) {
throw new UnsupportedOperationException();
}
@Override
public Future> restartInstances(Map> runnableToInstanceIds) {
throw new UnsupportedOperationException();
}
@Override
public Future restartInstances(String runnable, int instanceId, int... moreInstanceIds) {
throw new UnsupportedOperationException();
}
@Override
public Future restartInstances(String runnable, Set instanceIds) {
throw new UnsupportedOperationException();
}
@Override
public Future> updateLogLevels(Map logLevels) {
return Futures.immediateFailedFuture(new UnsupportedOperationException("updateLogLevels is not supported"));
}
@Override
public Future> updateLogLevels(String runnableName,
Map logLevelsForRunnable) {
return Futures.immediateFailedFuture(new UnsupportedOperationException("updateLogLevels is not supported"));
}
@Override
public Future resetLogLevels(String... loggerNames) {
return Futures.immediateFailedFuture(new UnsupportedOperationException("resetLogLevels is not supported"));
}
@Override
public Future resetRunnableLogLevels(String runnableName, String... loggerNames) {
return Futures.immediateFailedFuture(new UnsupportedOperationException("resetRunnableLogLevels is not supported"));
}
@Override
public RunId getRunId() {
return runId;
}
@Override
public Future sendCommand(Command command) {
return Futures.immediateFailedFuture(new UnsupportedOperationException("sendCommand is not supported"));
}
@Override
public Future sendCommand(String runnableName, Command command) {
return Futures.immediateFailedFuture(new UnsupportedOperationException("sendCommand is not supported"));
}
@Override
public void onRunning(Runnable runnable, Executor executor) {
started.thenRunAsync(runnable, executor);
}
@Override
public void onTerminated(Runnable runnable, Executor executor) {
completion.whenCompleteAsync((remoteExecutionTwillController, throwable) -> runnable.run(), executor);
}
@Override
public void awaitTerminated() throws ExecutionException {
Uninterruptibles.getUninterruptibly(completion);
}
@Override
public void awaitTerminated(long timeout, TimeUnit timeoutUnit) throws TimeoutException, ExecutionException {
Uninterruptibles.getUninterruptibly(completion, timeout, timeoutUnit);
}
@Nullable
@Override
public TerminationStatus getTerminationStatus() {
if (!completion.isDone()) {
return null;
}
try {
awaitTerminated();
return TerminationStatus.SUCCEEDED;
} catch (ExecutionException e) {
return TerminationStatus.FAILED;
}
}
}