Andrew,

This is an interesting discovery.  Apparently, the DefaultTicketRegistryCleaner, as it's iterating through every ticket in the database, attempted to delete a ticket whose state has in the meantime changed (or was it deleted?)  My simple-minded initial reaction was to change the transaction isolation level, but that could result in blocking other ticket operations for excessively long time.

Another approach would be to write a custom JpaTicketRegistryCleaner that would cleverly, if at all possible, retrieve only expired tickets.  I don't have enough spare cycles at the moment to consider the feasibility of such an approach.

This brings up another potential issue.  I think that every distributed TicketRegistry implementation may be subject to this issue.  That is, concurrent updates to a ticket from multiple threads or, more challengingly, from multiple JVMs.  With DefaultTicketRegistry, this is nicely addressed by using ConcurrentHashMap.  I could not tell what behavior other distributed TicketRegistry implementations have.

Perhaps Hibernate gives us a hint here: the optimistic locking feature should be considered for all distributed TicketRegistry implementations.  If the registry cleaner detects an update to stale data, the cleaner could ignore this ticket and perhaps pick it up on the next pass.

Adam

Andrew Bruno wrote:
And I also see these errors in logs

ERROR 2008-10-27 04:53:44,833 [DefaultQuartzScheduler_Worker-8] org.quartz.core.ErrorLogger - Job (DEFAULT.jobDetailTicketRegistryCleaner threw an exception.
    org.quartz.SchedulerException: Job threw an unhandled exception. [See nested exception: org.springframework.scheduling.quartz.JobMethodInvocationFailedException: Invocation of method 'clean' on target class [class $Proxy72] failed; nested exception is org.springframework.orm.hibernate3.HibernateOptimisticLockingFailureException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1; nested exception is org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1]
            at org.quartz.core.JobRunShell.run(JobRunShell.java:214)
            at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:520)
    * Nested Exception (Underlying Cause) ---------------
    org.springframework.scheduling.quartz.JobMethodInvocationFailedException: Invocation of method 'clean' on target class [class $Proxy72] failed; nested exception is org.springframework.orm.hibernate3.HibernateOptimisticLockingFailureException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1; nested exception is org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1
            at org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean$MethodInvokingJob.executeInternal(MethodInvokingJobDetailFactoryBean.java:269)
            at org.springframework.scheduling.quartz.QuartzJobBean.execute(QuartzJobBean.java:86)
            at org.quartz.core.JobRunShell.run(JobRunShell.java:203)
            at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:520)
    Caused by: org.springframework.orm.hibernate3.HibernateOptimisticLockingFailureException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1; nested exception is org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1
            at org.springframework.orm.hibernate3.SessionFactoryUtils.convertHibernateAccessException(SessionFactoryUtils.java:657)
            at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:95)
            at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:212)
            at org.springframework.orm.jpa.JpaAccessor.translateIfNecessary(JpaAccessor.java:152)
            at org.springframework.orm.jpa.JpaTemplate.execute(JpaTemplate.java:189)
            at org.springframework.orm.jpa.JpaTemplate.executeFind(JpaTemplate.java:151)
            at org.springframework.orm.jpa.JpaTemplate.findByNamedParams(JpaTemplate.java:325)
            at org.jasig.cas.ticket.registry.JpaTicketRegistry.deleteTicketAndChildren(JpaTicketRegistry.java:70)
            at org.jasig.cas.ticket.registry.JpaTicketRegistry.deleteTicket(JpaTicketRegistry.java:63)
            at org.jasig.cas.ticket.registry.support.DefaultTicketRegistryCleaner.clean(DefaultTicketRegistryCleaner.java:73)
            at sun.reflect.GeneratedMethodAccessor94.invoke(Unknown Source)
            at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
            at java.lang.reflect.Method.invoke(Method.java:597)
            at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:310)
            at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:182)
            at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:149)
            at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:106)
            at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:171)
            at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204)
            at $Proxy72.clean(Unknown Source)
            at sun.reflect.GeneratedMethodAccessor92.invoke(Unknown Source)
            at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
            at java.lang.reflect.Method.invoke(Method.java:597)
            at org.springframework.util.MethodInvoker.invoke(MethodInvoker.java:276)
            at org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean$MethodInvokingJob.executeInternal(MethodInvokingJobDetailFactoryBean.java:260)
            ... 3 more
    Caused by: org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1
            at org.hibernate.jdbc.Expectations$BasicExpectation.checkBatched(Expectations.java:61)
            at org.hibernate.jdbc.Expectations$BasicExpectation.verifyOutcome(Expectations.java:46)
            at org.hibernate.jdbc.NonBatchingBatcher.addToBatch(NonBatchingBatcher.java:24)
            at org.hibernate.persister.entity.AbstractEntityPersister.delete(AbstractEntityPersister.java:2520)
            at org.hibernate.persister.entity.AbstractEntityPersister.delete(AbstractEntityPersister.java:2697)
            at org.hibernate.action.EntityDeleteAction.execute(EntityDeleteAction.java:74)
            at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:250)
            at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:234)
            at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:146)
            at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:298)
            at org.hibernate.event.def.DefaultAutoFlushEventListener.onAutoFlush(DefaultAutoFlushEventListener.java:41)
            at org.hibernate.impl.SessionImpl.autoFlushIfRequired(SessionImpl.java:969)
            at org.hibernate.impl.SessionImpl.list(SessionImpl.java:1114)
            at org.hibernate.impl.QueryImpl.list(QueryImpl.java:79)
            at org.hibernate.ejb.QueryImpl.getResultList(QueryImpl.java:66)
            at org.springframework.orm.jpa.JpaTemplate$10.doInJpa(JpaTemplate.java:333)
            at org.springframework.orm.jpa.JpaTemplate.execute(JpaTemplate.java:184)
            ... 23 more




On Tue, Oct 28, 2008 at 1:50 PM, Andrew Bruno <[EMAIL PROTECTED]> wrote:
Hello all,

We are using DB persistance for TicketGrantingTIcket table, and have managed to setup redundant load balanced servers successfully inc Single Sign Out.

During load testing we noticed a few spikes every 83 minutes (5,000,000 msecs) or so.

It appears that this query is being executed

select ticketgran0_.ID as ID2_, ticketgran0_.NUMBER_OF_TIMES_USED as NUMBER2_2_, ticketgran0_.CREATION_TIME as CREATION3_2_, ticketgran0_.EXPIRATION_POLICY as EXPIRATION4_2_,  ticketgran0_.LAST_TIME_USED as LAST5_2_, ticketgran0_.PREVIOUS_LAST_TIME_USED as PREVIOUS6_2_,  ticketgran0_.ticketGrantingTicket_ID as ticketG10_2_, ticketgran0_.AUTHENTICATION as AUTHENTI7_2_, ticketgran0_.EXPIRED as EXPIRED2_, ticketgran0_.SERVICES_GRANTED_ACCESS_TO as SERVICES9_2_ from TICKETGRANTINGTICKET ticketgran0_

and there is no where clause.

Looking through the code, I believe the culprit is

https://www.ja-sig.org/svn/cas3/tags/cas-3-2-2-rc1/cas-server-core/src/main/java/org/jasig/cas/ticket/registry/support/DefaultTicketRegistryCleaner.java

i.e. the clean method:

        log
            .info("Starting cleaning of expired tickets from ticket registry at ["
                + new Date() + "]");

--> GETS ALL TICKETS (returned 1500 rows in one of our tests)

        ticketsInCache = this.ticketRegistry.getTickets();

        for (final Ticket ticket : ticketsInCache) {
            if (ticket.isExpired()) {
                ticketsToRemove.add(ticket);
            }
        }

        log.info(ticketsToRemove.size()
            + " found to be removed.  Removing now.");

        for (final Ticket ticket : ticketsToRemove) {
            this.ticketRegistry.deleteTicket(ticket.getId());
        }

        log
            .info("Finished cleaning of expired tickets from ticket registry at ["
                + new Date() + "]");

Is there any reason why we need to get all the tickets, and the loop through them?

If I was to write my own DB cleaner, how do I decide what the expiration policy is?

Is there more to it that meets the eye?

Many thanks
Andrew









_______________________________________________ Yale CAS mailing list [email protected] http://tp.its.yale.edu/mailman/listinfo/cas


begin:vcard
fn:Adam Rybicki
n:Rybicki;Adam
org:Unicon, Inc.;Professional Services
adr:Suite 113;;3140 North Arizona Avenue;Chandler;AZ;85225;United States
email;internet:[EMAIL PROTECTED]
tel;work:+1-480-558-2400
tel;home:+1-310-265-8286
tel;cell:+1-310-980-2758
x-mozilla-html:FALSE
url:http://www.unicon.net/
version:2.1
end:vcard

Attachment: smime.p7s
Description: S/MIME Cryptographic Signature

_______________________________________________
Yale CAS mailing list
[email protected]
http://tp.its.yale.edu/mailman/listinfo/cas

Reply via email to