/* * The MIT License * * Copyright 2015 Ahseya. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.github.horrorho.liquiddonkey.cloud.engine; import com.github.horrorho.liquiddonkey.cloud.HttpAgent; import com.github.horrorho.liquiddonkey.cloud.outcome.Outcome; import com.github.horrorho.liquiddonkey.cloud.SignatureManager; import com.github.horrorho.liquiddonkey.cloud.client.ChunksClient; import com.github.horrorho.liquiddonkey.cloud.protobuf.ChunkServer; import com.github.horrorho.liquiddonkey.cloud.protobuf.ICloud; import com.github.horrorho.liquiddonkey.cloud.store.ChunkManager; import com.github.horrorho.liquiddonkey.settings.config.EngineConfig; import com.github.horrorho.liquiddonkey.util.SyncSupplier; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import java.util.function.Supplier; import java.util.stream.Collectors; import net.jcip.annotations.Immutable; import net.jcip.annotations.ThreadSafe; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * ConcurrentEngine. * * @author Ahseya */ @Immutable @ThreadSafe public class ConcurrentEngine { public static ConcurrentEngine from(EngineConfig config) { return from(config.threadCount(), config.threadStaggerDelayMs(), config.retryCount(), config.timeoutMs()); } public static ConcurrentEngine from(int threads, int staggerMs, int retryCount, long executorTimeoutMs) { return new ConcurrentEngine(threads, staggerMs, retryCount, executorTimeoutMs); } private static final Logger logger = LoggerFactory.getLogger(ConcurrentEngine.class); private final int threads; private final int staggerMs; private final int retryCount; private final long executorTimeoutMs; private final ChunksClient chunksClient = ChunksClient.create(); ConcurrentEngine(int threads, int staggerMs, int retryCount, long executorTimeoutMs) { this.threads = threads; this.staggerMs = staggerMs; this.retryCount = retryCount; this.executorTimeoutMs = executorTimeoutMs; } public void execute( HttpAgent agent, ChunkManager storeManager, SignatureManager signatureManager, Consumer<Map<ICloud.MBSFile, Outcome>> outcomesConsumer, List<ChunkServer.StorageHostChunkList> chunkListList ) throws InterruptedException, IOException, TimeoutException { List<ChunkServer.StorageHostChunkList> chunks = chunkListList.stream().collect(Collectors.toList()); logger.debug("-- execute() > chunks count: {}", chunks.size()); SyncSupplier<ChunkServer.StorageHostChunkList> syncSupplier = SyncSupplier.from(chunks); AtomicReference<Exception> fatal = new AtomicReference(null); Supplier<Donkey> donkeys = () -> new Donkey(agent, chunksClient, storeManager, signatureManager, retryCount); Supplier<Runner> runners = () -> new Runner(syncSupplier, outcomesConsumer, fatal, donkeys.get()); Exception ex = execute(runners, fatal); logger.debug("-- execute() > fatal: {}", ex); if (ex != null) { if (ex instanceof IOException) { throw (IOException) ex; } if (ex instanceof InterruptedException) { throw (InterruptedException) ex; } if (ex instanceof RuntimeException) { throw (RuntimeException) ex; } throw new IllegalStateException(ex); } } Exception execute(Supplier<Runner> runnersSupplier, AtomicReference<Exception> fatal) throws InterruptedException, TimeoutException { logger.trace("<< execute()"); boolean isTimedOut; List<Future<?>> futures = new ArrayList<>(); List<Runner> runners = new ArrayList<>(); ExecutorService executor = Executors.newCachedThreadPool(); logger.debug("-- execute() > executor created"); try { for (int i = 0; i < threads; i++) { Runner runner = runnersSupplier.get(); runners.add(runner); futures.add(executor.submit(runner)); logger.debug("-- execute() > donkey submitted: {}", i); TimeUnit.MILLISECONDS.sleep(staggerMs); } logger.debug("-- execute() > runners running: {}", threads); executor.shutdown(); logger.debug("-- execute() > awaiting termination, timeout (ms): {}", executorTimeoutMs); isTimedOut = !executor.awaitTermination(executorTimeoutMs, TimeUnit.MILLISECONDS); if (isTimedOut) { logger.warn("-- execute() > timed out"); throw new TimeoutException("Concurrent engine timed out"); } Exception ex = fatal.get(); logger.trace(">> execute() > fatal: {}", ex); return ex; } catch (InterruptedException ex) { logger.warn("-- execute() > interrupted: {}", ex); throw (ex); } finally { logger.debug("-- execute() > shutting down"); executor.shutdownNow(); // Kill Runners (aborting any http requests in progress). runners.stream().forEach(Runner::kill); long finished = futures.stream().filter(Future::isDone).count(); long pending = threads - finished; logger.debug("-- execute() > runnables, finished: {} pending: {}", finished, pending); logger.debug("-- execute() > has shut down"); } } }