Skip to content

Commit

Permalink
Move some exceptions into the API so we can reuse them. Add a propert…
Browse files Browse the repository at this point in the history
…y to make HA deployment mandatory, and fail deployment if HA doesn't come online.
  • Loading branch information
peteroyle committed Feb 18, 2016
1 parent ce7234b commit d8ec226
Show file tree
Hide file tree
Showing 27 changed files with 194 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jboss.seam.cron.impl.scheduling.exception;
package org.jboss.seam.cron.api.exception;

/**
* Exception thrown when there is a problem starting the Seam Cron module.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jboss.seam.cron.impl.scheduling.exception;
package org.jboss.seam.cron.api.exception;

/**
* Exception thrown when there is a problem starting the Seam Cron module.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jboss.seam.cron.impl.scheduling.exception;
package org.jboss.seam.cron.api.exception;

/**
* This exception indicates a probable programming error, eg using a managed bean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jboss.seam.cron.impl.scheduling.exception;
package org.jboss.seam.cron.api.exception;

/**
* Exception which is thrown when there is a problem with the configuration of
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import java.util.Properties;
import org.apache.commons.lang.StringUtils;
import org.apache.deltaspike.core.api.config.ConfigResolver;
import org.jboss.seam.cron.impl.scheduling.exception.SchedulerConfigurationException;
import org.jboss.seam.cron.api.exception.SchedulerConfigurationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@

import javax.enterprise.inject.spi.BeanManager;
import javax.inject.Inject;
import org.jboss.seam.cron.api.exception.CronProviderDestructionException;
import org.jboss.seam.cron.api.exception.CronProviderInitialisationException;

import org.jboss.seam.cron.impl.asynchronous.exception.AsynchronousMethodInvocationException;
import org.jboss.seam.cron.impl.scheduling.exception.CronProviderDestructionException;
import org.jboss.seam.cron.impl.scheduling.exception.CronProviderInitialisationException;
import org.jboss.seam.cron.spi.CronProviderLifecycle;
import org.jboss.seam.cron.spi.asynchronous.CronAsynchronousProvider;
import org.jboss.seam.cron.spi.asynchronous.Invoker;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@

import javax.enterprise.inject.spi.BeanManager;
import javax.inject.Inject;
import org.jboss.seam.cron.api.exception.CronProviderDestructionException;
import org.jboss.seam.cron.api.exception.CronProviderInitialisationException;

import org.jboss.seam.cron.impl.asynchronous.exception.AsynchronousMethodInvocationException;
import org.jboss.seam.cron.impl.scheduling.exception.CronProviderDestructionException;
import org.jboss.seam.cron.impl.scheduling.exception.CronProviderInitialisationException;
import org.jboss.seam.cron.spi.CronProviderLifecycle;
import org.jboss.seam.cron.spi.SeamCronExtension;
import org.jboss.seam.cron.spi.asynchronous.CronAsynchronousProvider;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@

import javax.enterprise.inject.spi.BeanManager;
import javax.inject.Inject;
import org.jboss.seam.cron.api.exception.CronProviderDestructionException;
import org.jboss.seam.cron.api.exception.CronProviderInitialisationException;

import org.jboss.seam.cron.impl.scheduling.exception.CronProviderDestructionException;
import org.jboss.seam.cron.impl.scheduling.exception.CronProviderInitialisationException;
import org.jboss.seam.cron.spi.CronProviderLifecycle;
import org.jboss.seam.cron.spi.queue.CronQueueProvider;
import org.jboss.seam.cron.spi.queue.RestrictDetail;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@

import javax.enterprise.inject.spi.BeanManager;
import javax.inject.Inject;
import org.jboss.seam.cron.api.exception.CronProviderDestructionException;
import org.jboss.seam.cron.api.exception.CronProviderInitialisationException;

import org.jboss.seam.cron.api.scheduling.Every;
import org.jboss.seam.cron.impl.scheduling.exception.CronProviderDestructionException;
import org.jboss.seam.cron.impl.scheduling.exception.CronProviderInitialisationException;
import org.jboss.seam.cron.spi.CronProviderLifecycle;
import org.jboss.seam.cron.spi.scheduling.trigger.IntervalTriggerDetail;
import org.jboss.seam.cron.spi.scheduling.trigger.ScheduledTriggerDetail;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@

import javax.enterprise.inject.spi.BeanManager;
import javax.inject.Inject;
import org.jboss.seam.cron.api.exception.CronProviderDestructionException;
import org.jboss.seam.cron.api.exception.CronProviderInitialisationException;

import org.jboss.seam.cron.impl.scheduling.exception.CronProviderDestructionException;
import org.jboss.seam.cron.impl.scheduling.exception.CronProviderInitialisationException;
import org.jboss.seam.cron.spi.CronProviderLifecycle;
import org.jboss.seam.cron.spi.scheduling.trigger.IntervalTriggerDetail;
import org.jboss.seam.cron.spi.scheduling.trigger.ScheduledTriggerDetail;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
import java.util.List;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import org.jboss.seam.cron.impl.scheduling.exception.CronProviderDestructionException;
import org.jboss.seam.cron.impl.scheduling.exception.CronProviderInitialisationException;
import org.jboss.seam.cron.api.exception.CronProviderDestructionException;
import org.jboss.seam.cron.api.exception.CronProviderInitialisationException;
import org.jboss.seam.cron.spi.CronProviderLifecycle;
import org.jboss.seam.cron.spi.scheduling.CronSchedulingProvider;
import org.jboss.seam.cron.spi.scheduling.trigger.IntervalTriggerDetail;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
import javax.ejb.Singleton;
import javax.ejb.Startup;
import javax.inject.Inject;
import org.jboss.seam.cron.api.exception.CronProviderInitialisationException;
import org.jboss.seam.cron.util.PropertyResolver;
import org.slf4j.Logger;

/**
Expand All @@ -34,10 +36,41 @@ public class TimerScheduleProviderEjb extends TimerScheduleProviderBase {

@PostConstruct
public void initUnlessHAIsPresent() {
if (scheduleConfigs.isHaServiceStarted()) {
log.info("HA SchedulerBean already started, skipping initialization of " + TimerScheduleProviderEjb.class.getSimpleName());
log.info("initializing the default @Startup TimerScheduleProviderEjb");
// give the HA singleton time to start up
long maxTimetoWait = 5000;
long waitPart = 500;
while (maxTimetoWait > 0) {
log.info("Waiting for HA Singleton SchedulerBean to come online ({} left)", maxTimetoWait);
try {
Thread.sleep(waitPart);
} catch (InterruptedException ex) {
throw new CronProviderInitialisationException("Woken while waiting for HA scheduler to activate, bailing out.", ex);
}
if (scheduleConfigs.isHaServiceStarted()) {
// stop waiting
maxTimetoWait = -1;
} else {
maxTimetoWait -= waitPart;
}
}
// check our status agains the configuration and decide whether to start up the non-HA version or bail out with an error
final String haSingletonMode = PropertyResolver.resolve("ha.singleton.mode", false);
final boolean haMandatory = haSingletonMode != null && haSingletonMode.equalsIgnoreCase("mandatory");
log.info("HA Singleton Mode: {}", haSingletonMode);
log.info("HA Is Mandatory: {}", haMandatory);
if (haMandatory) {
log.info("Non-HA " + TimerScheduleProviderEjb.class.getSimpleName() + " is disabled since HA mode is set to mandatory. Skipping initialization");
if (!scheduleConfigs.isHaServiceStarted()) {
throw new CronProviderInitialisationException("HA Service specified as mandatory, but it failed to start in the time allocated");
}
} else {
super.initScheduledTriggers();
if (scheduleConfigs.isHaServiceStarted()) {
log.info("HA SchedulerBean already started, skipping initialization of " + TimerScheduleProviderEjb.class.getSimpleName());
} else {
log.info("Starting the default, non-HA timer service bean " + TimerScheduleProviderEjb.class.getSimpleName());
super.initScheduledTriggers();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
*/
package org.jboss.seam.cron.scheduling.timerservice.singleton.jboss;

