/* * Copyright (c) 2008-2017, Hazelcast, Inc. All Rights Reserved. * * 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.hazelcast.client.proxy; import com.hazelcast.client.impl.ClientMessageDecoder; import com.hazelcast.client.impl.protocol.ClientMessage; import com.hazelcast.client.impl.protocol.codec.ExecutorServiceIsShutdownCodec; import com.hazelcast.client.impl.protocol.codec.ExecutorServiceShutdownCodec; import com.hazelcast.client.impl.protocol.codec.ExecutorServiceSubmitToAddressCodec; import com.hazelcast.client.impl.protocol.codec.ExecutorServiceSubmitToPartitionCodec; import com.hazelcast.client.spi.ClientContext; import com.hazelcast.client.spi.ClientPartitionService; import com.hazelcast.client.spi.ClientProxy; import com.hazelcast.client.spi.impl.ClientInvocation; import com.hazelcast.client.spi.impl.ClientInvocationFuture; import com.hazelcast.client.util.ClientAddressCancellableDelegatingFuture; import com.hazelcast.client.util.ClientDelegatingFuture; import com.hazelcast.client.util.ClientPartitionCancellableDelegatingFuture; import com.hazelcast.core.ExecutionCallback; import com.hazelcast.core.HazelcastException; import com.hazelcast.core.IExecutorService; import com.hazelcast.core.Member; import com.hazelcast.core.MemberSelector; import com.hazelcast.core.MultiExecutionCallback; import com.hazelcast.core.PartitionAware; import com.hazelcast.executor.impl.RunnableAdapter; import com.hazelcast.monitor.LocalExecutorStats; import com.hazelcast.nio.Address; import com.hazelcast.nio.serialization.Data; import com.hazelcast.spi.serialization.SerializationService; import com.hazelcast.util.Clock; import com.hazelcast.util.UuidUtil; import com.hazelcast.util.executor.CompletedFuture; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.Future; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import static com.hazelcast.util.ExceptionUtil.rethrow; import static com.hazelcast.util.Preconditions.checkNotNull; /** * @author ali 5/24/13 */ public class ClientExecutorServiceProxy extends ClientProxy implements IExecutorService { private static final int MIN_TIME_RESOLUTION_OF_CONSECUTIVE_SUBMITS = 10; private static final int MAX_CONSECUTIVE_SUBMITS = 100; private static final ClientMessageDecoder SUBMIT_TO_PARTITION_DECODER = new ClientMessageDecoder() { @Override public <T> T decodeClientMessage(ClientMessage clientMessage) { return (T) ExecutorServiceSubmitToPartitionCodec.decodeResponse(clientMessage).response; } }; private static final ClientMessageDecoder SUBMIT_TO_ADDRESS_DECODER = new ClientMessageDecoder() { @Override public <T> T decodeClientMessage(ClientMessage clientMessage) { return (T) ExecutorServiceSubmitToAddressCodec.decodeResponse(clientMessage).response; } }; private final Random random = new Random(-System.currentTimeMillis()); private final AtomicInteger consecutiveSubmits = new AtomicInteger(); private volatile long lastSubmitTime; public ClientExecutorServiceProxy(String serviceName, String objectId, ClientContext context) { super(serviceName, objectId, context); } // execute on members @Override public void execute(Runnable command) { submit(command); } @Override public void executeOnKeyOwner(Runnable command, Object key) { Callable<?> callable = createRunnableAdapter(command); submitToKeyOwner(callable, key); } @Override public void executeOnMember(Runnable command, Member member) { Callable<?> callable = createRunnableAdapter(command); submitToMember(callable, member); } @Override public void executeOnMembers(Runnable command, Collection<Member> members) { Callable<?> callable = createRunnableAdapter(command); for (Member member : members) { submitToMember(callable, member); } } @Override public void execute(Runnable command, MemberSelector memberSelector) { List<Member> members = selectMembers(memberSelector); int selectedMember = random.nextInt(members.size()); executeOnMember(command, members.get(selectedMember)); } @Override public void executeOnMembers(Runnable command, MemberSelector memberSelector) { List<Member> members = selectMembers(memberSelector); executeOnMembers(command, members); } @Override public void executeOnAllMembers(Runnable command) { Callable<?> callable = createRunnableAdapter(command); final Collection<Member> memberList = getContext().getClusterService().getMemberList(); for (Member member : memberList) { submitToMember(callable, member); } } // submit to members @Override public <T> Future<T> submitToMember(Callable<T> task, Member member) { final Address memberAddress = getMemberAddress(member); return submitToTargetInternal(task, memberAddress, null, false); } @Override public <T> Map<Member, Future<T>> submitToMembers(Callable<T> task, Collection<Member> members) { Map<Member, Future<T>> futureMap = new HashMap<Member, Future<T>>(members.size()); for (Member member : members) { final Address memberAddress = getMemberAddress(member); Future<T> f = submitToTargetInternal(task, memberAddress, null, true); futureMap.put(member, f); } return futureMap; } @Override public <T> Future<T> submit(Callable<T> task, MemberSelector memberSelector) { List<Member> members = selectMembers(memberSelector); int selectedMember = random.nextInt(members.size()); return submitToMember(task, members.get(selectedMember)); } @Override public <T> Map<Member, Future<T>> submitToMembers(Callable<T> task, MemberSelector memberSelector) { List<Member> members = selectMembers(memberSelector); return submitToMembers(task, members); } @Override public <T> Map<Member, Future<T>> submitToAllMembers(Callable<T> task) { final Collection<Member> memberList = getContext().getClusterService().getMemberList(); Map<Member, Future<T>> futureMap = new HashMap<Member, Future<T>>(memberList.size()); for (Member m : memberList) { Future<T> f = submitToTargetInternal(task, m.getAddress(), null, true); futureMap.put(m, f); } return futureMap; } // submit to members callback @Override public void submitToMember(Runnable command, Member member, ExecutionCallback callback) { Callable<?> callable = createRunnableAdapter(command); submitToMember(callable, member, callback); } @Override public void submitToMembers(Runnable command, Collection<Member> members, MultiExecutionCallback callback) { Callable<?> callable = createRunnableAdapter(command); MultiExecutionCallbackWrapper multiExecutionCallbackWrapper = new MultiExecutionCallbackWrapper(members.size(), callback); for (Member member : members) { final ExecutionCallbackWrapper executionCallback = new ExecutionCallbackWrapper(multiExecutionCallbackWrapper, member); submitToMember(callable, member, executionCallback); } } @Override public <T> void submitToMember(Callable<T> task, Member member, ExecutionCallback<T> callback) { final Address memberAddress = getMemberAddress(member); submitToTargetInternal(task, memberAddress, callback); } @Override public <T> void submitToMembers(Callable<T> task, Collection<Member> members, MultiExecutionCallback callback) { MultiExecutionCallbackWrapper multiExecutionCallbackWrapper = new MultiExecutionCallbackWrapper(members.size(), callback); for (Member member : members) { final ExecutionCallbackWrapper<T> executionCallback = new ExecutionCallbackWrapper<T>(multiExecutionCallbackWrapper, member); submitToMember(task, member, executionCallback); } } @Override public void submit(Runnable task, MemberSelector memberSelector, ExecutionCallback callback) { List<Member> members = selectMembers(memberSelector); int selectedMember = random.nextInt(members.size()); submitToMember(task, members.get(selectedMember), callback); } @Override public void submitToMembers(Runnable task, MemberSelector memberSelector, MultiExecutionCallback callback) { List<Member> members = selectMembers(memberSelector); submitToMembers(task, members, callback); } @Override public <T> void submit(Callable<T> task, MemberSelector memberSelector, ExecutionCallback<T> callback) { List<Member> members = selectMembers(memberSelector); int selectedMember = random.nextInt(members.size()); submitToMember(task, members.get(selectedMember), callback); } @Override public <T> void submitToMembers(Callable<T> task, MemberSelector memberSelector, MultiExecutionCallback callback) { List<Member> members = selectMembers(memberSelector); submitToMembers(task, members, callback); } @Override public void submitToAllMembers(Runnable command, MultiExecutionCallback callback) { Callable<?> callable = createRunnableAdapter(command); submitToAllMembers(callable, callback); } @Override public <T> void submitToAllMembers(Callable<T> task, MultiExecutionCallback callback) { final Collection<Member> memberList = getContext().getClusterService().getMemberList(); MultiExecutionCallbackWrapper multiExecutionCallbackWrapper = new MultiExecutionCallbackWrapper(memberList.size(), callback); for (Member member : memberList) { final ExecutionCallbackWrapper<T> executionCallback = new ExecutionCallbackWrapper<T>(multiExecutionCallbackWrapper, member); submitToMember(task, member, executionCallback); } } // submit random @Override public Future<?> submit(Runnable command) { final Object partitionKey = getTaskPartitionKey(command); Callable<?> callable = createRunnableAdapter(command); if (partitionKey != null) { return submitToKeyOwner(callable, partitionKey); } return submitToRandomInternal(callable, null, false); } @Override public <T> Future<T> submit(Runnable command, T result) { final Object partitionKey = getTaskPartitionKey(command); Callable<T> callable = createRunnableAdapter(command); if (partitionKey != null) { return submitToKeyOwnerInternal(callable, partitionKey, result, false); } return submitToRandomInternal(callable, result, false); } @Override public <T> Future<T> submit(Callable<T> task) { final Object partitionKey = getTaskPartitionKey(task); if (partitionKey != null) { return submitToKeyOwner(task, partitionKey); } return submitToRandomInternal(task, null, false); } @Override public <T> void submit(Runnable command, ExecutionCallback<T> callback) { final Object partitionKey = getTaskPartitionKey(command); Callable<T> callable = createRunnableAdapter(command); if (partitionKey != null) { submitToKeyOwnerInternal(callable, partitionKey, callback); } else { submitToRandomInternal(callable, callback); } } @Override public <T> void submit(Callable<T> task, ExecutionCallback<T> callback) { final Object partitionKey = getTaskPartitionKey(task); if (partitionKey != null) { submitToKeyOwnerInternal(task, partitionKey, callback); } else { submitToRandomInternal(task, callback); } } // submit to key @Override public <T> Future<T> submitToKeyOwner(Callable<T> task, Object key) { return submitToKeyOwnerInternal(task, key, null, false); } @Override public void submitToKeyOwner(Runnable command, Object key, ExecutionCallback callback) { Callable<?> callable = createRunnableAdapter(command); submitToKeyOwner(callable, key, callback); } @Override public <T> void submitToKeyOwner(Callable<T> task, Object key, ExecutionCallback<T> callback) { submitToKeyOwnerInternal(task, key, callback); } // end @Override public LocalExecutorStats getLocalExecutorStats() { throw new UnsupportedOperationException("Locality is ambiguous for client!"); } @Override public void shutdown() { ClientMessage request = ExecutorServiceShutdownCodec.encodeRequest(name); invoke(request); } @Override public List<Runnable> shutdownNow() { shutdown(); return Collections.emptyList(); } @Override public boolean isShutdown() { ClientMessage request = ExecutorServiceIsShutdownCodec.encodeRequest(name); ClientMessage response = invoke(request); ExecutorServiceIsShutdownCodec.ResponseParameters resultParameters = ExecutorServiceIsShutdownCodec.decodeResponse(response); return resultParameters.response; } @Override public boolean isTerminated() { return isShutdown(); } @Override public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { return false; } @Override public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException { final List<Future<T>> futures = new ArrayList<Future<T>>(tasks.size()); final List<Future<T>> result = new ArrayList<Future<T>>(tasks.size()); for (Callable<T> task : tasks) { futures.add(submitToRandomInternal(task, null, true)); } Executor userExecutor = getContext().getExecutionService().getUserExecutor(); for (Future<T> future : futures) { Object value = retrieveResult(future); result.add(new CompletedFuture<T>(getSerializationService(), value, userExecutor)); } return result; } @Override public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException { throw new UnsupportedOperationException(); } @Override public <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException { throw new UnsupportedOperationException(); } @Override public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { throw new UnsupportedOperationException(); } private Object getTaskPartitionKey(Object task) { if (task instanceof PartitionAware) { return ((PartitionAware) task).getPartitionKey(); } return null; } private <T> RunnableAdapter<T> createRunnableAdapter(Runnable command) { if (command == null) { throw new NullPointerException(); } return new RunnableAdapter<T>(command); } private <T> Future<T> submitToKeyOwnerInternal(Callable<T> task, Object key, T defaultValue, boolean preventSync) { checkNotNull(task, "task should not be null"); String uuid = getUUID(); int partitionId = getPartitionId(key); ClientMessage request = ExecutorServiceSubmitToPartitionCodec.encodeRequest(name, uuid, toData(task), partitionId); ClientInvocationFuture f = invokeOnPartitionOwner(request, partitionId); return checkSync(f, uuid, partitionId, preventSync, defaultValue); } private <T> void submitToKeyOwnerInternal(Callable<T> task, Object key, ExecutionCallback<T> callback) { checkNotNull(task, "task should not be null"); String uuid = getUUID(); int partitionId = getPartitionId(key); ClientMessage request = ExecutorServiceSubmitToPartitionCodec.encodeRequest(name, uuid, toData(task), partitionId); ClientInvocationFuture f = invokeOnPartitionOwner(request, partitionId); ClientDelegatingFuture<T> delegatingFuture = new ClientDelegatingFuture<T>(f, getSerializationService(), SUBMIT_TO_PARTITION_DECODER); delegatingFuture.andThen(callback); } private <T> Future<T> submitToRandomInternal(Callable<T> task, T defaultValue, boolean preventSync) { checkNotNull(task, "task should not be null"); String uuid = getUUID(); int partitionId = randomPartitionId(); ClientMessage request = ExecutorServiceSubmitToPartitionCodec.encodeRequest(name, uuid, toData(task), partitionId); ClientInvocationFuture f = invokeOnPartitionOwner(request, partitionId); return checkSync(f, uuid, partitionId, preventSync, defaultValue); } private <T> void submitToRandomInternal(Callable<T> task, ExecutionCallback<T> callback) { checkNotNull(task, "task should not be null"); String uuid = getUUID(); int partitionId = randomPartitionId(); ClientMessage request = ExecutorServiceSubmitToPartitionCodec.encodeRequest(name, uuid, toData(task), partitionId); ClientInvocationFuture f = invokeOnPartitionOwner(request, partitionId); ClientDelegatingFuture<T> delegatingFuture = new ClientDelegatingFuture<T>(f, getSerializationService(), SUBMIT_TO_PARTITION_DECODER); delegatingFuture.andThen(callback); } private <T> Future<T> submitToTargetInternal(Callable<T> task, Address address , T defaultValue, boolean preventSync) { checkNotNull(task, "task should not be null"); String uuid = getUUID(); ClientMessage request = ExecutorServiceSubmitToAddressCodec.encodeRequest(name, uuid, toData(task), address); ClientInvocationFuture f = invokeOnTarget(request, address); return checkSync(f, uuid, address, preventSync, defaultValue); } private <T> void submitToTargetInternal(Callable<T> task, Address address, ExecutionCallback<T> callback) { checkNotNull(task, "task should not be null"); String uuid = getUUID(); ClientMessage request = ExecutorServiceSubmitToAddressCodec.encodeRequest(name, uuid, toData(task), address); ClientInvocationFuture f = invokeOnTarget(request, address); ClientDelegatingFuture<T> delegatingFuture = new ClientDelegatingFuture<T>(f, getSerializationService(), SUBMIT_TO_ADDRESS_DECODER); delegatingFuture.andThen(callback); } @Override public String toString() { return "IExecutorService{" + "name='" + name + '\'' + '}'; } private <T> Future<T> checkSync(ClientInvocationFuture f, String uuid, Address address, boolean preventSync, T defaultValue) { boolean sync = isSyncComputation(preventSync); if (sync) { Object response = retrieveResultFromMessage(f); Executor userExecutor = getContext().getExecutionService().getUserExecutor(); return new CompletedFuture<T>(getSerializationService(), response, userExecutor); } else { return new ClientAddressCancellableDelegatingFuture<T>(f, getContext(), uuid, address, defaultValue, SUBMIT_TO_ADDRESS_DECODER); } } private <T> Future<T> checkSync(ClientInvocationFuture f, String uuid, int partitionId, boolean preventSync, T defaultValue) { boolean sync = isSyncComputation(preventSync); if (sync) { Object response = retrieveResultFromMessage(f); Executor userExecutor = getContext().getExecutionService().getUserExecutor(); return new CompletedFuture<T>(getSerializationService(), response, userExecutor); } else { return new ClientPartitionCancellableDelegatingFuture<T>(f, getContext(), uuid, partitionId, defaultValue, SUBMIT_TO_PARTITION_DECODER); } } private <T> Object retrieveResult(Future<T> f) { Object response; try { response = f.get(); } catch (Exception e) { response = e; } return response; } private Object retrieveResultFromMessage(ClientInvocationFuture f) { Object response; try { SerializationService serializationService = getClient().getSerializationService(); Data data = ExecutorServiceSubmitToAddressCodec.decodeResponse(f.get()).response; response = serializationService.toObject(data); } catch (Exception e) { response = e; } return response; } private boolean isSyncComputation(boolean preventSync) { long now = Clock.currentTimeMillis(); long last = lastSubmitTime; lastSubmitTime = now; AtomicInteger consecutiveSubmits = this.consecutiveSubmits; if (last + MIN_TIME_RESOLUTION_OF_CONSECUTIVE_SUBMITS < now) { consecutiveSubmits.set(0); return false; } return !preventSync && consecutiveSubmits.incrementAndGet() % MAX_CONSECUTIVE_SUBMITS == 0; } private List<Member> selectMembers(MemberSelector memberSelector) { if (memberSelector == null) { throw new IllegalArgumentException("memberSelector must not be null"); } List<Member> selected = new ArrayList<Member>(); Collection<Member> members = getContext().getClusterService().getMemberList(); for (Member member : members) { if (memberSelector.select(member)) { selected.add(member); } } if (selected.isEmpty()) { throw new RejectedExecutionException("No member selected with memberSelector[" + memberSelector + "]"); } return selected; } private static final class ExecutionCallbackWrapper<T> implements ExecutionCallback<T> { MultiExecutionCallbackWrapper multiExecutionCallbackWrapper; Member member; private ExecutionCallbackWrapper(MultiExecutionCallbackWrapper multiExecutionCallback, Member member) { this.multiExecutionCallbackWrapper = multiExecutionCallback; this.member = member; } @Override public void onResponse(T response) { multiExecutionCallbackWrapper.onResponse(member, response); } @Override public void onFailure(Throwable t) { } } private static final class MultiExecutionCallbackWrapper implements MultiExecutionCallback { private final MultiExecutionCallback multiExecutionCallback; private final Map<Member, Object> values; private final AtomicInteger members; private MultiExecutionCallbackWrapper(int memberSize, MultiExecutionCallback multiExecutionCallback) { this.multiExecutionCallback = multiExecutionCallback; this.values = Collections.synchronizedMap(new HashMap<Member, Object>(memberSize)); this.members = new AtomicInteger(memberSize); } @Override public void onResponse(Member member, Object value) { multiExecutionCallback.onResponse(member, value); values.put(member, value); int waitingResponse = members.decrementAndGet(); if (waitingResponse == 0) { onComplete(values); } } @Override public void onComplete(Map<Member, Object> values) { multiExecutionCallback.onComplete(values); } } private ClientInvocationFuture invokeOnPartitionOwner(ClientMessage request, int partitionId) { try { ClientInvocation clientInvocation = new ClientInvocation(getClient(), request, partitionId); return clientInvocation.invoke(); } catch (Exception e) { throw rethrow(e); } } private ClientInvocationFuture invokeOnTarget(ClientMessage request, Address target) { try { ClientInvocation invocation = new ClientInvocation(getClient(), request, target); return invocation.invoke(); } catch (Exception e) { throw rethrow(e); } } private String getUUID() { return UuidUtil.newUnsecureUuidString(); } private Address getMemberAddress(Member member) { Member m = getContext().getClusterService().getMember(member.getUuid()); if (m == null) { throw new HazelcastException(member + " is not available!"); } return m.getAddress(); } private int getPartitionId(Object key) { ClientPartitionService partitionService = getContext().getPartitionService(); return partitionService.getPartitionId(key); } private int randomPartitionId() { ClientPartitionService partitionService = getContext().getPartitionService(); return random.nextInt(partitionService.getPartitionCount()); } }