/* * 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.internal.metrics.MetricsProvider; import com.hazelcast.internal.metrics.MetricsRegistry; import com.hazelcast.internal.metrics.Probe; import com.hazelcast.internal.util.concurrent.MPSCQueue; import com.hazelcast.logging.ILogger; import com.hazelcast.nio.Packet; import com.hazelcast.spi.impl.PacketHandler; import com.hazelcast.spi.impl.operationexecutor.OperationHostileThread; import com.hazelcast.spi.properties.HazelcastProperties; import com.hazelcast.spi.properties.HazelcastProperty; import com.hazelcast.util.concurrent.BackoffIdleStrategy; import com.hazelcast.util.concurrent.BusySpinIdleStrategy; import com.hazelcast.util.concurrent.IdleStrategy; import java.util.concurrent.BlockingQueue; import static com.hazelcast.instance.OutOfMemoryErrorDispatcher.inspectOutOfMemoryError; import static com.hazelcast.internal.metrics.ProbeLevel.MANDATORY; import static com.hazelcast.nio.Packet.FLAG_OP_RESPONSE; import static com.hazelcast.util.EmptyStatement.ignore; import static com.hazelcast.util.Preconditions.checkNotNull; import static com.hazelcast.util.Preconditions.checkTrue; import static com.hazelcast.util.ThreadUtil.createThreadName; import static com.hazelcast.util.concurrent.BackoffIdleStrategy.createBackoffIdleStrategy; import static java.util.concurrent.TimeUnit.MICROSECONDS; import static java.util.concurrent.TimeUnit.NANOSECONDS; /** * The {@link AsyncInboundResponseHandler} is a PacketHandler that asynchronously process operation-response packets. The * actual processing is done by the {@link InboundResponseHandler}. * * So when a response is received from a remote system, it is put in the responseQueue of the ResponseThread. * Then the ResponseThread takes it from this responseQueue and calls the {@link PacketHandler} for the * actual processing. * * The reason that the IO thread doesn't immediately deals with the response is that deserializing the * {@link com.hazelcast.spi.impl.operationservice.impl.responses.Response} and let the invocation-future * deal with the response can be rather expensive. */ public class AsyncInboundResponseHandler implements PacketHandler, MetricsProvider { public static final HazelcastProperty IDLE_STRATEGY = new HazelcastProperty("hazelcast.operation.responsequeue.idlestrategy", "block"); private static final long IDLE_MAX_SPINS = 20; private static final long IDLE_MAX_YIELDS = 50; private static final long IDLE_MIN_PARK_NS = NANOSECONDS.toNanos(1); private static final long IDLE_MAX_PARK_NS = MICROSECONDS.toNanos(100); final ResponseThread responseThread; private final ILogger logger; AsyncInboundResponseHandler(ClassLoader classLoader, String hzName, ILogger logger, PacketHandler responsePacketHandler, HazelcastProperties properties) { this.logger = logger; this.responseThread = new ResponseThread(classLoader, hzName, responsePacketHandler, properties); } @Probe(name = "responseQueueSize", level = MANDATORY) public int getQueueSize() { return responseThread.responseQueue.size(); } @Override public void handle(Packet packet) { checkNotNull(packet, "packet can't be null"); checkTrue(packet.getPacketType() == Packet.Type.OPERATION, "Packet type is not OPERATION"); checkTrue(packet.isFlagRaised(FLAG_OP_RESPONSE), "FLAG_OP_RESPONSE is not set"); responseThread.responseQueue.add(packet); } @Override public void provideMetrics(MetricsRegistry registry) { registry.scanAndRegister(this, "operation"); } public void start() { responseThread.start(); } public void shutdown() { responseThread.shutdown(); } public static IdleStrategy getIdleStrategy(HazelcastProperties properties, HazelcastProperty property) { String idleStrategyString = properties.getString(property); if ("block".equals(idleStrategyString)) { return null; } else if ("busyspin".equals(idleStrategyString)) { return new BusySpinIdleStrategy(); } else if ("backoff".equals(idleStrategyString)) { return new BackoffIdleStrategy(IDLE_MAX_SPINS, IDLE_MAX_YIELDS, IDLE_MIN_PARK_NS, IDLE_MAX_PARK_NS); } else if (idleStrategyString.startsWith("backoff,")) { return createBackoffIdleStrategy(idleStrategyString); } else { throw new IllegalStateException("Unrecognized " + property.getName() + " value=" + idleStrategyString); } } /** * The ResponseThread needs to implement the OperationHostileThread interface to make sure that the OperationExecutor * is not going to schedule any operations on this task due to retry. */ private final class ResponseThread extends Thread implements OperationHostileThread { private final BlockingQueue<Packet> responseQueue; private final PacketHandler responsePacketHandler; private volatile boolean shutdown; private ResponseThread(ClassLoader classLoader, String hzName, PacketHandler responsePacketHandler, HazelcastProperties properties) { super(createThreadName(hzName, "response")); setContextClassLoader(classLoader); this.responsePacketHandler = responsePacketHandler; this.responseQueue = new MPSCQueue<Packet>(this, getIdleStrategy(properties, IDLE_STRATEGY)); } @Override public void run() { try { doRun(); } catch (InterruptedException e) { ignore(e); } catch (Throwable t) { inspectOutOfMemoryError(t); logger.severe(t); } } private void doRun() throws InterruptedException { while (!shutdown) { Packet response = responseQueue.take(); try { responsePacketHandler.handle(response); } catch (Throwable e) { inspectOutOfMemoryError(e); logger.severe("Failed to process response: " + response + " on:" + getName(), e); } } } private void shutdown() { shutdown = true; interrupt(); } } }