/*
* 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.spi.impl.operationservice.impl;
import com.hazelcast.core.ExecutionCallback;
import com.hazelcast.instance.MemberImpl;
import com.hazelcast.instance.Node;
import com.hazelcast.internal.cluster.ClusterClock;
import com.hazelcast.internal.management.dto.SlowOperationDTO;
import com.hazelcast.internal.metrics.MetricsProvider;
import com.hazelcast.internal.metrics.MetricsRegistry;
import com.hazelcast.internal.metrics.Probe;
import com.hazelcast.internal.partition.InternalPartitionService;
import com.hazelcast.internal.serialization.InternalSerializationService;
import com.hazelcast.internal.util.counters.Counter;
import com.hazelcast.internal.util.counters.MwCounter;
import com.hazelcast.logging.ILogger;
import com.hazelcast.nio.Address;
import com.hazelcast.nio.Packet;
import com.hazelcast.spi.ExecutionService;
import com.hazelcast.spi.InternalCompletableFuture;
import com.hazelcast.spi.InvocationBuilder;
import com.hazelcast.spi.LiveOperations;
import com.hazelcast.spi.LiveOperationsTracker;
import com.hazelcast.spi.Operation;
import com.hazelcast.spi.OperationFactory;
import com.hazelcast.spi.OperationService;
import com.hazelcast.spi.impl.NodeEngineImpl;
import com.hazelcast.spi.impl.PacketHandler;
import com.hazelcast.spi.impl.PartitionSpecificRunnable;
import com.hazelcast.spi.impl.operationexecutor.OperationExecutor;
import com.hazelcast.spi.impl.operationexecutor.impl.OperationExecutorImpl;
import com.hazelcast.spi.impl.operationexecutor.slowoperationdetector.SlowOperationDetector;
import com.hazelcast.spi.impl.operationservice.InternalOperationService;
import com.hazelcast.util.EmptyStatement;
import com.hazelcast.util.executor.ExecutorType;
import com.hazelcast.util.executor.ManagedExecutorService;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import static com.hazelcast.internal.metrics.ProbeLevel.MANDATORY;
import static com.hazelcast.internal.util.counters.MwCounter.newMwCounter;
import static com.hazelcast.spi.InvocationBuilder.DEFAULT_CALL_TIMEOUT;
import static com.hazelcast.spi.InvocationBuilder.DEFAULT_DESERIALIZE_RESULT;
import static com.hazelcast.spi.InvocationBuilder.DEFAULT_REPLICA_INDEX;
import static com.hazelcast.spi.InvocationBuilder.DEFAULT_TRY_COUNT;
import static com.hazelcast.spi.InvocationBuilder.DEFAULT_TRY_PAUSE_MILLIS;
import static com.hazelcast.spi.impl.operationutil.Operations.isJoinOperation;
import static com.hazelcast.spi.properties.GroupProperty.OPERATION_CALL_TIMEOUT_MILLIS;
import static com.hazelcast.util.CollectionUtil.toIntegerList;
import static com.hazelcast.util.Preconditions.checkNotNegative;
import static com.hazelcast.util.Preconditions.checkNotNull;
import static java.util.Collections.newSetFromMap;
import static java.util.concurrent.TimeUnit.SECONDS;
/**
* This is the implementation of the {@link com.hazelcast.spi.impl.operationservice.InternalOperationService}.
* <p/>
* <h1>System Operation</h1>
* When a {@link com.hazelcast.spi.UrgentSystemOperation} is invoked on this OperationService, it will be executed with a
* high urgency by making use of a urgent queue. So when the system is under load, and the operation queues are
* filled, then system operations are executed before normal operation. The advantage is that when a system is under
* pressure, it still is able to do things like recognizing new members in the cluster and moving partitions around.
* <p/>
* When a UrgentSystemOperation is send to a remote machine, it is wrapped in a {@link Packet} and the packet is marked as a
* urgent packet. When this packet is received on the remove OperationService, the urgent flag is checked and if
* needed, the operation is set on the urgent queue. So local and remote execution of System operations will obey
* the urgency.
*
* @see Invocation
* @see InvocationBuilderImpl
* @see PartitionInvocation
* @see TargetInvocation
*/
public final class OperationServiceImpl implements InternalOperationService, MetricsProvider, LiveOperationsTracker {
private static final int ASYNC_QUEUE_CAPACITY = 100000;
private static final long TERMINATION_TIMEOUT_MILLIS = SECONDS.toMillis(10);
final InvocationRegistry invocationRegistry;
final OperationExecutor operationExecutor;
@Probe(name = "operationTimeoutCount", level = MANDATORY)
final MwCounter operationTimeoutCount = newMwCounter();
@Probe(name = "callTimeoutCount", level = MANDATORY)
final MwCounter callTimeoutCount = newMwCounter();
@Probe(name = "retryCount", level = MANDATORY)
final MwCounter retryCount = newMwCounter();
@Probe(name = "failedBackups", level = MANDATORY)
final Counter failedBackupsCount = newMwCounter();
final NodeEngineImpl nodeEngine;
final Node node;
final ILogger logger;
final OperationBackupHandler backupHandler;
final BackpressureRegulator backpressureRegulator;
final OutboundResponseHandler outboundResponseHandler;
final OutboundOperationHandler outboundOperationHandler;
volatile Invocation.Context invocationContext;
private final InvocationMonitor invocationMonitor;
private final SlowOperationDetector slowOperationDetector;
private final AsyncInboundResponseHandler asyncInboundResponseHandler;
private final InternalSerializationService serializationService;
private final InboundResponseHandler inboundResponseHandler;
private final Address thisAddress;
// contains the current executing asyncOperations. This information is needed for the operation-ping.
// this is a temporary solution till we found a better async operation abstraction
@Probe
private final Set<Operation> asyncOperations
= newSetFromMap(new ConcurrentHashMap<Operation, Boolean>());
public OperationServiceImpl(NodeEngineImpl nodeEngine) {
this.nodeEngine = nodeEngine;
this.node = nodeEngine.getNode();
this.thisAddress = node.getThisAddress();
this.logger = node.getLogger(OperationService.class);
this.serializationService = (InternalSerializationService) nodeEngine.getSerializationService();
this.backpressureRegulator = new BackpressureRegulator(
node.getProperties(), node.getLogger(BackpressureRegulator.class));
this.outboundResponseHandler = new OutboundResponseHandler(
thisAddress, serializationService, node, node.getLogger(OutboundResponseHandler.class));
this.invocationRegistry = new InvocationRegistry(
node.getLogger(OperationServiceImpl.class), backpressureRegulator.newCallIdSequence());
this.invocationMonitor = new InvocationMonitor(
nodeEngine, thisAddress, node.getProperties(), invocationRegistry,
node.getLogger(InvocationMonitor.class), serializationService, nodeEngine.getServiceManager());
this.outboundOperationHandler = new OutboundOperationHandler(node, thisAddress, serializationService);
this.backupHandler = new OperationBackupHandler(this, outboundOperationHandler);
String hzName = nodeEngine.getHazelcastInstance().getName();
this.inboundResponseHandler = new InboundResponseHandler(
node.getLogger(InboundResponseHandler.class), node.getSerializationService(), invocationRegistry, nodeEngine);
ClassLoader configClassLoader = node.getConfigClassLoader();
this.asyncInboundResponseHandler = new AsyncInboundResponseHandler(configClassLoader, hzName,
node.getLogger(AsyncInboundResponseHandler.class),
inboundResponseHandler, node.getProperties());
this.operationExecutor = new OperationExecutorImpl(
node.getProperties(), node.loggingService, thisAddress, new OperationRunnerFactoryImpl(this),
node.getNodeExtension(), hzName, configClassLoader);
this.slowOperationDetector = new SlowOperationDetector(node.loggingService,
operationExecutor.getGenericOperationRunners(), operationExecutor.getPartitionOperationRunners(),
node.getProperties(), hzName);
}
public OutboundResponseHandler getOutboundResponseHandler() {
return outboundResponseHandler;
}
public PacketHandler getAsyncInboundResponseHandler() {
return asyncInboundResponseHandler;
}
public InvocationMonitor getInvocationMonitor() {
return invocationMonitor;
}
@Override
public List<SlowOperationDTO> getSlowOperationDTOs() {
return slowOperationDetector.getSlowOperationDTOs();
}
public InvocationRegistry getInvocationRegistry() {
return invocationRegistry;
}
public InboundResponseHandler getInboundResponseHandler() {
return inboundResponseHandler;
}
@Override
public int getPartitionThreadCount() {
return operationExecutor.getPartitionThreadCount();
}
@Override
public int getGenericThreadCount() {
return operationExecutor.getGenericThreadCount();
}
@Override
public int getRunningOperationsCount() {
return operationExecutor.getRunningOperationCount();
}
@Override
public long getExecutedOperationCount() {
return operationExecutor.getExecutedOperationCount();
}
@Override
public int getRemoteOperationsCount() {
return invocationRegistry.size();
}
@Override
public int getOperationExecutorQueueSize() {
return operationExecutor.getQueueSize();
}
@Override
public int getPriorityOperationExecutorQueueSize() {
return operationExecutor.getPriorityQueueSize();
}
public OperationExecutor getOperationExecutor() {
return operationExecutor;
}
@Override
public int getResponseQueueSize() {
return asyncInboundResponseHandler.getQueueSize();
}
@Override
public void populate(LiveOperations liveOperations) {
operationExecutor.scan(liveOperations);
for (Operation op : asyncOperations) {
liveOperations.add(op.getCallerAddress(), op.getCallId());
}
}
@Override
public void execute(PartitionSpecificRunnable task) {
operationExecutor.execute(task);
}
@Override
public InvocationBuilder createInvocationBuilder(String serviceName, Operation op, int partitionId) {
checkNotNegative(partitionId, "Partition id cannot be negative!");
return new InvocationBuilderImpl(invocationContext, serviceName, op, partitionId);
}
@Override
public InvocationBuilder createInvocationBuilder(String serviceName, Operation op, Address target) {
checkNotNull(target, "Target cannot be null!");
return new InvocationBuilderImpl(invocationContext, serviceName, op, target);
}
@Override
public void run(Operation op) {
operationExecutor.run(op);
}
@Override
public void execute(Operation op) {
operationExecutor.execute(op);
}
@Override
public boolean isRunAllowed(Operation op) {
return operationExecutor.isRunAllowed(op);
}
@Override
@SuppressWarnings("unchecked")
public <E> InternalCompletableFuture<E> invokeOnPartition(String serviceName, Operation op, int partitionId) {
op.setServiceName(serviceName)
.setPartitionId(partitionId)
.setReplicaIndex(DEFAULT_REPLICA_INDEX);
return new PartitionInvocation(
invocationContext, op, DEFAULT_TRY_COUNT, DEFAULT_TRY_PAUSE_MILLIS,
DEFAULT_CALL_TIMEOUT, DEFAULT_DESERIALIZE_RESULT).invoke();
}
@Override
@SuppressWarnings("unchecked")
public <E> InternalCompletableFuture<E> invokeOnPartition(Operation op) {
return new PartitionInvocation(
invocationContext, op, DEFAULT_TRY_COUNT, DEFAULT_TRY_PAUSE_MILLIS,
DEFAULT_CALL_TIMEOUT, DEFAULT_DESERIALIZE_RESULT).invoke();
}
@Override
@SuppressWarnings("unchecked")
public <E> InternalCompletableFuture<E> invokeOnTarget(String serviceName, Operation op, Address target) {
op.setServiceName(serviceName);
return new TargetInvocation(invocationContext, op, target, DEFAULT_TRY_COUNT,
DEFAULT_TRY_PAUSE_MILLIS, DEFAULT_CALL_TIMEOUT, DEFAULT_DESERIALIZE_RESULT).invoke();
}
@Override
@SuppressWarnings("unchecked")
public <V> void asyncInvokeOnPartition(String serviceName, Operation op, int partitionId, ExecutionCallback<V> callback) {
op.setServiceName(serviceName).setPartitionId(partitionId).setReplicaIndex(DEFAULT_REPLICA_INDEX);
InvocationFuture future = new PartitionInvocation(invocationContext, op, DEFAULT_TRY_COUNT, DEFAULT_TRY_PAUSE_MILLIS,
DEFAULT_CALL_TIMEOUT, DEFAULT_DESERIALIZE_RESULT).invokeAsync();
if (callback != null) {
future.andThen(callback);
}
}
public void onStartAsyncOperation(Operation op) {
asyncOperations.add(op);
}
public void onCompletionAsyncOperation(Operation op) {
asyncOperations.remove(op);
}
// =============================== processing operation ===============================
@Override
public boolean isCallTimedOut(Operation op) {
// Join operations should not be checked for timeout because caller is not member of this cluster
// and can have a different clock.
if (isJoinOperation(op)) {
return false;
}
long callTimeout = op.getCallTimeout();
long invocationTime = op.getInvocationTime();
long expireTime = invocationTime + callTimeout;
if (expireTime <= 0 || expireTime == Long.MAX_VALUE) {
return false;
}
ClusterClock clusterClock = nodeEngine.getClusterService().getClusterClock();
long now = clusterClock.getClusterTime();
if (expireTime < now) {
return true;
}
return false;
}
@Override
public Map<Integer, Object> invokeOnAllPartitions(String serviceName, OperationFactory operationFactory) throws Exception {
Map<Address, List<Integer>> memberPartitions = nodeEngine.getPartitionService().getMemberPartitionsMap();
InvokeOnPartitions invokeOnPartitions = new InvokeOnPartitions(this, serviceName, operationFactory, memberPartitions);
return invokeOnPartitions.invoke();
}
@Override
public Map<Integer, Object> invokeOnPartitions(String serviceName, OperationFactory operationFactory,
Collection<Integer> partitions) throws Exception {
Map<Address, List<Integer>> memberPartitions = new HashMap<Address, List<Integer>>(3);
InternalPartitionService partitionService = nodeEngine.getPartitionService();
for (int partition : partitions) {
Address owner = partitionService.getPartitionOwnerOrWait(partition);
if (!memberPartitions.containsKey(owner)) {
memberPartitions.put(owner, new ArrayList<Integer>());
}
memberPartitions.get(owner).add(partition);
}
InvokeOnPartitions invokeOnPartitions = new InvokeOnPartitions(this, serviceName, operationFactory, memberPartitions);
return invokeOnPartitions.invoke();
}
@Override
public Map<Integer, Object> invokeOnPartitions(String serviceName, OperationFactory operationFactory, int[] partitions)
throws Exception {
return invokeOnPartitions(serviceName, operationFactory, toIntegerList(partitions));
}
@Override
public boolean send(Operation op, Address target) {
return outboundOperationHandler.send(op, target);
}
public void onMemberLeft(MemberImpl member) {
invocationMonitor.onMemberLeft(member);
}
public void reset() {
invocationRegistry.reset();
}
@Override
public void provideMetrics(MetricsRegistry registry) {
registry.scanAndRegister(this, "operation");
registry.collectMetrics(invocationRegistry, invocationMonitor, inboundResponseHandler, asyncInboundResponseHandler,
operationExecutor);
}
public void start() {
logger.finest("Starting OperationService");
initInvocationContext();
invocationMonitor.start();
operationExecutor.start();
asyncInboundResponseHandler.start();
slowOperationDetector.start();
}
private void initInvocationContext() {
ManagedExecutorService asyncExecutor = nodeEngine.getExecutionService().register(
ExecutionService.ASYNC_EXECUTOR, Runtime.getRuntime().availableProcessors(),
ASYNC_QUEUE_CAPACITY, ExecutorType.CONCRETE);
this.invocationContext = new Invocation.Context(
asyncExecutor,
nodeEngine.getClusterService().getClusterClock(),
nodeEngine.getClusterService(),
node.connectionManager,
node.nodeEngine.getExecutionService(),
nodeEngine.getProperties().getMillis(OPERATION_CALL_TIMEOUT_MILLIS),
invocationRegistry,
invocationMonitor,
nodeEngine.getLogger(Invocation.class),
node,
nodeEngine,
nodeEngine.getPartitionService(),
this,
operationExecutor,
retryCount,
serializationService,
nodeEngine.getThisAddress(),
outboundOperationHandler);
}
/**
* Shuts down invocation infrastructure.
* New invocation requests will be rejected after shutdown and all pending invocations
* will be notified with a failure response.
*/
public void shutdownInvocations() {
logger.finest("Shutting down invocations");
invocationRegistry.shutdown();
invocationMonitor.shutdown();
asyncInboundResponseHandler.shutdown();
try {
invocationMonitor.awaitTermination(TERMINATION_TIMEOUT_MILLIS);
} catch (InterruptedException e) {
//restore the interrupt.
//todo: we need a better mechanism for dealing with interruption and waiting for termination
Thread.currentThread().interrupt();
EmptyStatement.ignore(e);
}
}
public void shutdownOperationExecutor() {
logger.finest("Shutting down operation executors");
operationExecutor.shutdown();
slowOperationDetector.shutdown();
}
}