7.03.2012

JASIG-CAS Deadlock Problem

Jasig-CAS is a well-known Web Based Single-Sign-Framework developed with almost all Spring features. Basically you can get more information from the documentation.

TicketRegistryCleaner  deletes tickets from datasource based on your configuration. In a High Available (HA) configuration tickets are persisted on databases to serve multiple clients and services with multiple cas-servers. This deletion locks and sometimes casuses deadlocks in oracle since it uses update for. Jasig-Wiki suggests developers to use appropriate locking strategies for their architecture.  Although using JPALocking strategy for HA environments is not enough to solve deadlock problems in jasig-cas.

After working with our oracle database administrators i decided to develop a new TicketRegistryCleaner that is only active in one of cas-servers and just cleans the tickets at midnight 02:00 to 05:00.

This kind of approach is not solving the deadlocks, it  just pushes the deadlock problems to midnights. If it is necessary for the cleanup a stored procedure can be written to delete the unused and timeout tickets.  In the below you will see scheduled registry cleaner source code.

Ticket Registry Cleaner
  1. package com.montoya.sso.ticket.registry.support;
  2.  
  3. import java.util.ArrayList;
  4. import java.util.Calendar;
  5. import java.util.Collection;
  6. import java.util.Date;
  7. import java.util.List;
  8.  
  9. import javax.validation.constraints.NotNull;
  10.  
  11. import org.jasig.cas.ticket.Ticket;
  12. import org.jasig.cas.ticket.TicketGrantingTicket;
  13. import org.jasig.cas.ticket.registry.RegistryCleaner;
  14. import org.jasig.cas.ticket.registry.TicketRegistry;
  15. import org.jasig.cas.ticket.registry.support.JdbcLockingStrategy;
  16. import org.jasig.cas.ticket.registry.support.LockingStrategy;
  17. import org.slf4j.Logger;
  18. import org.slf4j.LoggerFactory;
  19.  
  20. public class MontoyaTicketRegistryCleaner implements RegistryCleaner {
  21.  
  22.     /** The Commons Logging instance. */
  23.     private final Logger log = LoggerFactory.getLogger(getClass());
  24.     
  25.     @NotNull
  26.     private int startTime;
  27.     
  28.     @NotNull
  29.     private int endTime;
  30.     
  31.  
  32.     /** The instance of the TicketRegistry to clean. */
  33.     @NotNull
  34.     private TicketRegistry ticketRegistry;
  35.  
  36.     /** Execution locking strategy */
  37.     @NotNull
  38.     private LockingStrategy lock;
  39.  
  40.     private boolean logUserOutOfServices = true;
  41.  
  42.     private boolean active;
  43.  
  44.     public void setActive(boolean active) {
  45.         this.active = active;
  46.     }
  47.  
  48.     public void setStartTime(int startTime) {
  49.         this.startTime = startTime;
  50.     }
  51.  
  52.     public void setEndTime(int endTime) {
  53.         this.endTime = endTime;
  54.     }
  55.  
  56.     /**
  57.      * @see org.jasig.cas.ticket.registry.RegistryCleaner#clean()
  58.      */
  59.     public void clean() {
  60.         if (!active) {
  61.             log.info("MontoyaTicketRegistryCleaner is not active on this node");
  62.             return;
  63.         }
  64.  
  65.         int hour = Calendar.getInstance().get(Calendar.HOUR_OF_DAY);
  66.         if (hour < startTime || hour > endTime) {
  67.             log.debug("TicketRegistryCleaner is active but it is not working except {} - {} ",startTime,endTime);
  68.             return;
  69.         }
  70.  
  71.         this.log.info("Beginning ticket cleanup.");
  72.         this.log.debug("Attempting to acquire ticket cleanup lock.");
  73.         if (!this.lock.acquire()) {
  74.             this.log.info("Could not obtain lock.  Aborting cleanup.");
  75.             return;
  76.         }
  77.         this.log.debug("Acquired lock.  Proceeding with cleanup.");
  78.         try {
  79.             final List<Ticket> ticketsToRemove = new ArrayList<Ticket>();
  80.             final Collection<Ticket> ticketsInCache;
  81.             ticketsInCache = this.ticketRegistry.getTickets();
  82.             for (final Ticket ticket : ticketsInCache) {
  83.                 if (ticket.isExpired()) {
  84.                     ticketsToRemove.add(ticket);
  85.                 }
  86.             }
  87.  
  88.             this.log.info(ticketsToRemove.size() + " tickets found to be removed.");
  89.             for (final Ticket ticket : ticketsToRemove) {
  90.                 if (this.logUserOutOfServices && ticket instanceof TicketGrantingTicket) {
  91.                     ((TicketGrantingTicket) ticket).expire();
  92.                 }
  93.                 this.ticketRegistry.deleteTicket(ticket.getId());
  94.             }
  95.         } finally {
  96.             this.log.debug("Releasing ticket cleanup lock.");
  97.             this.lock.release();
  98.         }
  99.  
  100.         this.log.info("Finished ticket cleanup.");
  101.     }
  102.  
  103.     /**
  104.      * @param ticketRegistry
  105.      *            The ticketRegistry to set.
  106.      */
  107.     public void setTicketRegistry(final TicketRegistry ticketRegistry) {
  108.         this.ticketRegistry = ticketRegistry;
  109.     }
  110.  
  111.     /**
  112.      * @param strategy
  113.      *            Ticket cleanup locking strategy. An exclusive locking strategy
  114.      *            is preferable if not required for some ticket backing stores,
  115.      *            such as JPA, in a clustered CAS environment. Use
  116.      *            {@link JdbcLockingStrategy} for
  117.      *            {@link org.jasig.cas.ticket.registry.JpaTicketRegistry} in a
  118.      *            clustered CAS environment.
  119.      */
  120.     public void setLock(final LockingStrategy strategy) {
  121.         this.lock = strategy;
  122.     }
  123.  
  124.     /**
  125.      * Whether to log users out of services when we remove an expired ticket.
  126.      * The default is true. Set this to false to disable.
  127.      *
  128.      * @param logUserOutOfServices
  129.      *            whether to log the user out of services or not.
  130.      */
  131.     public void setLogUserOutOfServices(final boolean logUserOutOfServices) {
  132.         this.logUserOutOfServices = logUserOutOfServices;
  133.     }
  134.  
  135. }

Hiç yorum yok: