GithubHelp home page GithubHelp logo

grails-jesque's Introduction

Grails Jesque

Build Status

Jesque is an implementation of Resque in Java. It is fully-interoperable with the Ruby and Node.js (Coffee-Resque) implementations.

The grails jesque plugin uses jesque and the grails redis plugin as a dependency. While it uses jesque for the core functionality it makes it groovier to use in grails.

There is also a grails jesque-web plugin initially ported from the jesque-web spring-mvc app, which itself was based on the Sinatra web app resque-web in resque. Either UI will allow you to see what's in your queues and view and re-process failed messages.

A scheduler (a la Quartz) has been added to support scheduled injection of jobs. The syntax is very similar to the grails Quartz plugin.

Demo Project

There is a demo project located in github.

How do I use it?

Add the jesque plugin to grails, it will automatically pull in jesque with it's dependencies, and the grails redis plugin.

dependencies {
    compile('org.grails.plugins:jesque:1.2.1')
}

You must also have redis installed in your environment.

Example to enqueue

class BackgroundJob {
    def someOtherService //auto-wiring supported

    def perform( arg1, arg2 ) {
        def domainObject = DomainClass.get(arg1) //GORM supported
        domainObject.message = arg2
        domainObject.save()
    }
}

class SomeOtherClass {
    def jesqueService

    def doWorkAsync() {
        jesqueService.enqueue( 'myQueueName', BackgroundJob.simpleName, 1, 'hi there')
    }

    def doWorkAsyncLater() {
            jesqueService.enqueueAt(System.currentTimeMillis() + (1000 * 60), 'myQueueName', BackgroundJob.simpleName, 1, 'hi there')
        }
}

Workers can be started manually by calling

    jesqueService.startWorker( 'DemoJesqueJobQueue', DemoJesqueJob.simpleName, DemoJesqueJob )

or automatically upon start-up with the following config

---
grails:
    redis:
        port: 6379
        host: localhost
    jesque:
        enabled: true
        pruneWorkersOnStartup: true
        createWorkersOnStartup: true
        schedulerThreadActive: true
        startPaused: false
        autoFlush: true
        workers:
            DemoJesqueJobPool:
                queueNames:
                    - "DemoQueue1"
                    - "DemoQueue2"
                jobTypes:
                    - org.grails.jesque.demo.DemoJesqueJob
                    - org.grails.jesque.demo.DemoTwoJesqueJob

The redis pool used is configured in the redis plugin:

grails:
    redis:
        host: localhost
        port: 6379

Or using sentinels

grails:
    redis:
        sentinels:
            - 10.0.0.1:26379
            - 10.0.0.2:26379
        masterName: foobar        

Jobs

Jobs should be placed in grails-app/jobs similar to the Quartz plugin. However, to not clash with quartz, and to retain similarties with resque, the method to execute must be called perform.

You can run the script create-jesque-job to create a shell of a job for you automatically. The following will create a BackgroundJob in the grails-app/jobs folder.

grails create-jesque-job org.grails.jesque.demo.DemoJesqueJob
package org.grails.jesque.demo

import groovy.util.logging.Slf4j

@Slf4j
class DemoJesqueJob {

    static queue = 'DemoJesqueJobQueue'
    static workerPool = 'DemoJesqueJobPool'

    static triggers = {
        cron name: 'DemoJesqueJobTrigger', cronExpression: '0/15 * * * * ? *'
    }

    def perform() {
        log.info "Executing Job"
    }
}

Custom Worker Listener

You can define one or more custom WorkerListener classes that will be automatically added to all workers started from within jesqueService. You can implement the GrailsApplicationAware interface if you need access to the grailsApplication in your worker listener.

grails {
    jesque {
        custom {
            listener.clazz = [LoggingWorkerListener] // accepts String, Class or List<String> or List<Class>
        }
    }
}

All Listeners have to implement the WorkerListener Interface otherwise they will simply be ignored

Roadmap

  • Upgrade custom Listener and Worker to grails 3 support
  • Ability to execute methods on services without creating a job object
  • Wrap above ability automatically with annotation and dynamically creating a method with the same name + "Async" suffix
  • Create grails/groovy docs (gdoc?) to extensively document options
  • Dynamic wake time of delayed jobs thread to reduce polling

Release Notes

  • 0.2.0 - released 2011-10-17
    • First publicly announced version
  • 0.3.0 - released 2012-02-03
    • First implementation of scheduler
  • 0.4.0 - released 2012-06-06
    • Gracefully shutdown threads
    • Handle changes to scheduled jobs during development
    • Upgrade to Jedis 2.1.0 (Note, this is binary incompatible with Jedis 2.0.0, Grails Jesque < 0.4.0 will not run with Jedis 2.1.0 and >= 0.4.0 must run with Jedis >= 2.1.0)
    • Change artefact name from "Job" to "JesqueJob" to not clash with other grails plugins (e.g. quartz) that use an artefact name of "Job" (issue #14)
  • 0.5.0 - released 2012-11-20
    • Add delayed job queue implementation
    • Ability to use grailsConfiguration in triggers closure
  • 0.5.1 - released 2012-11-27
    • Add some logging to the exception handler to track down shutdown issues
    • Add ability to prevent jesque from starting via config (issue #22)
  • 0.6.0 - released 2013-03-05
    • Upgrade to Jesque 1.3.1
    • Fix edge case with errors after calling jedis.multi() but before calling trans.exec() (issue #26)
  • 0.6.1 - release 2013-03-22
    • Use Jesque's admin functionality to allow start/stop/pause of workers in a cluster
  • 0.6.2 - release 2013-06-13
    • Add ability to specify Redis DB
  • 0.8.0 - TBD - 0.8.0-SNAPSHOT version available now
    • Added priorityEnqueue methods to JesqueService
    • Added ability to define a custom WorkerListener
    • Added ability to define a custom WorkerImpl
    • Added ability to specify a custom job exception handler
    • Updated to grails version 2.3.8
    • Move tests outside of main project
    • Remove test and hibernate dependencies
    • Updated dependencies
  • 1.0.0 - Grails 3.x
    • Some features have not been ported over yet such as custom Worker and Listener
    • Design was changed to use list based jobTypes instead of Map for simplicity in YAML
  • 1.0.1 - Bug fix for NPE
  • 1.0.6 - Background the starting of the jobs to speed up booting
  • 1.1.0 - Adding sentinel support
  • 1.1.2 - Bug fix for wiring jobs
  • 1.1.4 - Bug fix for worker persistence context
  • 1.1.5 - Fixing sentinel support
  • 1.1.6 - Fixing sentinel support (again)
  • 1.1.9 - Closing pooled resource connection
    • Rewrite of the worker impl
  • 1.2.0 - Fixing exception handling in Grails Worker
  • 1.2.1 - Using close instead of return to pool

License

Copyright 2011 Michael Cameron

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.

grails-jesque's People

Contributors

ctoestreich avatar peh avatar rlovtangen avatar

Stargazers

 avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

grails-jesque's Issues

hibernate session is not closed if flush fails

In WorkerPersistenceListener, if the flush fails (e.g. due to an ValidationException), we don't call persistenceInterceptor.destroy() and hence the hibernate session leaks through to the next job.

this is the stacktrace from the failing flush:

00:54:03.878 [Worker-0 Jesque-2.1.1:Processing Job] ERROR n.g.j.worker.WorkerListenerDelegate - Failure executing listener grails.plugins.jesque.WorkerPersistenceListener@36e2afeb for event JOB_SUCCESS from queue genericQueue on worker resque:worker:florian.langenhahn:29589-0:JAVA_DYNAMIC_QUEUES,genericQueue
grails.validation.ValidationException: Validation error whilst flushing entity [Domain]:
- Field error in object 'Domain' on field 'foo': ...
	at org.grails.datastore.mapping.validation.ValidationException.newInstance(ValidationException.java:81) [11 skipped]
	at org.grails.orm.hibernate.support.ClosureEventListener.doValidate(ClosureEventListener.java:375)
	at org.grails.orm.hibernate.support.ClosureEventListener$7.call(ClosureEventListener.java:323)
	at org.grails.orm.hibernate.support.ClosureEventListener$7.call(ClosureEventListener.java:312)
	at org.grails.orm.hibernate.support.ClosureEventListener.doWithManualSession(ClosureEventListener.java:337)
	at org.grails.orm.hibernate.support.ClosureEventListener.onPreUpdate(ClosureEventListener.java:312)
	at org.grails.orm.hibernate.EventTriggeringInterceptor.onPreUpdate(EventTriggeringInterceptor.java:166)
	at org.grails.orm.hibernate.EventTriggeringInterceptor.onPersistenceEvent(EventTriggeringInterceptor.java:91)
	at org.grails.datastore.mapping.engine.event.AbstractPersistenceEventListener.onApplicationEvent(AbstractPersistenceEventListener.java:47)
	at org.grails.orm.hibernate.support.ClosureEventTriggeringInterceptor.publishEvent(ClosureEventTriggeringInterceptor.java:165) [4 skipped]
	at org.grails.orm.hibernate.support.ClosureEventTriggeringInterceptor.onPreUpdate(ClosureEventTriggeringInterceptor.java:138)
	at org.hibernate.action.internal.EntityUpdateAction.preUpdate(EntityUpdateAction.java:257)
	at org.hibernate.action.internal.EntityUpdateAction.execute(EntityUpdateAction.java:134)
	at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:465)
	at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:351)
	at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:350)
	at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:56)
	at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1258)
	at org.grails.orm.hibernate4.support.HibernatePersistenceContextInterceptor.flush(HibernatePersistenceContextInterceptor.java:143)
	at org.grails.orm.hibernate.support.AbstractMultipleDataSourceAggregatePersistenceContextInterceptor.flush(AbstractMultipleDataSourceAggregatePersistenceContextInterceptor.java:97)
	at grails.persistence.support.PersistenceContextInterceptor$flush$0.call(Unknown Source)
	at grails.plugins.jesque.WorkerPersistenceListener.unbindSession(WorkerPersistenceListener.groovy:37)
	at grails.plugins.jesque.WorkerPersistenceListener.onEvent(WorkerPersistenceListener.groovy:52)

and this stacktrace is from the following job:

00:54:03.981 [Worker-0 Jesque-2.1.1:Processing Job] ERROR n.g.j.worker.WorkerListenerDelegate - Failure executing listener grails.plugins.jesque.WorkerPersistenceListener@36e2afeb for event JOB_SUCCESS from queue genericQueue on worker resque:worker:florian.langenhahn:29589-0:JAVA_DYNAMIC_QUEUES,genericQueue
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [Domain#864010]
	at org.hibernate.persister.entity.AbstractEntityPersister.check(AbstractEntityPersister.java:2541)
	at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3285)
	at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:3183)
	at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3525)
	at org.hibernate.action.internal.EntityUpdateAction.execute(EntityUpdateAction.java:159)
	at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:465)
	at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:351)
	at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:350)
	at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:56)
	at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1258)
	at org.grails.orm.hibernate4.support.HibernatePersistenceContextInterceptor.flush(HibernatePersistenceContextInterceptor.java:143)
	at org.grails.orm.hibernate.support.AbstractMultipleDataSourceAggregatePersistenceContextInterceptor.flush(AbstractMultipleDataSourceAggregatePersistenceContextInterceptor.java:97)
	at grails.persistence.support.PersistenceContextInterceptor$flush$0.call(Unknown Source)
	at grails.plugins.jesque.WorkerPersistenceListener.unbindSession(WorkerPersistenceListener.groovy:37)
	at grails.plugins.jesque.WorkerPersistenceListener.onEvent(WorkerPersistenceListener.groovy:52)

I guess a fix would be to simply put a try/catch around the flush.

Use Jesqueue's build in delayed job feature

Since quite some time now the jesque library supports delayed enqueuing of Jobs.
the grails plugin still uses it's own delay implementation which is not super different from the "original" one.
I guess switching to the original one is not to awfully hard no?

Improve instructions for jesque Cron Jobs

Many basic cron expressions fail. For example, run every minute: '* * * * *'

I googled enough to find out that the Quartz cron expression syntax is not the same as standard unix cron expression. This is not obvious to any experienced *nix user who isn't already familiar with Quartz quirks.

It would be helpful if the instructions on the Readme (and the especially the example cron job code further down the Readme) reflected this explicitly and pointed users to the special Quartz cron expression rules page. Perhaps with a warning that they cannot use unix cron syntax in the cron expression field.

Recurring job simple format throws Invalid Format exception

Trying the example created by the create-jesque-job command results in Invalid Format exception

static triggers = {
     simple repeatInterval: 5000l // execute job once in 5 seconds

}

v1.2.1

2017-05-21 14:05:21,331 [ERROR] JesqueGrailsPlugin Initializing Jesque failed java.lang.Exception: Invalid format at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.lang.reflect.Constructor.newInstance(Constructor.java:423) at org.springsource.loaded.ri.ReflectiveInterceptor.jlrConstructorNewInstance(ReflectiveInterceptor.java:1075) at org.codehaus.groovy.reflection.CachedConstructor.invoke(CachedConstructor.java:83) at org.codehaus.groovy.runtime.callsite.ConstructorSite$ConstructorSiteNoUnwrapNoCoerce.callConstructor(ConstructorSite.java:105) at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallConstructor(CallSiteArray.java:60) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:235) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:247) at grails.plugins.jesque.TriggersConfigBuilder.createTrigger(TriggersConfigBuilder.groovy:71) at grails.plugins.jesque.TriggersConfigBuilder$createTrigger.callCurrent(Unknown Source) at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallCurrent(CallSiteArray.java:52) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:154) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:182) at grails.plugins.jesque.TriggersConfigBuilder.createNode(TriggersConfigBuilder.groovy:37) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springsource.loaded.ri.ReflectiveInterceptor.jlrMethodInvoke(ReflectiveInterceptor.java:1426) at org.codehaus.groovy.runtime.callsite.PogoMetaMethodSite$PogoCachedMethodSiteNoUnwrapNoCoerce.invoke(PogoMetaMethodSite.java:210) at org.codehaus.groovy.runtime.callsite.PogoMetaMethodSite.callCurrent(PogoMetaMethodSite.java:59) at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallCurrent(CallSiteArray.java:52) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:154) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:182) at grails.plugins.jesque.TriggersConfigBuilder.createNode(TriggersConfigBuilder.groovy:33) at groovy.util.BuilderSupport.doInvokeMethod(BuilderSupport.java:84) at groovy.util.BuilderSupport.invokeMethod(BuilderSupport.java:67) at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeOnDelegationObjects(ClosureMetaClass.java:446) at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:369) at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1024) at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.callCurrent(PogoMetaClassSite.java:69) at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallCurrent(CallSiteArray.java:52) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:154) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:166) at com.mypackage.TestRecurringJob$__clinit__closure1.doCall(TestRecurringJob.groovy:20) at com.mypackage.TestRecurringJob$__clinit__closure1.doCall(TestRecurringJob.groovy) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springsource.loaded.ri.ReflectiveInterceptor.jlrMethodInvoke(ReflectiveInterceptor.java:1426) at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93) at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325) at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:294) at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1024) at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.call(PogoMetaClassSite.java:42) at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48) at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.call(PogoMetaClassSite.java:57) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:117) at grails.plugins.jesque.TriggersConfigBuilder.build(TriggersConfigBuilder.groovy:18) at grails.plugins.jesque.DefaultGrailsJesqueJobClass.evaluateTriggers(DefaultGrailsJesqueJobClass.java:29) at grails.plugins.jesque.DefaultGrailsJesqueJobClass.getTriggers(DefaultGrailsJesqueJobClass.java:38) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springsource.loaded.ri.ReflectiveInterceptor.jlrMethodInvoke(ReflectiveInterceptor.java:1426) at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93) at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325) at org.codehaus.groovy.runtime.metaclass.MethodMetaProperty$GetBeanMethodMetaProperty.getProperty(MethodMetaProperty.java:76) at org.codehaus.groovy.runtime.callsite.GetEffectivePojoPropertySite.getProperty(GetEffectivePojoPropertySite.java:64) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callGetProperty(AbstractCallSite.java:296) at grails.plugins.jesque.JesqueConfigurationService.scheduleJob(JesqueConfigurationService.groovy:84) at grails.plugins.jesque.JesqueConfigurationService$scheduleJob.call(Unknown Source) at grails.jesque.JesqueGrailsPlugin$_doWithApplicationContext_closure3$_closure9.doCall(JesqueGrailsPlugin.groovy:172) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springsource.loaded.ri.ReflectiveInterceptor.jlrMethodInvoke(ReflectiveInterceptor.java:1426) at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93) at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325) at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:294) at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1024) at groovy.lang.Closure.call(Closure.java:414) at groovy.lang.Closure.call(Closure.java:430) at org.codehaus.groovy.runtime.DefaultGroovyMethods.each(DefaultGroovyMethods.java:2030) at org.codehaus.groovy.runtime.DefaultGroovyMethods.each(DefaultGroovyMethods.java:1890) at org.codehaus.groovy.runtime.dgm$159.invoke(Unknown Source) at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite$PojoMetaMethodSiteNoUnwrapNoCoerce.invoke(PojoMetaMethodSite.java:274) at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite.call(PojoMetaMethodSite.java:56) at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:113) at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:125) at grails.jesque.JesqueGrailsPlugin$_doWithApplicationContext_closure3.doCall(JesqueGrailsPlugin.groovy:171) at grails.jesque.JesqueGrailsPlugin$_doWithApplicationContext_closure3.doCall(JesqueGrailsPlugin.groovy) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springsource.loaded.ri.ReflectiveInterceptor.jlrMethodInvoke(ReflectiveInterceptor.java:1426) at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93) at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325) at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:294) at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1024) at groovy.lang.Closure.call(Closure.java:414) at groovy.lang.Closure.call(Closure.java:408) at groovyx.gpars.group.PGroup$3.call(PGroup.java:289) at groovyx.gpars.group.PGroup$4.run(PGroup.java:313) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at java.lang.Thread.run(Thread.java:748)

Validate classes in jobTypes exist

If the class in the jobTypes doesn't exist, the config should fail. Currently it is just silently running and not firing any jobs when the class is missing.

JesqueDelayedJobThreadService throws IllegalFieldValueException

The following exception keeps showing up in my logs.

Related Stack and libraries:
grails 3.1.16
java 8
ubuntu 14LTS
compile "org.grails.plugins:jesque:1.2.1"
compile 'joda-time:joda-time:2.8.2'

 2017-06-22 22:55:27,065 [ERROR] JesqueDelayedJobThreadService Jesque delayed job exception, attempt 1 of 120
 org.joda.time.IllegalFieldValueException: Value 292278994 for year must be in the range [-292275054,292278993]
 	at org.joda.time.field.FieldUtils.verifyValueBounds(FieldUtils.java:234)
 	at org.joda.time.chrono.BasicYearDateTimeField.set(BasicYearDateTimeField.java:83)
 	at org.joda.time.base.BaseDateTime.<init>(BaseDateTime.java:129)
 	at org.joda.time.base.BaseDateTime.<init>(BaseDateTime.java:97)
 	at org.joda.time.DateTime.<init>(DateTime.java:209)
 	at sun.reflect.GeneratedConstructorAccessor122.newInstance(Unknown Source)
 	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
 	at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
 	at org.codehaus.groovy.reflection.CachedConstructor.invoke(CachedConstructor.java:83)
 	at org.codehaus.groovy.runtime.callsite.ConstructorSite$ConstructorSiteNoUnwrapNoCoerce.callConstructor(ConstructorSite.java:105)
 	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:247)
 	at grails.plugins.jesque.JesqueDelayedJobService$_nextFireTime_closure3.doCall(JesqueDelayedJobService.groovy:56)
 	at sun.reflect.GeneratedMethodAccessor268.invoke(Unknown Source)
 	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
 	at java.lang.reflect.Method.invoke(Method.java:498)
 	at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93)
 	at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325)
 	at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:294)
 	at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1024)
 	at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.call(PogoMetaClassSite.java:42)
 	at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48)
 	at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.call(PogoMetaClassSite.java:57)
 	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:125)
 	at grails.plugins.redis.RedisService.withRedis(RedisService.groovy:102)
 	at grails.plugins.redis.RedisService$withRedis.call(Unknown Source)
 	at grails.plugins.jesque.JesqueDelayedJobService.nextFireTime(JesqueDelayedJobService.groovy:50)
 	at grails.plugins.jesque.JesqueDelayedJobService$nextFireTime$0.call(Unknown Source)
 	at grails.plugins.jesque.JesqueDelayedJobThreadService.mainThreadLoop(JesqueDelayedJobThreadService.groovy:48)
 	at sun.reflect.GeneratedMethodAccessor266.invoke(Unknown Source)
 	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
 	at java.lang.reflect.Method.invoke(Method.java:498)
 	at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93)
 	at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325)
 	at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:384)
 	at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1024)
 	at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.callCurrent(PogoMetaClassSite.java:69)
 	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:158)
 	at grails.plugins.jesque.JesqueDelayedJobThreadService$_run_closure1.doCall(JesqueDelayedJobThreadService.groovy:38)
 	at grails.plugins.jesque.JesqueDelayedJobThreadService$_run_closure1.doCall(JesqueDelayedJobThreadService.groovy)
 	at sun.reflect.GeneratedMethodAccessor265.invoke(Unknown Source)
 	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
 	at java.lang.reflect.Method.invoke(Method.java:498)
 	at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93)
 	at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325)
 	at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:294)
 	at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1024)
 	at org.codehaus.groovy.runtime.callsite.PogoMetaClassSite.call(PogoMetaClassSite.java:42)
 	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:117)
 	at grails.plugins.jesque.JesqueDelayedJobThreadService.withRetryUsingBackoff(JesqueDelayedJobThreadService.groovy:73)
 	at sun.reflect.GeneratedMethodAccessor264.invoke(Unknown Source)
 	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
 	at java.lang.reflect.Method.invoke(Method.java:498)
 	at org.codehaus.groovy.runtime.callsite.PogoMetaMethodSite$PogoCachedMethodSiteNoUnwrap.invoke(PogoMetaMethodSite.java:190)
 	at org.codehaus.groovy.runtime.callsite.PogoMetaMethodSite.callCurrent(PogoMetaMethodSite.java:59)
 	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:182)
 	at grails.plugins.jesque.JesqueDelayedJobThreadService.run(JesqueDelayedJobThreadService.groovy:37)
 	at java.lang.Thread.run(Thread.java:745)

Changelog file

A Changelog file would be very helpful for this plugin. In addition, tagging git releases to match the deploys to bintray would also be useful.

Thanks!

programmatically scheduling a recurring task silently fails

The static trigger inside a class with a quartz cron expression works.
However, this has the down side that every time a server reboots, it restarts the cronjob.
This is not acceptable behavior in many applications.
We need to be able to programmatically start and stop recurring tasks at runtime.

Removing a recurring task works programatically. (Though it does throw multiple exceptions.)
jesqueSchedulerService.deleteSchedule("MyDemoJesqueJobTrigger")

However, the jesqueSchedulerService#schedule functionality silently fails to add the same recurring job that works from static triggers.
jesqueSchedulerService.schedule("MyDemoJesqueJobTrigger, "0/5 * * * * ?", Constants.JESQUE_CRONJOB_QUEUE_NAME, TestRecurringJob.simpleName, [])

This scheduling behavior works correct in jesque.
https://github.com/gresrun/jesque#recurring-jobs

Jesque lacks the quartz cron expressions support that grails jesque has. The quartz cron expression is much better for many use cases than setting the milliseconds between job executions.

v1.2.1 plugin
3.1.16 grails

Support Job with named vars., not just unnamed args.

Jesque supports Jobs with named arguments, 'vars', in addition to 'args'. @see Job.java

The grails JesqueService doesn't support this explicitly, but I can make my own Job instance to properly pass the named vars.


 Job newJob = new Job(MyBackgroundJob.simpleName, ["myFirstArgName":123, "mySecondArgName":"some value"])
jesqueService.enqueue(Constants.JESQUE_QUEUE_NAME, newJob)

The problem is in the grails GrailsWorkerImpl execute method.
It is only looks for the Job.args, not Job.vars.

	protected void execute(final Job job, final String curQueue, final Object instance, final Object[] args) {
		this.jedis.set(key(WORKER, this.name), statusMsg(curQueue, job))
		try {
			log.info "Running perform on instance ${job.className}"
			final Object result
			this.listenerDelegate.fireEvent(JOB_EXECUTE, this, curQueue, job, instance, null, null)
			result = instance.perform(*args)
			success(job, instance, result, curQueue)
		} finally {
			this.jedis.del(key(WORKER, this.name))
		}
	}

	protected Object execute(final Job job, final String curQueue, final Object instance) throws Exception {
		log.info "Executing jog ${job.className}"
		if (instance instanceof WorkerAware) {
			((WorkerAware) instance).setWorker(this);
		}
		return execute(job, curQueue, instance, job.args)
	}

Whereas the Java version is more convoluted, but when they do execute, it handles both args and vars in JesqueUtils.java, ReflectionUtils.java

    /**
     * Materializes a job by assuming the {@link Job#getClassName()} is a
     * fully-qualified Java type.
     *
     * @param job
     *            the job to materialize
     * @return the materialized job
     * @throws ClassNotFoundException
     *             if the class could not be found
     * @throws Exception
     *             if there was an exception creating the object
     */
    public static Object materializeJob(final Job job) throws ClassNotFoundException, Exception {
        final Class<?> clazz = ReflectionUtils.forName(job.getClassName());
        // A bit redundant since we check when the job type is added...
        if (!Runnable.class.isAssignableFrom(clazz) && !Callable.class.isAssignableFrom(clazz)) {
            throw new ClassCastException("jobs must be a Runnable or a Callable: " + clazz.getName() + " - " + job);
        }
        **return ReflectionUtils.createObject(clazz, job.getArgs(), job.getVars());**
    }

[ERROR] WorkerImpl Could not close Jedis connection on server restart

Using v1.2.1, when I restart my app deployed to Tomcat, the logs spew the following redis errors for each workerimpl

Apr 27 01:24:56  myenv_i-06c3e45d653a46807 catalina.out:  27-Apr-2017 08:24:56.560 INFO [main] org.apache.catalina.core.StandardServer.await A valid shutdown command was received via the shutdown port. Stopping the Server instance.
Apr 27 01:24:56 myenv_i-06c3e45d653a46807 catalina.out:  27-Apr-2017 08:24:56.561 INFO [main] org.apache.coyote.AbstractProtocol.pause Pausing ProtocolHandler ["http-nio-8080"]
Apr 27 01:24:56 myenv_i-06c3e45d653a46807 catalina.out:  27-Apr-2017 08:24:56.563 INFO [main] org.apache.coyote.AbstractProtocol.pause Pausing ProtocolHandler ["ajp-nio-8009"]
Apr 27 01:24:56 myenv_i-06c3e45d653a46807 catalina.out:  27-Apr-2017 08:24:56.563 INFO [main] org.apache.catalina.core.StandardService.stopInternal Stopping service Catalina

2017-04-27 08:24:56,810 [INFO ] JesqueDelayedJobThreadService Stopping the jesque delayed job thread
Apr 27 01:24:57 myenv_i-06c3e45d653a46807 catalina.out:  2017-04-27 08:24:56,945 [INFO ] JesqueDelayedJobThreadService Stopping jesque delayed job thread because thread state changed to Stopped
Apr 27 01:24:57 myenv_i-06c3e45d653a46807 catalina.out:  2017-04-27 08:24:56,958 [ERROR] WorkerImpl Could not close Jedis connection
Apr 27 01:24:57 myenv_i-06c3e45d653a46807 catalina.out:  redis.clients.jedis.exceptions.JedisException: Could not return the resource to the pool
Apr 27 01:24:57 myenv_i-06c3e45d653a46807 catalina.out:  	at redis.clients.util.Pool.returnBrokenResourceObject(Pool.java:90)
Apr 27 01:24:57 myenv_i-06c3e45d653a46807 catalina.out:  	at redis.clients.jedis.JedisPool.returnBrokenResource(JedisPool.java:111)
Apr 27 01:24:57 myenv_i-06c3e45d653a46807 catalina.out:  	at redis.clients.jedis.JedisPool.returnResource(JedisPool.java:126)
Apr 27 01:24:57 myenv_i-06c3e45d653a46807 catalina.out:  	at redis.clients.jedis.JedisPool.returnResource(JedisPool.java:12)
Apr 27 01:24:57 myenv_i-06c3e45d653a46807 catalina.out:  	at redis.clients.jedis.Jedis.close(Jedis.java:3314)
Apr 27 01:24:57 myenv_i-06c3e45d653a46807 catalina.out:  	at grails.plugins.jesque.WorkerImpl.returnJedis(WorkerImpl.groovy:733)
Apr 27 01:24:57 myenv_i-06c3e45d653a46807 catalina.out:  	at grails.plugins.jesque.WorkerImpl.withJedis(WorkerImpl.groovy:715)
Apr 27 01:24:57 myenv_i-06c3e45d653a46807 catalina.out:  	at grails.plugins.jesque.WorkerImpl.run(WorkerImpl.groovy:141)
Apr 27 01:24:57 myenv_i-06c3e45d653a46807 catalina.out:  	at java.lang.Thread.run(Thread.java:745)
Apr 27 01:24:57 myenv_i-06c3e45d653a46807 catalina.out:  Caused by: java.lang.IllegalStateException: Invalidated object not currently part of this pool
Apr 27 01:24:57 myenv_i-06c3e45d653a46807 catalina.out:  	at org.apache.commons.pool2.impl.GenericObjectPool.invalidateObject(GenericObjectPool.java:640)
Apr 27 01:24:57 myenv_i-06c3e45d653a46807 catalina.out:  	at redis.clients.util.Pool.returnBrokenResourceObject(Pool.java:88)
Apr 27 01:24:57 myenv_i-06c3e45d653a46807 catalina.out:  	... 8 common frames omitted
Apr 27 01:24:57 myenv_i-06c3e45d653a46807 catalina.out:  Exception in thread "Worker-0 Jesque-2.1.1:STOPPING" redis.clients.jedis.exceptions.JedisException: Could not return the resource to the pool
Apr 27 01:24:57 myenv_i-06c3e45d653a46807 catalina.out:  	at redis.clients.util.Pool.returnBrokenResourceObject(Pool.java:90)
Apr 27 01:24:57 myenv_i-06c3e45d653a46807 catalina.out:  	at redis.clients.jedis.JedisPool.returnBrokenResource(JedisPool.java:111)
Apr 27 01:24:57 myenv_i-06c3e45d653a46807 catalina.out:  	at redis.clients.jedis.JedisPool.returnResource(JedisPool.java:126)
Apr 27 01:24:57 myenv_i-06c3e45d653a46807 catalina.out:  	at redis.clients.jedis.JedisPool.returnResource(JedisPool.java:12)
Apr 27 01:24:57 myenv_i-06c3e45d653a46807 catalina.out:  	at redis.clients.jedis.Jedis.close(Jedis.java:3314)
Apr 27 01:24:57 myenv_i-06c3e45d653a46807 catalina.out:  	at grails.plugins.jesque.WorkerImpl.returnJedis(WorkerImpl.groovy:733)
Apr 27 01:24:57 myenv_i-06c3e45d653a46807 catalina.out:  	at grails.plugins.jesque.WorkerImpl.withJedis(WorkerImpl.groovy:715)
Apr 27 01:24:57 myenv_i-06c3e45d653a46807 catalina.out:  	at grails.plugins.jesque.WorkerImpl.run(WorkerImpl.groovy:141)
Apr 27 01:24:57 myenv_i-06c3e45d653a46807 catalina.out:  	at java.lang.Thread.run(Thread.java:745)
Apr 27 01:24:57 myenv_i-06c3e45d653a46807 catalina.out:  Caused by: java.lang.IllegalStateException: Invalidated object not currently part of this pool
Apr 27 01:24:57 myenv_i-06c3e45d653a46807 catalina.out:  	at org.apache.commons.pool2.impl.GenericObjectPool.invalidateObject(GenericObjectPool.java:640)
Apr 27 01:24:57 myenv_i-06c3e45d653a46807 catalina.out:  	at redis.clients.util.Pool.returnBrokenResourceObject(Pool.java:88)
Apr 27 01:24:57 myenv_i-06c3e45d653a46807 catalina.out:  	... 8 more

And other error for each worker:


Apr 27 01:25:02 myenv_i-06c3e45d653a46807 catalina.out:  27-Apr-2017 08:25:02.564 WARNING [localhost-startStop-2] org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesThreads The web application [ROOT] appears to have started a thread named [Thread-28] but has failed to stop it. This is very likely to create a memory leak. Stack trace of thread:
Apr 27 01:25:02 myenv_i-06c3e45d653a46807 catalina.out:   java.net.SocketInputStream.socketRead0(Native Method)
Apr 27 01:25:02 myenv_i-06c3e45d653a46807 catalina.out:   java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
Apr 27 01:25:02 myenv_i-06c3e45d653a46807 catalina.out:   java.net.SocketInputStream.read(SocketInputStream.java:171)
Apr 27 01:25:02 myenv_i-06c3e45d653a46807 catalina.out:   java.net.SocketInputStream.read(SocketInputStream.java:141)
Apr 27 01:25:02 myenv_i-06c3e45d653a46807 catalina.out:   java.net.SocketInputStream.read(SocketInputStream.java:127)
Apr 27 01:25:02 myenv_i-06c3e45d653a46807 catalina.out:   redis.clients.util.RedisInputStream.ensureFill(RedisInputStream.java:195)
Apr 27 01:25:02 myenv_i-06c3e45d653a46807 catalina.out:   redis.clients.util.RedisInputStream.readByte(RedisInputStream.java:40)
Apr 27 01:25:02 myenv_i-06c3e45d653a46807 catalina.out:   redis.clients.jedis.Protocol.process(Protocol.java:141)
Apr 27 01:25:02 myenv_i-06c3e45d653a46807 catalina.out:   redis.clients.jedis.Protocol.read(Protocol.java:205)
Apr 27 01:25:02 myenv_i-06c3e45d653a46807 catalina.out:   redis.clients.jedis.Connection.readProtocolWithCheckingBroken(Connection.java:297)
Apr 27 01:25:02 myenv_i-06c3e45d653a46807 catalina.out:   redis.clients.jedis.Connection.getRawObjectMultiBulkReply(Connection.java:242)
Apr 27 01:25:02 myenv_i-06c3e45d653a46807 catalina.out:   redis.clients.jedis.JedisPubSub.process(JedisPubSub.java:108)
Apr 27 01:25:02 myenv_i-06c3e45d653a46807 catalina.out:   redis.clients.jedis.JedisPubSub.proceed(JedisPubSub.java:102)
Apr 27 01:25:02 myenv_i-06c3e45d653a46807 catalina.out:   redis.clients.jedis.Jedis.subscribe(Jedis.java:2591)
Apr 27 01:25:02 myenv_i-06c3e45d653a46807 catalina.out:   grails.plugins.jesque.AdminImpl.run(AdminImpl.java:166)
Apr 27 01:25:02 myenv_i-06c3e45d653a46807 catalina.out:   java.lang.Thread.run(Thread.java:745)

Using:
grails jesque v1.2.1
grails 3.1.16
tomcat 8.0.41
Linux

jobs with parameters are not "registered"

This really simple job:

class ApplicationDeploymentJob {

    def applicationService

    def perform(def appId, def version, def hostname) {
        applicationService.addDeployment(appId as long, version, hostname)
    }
}

is not being successfully registered as a JesqueJobClass.
in JesqueJobArtefactHandler line 46 is not finding the perform method.

pooled resource is not being returned to pool

the current WorkerImpl is never returning Jedis ressources to the pool. Like this the pool is exhausted insanly fast (with default configuration) or has to be tuned alot to handle all those connection (local test with 4 Workers and 1000 maxTotal connections resultet in pool exhaustion in under one minute)

scheduled job enqueued twice

We recently discovered that sometimes some of our scheduled jobs are enqueued twice.
It happens fairly random and does not seem to be related to the number of scheduled jobs scheduled at that specific time.
We suspect that this is caused by some kind of race-condition in the JesqueSchedulerService.

Some info about our setup:
There are 4 EC2 instances that are running the scheduler thread. They are connected to an ElastiCache cache.m4.xlarge Redis instance.

No errors are shown when startup fails

currently, when simply creating a new grails app, adding jesque and a worker poo, running create-jesque-job and starting the app the result is no started worker no jobs being worked on and not a single error in the startup log.
While debugging this i found out that the created trigger is not supported as is
static triggers = { simple repeatInterval: 5000l // execute job once in 5 seconds }

the plugin should at least give some information that the startup failed and which job caused the fail

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.