/*
* 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.impl;
import com.hazelcast.instance.OutOfMemoryErrorDispatcher;
import com.hazelcast.internal.partition.impl.MigrationManager.MigrateTask;
import com.hazelcast.logging.ILogger;
import com.hazelcast.spi.properties.GroupProperty;
import java.util.concurrent.TimeUnit;
import static com.hazelcast.util.ThreadUtil.createThreadName;
import static java.lang.Math.max;
/**
* MigrationThread is responsible to execute migration related tasks submitted to its
* migration-queue.
*/
class MigrationThread extends Thread implements Runnable {
private static final long DEFAULT_MIGRATION_SLEEP_INTERVAL = 250L;
private final MigrationManager migrationManager;
private final MigrationQueue queue;
private final ILogger logger;
/**
* Time in milliseconds to sleep after {@link MigrateTask}
*/
private final long partitionMigrationInterval;
/**
* Time in milliseconds to sleep when the migration queue is empty or migrations are not allowed
*/
private final long sleepTime;
private volatile MigrationRunnable activeTask;
private volatile boolean running = true;
MigrationThread(MigrationManager migrationManager, String hzName, ILogger logger,
MigrationQueue queue) {
super(createThreadName(hzName, "migration"));
this.migrationManager = migrationManager;
this.queue = queue;
partitionMigrationInterval = migrationManager.partitionMigrationInterval;
sleepTime = max(DEFAULT_MIGRATION_SLEEP_INTERVAL, partitionMigrationInterval);
this.logger = logger;
}
@Override
public void run() {
try {
while (running) {
doRun();
}
} catch (InterruptedException e) {
if (logger.isFinestEnabled()) {
logger.finest("MigrationThread is interrupted: " + e.getMessage());
}
} catch (OutOfMemoryError e) {
OutOfMemoryErrorDispatcher.onOutOfMemory(e);
} finally {
queue.clear();
}
}
/**
* Polls the migration queue and processes the tasks, sleeping if there are no tasks, if migration is not allowed or
* if configured to do so (see {@link GroupProperty#PARTITION_MIGRATION_INTERVAL}).
*
* @throws InterruptedException if the sleep was interrupted
*/
private void doRun() throws InterruptedException {
boolean migrating = false;
for (; ; ) {
if (!migrationManager.isMigrationAllowed()) {
break;
}
MigrationRunnable runnable = queue.poll(1, TimeUnit.SECONDS);
if (runnable == null) {
break;
}
migrating |= runnable instanceof MigrationManager.MigrateTask;
processTask(runnable);
if (migrating && partitionMigrationInterval > 0) {
Thread.sleep(partitionMigrationInterval);
}
}
boolean hasNoTasks = !queue.hasMigrationTasks();
if (hasNoTasks) {
if (migrating) {
logger.info("All migration tasks have been completed, queues are empty.");
}
Thread.sleep(sleepTime);
} else if (!migrationManager.isMigrationAllowed()) {
Thread.sleep(sleepTime);
}
}
private boolean processTask(MigrationRunnable runnable) {
try {
if (runnable == null || !running) {
return false;
}
activeTask = runnable;
runnable.run();
} catch (Throwable t) {
logger.warning(t);
} finally {
queue.afterTaskCompletion(runnable);
activeTask = null;
}
return true;
}
MigrationRunnable getActiveTask() {
return activeTask;
}
/**
* Interrupts the migration thread and joins on it.
* <strong>Must not be called on the migration thread itself</strong> because it will result in infinite blocking.
*/
void stopNow() {
assert currentThread() != this : "stopNow must not be called on the migration thread";
running = false;
queue.clear();
interrupt();
boolean currentThreadInterrupted = false;
while (true) {
try {
join();
} catch (InterruptedException e) {
currentThreadInterrupted = true;
continue;
}
break;
}
if (currentThreadInterrupted) {
currentThread().interrupt();
}
}
}