/**
* Copyright 2016 Nikita Koksharov
*
* 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 org.redisson.executor;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import org.redisson.RedissonExecutorService;
import org.redisson.api.RBlockingQueue;
import org.redisson.api.RFuture;
import org.redisson.api.RedissonClient;
import org.redisson.api.RemoteInvocationOptions;
import org.redisson.client.codec.Codec;
import org.redisson.client.codec.LongCodec;
import org.redisson.client.protocol.RedisCommands;
import org.redisson.command.CommandExecutor;
import org.redisson.remote.RemoteServiceCancelRequest;
import org.redisson.remote.RemoteServiceCancelResponse;
import org.redisson.remote.RemoteServiceRequest;
import io.netty.util.Timeout;
import io.netty.util.TimerTask;
/**
*
* @author Nikita Koksharov
*
*/
public class ScheduledExecutorRemoteService extends ExecutorRemoteService {
private String requestId;
private String schedulerTasksName;
private String schedulerQueueName;
private String schedulerChannelName;
public ScheduledExecutorRemoteService(Codec codec, RedissonClient redisson, String name, CommandExecutor commandExecutor) {
super(codec, redisson, name, commandExecutor);
}
public void setRequestId(String requestId) {
this.requestId = requestId;
}
public void setSchedulerTasksName(String schedulerTasksName) {
this.schedulerTasksName = schedulerTasksName;
}
public void setSchedulerChannelName(String schedulerChannelName) {
this.schedulerChannelName = schedulerChannelName;
}
public void setSchedulerQueueName(String scheduledQueueName) {
this.schedulerQueueName = scheduledQueueName;
}
@Override
protected RFuture<Boolean> addAsync(RBlockingQueue<RemoteServiceRequest> requestQueue, RemoteServiceRequest request) {
Long startTime = (Long)request.getArgs()[3];
byte[] encodedRequest = encode(request);
if (requestId != null) {
return commandExecutor.evalWriteAsync(name, LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
// check if executor service not in shutdown state and previous task exists
"if redis.call('exists', KEYS[2]) == 0 and redis.call('hexists', KEYS[5], ARGV[2]) == 1 then "
+ "redis.call('zadd', KEYS[3], ARGV[1], ARGV[2]);"
+ "redis.call('hset', KEYS[5], ARGV[2], ARGV[3]);"
+ "redis.call('incr', KEYS[1]);"
// if new task added to queue head then publish its startTime
// to all scheduler workers
+ "local v = redis.call('zrange', KEYS[3], 0, 0); "
+ "if v[1] == ARGV[2] then "
+ "redis.call('publish', KEYS[4], ARGV[1]); "
+ "end "
+ "return 1;"
+ "end;"
+ "return 0;",
Arrays.<Object>asList(tasksCounterName, statusName, schedulerQueueName, schedulerChannelName, schedulerTasksName),
startTime, request.getRequestId(), encodedRequest);
}
return commandExecutor.evalWriteAsync(name, LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
// check if executor service not in shutdown state
"if redis.call('exists', KEYS[2]) == 0 then "
+ "redis.call('zadd', KEYS[3], ARGV[1], ARGV[2]);"
+ "redis.call('hset', KEYS[5], ARGV[2], ARGV[3]);"
+ "redis.call('incr', KEYS[1]);"
+ "local v = redis.call('zrange', KEYS[3], 0, 0); "
// if new task added to queue head then publish its startTime
// to all scheduler workers
+ "if v[1] == ARGV[2] then "
+ "redis.call('publish', KEYS[4], ARGV[1]); "
+ "end "
+ "return 1;"
+ "end;"
+ "return 0;",
Arrays.<Object>asList(tasksCounterName, statusName, schedulerQueueName, schedulerChannelName, schedulerTasksName),
startTime, request.getRequestId(), encodedRequest);
}
@Override
protected void awaitResultAsync(final RemoteInvocationOptions optionsCopy, final RemotePromise<Object> result,
final RemoteServiceRequest request, final String responseName) {
if (!optionsCopy.isResultExpected()) {
return;
}
Long startTime = 0L;
if (request != null && request.getArgs() != null && request.getArgs().length > 3) {
startTime = (Long)request.getArgs()[3];
}
long delay = startTime - System.currentTimeMillis();
if (delay > 0) {
commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
@Override
public void run(Timeout timeout) throws Exception {
ScheduledExecutorRemoteService.super.awaitResultAsync(optionsCopy, result, request, responseName);
}
}, delay, TimeUnit.MILLISECONDS);
} else {
super.awaitResultAsync(optionsCopy, result, request, responseName);
}
}
@Override
protected boolean remove(RBlockingQueue<RemoteServiceRequest> requestQueue, RemoteServiceRequest request) {
return commandExecutor.evalWrite(name, LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
// remove from scheduler queue
"if redis.call('zrem', KEYS[2], ARGV[1]) > 0 then "
+ "redis.call('hdel', KEYS[6], ARGV[1]); "
+ "if redis.call('decr', KEYS[3]) == 0 then "
+ "redis.call('del', KEYS[3]);"
+ "if redis.call('get', KEYS[4]) == ARGV[2] then "
+ "redis.call('set', KEYS[4], ARGV[3]);"
+ "redis.call('publish', KEYS[5], ARGV[3]);"
+ "end;"
+ "end;"
+ "return 1;"
+ "end;"
+ "local task = redis.call('hget', KEYS[6], ARGV[1]); "
// remove from executor queue
+ "if task ~= nil and redis.call('lrem', KEYS[1], 1, task) > 0 then "
+ "redis.call('hdel', KEYS[6], ARGV[1]); "
+ "if redis.call('decr', KEYS[3]) == 0 then "
+ "redis.call('del', KEYS[3]);"
+ "if redis.call('get', KEYS[4]) == ARGV[2] then "
+ "redis.call('set', KEYS[4], ARGV[3]);"
+ "redis.call('publish', KEYS[5], ARGV[3]);"
+ "end;"
+ "end;"
+ "return 1;"
+ "end;"
// delete scheduled task
+ "redis.call('hdel', KEYS[6], ARGV[1]); "
+ "return 0;",
Arrays.<Object>asList(requestQueue.getName(), schedulerQueueName, tasksCounterName, statusName, terminationTopicName, schedulerTasksName),
request.getRequestId(), RedissonExecutorService.SHUTDOWN_STATE, RedissonExecutorService.TERMINATED_STATE);
}
@Override
protected String generateRequestId() {
if (requestId == null) {
return super.generateRequestId();
}
return requestId;
}
public boolean cancelExecution(String requestId) {
Class<?> syncInterface = RemoteExecutorService.class;
String requestQueueName = getRequestQueueName(syncInterface);
String cancelRequestName = getCancelRequestQueueName(syncInterface, requestId);
if (!redisson.getMap(schedulerTasksName, LongCodec.INSTANCE).containsKey(requestId)) {
return false;
}
RBlockingQueue<RemoteServiceRequest> requestQueue = redisson.getBlockingQueue(requestQueueName, getCodec());
RemoteServiceRequest request = new RemoteServiceRequest(requestId);
if (remove(requestQueue, request)) {
return true;
}
RBlockingQueue<RemoteServiceCancelRequest> cancelRequestQueue = redisson.getBlockingQueue(cancelRequestName, getCodec());
cancelRequestQueue.putAsync(new RemoteServiceCancelRequest(true, requestId + ":cancel-response"));
cancelRequestQueue.expireAsync(60, TimeUnit.SECONDS);
String responseQueueName = getResponseQueueName(syncInterface, requestId + ":cancel-response");
RBlockingQueue<RemoteServiceCancelResponse> responseQueue = redisson.getBlockingQueue(responseQueueName, getCodec());
try {
RemoteServiceCancelResponse response = responseQueue.poll(60, TimeUnit.SECONDS);
if (response == null) {
return false;
}
return response.isCanceled();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
}