import javax.annotation.PostConstruct;
import javax.ejb.Lock;
import javax.ejb.LockType;
import javax.ejb.Singleton;
Expand All @@ -34,6 +35,7 @@ public class SchedulerBean extends TimerScheduleProviderBase implements Schedule

@Override
public void initialize(String info) {
LOGGER.info("Setting HA service started to true");
config.setHaServiceStarted(true);
super.initScheduledTriggers();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/**
* JBoss, Home of Professional Open Source Copyright 2011, Red Hat, Inc. and/or its affiliates, and individual contributors by the @authors
* tag. See the copyright.txt in the distribution for a full listing of individual contributors.
*
* 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.jboss.seam.cron.scheduling.test.timerservice;

import java.io.File;
import javax.inject.Inject;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.seam.cron.scheduling.timerservice.TimerScheduleProviderEjb;
import static org.jboss.seam.cron.test.SeamCronTestBase.addCronAsJar;
import static org.jboss.seam.cron.test.SeamCronTestBase.addNonCDILibraries;
import org.jboss.seam.cron.test.scheduling.beans.ScheduledBean;
import org.jboss.seam.cron.test.scheduling.tck.SeamCronSchedulingTCKTest;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import static org.junit.Assert.assertNotNull;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* NOTE: This test should actually cause an error, since the code that enforces HA deployment should fail on deployment with a runtime
* exception. Because we can't specifically test for that with Arquillian we just have to run this test manually from time to time and make
* sure it fails with the appropriate error.
*
* @author Peter Royle
*/
@RunWith(Arquillian.class)
@Ignore
public class TimerServiceSchedulingHaFailureTest {

private static Logger log = LoggerFactory.getLogger(TimerServiceSchedulingHaFailureTest.class);

@Inject
ScheduledBean bean;

@Deployment()
public static WebArchive createDefaultArchive() {
JavaArchive baseArchive = SeamCronSchedulingTCKTest.createSchedulingTckTestArchive(false, false)
.addPackages(true, TimerScheduleProviderEjb.class.getPackage());
WebArchive archive = ShrinkWrap.create(WebArchive.class, "test-long.war");
archive.addAsLibrary(baseArchive)
.addAsResource(new File("src/test/resources/cron-ha.properties"), "cron.properties")
.addAsWebInfResource(new File("src/main/resources/META-INF/beans.xml"), "beans.xml");

addNonCDILibraries(archive);
addCronAsJar(archive);

log.debug(archive.toString(true));
return archive;
}

@Test
public void isDeployed() {
assertNotNull(bean);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# JBoss, Home of Professional Open Source
# Copyright 2009, Red Hat, Inc. and/or its affiliates, and individual
# contributors by the @authors tag. See the copyright.txt in the
# distribution for a full listing of individual contributors.
#
# 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.
#
# This file defines the set of available schedules in the form:
# name=schedule
# Each schedule may be used for ne or more jobs. If more than one job is
# is assigned to a single schedule then those jobs will be executed in a
# round robin style.

# Open issues:
# 1. For round robin, what if the previous job has not completed? Should it
# wait until next scheduled time or should it start as soon as the previous
# job finishes?
# 2. What is the best interpretation of 12:00/5? For a single job it would
# most obviously be that the job would run every 5 minutes, starting at 12:00.
# But if you added multiple jobs, would they continue to round robin around
# the clock or would they only ru until all the jobs have been executed once,
# and then wait for 12:00 to come again?

# This schedule is called "test.one" and runs every 5 seconds
test.one=*/5 * * ? * *

# This schedule is called "test.two" and runs at precisely 1:12 PM every day
test.two=13:12

# This schedule is called "test.three" and will execute every 5 minutes,
# starting at 12:00PM.
test.three=12:00/5
# could also be per-job,
test.three.qeueumode=delayed

# HACK (because we can't actually set a system property with the version of Arquillian and JBoss AS we're using)
test.system.property=*/5 * * ? * *

# As per default, if the HA singleton doesn't start, we'll just fall back to a timerservice per JVM
# (use 'mandatory' to fail on startup if the HA singleton doesn't start)
ha.singleton.mode=mandatory
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#
# JBoss, Home of Professional Open Source
# Copyright 2009, Red Hat, Inc. and/or its affiliates, and individual
# contributors by the @authors tag. See the copyright.txt in the
Expand All @@ -14,16 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# 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.
#
# This file defines the set of available schedules in the form:
# name=schedule
# Each schedule may be used for ne or more jobs. If more than one job is
Expand Down Expand Up @@ -54,3 +43,7 @@ test.three.qeueumode=delayed

# HACK (because we can't actually set a system property with the version of Arquillian and JBoss AS we're using)
test.system.property=*/5 * * ? * *

# As per default, if the HA singleton doesn't start, we'll just fall back to a timerservice per JVM
# (use 'mandatory' to fail on startup if the HA singleton doesn't start)
#ha.singleton.mode=optional
4 changes: 3 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,9 @@ JBoss AS/EAP:
</jboss-deployment-structure>
```
* Note that HA Singleton mode will only be activated when using the standalone-ha.xml or standalone-full-ha.xml server configurations.
Otherwise it will default to the non-HA behavior of executing each job on all available nodes at once.
Otherwise it will default to the non-HA behavior of executing each job on all available nodes at once. To force HA mode set
"ha.singleton.mode=mandatory" as a system property or in your cron.properties file, which will cause the deployment to fail if the
HA singleton timer service fails to come online within 5 seconds of deployment.

Note: This is only supported in JBoss AS/EAP at the moment.
On other application servers each scheduled observer method will be executed on all server instances at the same time.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
*/
package org.jboss.seam.cron.spi;

import org.jboss.seam.cron.impl.scheduling.exception.CronProviderDestructionException;
import org.jboss.seam.cron.impl.scheduling.exception.CronProviderInitialisationException;
import org.jboss.seam.cron.api.exception.CronProviderDestructionException;
import org.jboss.seam.cron.api.exception.CronProviderInitialisationException;
import org.jboss.seam.cron.spi.scheduling.CronSchedulingProvider;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
*/
package org.jboss.seam.cron.spi;

import org.jboss.seam.cron.impl.scheduling.exception.CronProviderInitialisationException;
import org.jboss.seam.cron.api.exception.CronProviderInitialisationException;
import org.jboss.seam.cron.spi.scheduling.CronSchedulingInstaller;
import java.util.HashSet;
import java.util.Set;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
import javax.interceptor.InvocationContext;
import org.jboss.seam.cron.api.asynchronous.Asynchronous;
import org.jboss.seam.cron.api.queue.Queue;
import org.jboss.seam.cron.impl.scheduling.exception.InternalException;
import org.jboss.seam.cron.api.exception.InternalException;
import org.jboss.seam.cron.spi.SeamCronExtension;
import org.slf4j.Logger;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
import javax.interceptor.InvocationContext;
import org.jboss.seam.cron.api.asynchronous.AsyncResult;
import org.jboss.seam.cron.api.asynchronous.Asynchronous;
import org.jboss.seam.cron.impl.scheduling.exception.InternalException;
import org.jboss.seam.cron.api.exception.InternalException;
import org.slf4j.Logger;
import static org.jboss.seam.cron.spi.asynchronous.AsynchronousInterceptor.INVOKED_IN_THREAD;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
import javax.inject.Inject;
import org.jboss.seam.cron.api.queue.Queue;
import org.jboss.seam.cron.api.restriction.AsyncRestriction;
import org.jboss.seam.cron.impl.scheduling.exception.CronProviderInitialisationException;
import org.jboss.seam.cron.api.exception.CronProviderInitialisationException;
import org.jboss.seam.cron.util.CdiUtils;
import org.slf4j.Logger;

Expand Down
Loading

0 comments on commit d8ec226

Please sign in to comment.