Skip to content

SchedulerFactoryBean has race condition in Quartz cluster [SPR-1812] #6506

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
spring-projects-issues opened this issue Mar 22, 2006 · 5 comments
Assignees
Labels
type: bug A general bug
Milestone

Comments

@spring-projects-issues
Copy link
Collaborator

spring-projects-issues commented Mar 22, 2006

Jasper Rosenberg opened SPR-1812 and commented

If you try to have the SchedulerFactoryBean add the same Trigger in two Quartz clustered instances (and you are only adding Triggers), you can get a race condition in registerJobsAndTriggers() where both instances don't find the Trigger, and both then try to add it using scheduleJob. In this case, the last one in will fail with an org.quartz.ObjectAlreadyExistsException, which will keep the SchedulerFactoryBean from successfully starting.

There is discussion of this issue in the Quartz forums here: http://forums.opensymphony.com/thread.jspa?messageID=45028&#45028
and here: http://forums.opensymphony.com/thread.jspa?messageID=45027&#45027

There is a similar issue in the org.quartz.plugins.xml.JobInitializationPlugin as documented here:
http://jira.opensymphony.com/browse/QUARTZ-295

Spring is actually exposed to this QUARTZ-295 problem as well because registerJobsAndTriggers() can make use of ResourceJobSchedulingDataProcessor which extends the JobSchedulingDataProcessor. However, this can only manifest itself if the user didn't provide a transactionManager to the SchedulerFactoryBean.

Unfortunately, there is no great way to fix these problems right now without some enhancements to the Quartz interface.

My best suggestion for solving the first problem right now is to catch the ObjectAlreadyExistsException thrown by this.scheduler.scheduleJob(trigger) and either
a. Simply swallow the exception, possibly with a log message indicating that this is assumed to be due to a cluster race condition and can be safely ignored.
b. Try to rescheduleJob() the Trigger if overwriteExistingJobs is true.

There isn't much that can be done about the second problem other than perhaps emphasising in the javadoc that you should be passing in a transactionManager if you are using the jobSchedulingDataLocation(s) properties.

As an aside, it is a little unclear to me why, if a Trigger is not new, but is a JobDetailAwareTrigger, SchedulerFactoryBean does not try to replace the Trigger's JobDetail. I'm not sure the current behavior would be expected, and I think it would be easy to assume the opposite based on the current SchedulerFactoryBean javadoc.


Affects: 1.2.7, 2.0 M3

Issue Links:

@spring-projects-issues
Copy link
Collaborator Author

Juergen Hoeller commented

Thanks for pointing this out in such clear detail, Jasper! You insider input is greatly appreciated :-)

I've addressed these issues as you suggested, also re-registering JobDetails held by a JobDetailAwareTrigger.

Juergen

@spring-projects-issues
Copy link
Collaborator Author

Jasper Rosenberg commented

Glad I could be helpful. I've certainly taken advantage of the great Spring-Quartz integration on my own projects.

I took a glance at the changes you made, and they look good to me. I did have one quick thought though, which was that the javadoc for the return value of addTriggerToScheduler() doesn't quite synch with the behavior of the method.

It reads:
@return <code>true</code> if the trigger was actually added, <code>false</code> if it already existed before

I think it probably should read something along the lines of:
@return <code>true</code> if the trigger was actually added or replaced, <code>false</code> if it already existed and <code>overwriteExistingJobs</code> is false

I think that if I understood the intent correctly, you would also want to return false when the scheduleJob(trigger) fails and overwriteExistingJobs is false, something like:

 if (!triggerExists) {
        try {
                this.scheduler.scheduleJob(trigger);
        }
        catch (ObjectAlreadyExistsException ex) {
                if (logger.isDebugEnabled()) {
                        logger.debug("Unexpectedly found existing trigger, assumably due to cluster race condition: " +
                                        ex.getMessage() + " - can safely be ignored");
                }
                if (this.overwriteExistingJobs) {
                        this.scheduler.rescheduleJob(trigger.getName(), trigger.getGroup(), trigger);
                }
                else { // The new block
                        return false;
                }
        }
}

Of course, all this is technically moot right now since it is the ignored return value of a private method :)

@spring-projects-issues
Copy link
Collaborator Author

Christian Greene commented

