/* * Copyright 2014 Avanza Bank AB * * 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 com.avanza.astrix.gs; import java.io.Serializable; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import java.util.function.UnaryOperator; import org.openspaces.core.GigaSpace; import org.openspaces.core.executor.DistributedTask; import org.openspaces.core.executor.Task; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.avanza.astrix.config.DynamicConfig; import com.avanza.astrix.config.DynamicIntProperty; import com.avanza.astrix.core.ServiceUnavailableException; import com.avanza.astrix.core.util.NamedThreadFactory; import com.avanza.astrix.remoting.util.GsUtil; import com.gigaspaces.async.AsyncFuture; import com.gigaspaces.internal.client.spaceproxy.SpaceProxyImpl; import com.j_spaces.core.IJSpace; import rx.Observable; import rx.Subscriber; /** * * @author Elias Lindholm (elilin) * */ public final class SpaceTaskDispatcher { /* * IMPLEMENTATION NOTE: * * The SpaceTaskDispatcher is required due to a flaw in the design of GigaSpace.execute methods. * In rare conditions that method might block, by default up to as long as 30 seconds if no * resources are available to process the request. In order to ensure a non-blocking programming model * we associate each clustered proxy with a dedicated thread pool to submit task executions, which ensures * that a service invocation will never block, see com.avanza.astrix.gs.remoting.GsRemotingTransport */ private static final Logger log = LoggerFactory.getLogger(SpaceTaskDispatcher.class); private final GigaSpace gigaSpace; private final ThreadPoolExecutor executorService; public SpaceTaskDispatcher(GigaSpace gigaSpace, DynamicConfig config) { this.gigaSpace = gigaSpace; /* * TODO * (1) Improve configuration mechanism used to configure thread pool. */ String spaceInstanceName = gigaSpace.getName(); DynamicIntProperty poolSize = config.getIntProperty("astrix.beans.gigaspace." + spaceInstanceName + ".spaceTaskDispatcher.poolsize", 10); this.executorService = new ThreadPoolExecutor(poolSize.get(), poolSize.get(), 0, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), new NamedThreadFactory(String.format("SpaceTaskDispatcher[%s]", spaceInstanceName))); poolSize.addListener(newValue -> { log.info(String.format("Changing pool-size for SpaceTaskDistpatcher. space=%s newSize=%s, oldSize=%s", SpaceTaskDispatcher.this.gigaSpace.getName(), newValue, executorService.getMaximumPoolSize())); executorService.setCorePoolSize(newValue); executorService.setMaximumPoolSize(newValue); }); } public IJSpace getSpace() { return gigaSpace.getSpace(); } public int partitionCount() { IJSpace space = this.gigaSpace.getSpace(); if (space instanceof SpaceProxyImpl) { return SpaceProxyImpl.class.cast(space).getSpaceClusterInfo().getNumberOfPartitions(); } throw new IllegalStateException("Cant decide cluster topology on clustered proxy: " + this.gigaSpace.getName()); } /** * Creates a lazy Observable that will execute a given Task asynchronously once subscribed to. * * @param task * @param routingKey * @return */ public <T extends Serializable> Observable<T> observe(final Task<T> task, final Object routingKey) { return Observable.create(subscriber -> { usingErrorReporter(subscriber, serviceUnavailable()).accept(() -> { // Use ExecutorService to ensure non-blocking programming model when subscribing to remote task invocation executorService.execute(() -> { submitRoutedTaskExecution(subscriber, task, routingKey); }); }); }); } private <T extends Serializable> void submitRoutedTaskExecution(Subscriber<? super T> subscriber, final Task<T> task, final Object routingKey) { usingErrorReporter(subscriber, serviceUnavailable()).accept(() -> { // Submit task on current thread in executorService AsyncFuture<T> taskResult = gigaSpace.execute(task, routingKey); GsUtil.subscribe(taskResult, subscriber); }); } /** * Creates a lazy Observable that will execute a given DistributedTask asynchronously once subscribed to. * * @param task * @param routingKey * @return */ public <T extends Serializable, R> Observable<R> observe(final DistributedTask<T, R> distributedTask) { return Observable.create(t1 -> { usingErrorReporter(t1, serviceUnavailable()).accept(() -> { executorService.execute(() -> { submitDistributedTaskExecution(distributedTask, t1); }); }); }); } private <R, T extends Serializable> void submitDistributedTaskExecution(final DistributedTask<T, R> distributedTask, Subscriber<? super R> t1) { usingErrorReporter(t1, serviceUnavailable()).accept(() -> { // Submit task on current thread in executorService AsyncFuture<R> taskResult = gigaSpace.execute(distributedTask); GsUtil.subscribe(taskResult, t1); }); } private Consumer<Runnable> usingErrorReporter(Subscriber<?> subscriber, UnaryOperator<Exception> exceptionTranslator) { return command -> { try { command.run(); } catch (Exception e) { subscriber.onError(exceptionTranslator.apply(e)); } }; } private UnaryOperator<Exception> serviceUnavailable() { return e -> new ServiceUnavailableException("Failed to submit remote invocation task", e); } /** * Destroys the {@link SpaceTaskDispatcher} by shutting down the underlying * {@link ExecutorService}. <p> */ public void destroy() { this.executorService.shutdown(); } public String getSpaceName() { return gigaSpace.getName(); } }