/* * ToroDB * Copyright © 2014 8Kdata Technology (www.8kdata.com) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.torodb.concurrent; import akka.Done; import akka.NotUsed; import akka.stream.ActorMaterializer; import akka.stream.FlowShape; import akka.stream.Graph; import akka.stream.Materializer; import akka.stream.UniformFanInShape; import akka.stream.UniformFanOutShape; import akka.stream.javadsl.Balance; import akka.stream.javadsl.Flow; import akka.stream.javadsl.GraphDSL; import akka.stream.javadsl.Keep; import akka.stream.javadsl.Merge; import akka.stream.javadsl.Sink; import akka.stream.javadsl.Source; import com.google.common.base.Preconditions; import com.torodb.core.annotations.ParallelLevel; import com.torodb.core.concurrent.StreamExecutor; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.ExecutorService; import java.util.concurrent.ThreadFactory; import java.util.function.BiFunction; import java.util.stream.Stream; import javax.inject.Inject; /** * */ public class AkkaStreamExecutor extends ActorSystemTorodbService implements StreamExecutor { private static final Logger LOGGER = LogManager.getLogger(AkkaStreamExecutor.class); private final int parallelLevel; private final Sink<Runnable, CompletionStage<Done>> genericRunnableGraph; private Materializer materializer; @Inject public AkkaStreamExecutor(ThreadFactory threadFactory, @ParallelLevel int parallelLevel, ExecutorService executor, String prefix) { super(threadFactory, () -> executor, prefix); this.parallelLevel = parallelLevel; genericRunnableGraph = Flow.fromFunction( (Runnable runnable) -> { runnable.run(); return NotUsed.getInstance(); }) .via(createBalanceGraph(Flow.create())) .toMat(Sink.ignore(), Keep.right()); } @Override protected Logger getLogger() { return LOGGER; } @Override protected void startUp() throws Exception { super.startUp(); materializer = ActorMaterializer.create(getActorSystem()); } @Override public CompletableFuture<?> executeRunnables(Stream<Runnable> runnables) { Preconditions.checkState(isRunning(), "This service is not running"); return Source.fromIterator(() -> runnables.iterator()) .toMat(genericRunnableGraph, Keep.right()) .run(materializer) .toCompletableFuture(); } @Override public <I> CompletableFuture<?> execute(Stream<Callable<I>> callables) { Preconditions.checkState(isRunning(), "This service is not running"); Flow<Callable<I>, I, NotUsed> flow = Flow.fromFunction(callable -> callable.call()); Graph<FlowShape<Callable<I>, I>, NotUsed> balanceGraph = createBalanceGraph(flow); return Source.fromIterator(() -> callables.iterator()) .via(balanceGraph) .toMat(Sink.ignore(), Keep.right()) .run(materializer) .toCompletableFuture(); } @Override public <I, O> CompletableFuture<O> fold(Stream<Callable<I>> callables, O zero, BiFunction<O, I, O> fun) { Preconditions.checkState(isRunning(), "This service is not running"); Flow<Callable<I>, I, NotUsed> flow = Flow.fromFunction(callable -> callable.call()); Graph<FlowShape<Callable<I>, I>, NotUsed> balanceGraph = createBalanceGraph(flow); return Source.fromIterator(() -> callables.iterator()) .via(balanceGraph) .toMat(Sink.fold(zero, (acum, newValue) -> fun.apply(acum, newValue)), Keep.right()) .run(materializer) .toCompletableFuture(); } private <I, O> Graph<FlowShape<I, O>, NotUsed> createBalanceGraph(Flow<I, O, NotUsed> flow) { if (parallelLevel == 1) { return flow; } return GraphDSL.create(builder -> { UniformFanOutShape<I, I> balance = builder.add( Balance.create(parallelLevel, false) ); UniformFanInShape<O, O> merge = builder.add( Merge.create(parallelLevel, false) ); for (int i = 0; i < parallelLevel; i++) { builder.from(balance.out(i)) .via(builder.add( flow.async()) ) .toInlet(merge.in(i)); } return FlowShape.of(balance.in(), merge.out()); }); } }