Juergen Hoeller, my team is also getting an error similar to this but while "rescheduling" the job's trigger. The catch-and-ignore put into ScheduleAccessor#addTriggerToScheduler to solve the above error only catches primary key violations when Scheduler.scheduleJob is called but doesn't cover when Scheduler.rescheduleJob is called (if the job's triggerExists).

The error causes the context to fail to load and thus the application fails to start. Can the try-catch be expanded to wrap the entire if (triggerExists) else clause?

Thanks,

The stack trace:

weblogic.application.ModuleException: Error : 1, Position : 0, Sql = INSERT INTO QRTZ_CRON_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP, CRON_EXPRESSION, TIME_ZONE_ID) VALUES('XadminQuartzScheduler', :1 , :2 , :3 , :4 ), OriginalSql = INSERT INTO QRTZ_CRON_TRIGGERS (SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP, CRON_EXPRESSION, TIME_ZONE_ID) VALUES('XadminQuartzScheduler', ?, ?, ?, ?), Error Msg = ORA-00001: unique constraint (XADMIN.QRTZ_CRON_TRIGGERS_PK) violated
:oracle.jdbc.OracleDatabaseException:ORA-00001: unique constraint (XADMIN.QRTZ_CRON_TRIGGERS_PK) violated``at oracle.jdbc.driver.T4CTTIoer11.processError(T4CTTIoer11.java:499)
at oracle.jdbc.driver.T4CTTIoer11.processError(T4CTTIoer11.java:447)
at oracle.jdbc.driver.T4C8Oall.processError(T4C8Oall.java:1055)
at oracle.jdbc.driver.T4CTTIfun.receive(T4CTTIfun.java:624)
at oracle.jdbc.driver.T4CTTIfun.doRPC(T4CTTIfun.java:253)
at oracle.jdbc.driver.T4C8Oall.doOALL(T4C8Oall.java:613)
at oracle.jdbc.driver.T4CPreparedStatement.doOall8(T4CPreparedStatement.java:227)
at oracle.jdbc.driver.T4CPreparedStatement.doOall8(T4CPreparedStatement.java:60)
at oracle.jdbc.driver.T4CPreparedStatement.executeForRows(T4CPreparedStatement.java:911)
at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java:1194)
at oracle.jdbc.driver.OraclePreparedStatement.executeInternal(OraclePreparedStatement.java:3833)
at oracle.jdbc.driver.T4CPreparedStatement.executeInternal(T4CPreparedStatement.java:1344)
at oracle.jdbc.driver.OraclePreparedStatement.executeLargeUpdate(OraclePreparedStatement.java:3918)
at oracle.jdbc.driver.OraclePreparedStatement.executeUpdate(OraclePreparedStatement.java:3898)
at oracle.jdbc.driver.OraclePreparedStatementWrapper.executeUpdate(OraclePreparedStatementWrapper.java:1062)
at weblogic.jdbc.wrapper.PreparedStatement.executeUpdate(PreparedStatement.java:170)
at dtv.data2.access.impl.jdbc.WrapperPreparedStatement.executeUpdate(WrapperPreparedStatement.java:99)
at org.quartz.impl.jdbcjobstore.CronTriggerPersistenceDelegate.insertExtendedTriggerProperties(CronTriggerPersistenceDelegate.java:78)
at org.quartz.impl.jdbcjobstore.oracle.OracleDelegate.insertTrigger(OracleDelegate.java:359)
at org.quartz.impl.jdbcjobstore.JobStoreSupport.storeTrigger(JobStoreSupport.java:1225)
at org.quartz.impl.jdbcjobstore.JobStoreSupport.replaceTrigger(JobStoreSupport.java:1503)
at org.quartz.impl.jdbcjobstore.JobStoreSupport$11.execute(JobStoreSupport.java:1478)
at org.quartz.impl.jdbcjobstore.JobStoreCMT.executeInLock(JobStoreCMT.java:245)
at org.quartz.impl.jdbcjobstore.JobStoreSupport.replaceTrigger(JobStoreSupport.java:1474)
at org.quartz.core.QuartzScheduler.rescheduleJob(QuartzScheduler.java:1115)
at org.quartz.impl.StdScheduler.rescheduleJob(StdScheduler.java:321)
at org.springframework.scheduling.quartz.SchedulerAccessor.addTriggerToScheduler(SchedulerAccessor.java:313)
at org.springframework.scheduling.quartz.SchedulerAccessor.registerJobsAndTriggers(SchedulerAccessor.java:245)
at org.springframework.scheduling.quartz.SchedulerFactoryBean.afterPropertiesSet(SchedulerFactoryBean.java:543)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1765)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1702)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:579)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:501)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:317)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:315)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:367)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:110)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1609)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1361)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:578)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:501)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:317)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:315)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:760)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:869)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:550)
at org.springframework.web.context.ContextLoader.configureAndRefreshWebApplicationContext(ContextLoader.java:409)
at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:291)
at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:103)
at weblogic.servlet.internal.EventsManager$FireContextListenerAction.run(EventsManager.java:705)
at weblogic.security.acl.internal.AuthenticatedSubject.doAs(AuthenticatedSubject.java:326)
at weblogic.security.service.SecurityManager.runAsForUserCode(SecurityManager.java:197)
at weblogic.servlet.provider.WlsSecurityProvider.runAsForUserCode(WlsSecurityProvider.java:203)
at weblogic.servlet.provider.WlsSubjectHandle.run(WlsSubjectHandle.java:71)
at weblogic.servlet.internal.EventsManager.executeContextListener(EventsManager.java:251)
at weblogic.servlet.internal.EventsManager.notifyContextCreatedEvent(EventsManager.java:204)
at weblogic.servlet.internal.EventsManager.notifyContextCreatedEvent(EventsManager.java:189)
at weblogic.servlet.internal.WebAppServletContext.preloadResources(WebAppServletContext.java:1921)
at weblogic.servlet.internal.WebAppServletContext.start(WebAppServletContext.java:3101)
at weblogic.servlet.internal.WebAppModule.startContexts(WebAppModule.java:1843)
at weblogic.servlet.internal.WebAppModule.start(WebAppModule.java:884)
at weblogic.application.internal.ExtensibleModuleWrapper$StartStateChange.next(ExtensibleModuleWrapper.java:360)
at weblogic.application.internal.ExtensibleModuleWrapper$StartStateChange.next(ExtensibleModuleWrapper.java:356)
at weblogic.application.utils.StateMachineDriver.nextState(StateMachineDriver.java:45)
at weblogic.application.internal.ExtensibleModuleWrapper.start(ExtensibleModuleWrapper.java:138)
at weblogic.application.internal.flow.ModuleListenerInvoker.start(ModuleListenerInvoker.java:124)
at weblogic.application.internal.flow.ModuleStateDriver$3.next(ModuleStateDriver.java:233)
at weblogic.application.internal.flow.ModuleStateDriver$3.next(ModuleStateDriver.java:228)
at weblogic.application.utils.StateMachineDriver.nextState(StateMachineDriver.java:45)
at weblogic.application.internal.flow.ModuleStateDriver.start(ModuleStateDriver.java:78)
at weblogic.application.internal.flow.StartModulesFlow.activate(StartModulesFlow.java:52)
at weblogic.application.internal.BaseDeployment$2.next(BaseDeployment.java:752)
at weblogic.application.utils.StateMachineDriver.nextState(StateMachineDriver.java:45)
at weblogic.application.internal.BaseDeployment.activate(BaseDeployment.java:262)
at weblogic.application.internal.SingleModuleDeployment.activate(SingleModuleDeployment.java:52)
at weblogic.application.internal.DeploymentStateChecker.activate(DeploymentStateChecker.java:165)
at weblogic.deploy.internal.targetserver.AppContainerInvoker.activate(AppContainerInvoker.java:90)
at weblogic.deploy.internal.targetserver.operations.AbstractOperation.activate(AbstractOperation.java:631)
at weblogic.deploy.internal.targetserver.operations.ActivateOperation.activateDeployment(ActivateOperation.java:171)
at weblogic.deploy.internal.targetserver.operations.ActivateOperation.doCommit(ActivateOperation.java:121)
at weblogic.deploy.internal.targetserver.operations.AbstractOperation.commit(AbstractOperation.java:348)
at weblogic.deploy.internal.targetserver.DeploymentManager.handleDeploymentCommit(DeploymentManager.java:907)
at weblogic.deploy.internal.targetserver.DeploymentManager.activateDeploymentList(DeploymentManager.java:1468)
at weblogic.deploy.internal.targetserver.DeploymentManager.handleCommit(DeploymentManager.java:459)
at weblogic.deploy.internal.targetserver.DeploymentServiceDispatcher.commit(DeploymentServiceDispatcher.java:181)
at weblogic.deploy.service.internal.targetserver.DeploymentReceiverCallbackDeliverer.doCommitCallback(DeploymentReceiverCallbackDeliverer.java:217)
at weblogic.deploy.service.internal.targetserver.DeploymentReceiverCallbackDeliverer.access$100(DeploymentReceiverCallbackDeliverer.java:14)
at weblogic.deploy.service.internal.targetserver.DeploymentReceiverCallbackDeliverer$2.run(DeploymentReceiverCallbackDeliverer.java:69)
at weblogic.work.SelfTuningWorkManagerImpl$WorkAdapterImpl.run(SelfTuningWorkManagerImpl.java:670)
at weblogic.invocation.ComponentInvocationContextManager._runAs(ComponentInvocationContextManager.java:352)
at weblogic.invocation.ComponentInvocationContextManager.runAs(ComponentInvocationContextManager.java:337)
at weblogic.work.LivePartitionUtility.doRunWorkUnderContext(LivePartitionUtility.java:57)
at weblogic.work.PartitionUtility.runWorkUnderContext(PartitionUtility.java:41)
at weblogic.work.SelfTuningWorkManagerImpl.runWorkUnderContext(SelfTuningWorkManagerImpl.java:644)
at weblogic.work.ExecuteThread.execute(ExecuteThread.java:415)
at weblogic.work.ExecuteThread.run(ExecuteThread.java:355)

@spring-projects-issues
Copy link
Collaborator Author

Juergen Hoeller commented

Christian Greene, since this is an old and closed ticket, please create a new JIRA issue for the rescheduling part. I'll address it ASAP for 5.0.9 and 4.3.19 then.

@spring-projects-issues
Copy link
Collaborator Author

spring-projects-issues commented Aug 1, 2018

Christian Greene commented

 

#21651 created

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: bug A general bug
Projects
None yet
Development

No branches or pull requests

2 participants