/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 gobblin.runtime; import com.google.common.base.Predicate; import gobblin.metastore.FsStateStore; import gobblin.metastore.StateStore; import java.io.IOException; import java.util.List; import java.util.Properties; import java.util.Queue; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; import org.apache.hadoop.fs.Path; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.ImmutableList; import com.google.common.collect.Queues; import com.google.common.eventbus.EventBus; import com.google.common.util.concurrent.AbstractScheduledService; import gobblin.configuration.ConfigurationKeys; import gobblin.util.ParallelRunner; /** * An {@link AbstractScheduledService} for collecting output {@link TaskState}s of completed {@link Task}s * stored in files, which get deleted once the {@link TaskState}s they store are successfully collected. * For each batch of {@link TaskState}s collected, it posts a {@link NewTaskCompletionEvent} to notify * parties that are interested in such events. * * @author Yinan Li */ public class TaskStateCollectorService extends AbstractScheduledService { private static final Logger LOGGER = LoggerFactory.getLogger(TaskStateCollectorService.class); private final JobState jobState; private final EventBus eventBus; // Number of ParallelRunner threads to be used for state serialization/deserialization private final int stateSerDeRunnerThreads; // Interval in seconds between two runs of the collector of output TaskStates private final int outputTaskStatesCollectorIntervalSeconds; private final StateStore<TaskState> taskStateStore; private final Path outputTaskStateDir; public TaskStateCollectorService(Properties jobProps, JobState jobState, EventBus eventBus, StateStore<TaskState> taskStateStore, Path outputTaskStateDir) { this.jobState = jobState; this.eventBus = eventBus; this.taskStateStore = taskStateStore; this.outputTaskStateDir = outputTaskStateDir; this.stateSerDeRunnerThreads = Integer.parseInt(jobProps.getProperty(ParallelRunner.PARALLEL_RUNNER_THREADS_KEY, Integer.toString(ParallelRunner.DEFAULT_PARALLEL_RUNNER_THREADS))); this.outputTaskStatesCollectorIntervalSeconds = Integer.parseInt(jobProps.getProperty(ConfigurationKeys.TASK_STATE_COLLECTOR_INTERVAL_SECONDS, Integer.toString(ConfigurationKeys.DEFAULT_TASK_STATE_COLLECTOR_INTERVAL_SECONDS))); } @Override protected void runOneIteration() throws Exception { collectOutputTaskStates(); } @Override protected Scheduler scheduler() { return Scheduler.newFixedRateSchedule(this.outputTaskStatesCollectorIntervalSeconds, this.outputTaskStatesCollectorIntervalSeconds, TimeUnit.SECONDS); } @Override protected void startUp() throws Exception { LOGGER.info("Starting the " + TaskStateCollectorService.class.getSimpleName()); super.startUp(); } @Override protected void shutDown() throws Exception { LOGGER.info("Stopping the " + TaskStateCollectorService.class.getSimpleName()); try { runOneIteration(); } finally { super.shutDown(); } } /** * Collect output {@link TaskState}s of tasks of the job launched. * * <p> * This method collects all available output {@link TaskState} files at the time it is called. It * uses a {@link ParallelRunner} to deserialize the {@link TaskState}s. Each {@link TaskState} * file gets deleted after the {@link TaskState} it stores is successfully collected. * </p> * * @throws IOException if it fails to collect the output {@link TaskState}s */ private void collectOutputTaskStates() throws IOException { List<String> taskStateNames = taskStateStore.getTableNames(outputTaskStateDir.getName(), new Predicate<String>() { @Override public boolean apply(String input) { return input.endsWith(AbstractJobLauncher.TASK_STATE_STORE_TABLE_SUFFIX) && !input.startsWith(FsStateStore.TMP_FILE_PREFIX); }}); if (taskStateNames == null || taskStateNames.size() == 0) { LOGGER.warn("No output task state files found in " + this.outputTaskStateDir); return; } final Queue<TaskState> taskStateQueue = Queues.newConcurrentLinkedQueue(); try (ParallelRunner stateSerDeRunner = new ParallelRunner(this.stateSerDeRunnerThreads, null)) { for (final String taskStateName : taskStateNames) { LOGGER.debug("Found output task state file " + taskStateName); // Deserialize the TaskState and delete the file stateSerDeRunner.submitCallable(new Callable<Void>() { @Override public Void call() throws Exception { TaskState taskState = taskStateStore.getAll(outputTaskStateDir.getName(), taskStateName).get(0); taskStateQueue.add(taskState); taskStateStore.delete(outputTaskStateDir.getName(), taskStateName); return null; } }, "Deserialize state for " + taskStateName); } } catch (IOException ioe) { LOGGER.warn("Could not read all task state files."); } LOGGER.info(String.format("Collected task state of %d completed tasks", taskStateQueue.size())); // Add the TaskStates of completed tasks to the JobState so when the control // returns to the launcher, it sees the TaskStates of all completed tasks. for (TaskState taskState : taskStateQueue) { taskState.setJobState(this.jobState); this.jobState.addTaskState(taskState); } // Notify the listeners for the completion of the tasks this.eventBus.post(new NewTaskCompletionEvent(ImmutableList.copyOf(taskStateQueue))); } }