/*
* 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.internal.partition.operation;
import com.hazelcast.core.Member;
import com.hazelcast.core.MemberLeftException;
import com.hazelcast.internal.partition.InternalPartitionService;
import com.hazelcast.internal.partition.MigrationCycleOperation;
import com.hazelcast.internal.partition.MigrationInfo;
import com.hazelcast.internal.partition.PartitionRuntimeState;
import com.hazelcast.internal.partition.impl.InternalPartitionServiceImpl;
import com.hazelcast.internal.partition.impl.PartitionDataSerializerHook;
import com.hazelcast.logging.ILogger;
import com.hazelcast.nio.Address;
import com.hazelcast.nio.ObjectDataInput;
import com.hazelcast.nio.ObjectDataOutput;
import com.hazelcast.spi.ExceptionAction;
import com.hazelcast.spi.NodeEngine;
import com.hazelcast.spi.OperationService;
import com.hazelcast.spi.exception.TargetNotMemberException;
import com.hazelcast.spi.impl.NodeEngineImpl;
import com.hazelcast.spi.impl.operationservice.InternalOperationService;
import com.hazelcast.util.Preconditions;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Used for committing a promotion on destination. Sent by the master to update the partition table on destination and
* commit the promotion.
* The promotion is executed in two stages which are denoted by the {@link #beforeStateCompleted} property. First it invokes
* {@link BeforePromotionOperation}s for every promoted partition. After all operations return it will reschedule itself
* and finalize the promotions by sending {@link FinalizePromotionOperation} for every promotion.
*/
public class PromotionCommitOperation extends AbstractPartitionOperation implements MigrationCycleOperation {
private PartitionRuntimeState partitionState;
private Collection<MigrationInfo> promotions;
private String expectedMemberUuid;
private transient boolean success;
// Used while PromotionCommitOperation is running to separate before and after phases
private transient boolean beforeStateCompleted;
public PromotionCommitOperation() {
}
public PromotionCommitOperation(PartitionRuntimeState partitionState, Collection<MigrationInfo> promotions,
String expectedMemberUuid) {
Preconditions.checkNotNull(promotions);
this.partitionState = partitionState;
this.promotions = promotions;
this.expectedMemberUuid = expectedMemberUuid;
}
@Override
public void beforeRun() throws Exception {
if (beforeStateCompleted) {
return;
}
NodeEngine nodeEngine = getNodeEngine();
final Member localMember = nodeEngine.getLocalMember();
if (!localMember.getUuid().equals(expectedMemberUuid)) {
throw new IllegalStateException("This " + localMember
+ " is promotion commit destination but most probably it's restarted "
+ "and not the expected target.");
}
Address masterAddress = nodeEngine.getMasterAddress();
Address callerAddress = getCallerAddress();
if (!callerAddress.equals(masterAddress)) {
throw new IllegalStateException("Caller is not master node! Caller: " + callerAddress
+ ", Master: " + masterAddress);
}
}
@Override
public void run() {
if (beforeStateCompleted) {
finalizePromotion();
}
}
@Override
public void afterRun() throws Exception {
if (!beforeStateCompleted) {
// Triggering before-promotion tasks in afterRun() after response phase is done,
// to avoid inadvertently reading `beforeStateCompleted` as true when asked to send a response.
// `beforeStateCompleted` will be set when all before-promotion tasks are completed.
beforePromotion();
}
}
/**
* Sends {@link BeforePromotionOperation}s for all promotions and register a callback on each operation to track when
* operations are finished.
*/
private void beforePromotion() {
NodeEngineImpl nodeEngine = (NodeEngineImpl) getNodeEngine();
InternalOperationService operationService = nodeEngine.getOperationService();
InternalPartitionServiceImpl partitionService = getService();
ILogger logger = getLogger();
if (logger.isFineEnabled()) {
logger.fine("Submitting BeforePromotionOperations for " + promotions.size() + " promotions.");
}
Runnable beforePromotionsCallback = new BeforePromotionOperationCallback(this, new AtomicInteger(promotions.size()));
for (MigrationInfo promotion : promotions) {
if (logger.isFinestEnabled()) {
logger.finest("Submitting BeforePromotionOperation for promotion: " + promotion);
}
BeforePromotionOperation op = new BeforePromotionOperation(promotion, beforePromotionsCallback);
op.setPartitionId(promotion.getPartitionId()).setNodeEngine(nodeEngine).setService(partitionService);
operationService.execute(op);
}
}
/** Processes the sent partition state and sends {@link FinalizePromotionOperation} for all promotions. */
private void finalizePromotion() {
NodeEngine nodeEngine = getNodeEngine();
InternalPartitionServiceImpl partitionService = getService();
OperationService operationService = nodeEngine.getOperationService();
partitionState.setEndpoint(getCallerAddress());
success = partitionService.processPartitionRuntimeState(partitionState);
ILogger logger = getLogger();
if (logger.isFineEnabled()) {
logger.fine("Submitting FinalizePromotionOperations for " + promotions.size() + " promotions. Result: " + success);
}
for (MigrationInfo promotion : promotions) {
if (logger.isFinestEnabled()) {
logger.finest("Submitting FinalizePromotionOperation for promotion: " + promotion + ". Result: " + success);
}
FinalizePromotionOperation op = new FinalizePromotionOperation(promotion, success);
op.setPartitionId(promotion.getPartitionId()).setNodeEngine(nodeEngine).setService(partitionService);
operationService.execute(op);
}
}
@Override
public int getId() {
return PartitionDataSerializerHook.PROMOTION_COMMIT;
}
/**
* Checks if all {@link BeforePromotionOperation}s have been executed.
* On completion sets the {@link #beforeStateCompleted} to {@code true} and reschedules this {@link PromotionCommitOperation}.
*/
private static class BeforePromotionOperationCallback implements Runnable {
private final PromotionCommitOperation promotionCommitOperation;
private final AtomicInteger tasks;
BeforePromotionOperationCallback(PromotionCommitOperation promotionCommitOperation, AtomicInteger tasks) {
this.promotionCommitOperation = promotionCommitOperation;
this.tasks = tasks;
}
@Override
public void run() {
final int remainingTasks = tasks.decrementAndGet();
ILogger logger = promotionCommitOperation.getLogger();
if (logger.isFinestEnabled()) {
logger.finest("Remaining before promotion tasks: " + remainingTasks);
}
if (remainingTasks == 0) {
logger.fine("All before promotion tasks are completed.");
promotionCommitOperation.onBeforePromotionsComplete();
}
}
}
/** Reruns this operation with {@link #beforeStateCompleted} set to {@code true}. */
private void onBeforePromotionsComplete() {
beforeStateCompleted = true;
getNodeEngine().getOperationService().execute(this);
}
@Override
public boolean returnsResponse() {
return beforeStateCompleted;
}
@Override
public Object getResponse() {
return success;
}
@Override
public String getServiceName() {
return InternalPartitionService.SERVICE_NAME;
}
@Override
public ExceptionAction onInvocationException(Throwable throwable) {
if (throwable instanceof MemberLeftException
|| throwable instanceof TargetNotMemberException) {
return ExceptionAction.THROW_EXCEPTION;
}
return super.onInvocationException(throwable);
}
@Override
public void onExecutionFailure(Throwable e) {
// promotion failed, should return failure result
beforeStateCompleted = true;
}
@Override
protected void readInternal(ObjectDataInput in) throws IOException {
super.readInternal(in);
expectedMemberUuid = in.readUTF();
partitionState = new PartitionRuntimeState();
partitionState.readData(in);
int len = in.readInt();
if (len > 0) {
promotions = new ArrayList<MigrationInfo>(len);
for (int i = 0; i < len; i++) {
MigrationInfo migrationInfo = new MigrationInfo();
migrationInfo.readData(in);
promotions.add(migrationInfo);
}
}
}
@Override
protected void writeInternal(ObjectDataOutput out) throws IOException {
super.writeInternal(out);
out.writeUTF(expectedMemberUuid);
partitionState.writeData(out);
int len = promotions.size();
out.writeInt(len);
for (MigrationInfo migrationInfo : promotions) {
migrationInfo.writeData(out);
}
}
}