All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hazelcast.client.spi.impl.ClientResponseHandlerSupplier Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2008-2020, 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.spi.impl;

import com.hazelcast.client.impl.clientside.HazelcastClientInstanceImpl;
import com.hazelcast.client.impl.protocol.ClientMessage;
import com.hazelcast.client.impl.protocol.codec.ErrorCodec;
import com.hazelcast.internal.util.concurrent.MPSCQueue;
import com.hazelcast.logging.ILogger;
import com.hazelcast.spi.properties.HazelcastProperties;
import com.hazelcast.spi.properties.HazelcastProperty;
import com.hazelcast.util.MutableInteger;
import com.hazelcast.util.function.Consumer;
import com.hazelcast.util.function.Supplier;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;

import static com.hazelcast.client.spi.properties.ClientProperty.RESPONSE_THREAD_COUNT;
import static com.hazelcast.client.spi.properties.ClientProperty.RESPONSE_THREAD_DYNAMIC;
import static com.hazelcast.instance.OutOfMemoryErrorDispatcher.onOutOfMemory;
import static com.hazelcast.spi.impl.operationservice.impl.InboundResponseHandlerSupplier.getIdleStrategy;
import static com.hazelcast.util.HashUtil.hashToIndex;

/**
 * A {@link Supplier} for {@link Supplier} instance that processes responses for client
 * invocations.
 *
 * Depending on the configuration the supplier provides:
 * 
    *
  1. a on thread ClientResponseHandler (so no offloading to a different thread)
  2. *
  3. a single threaded ClientResponseHandler that offloads the response processing * a ResponseThread/li> *
  4. a multi threaded ClientResponseHandler that offloads the response processing * to a pool of ResponseThreads.
  5. *
*

* {@see InboundResponseHandlerSupplier}. */ public class ClientResponseHandlerSupplier implements Supplier> { // expert setting; we don't want to expose this to the public private static final HazelcastProperty IDLE_STRATEGY = new HazelcastProperty("hazelcast.client.responsequeue.idlestrategy", "block"); // expert setting; we don't want to expose this to the public // there can be some concurrent request from HZ itself that we need to exclude // with a single user thread doing e.g. map.get, 4 is the minimum number of concurrent invocations // before we consider the system to be actually concurrent and to process on response threads. private static final HazelcastProperty MIN_CONCURRENT_INVOCATIONS = new HazelcastProperty("hazelcast.client.responsequeue.dynamic.min.concurrent.invocations", "4"); private static final ThreadLocal INT_HOLDER = new ThreadLocal() { @Override protected MutableInteger initialValue() { return new MutableInteger(); } }; private final AbstractClientInvocationService invocationService; private final ResponseThread[] responseThreads; private final HazelcastClientInstanceImpl client; private final ILogger logger; private final Consumer responseHandler; private final boolean responseThreadsDynamic; private final int minConcurrentInvocations; public ClientResponseHandlerSupplier(AbstractClientInvocationService invocationService) { this.invocationService = invocationService; this.client = invocationService.client; this.logger = invocationService.invocationLogger; HazelcastProperties properties = client.getProperties(); this.minConcurrentInvocations = properties.getInteger(MIN_CONCURRENT_INVOCATIONS); int responseThreadCount = properties.getInteger(RESPONSE_THREAD_COUNT); if (responseThreadCount < 0) { throw new IllegalArgumentException(RESPONSE_THREAD_COUNT.getName() + " can't be smaller than 0"); } this.responseThreadsDynamic = properties.getBoolean(RESPONSE_THREAD_DYNAMIC); logger.info("Running with " + responseThreadCount + " response threads, dynamic=" + responseThreadsDynamic); this.responseThreads = new ResponseThread[responseThreadCount]; for (int k = 0; k < responseThreads.length; k++) { responseThreads[k] = new ResponseThread(invocationService.client.getName() + ".responsethread-" + k + "-"); } if (responseThreadCount == 0) { this.responseHandler = new SyncResponseHandler(); } else if (responseThreadsDynamic) { this.responseHandler = new DynamicResponseHandler(); } else { this.responseHandler = new AsyncResponseHandler(); } } public void start() { if (responseThreadsDynamic) { // when dynamic mode is enabled, response threads should not be started. // this is useful for ephemeral clients where thread creation can be a // significant part of the total execution time. return; } for (ResponseThread responseThread : responseThreads) { responseThread.start(); } } public void shutdown() { for (ResponseThread responseThread : responseThreads) { responseThread.interrupt(); } } @Override public Consumer get() { return responseHandler; } private void process(ClientMessage response) { try { handleResponse(response); } catch (Exception e) { logger.severe("Failed to process response: " + response + " on responseThread: " + Thread.currentThread().getName(), e); } } private void handleResponse(ClientMessage message) { long correlationId = message.getCorrelationId(); ClientInvocation future = invocationService.deRegisterCallId(correlationId); if (future == null) { logger.warning("No call for callId: " + correlationId + ", response: " + message); return; } if (ErrorCodec.TYPE == message.getMessageType()) { future.notifyException(client.getClientExceptionFactory().createException(message)); } else { future.notify(message); } } private ResponseThread nextResponseThread() { if (responseThreads.length == 1) { return responseThreads[0]; } else { int index = hashToIndex(INT_HOLDER.get().getAndInc(), responseThreads.length); return responseThreads[index]; } } private class ResponseThread extends Thread { private final BlockingQueue responseQueue; private final AtomicBoolean started = new AtomicBoolean(); ResponseThread(String name) { super(name); setContextClassLoader(client.getClientConfig().getClassLoader()); this.responseQueue = new MPSCQueue(this, getIdleStrategy(client.getProperties(), IDLE_STRATEGY)); } @Override public void run() { try { doRun(); } catch (OutOfMemoryError e) { onOutOfMemory(e); } catch (Throwable t) { invocationService.invocationLogger.severe(t); } } private void doRun() { while (!invocationService.isShutdown()) { ClientMessage response; try { response = responseQueue.take(); } catch (InterruptedException e) { continue; } process(response); } } private void queue(ClientMessage message) { responseQueue.add(message); } @SuppressFBWarnings(value = "IA_AMBIGUOUS_INVOCATION_OF_INHERITED_OR_OUTER_METHOD", justification = "The thread.start method is the one we want to call") private void ensureStarted() { if (!started.get() && started.compareAndSet(false, true)) { start(); } } } class SyncResponseHandler implements Consumer { @Override public void accept(ClientMessage message) { process(message); } } class AsyncResponseHandler implements Consumer { @Override public void accept(ClientMessage message) { nextResponseThread().queue(message); } } // dynamically switches between direct processing on io thread and processing on // response thread based on the number of invocations. class DynamicResponseHandler implements Consumer { @Override public void accept(ClientMessage message) { if (invocationService.concurrentInvocations() <= minConcurrentInvocations) { process(message); } else { ResponseThread responseThread = nextResponseThread(); responseThread.queue(message); responseThread.ensureStarted(); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy