/* * 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.scheduledexecutor.impl; import com.hazelcast.core.HazelcastInstance; import com.hazelcast.core.HazelcastInstanceAware; import com.hazelcast.core.MembershipAdapter; import com.hazelcast.core.MembershipEvent; import com.hazelcast.instance.HazelcastInstanceImpl; import com.hazelcast.nio.Address; import com.hazelcast.partition.PartitionLostEvent; import com.hazelcast.partition.PartitionLostListener; import com.hazelcast.scheduledexecutor.IScheduledFuture; import com.hazelcast.scheduledexecutor.ScheduledTaskHandler; import com.hazelcast.scheduledexecutor.ScheduledTaskStatistics; import com.hazelcast.scheduledexecutor.StaleTaskException; import com.hazelcast.scheduledexecutor.impl.operations.CancelTaskOperation; import com.hazelcast.scheduledexecutor.impl.operations.DisposeTaskOperation; import com.hazelcast.scheduledexecutor.impl.operations.GetDelayOperation; import com.hazelcast.scheduledexecutor.impl.operations.GetResultOperation; import com.hazelcast.scheduledexecutor.impl.operations.GetStatisticsOperation; import com.hazelcast.scheduledexecutor.impl.operations.IsCanceledOperation; import com.hazelcast.scheduledexecutor.impl.operations.IsDoneOperation; import com.hazelcast.spi.InternalCompletableFuture; import com.hazelcast.spi.Operation; import com.hazelcast.spi.OperationService; import java.util.concurrent.Delayed; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import static com.hazelcast.util.ExceptionUtil.sneakyThrow; import static com.hazelcast.util.Preconditions.checkNotNull; @SuppressWarnings({"checkstyle:methodcount"}) public final class ScheduledFutureProxy<V> implements IScheduledFuture<V>, HazelcastInstanceAware, PartitionLostListener { private transient HazelcastInstance instance; private transient String partitionLostRegistration; private transient String membershipListenerRegistration; private transient boolean partitionLost; private transient boolean memberLost; private ScheduledTaskHandler handler; public ScheduledFutureProxy() { } public ScheduledFutureProxy(ScheduledTaskHandler handler) { this.handler = handler; } @Override public void setHazelcastInstance(HazelcastInstance hazelcastInstance) { unRegisterPartitionListenerIfExists(); unRegisterMembershipListenerIfExists(); this.instance = hazelcastInstance; registerPartitionListener(); registerMembershipListener(); } @Override public ScheduledTaskHandler getHandler() { return handler; } @Override public ScheduledTaskStatistics getStats() { checkAccessibleHandler(); checkAccessibleOwner(); Operation op = new GetStatisticsOperation(handler); return this.<ScheduledTaskStatistics>invoke(op).join(); } @Override public long getDelay(TimeUnit unit) { checkNotNull(unit, "Unit is null"); checkAccessibleHandler(); checkAccessibleOwner(); Operation op = new GetDelayOperation(handler, unit); return this.<Long>invoke(op).join(); } @Override public int compareTo(Delayed o) { throw new UnsupportedOperationException(); } @Override public boolean cancel(boolean mayInterruptIfRunning) { if (mayInterruptIfRunning) { // DelegateAndSkipOnConcurrentExecutionDecorator doesn't expose the Executor's future // therefore we don't have access to the runner thread to interrupt. We could access through Thread.currentThread() // inside the TaskRunner but it adds extra complexity. throw new UnsupportedOperationException("mayInterruptIfRunning flag is not supported."); } checkAccessibleHandler(); checkAccessibleOwner(); Operation op = new CancelTaskOperation(handler, mayInterruptIfRunning); return this.<Boolean>invoke(op).join(); } @Override public boolean isCancelled() { checkAccessibleHandler(); checkAccessibleOwner(); Operation op = new IsCanceledOperation(handler); return this.<Boolean>invoke(op).join(); } @Override public boolean isDone() { checkAccessibleHandler(); checkAccessibleOwner(); Operation op = new IsDoneOperation(handler); return this.<Boolean>invoke(op).join(); } private InternalCompletableFuture<V> get0() { checkAccessibleHandler(); checkAccessibleOwner(); Operation op = new GetResultOperation<V>(handler); return this.invoke(op); } @Override public V get() throws InterruptedException, ExecutionException { try { return this.get0().get(); } catch (ScheduledTaskResult.ExecutionExceptionDecorator ex) { return sneakyThrow(ex.getCause()); } } @Override public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { checkNotNull(unit, "Unit is null"); try { return this.get0().get(timeout, unit); } catch (ScheduledTaskResult.ExecutionExceptionDecorator ex) { return sneakyThrow(ex.getCause()); } } @Override public void dispose() { checkAccessibleHandler(); checkAccessibleOwner(); unRegisterPartitionListenerIfExists(); unRegisterMembershipListenerIfExists(); Operation op = new DisposeTaskOperation(handler); InternalCompletableFuture future = invoke(op); handler = null; future.join(); } @Override public void partitionLost(PartitionLostEvent event) { if (handler.getPartitionId() == event.getPartitionId() && event.getLostBackupCount() == instance.getConfig().getScheduledExecutorConfig( handler.getSchedulerName()).getDurability()) { unRegisterPartitionListenerIfExists(); this.partitionLost = true; } } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } ScheduledFutureProxy<?> proxy = (ScheduledFutureProxy<?>) o; return handler != null ? handler.equals(proxy.handler) : proxy.handler == null; } @Override public int hashCode() { return handler != null ? handler.hashCode() : 0; } private void registerPartitionListener() { if (handler.isAssignedToPartition()) { this.partitionLostRegistration = this.instance.getPartitionService().addPartitionLostListener(this); } } private void unRegisterPartitionListenerIfExists() { if (partitionLostRegistration != null) { this.instance.getPartitionService().removePartitionLostListener(this.partitionLostRegistration); } } private void registerMembershipListener() { if (handler.isAssignedToMember()) { this.membershipListenerRegistration = this.instance.getCluster().addMembershipListener(new MembershipAdapter() { @Override public void memberRemoved(MembershipEvent membershipEvent) { if (membershipEvent.getMember().getAddress().equals(handler.getAddress())) { ScheduledFutureProxy.this.memberLost = true; } } }); } } private void unRegisterMembershipListenerIfExists() { if (membershipListenerRegistration != null) { this.instance.getCluster().removeMembershipListener(membershipListenerRegistration); } } private void checkAccessibleOwner() { if (handler.isAssignedToPartition()) { if (partitionLost) { throw new IllegalStateException("Partition holding this Scheduled task was lost along with all backups."); } } else { if (memberLost) { throw new IllegalStateException("Member holding this Scheduled task was removed from the cluster."); } } } private void checkAccessibleHandler() { if (handler == null) { throw new StaleTaskException( "Scheduled task was previously disposed."); } } private <V> InternalCompletableFuture<V> invoke(Operation op) { if (handler.isAssignedToPartition()) { op.setPartitionId(handler.getPartitionId()); return invokeOnPartition(op); } else { return invokeOnAddress(op, handler.getAddress()); } } private <V> InternalCompletableFuture<V> invokeOnPartition(Operation op) { OperationService opService = ((HazelcastInstanceImpl) instance).node .getNodeEngine() .getOperationService(); return opService.invokeOnPartition(op); } private <V> InternalCompletableFuture<V> invokeOnAddress(Operation op, Address address) { OperationService opService = ((HazelcastInstanceImpl) instance).node .getNodeEngine() .getOperationService(); return opService.invokeOnTarget(op.getServiceName(), op, address); } }