com.nesscomputing.service.discovery.job.ZookeeperJobProcessor Maven / Gradle / Ivy
/**
* Copyright (C) 2012 Ness Computing, Inc.
*
* 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.nesscomputing.service.discovery.job;
import static java.lang.String.format;
import com.nesscomputing.lifecycle.Lifecycle;
import com.nesscomputing.lifecycle.LifecycleListener;
import com.nesscomputing.lifecycle.LifecycleStage;
import com.nesscomputing.service.discovery.client.DiscoveryClientConfig;
import com.nesscomputing.service.discovery.client.DiscoveryClientModule;
import java.io.IOException;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.ZooKeeper;
import com.google.common.base.Throwables;
import com.google.inject.Inject;
import com.google.inject.name.Named;
/**
* Accept jobs to be run on top of zookeeper and executes them.
*/
public class ZookeeperJobProcessor
{
private final BlockingQueue jobQueue = new ArrayBlockingQueue(20);
private final Thread processingThread;
@Inject
public ZookeeperJobProcessor(@Named(DiscoveryClientModule.ZOOKEEPER_CONNECT_NAME) final String connectString,
final DiscoveryClientConfig discoveryClientConfig)
{
this.processingThread = new Thread(new JobProcessingRunnable(connectString, discoveryClientConfig.getTickInterval().getMillis()));
this.processingThread.setName("zookeeper-job-processor");
this.processingThread.setDaemon(true);
}
@Inject(optional=true)
void injectLifecycle(final Lifecycle lifecycle)
{
lifecycle.addListener(LifecycleStage.START_STAGE, new LifecycleListener() {
@Override
public void onStage(final LifecycleStage stage) {
ZookeeperJobProcessor.this.start();
}
});
lifecycle.addListener(LifecycleStage.STOP_STAGE, new LifecycleListener() {
@Override
public void onStage(final LifecycleStage stage) {
ZookeeperJobProcessor.this.stop();
}
});
}
public void start()
{
processingThread.start();
}
public void stop()
{
processingThread.interrupt();
try {
processingThread.join();
}
catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
}
public Future submitJob(final ZookeeperJob job, final int retries, final int timeout, final TimeUnit timeoutUnit)
throws InterruptedException
{
final JobWrapper jobWrapper = new JobWrapper(job, retries);
if (jobQueue.offer(jobWrapper, timeout, timeoutUnit)) {
return jobWrapper;
}
else {
return null;
}
}
public Future submitJob(final ZookeeperJob job, final int retries)
throws InterruptedException
{
final JobWrapper jobWrapper = new JobWrapper(job, retries);
if (jobQueue.offer(jobWrapper)) {
return jobWrapper;
}
else {
return null;
}
}
public class JobProcessingRunnable extends ZookeeperProcessingTask
{
private int retries = 0;
private JobWrapper currentJob = null;
JobProcessingRunnable(final String connectString,
final long tickInterval)
{
super(connectString, tickInterval);
}
@Override
public long determineCurrentGeneration(final AtomicLong generation, final long tick)
{
if (!jobQueue.isEmpty()) {
return generation.incrementAndGet();
}
else {
return generation.get();
}
}
@Override
protected boolean doWork(final ZooKeeper zookeeper, final long tick) throws KeeperException, IOException
{
boolean result = true;
if (currentJob == null) {
currentJob = jobQueue.poll();
retries = currentJob.getRetries();
}
if (currentJob != null) {
if (currentJob.isCancelRequested()) {
currentJob.complete(JobWrapper.State.CANCEL_STATE);
currentJob = null;
}
else {
try {
if (currentJob.execute(zookeeper)) {
currentJob.complete(JobWrapper.State.DONE_STATE);
currentJob = null;
}
else {
result = processRetry();
}
}
catch (Throwable t) {
processRetry();
Throwables.propagateIfInstanceOf(t, KeeperException.class);
Throwables.propagateIfInstanceOf(t, IOException.class);
throw Throwables.propagate(t);
}
}
}
return result;
}
private boolean processRetry()
{
if (--retries > 0) {
// Run was not successful, run retries.
return false;
}
else {
currentJob.complete(JobWrapper.State.FAILED_STATE);
currentJob = null;
// Run was successful, job failed.
return true;
}
}
}
public static class JobWrapper implements Future
{
static enum State {
RUNNING_STATE, CANCEL_STATE, DONE_STATE, FAILED_STATE;
}
private final ZookeeperJob job;
private final int retries;
private final CountDownLatch latch = new CountDownLatch(1);
private volatile State state = State.RUNNING_STATE;
private volatile boolean cancelRequested = false;
JobWrapper(final ZookeeperJob job, final int retries)
{
this.job = job;
this.retries = retries;
}
@Override
public boolean cancel(boolean mayInterruptIfRunning)
{
if (isCancelled()) {
return false;
}
else {
cancelRequested = true;
return true;
}
}
@Override
public boolean isCancelled()
{
return state == State.CANCEL_STATE;
}
@Override
public boolean isDone()
{
return state == State.DONE_STATE;
}
@Override
public ZookeeperJob get() throws InterruptedException, ExecutionException
{
latch.await();
return doGet();
}
@Override
public ZookeeperJob get(final long timeout, final TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
{
if (!latch.await(timeout, unit)) {
throw new TimeoutException();
}
return doGet();
}
private ZookeeperJob doGet() throws ExecutionException
{
switch (state) {
case FAILED_STATE:
throw new ExecutionException("Job could not be completed!", null);
case DONE_STATE:
case CANCEL_STATE:
return job;
default:
throw new ExecutionException(format("JobWrapper is in an illegal state (%s)!", state), null);
}
}
boolean execute(final ZooKeeper zookeeper) throws IOException, KeeperException
{
return job.execute(zookeeper);
}
int getRetries()
{
return retries;
}
boolean isCancelRequested()
{
return cancelRequested;
}
void complete(final State state)
{
this.state = state;
latch.countDown();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy