/* * 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.HazelcastInstanceNotActiveException; import com.hazelcast.core.HazelcastOverloadException; import com.hazelcast.core.MemberLeftException; import com.hazelcast.internal.metrics.MetricsProvider; import com.hazelcast.internal.metrics.MetricsRegistry; import com.hazelcast.internal.metrics.Probe; import com.hazelcast.logging.ILogger; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeoutException; import static com.hazelcast.internal.metrics.ProbeLevel.MANDATORY; import static com.hazelcast.spi.OperationAccessor.deactivate; import static com.hazelcast.spi.OperationAccessor.setCallId; /** * The InvocationsRegistry is responsible for the registration of all pending invocations. Using the InvocationRegistry the * Invocation and its response(s) can be linked to each other. * <p/> * When an invocation is registered, a callId is determined. Based on this call-id, when a * {@link com.hazelcast.spi.impl.operationservice.impl.responses.Response} comes in, the * appropriate invocation can be looked up. * <p/> * Some idea's: * - use an ringbuffer to store all invocations instead of a CHM. The call-id can be used as sequence-id for this * ringbuffer. It can be that you run in slots that have not been released; if that happens, just keep increasing * the sequence (although you now get sequence-gaps). * - pre-allocate all invocations. Because the ringbuffer has a fixed capacity, pre-allocation should be easy. Also * the PartitionInvocation and TargetInvocation can be folded into Invocation. */ public class InvocationRegistry implements Iterable<Invocation>, MetricsProvider { private static final int CORE_SIZE_CHECK = 8; private static final int CORE_SIZE_FACTOR = 4; private static final int CONCURRENCY_LEVEL = 16; private static final int INITIAL_CAPACITY = 1000; private static final float LOAD_FACTOR = 0.75f; private static final double HUNDRED_PERCENT = 100d; @Probe(name = "invocations.pending", level = MANDATORY) private final ConcurrentMap<Long, Invocation> invocations; private final ILogger logger; private final CallIdSequence callIdSequence; private volatile boolean alive = true; public InvocationRegistry(ILogger logger, CallIdSequence callIdSequence) { this.logger = logger; this.callIdSequence = callIdSequence; int coreSize = Runtime.getRuntime().availableProcessors(); boolean reallyMultiCore = coreSize >= CORE_SIZE_CHECK; int concurrencyLevel = reallyMultiCore ? coreSize * CORE_SIZE_FACTOR : CONCURRENCY_LEVEL; this.invocations = new ConcurrentHashMap<Long, Invocation>(INITIAL_CAPACITY, LOAD_FACTOR, concurrencyLevel); } @Override public void provideMetrics(MetricsRegistry registry) { registry.scanAndRegister(this, "operation"); } @Probe(name = "invocations.usedPercentage") private double invocationsUsedPercentage() { int maxConcurrentInvocations = callIdSequence.getMaxConcurrentInvocations(); if (maxConcurrentInvocations == Integer.MAX_VALUE) { return 0; } return (HUNDRED_PERCENT * invocations.size()) / maxConcurrentInvocations; } @Probe(name = "invocations.lastCallId") long getLastCallId() { return callIdSequence.getLastCallId(); } /** * Registers an invocation. * * @param invocation The invocation to register. * @return false when InvocationRegistry is not alive and registration is not successful, true otherwise */ public boolean register(Invocation invocation) { final long callId; try { boolean force = invocation.op.isUrgent() || invocation.isRetryCandidate(); callId = callIdSequence.next(force); } catch (TimeoutException e) { throw new HazelcastOverloadException("Failed to start invocation due to overload: " + invocation, e); } try { // Fails with IllegalStateException if the operation is already active setCallId(invocation.op, callId); } catch (IllegalStateException e) { callIdSequence.complete(); throw e; } invocations.put(callId, invocation); if (!alive) { invocation.notifyError(new HazelcastInstanceNotActiveException()); return false; } return true; } /** * Deregisters an invocation. If the associated operation is inactive, takes no action and returns {@code false}. * This ensures the idempotence of deregistration. * * @param invocation The Invocation to deregister. * @return {@code true} if this call deregistered the invocation; {@code false} if the invocation wasn't registered */ public boolean deregister(Invocation invocation) { if (!deactivate(invocation.op)) { return false; } invocations.remove(invocation.op.getCallId()); callIdSequence.complete(); return true; } /** * Returns the number of pending invocations. * * @return the number of pending invocations. */ public int size() { return invocations.size(); } @Override public Iterator<Invocation> iterator() { return invocations.values().iterator(); } /** * Intention to expose the entry set is to mutate it. * * @return set of invocations in this registry */ public Set<Map.Entry<Long, Invocation>> entrySet() { return invocations.entrySet(); } /** * Gets the invocation for the given call id. * * @param callId the callId. * @return the Invocation for the given callId, or null if no invocation was found. */ public Invocation get(long callId) { return invocations.get(callId); } public void reset() { for (Invocation invocation : this) { try { invocation.notifyError(new MemberLeftException()); } catch (Throwable e) { logger.warning(invocation + " could not be notified with reset message -> " + e.getMessage()); } } } public void shutdown() { alive = false; for (Invocation invocation : this) { try { invocation.notifyError(new HazelcastInstanceNotActiveException()); } catch (Throwable e) { logger.warning(invocation + " could not be notified with shutdown message -> " + e.getMessage(), e); } } } }