package com.hubspot.blazar.command; import java.util.HashMap; import java.util.Map; import java.util.Stack; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Subparser; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.google.inject.Guice; import com.google.inject.Injector; import com.hubspot.blazar.base.GitInfo; import com.hubspot.blazar.config.BlazarConfigurationWrapper; import com.hubspot.blazar.data.service.BranchService; import com.hubspot.blazar.guice.BaseCommandModule; import com.hubspot.blazar.util.GitHubHelper; import io.dropwizard.cli.ConfiguredCommand; import io.dropwizard.setup.Bootstrap; public class CleanRepoMetadataCommand extends ConfiguredCommand<BlazarConfigurationWrapper> { private static final String COMMAND_NAME = "clean_repo_metadata"; private static final String COMMAND_DESC = "Finds repos no longer in the managed organizations and marks all branches as inactive"; private static final Logger LOG = LoggerFactory.getLogger(CleanRepoMetadataCommand.class); private static final String NOOP_FLAG = "noop"; private final ExecutorService executorService; public CleanRepoMetadataCommand() { super(COMMAND_NAME, COMMAND_DESC); final ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("CleanupWorker-%d").setDaemon(true).build(); this.executorService = new ThreadPoolExecutor(10, 10, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(3000), threadFactory); } @Override public void configure(Subparser subparser) { super.configure(subparser); subparser.addArgument("-n", "--noop") .dest(NOOP_FLAG) .type(Boolean.class) .help("Runs in noop mode; makes no changes to the database") .setDefault(false); } @Override protected void run( Bootstrap<BlazarConfigurationWrapper> bootstrap, Namespace namespace, BlazarConfigurationWrapper configuration) throws Exception { boolean noop = namespace.getBoolean(NOOP_FLAG); Injector injector = Guice.createInjector(new BaseCommandModule(bootstrap, configuration)); try { GitHubHelper gitHubHelper = injector.getInstance(GitHubHelper.class); BranchService branchService = injector.getInstance(BranchService.class); Stack<GitInfo> branchStack = new Stack<>(); branchStack.addAll(branchService.getAllActive()); Map<GitInfo, CompletableFuture<Void>> futures = new HashMap<>(); LOG.info("Starting cleanup of {} active branches", branchStack.size()); while (!branchStack.isEmpty()) { GitInfo branch = branchStack.pop(); GitBranchUpdater updater = new GitBranchUpdater(branch, gitHubHelper, configuration.getBlazarConfiguration(), branchService, noop); try { futures.put(branch, CompletableFuture.runAsync(updater, executorService)); } catch (RejectedExecutionException e) { try { LOG.debug("Execution rejected for {} waiting to re-submit", branch); Thread.sleep(1500); branchStack.push(branch); } catch (InterruptedException ie) { LOG.info("Interrupted while waiting for queue to drain"); throw e; } } } for (Map.Entry<GitInfo, CompletableFuture<Void>> futureEntry : futures.entrySet()) { try { futureEntry.getValue().get(); } catch (ExecutionException e) { LOG.error("Got exception while processing {}", futureEntry.getKey(), e); } catch (InterruptedException e) { LOG.error("Was interrupted while processing {}", futureEntry.getKey(), e); } } } finally { executorService.shutdownNow(); } } }