[jboss-user] [JBoss Cache: Core Edition] - Re: on child insert parent, child collection updated in DB b

dukehoops do-not-reply at jboss.com
Thu Mar 5 15:11:53 EST 2009


Here's the test class:

/*
  |  * To change this template, choose Tools | Templates
  |  * and open the template in the editor.
  |  */
  | package com.doppelganger.service.userservice;
  | 
  | import com.doppelganger.domain.User;
  | import com.doppelganger.domain.UserSession;
  | import com.doppelganger.test.AbstractFunctionalTest;
  | import com.doppelganger.test.TestEntityFactory;
  | import com.doppelganger.test.service.DgTxTemplateAdapter;
  | import com.doppelganger.test.service.TransactionCallbackWithException;
  | import java.sql.Timestamp;
  | import java.util.HashSet;
  | import java.util.Set;
  | import java.util.UUID;
  | import java.util.concurrent.ExecutorService;
  | import java.util.concurrent.Executors;
  | import java.util.concurrent.TimeUnit;
  | import net.eviltwinstudios.common.CommonUtils;
  | import org.springframework.beans.factory.annotation.Autowired;
  | import org.springframework.transaction.TransactionStatus;
  | 
  | /**
  |  * test cache consistency when many users login/read/logout in a loop
  |  * @author nikita
  |  */
  | public class MultiuserLoginTest extends AbstractFunctionalTest {
  | 
  |     final private int USER_COUNT = 4;
  |     final private int ITERATION_COUNT = 20;
  |     final private long THINK_TIME_MILLIS = 0;
  |     final private long LAUNCH_INTERVAL_MILLIS = 10;
  |     @Autowired
  |     private DgTxTemplateAdapter txTemplate;
  |     final private ExecutorService pool = Executors.newFixedThreadPool(USER_COUNT);
  |     final private Set<String> LTESTER_USERNAMES = new HashSet<String>();
  | 
  |     @Override
  |     protected void onSetUp() throws Exception {
  |         super.onSetUp();
  | 
  |         for (int i = 0; i < USER_COUNT; i++) {
  |             LTESTER_USERNAMES.add(TestEntityFactory.LOADTESTUSER_PREFIX + (100000 + i));
  |         }
  | 
  |     }
  | 
  |     public void testManyUsers() throws InterruptedException {
  | 
  |         Set<LtesterUserRunner> runners = new HashSet<LtesterUserRunner>();
  |         for (String userName : LTESTER_USERNAMES) {
  |             LtesterUserRunner r = new LtesterUserRunner(userName);
  |             runners.add(r);
  |             pool.execute(r);
  |             Thread.sleep(LAUNCH_INTERVAL_MILLIS); //rampup
  |         }
  | 
  |         assertEquals("not all user threads launched", USER_COUNT, runners.size());
  | 
  |         getLogger().info("all clients launched");
  | 
  |         pool.shutdown();
  |         boolean finishedInTime = pool.awaitTermination(5, TimeUnit.MINUTES);
  | 
  |         assertTrue("test took too long", finishedInTime);
  | 
  |         //check whether all runners suceeded
  |         for (LtesterUserRunner r : runners) {
  |             assertEquals("runner for user=" + r.getUserName() + " did not complete all iterations; cause=" +
  |                     CommonUtils.stackTraceToString(r.getCauseOfFailure()),
  |                     ITERATION_COUNT,
  |                     r.getCompletedIterations());
  |         }
  |     }
  | 
  |     /**
  |      * login Ltester user into environment and check cache state
  |      * @param ltesterUserName
  |      */
  |     public void login(final String ltesterUserName) throws Exception {
  | 
  | 
  |         //add userSession
  |         final UserSession us1 = (UserSession) txTemplate.executeWithException(new TransactionCallbackWithException() {
  | 
  |             public Object doInTransactionWithException(TransactionStatus status) throws Exception {
  | 
  |                 //add user session, throw exception if one does not exist
  |                 final User usr = getUserDao().findByNameChecked(ltesterUserName);
  | 
  |                 UserSession userSession = new UserSession(usr);
  |                 userSession.setToken(UUID.randomUUID().toString());
  | 
  |                 userSession.setLoginTime(new Timestamp(System.currentTimeMillis()));
  |                 userSession.resetSessionTimeout(); //ok here
  | 
  |                 //attach userSession to User
  |                 usr.initCurrentUserSession(userSession);
  | 
  |                 return userSession;
  | 
  |             }
  |         });
  | 
  |         //check that we can get at userSession
  |         final UserSession us2 = getCurrentUserSession(ltesterUserName);
  | 
  |         if (us2 == null) {
  |             throw new IllegalStateException("userSession null after successful login");
  |         }
  | 
  |         if (!us1.equals(us2)) {
  |             throw new IllegalStateException("us1 != us2");
  |         }
  |     }
  | 
  |     /**
  |      * read UserSessions of all test participants
  |      */
  |     public void readUserSessionsOfTesters() throws Exception {
  |         txTemplate.executeWithException(new TransactionCallbackWithException() {
  | 
  |             public Object doInTransactionWithException(TransactionStatus status) throws Exception {
  | 
  |                 for (String userName : LTESTER_USERNAMES) {
  |                     final User usr = getUserDao().findByNameChecked(userName);
  |                     usr.getCurrentUserSession(); //read user session
  |                 }
  |                 return null;
  |             }
  |         });
  |     }
  | 
  |     /**
  |      *
  |      * @param ltesterUserName
  |      * @return current userSession or null
  |      */
  |     private UserSession getCurrentUserSession(final String ltesterUserName) throws Exception {
  | 
  |         return (UserSession) txTemplate.executeWithException(new TransactionCallbackWithException() {
  | 
  |             public Object doInTransactionWithException(TransactionStatus status) throws Exception {
  | 
  |                 //add user session, throw exception if one does not exist
  |                 final User usr = getUserDao().findByNameChecked(ltesterUserName);
  | 
  |                 return usr.getCurrentUserSession();
  |             }
  |         });
  | 
  |     }
  | 
  |     public void logout(final String ltesterUserName) throws Exception {
  | 
  |         //remove userSession
  |         txTemplate.executeWithException(new TransactionCallbackWithException() {
  | 
  |             public Object doInTransactionWithException(TransactionStatus status) throws Exception {
  | 
  |                 //remove user session; throw exception if one didn't exist
  |                 final User usr = getUserDao().findByNameChecked(ltesterUserName);
  | 
  |                 if (usr.getCurrentUserSession() == null) {
  |                     throw new IllegalStateException("current userSession is null *before* logout; user=" + ltesterUserName);
  |                 }
  |                 usr.removeCurrentUserSession();
  | 
  |                 return null;
  |             }
  |         });
  | 
  |         //check that we can no longer get at userSession
  |         UserSession us = getCurrentUserSession(ltesterUserName);
  | 
  |         if (us != null) {
  |             throw new IllegalStateException("userSession remains after logout: us=" + us);
  |         }
  |     }
  | 
  |     class LtesterUserRunner implements Runnable {
  | 
  |         final private String userName;
  |         private int completedIterations = 0;
  |         private Throwable causeOfFailure;
  | 
  |         public LtesterUserRunner(final String usrName) {
  |             userName = usrName;
  |         }
  | 
  |         public void run() {
  |             try {
  |                 for (int i = 0; i < ITERATION_COUNT; i++) {
  |                     login(userName);
  |                     //loginWithTestService(userName);
  |                     //loginWithService(userName);
  |                     think();
  | 
  |                     //get users
  |                     //getFriends2(userName);
  |                     readUserSessionsOfTesters();
  |                     think();
  | 
  |                     logout(userName);
  |                     think();
  | 
  |                     ++completedIterations;
  |                 }
  | 
  |             } catch (Throwable t) {
  |                 this.causeOfFailure = t;
  |             }
  |         }
  | 
  |         /**
  |          * @return the userName
  |          */
  |         public String getUserName() {
  |             return userName;
  |         }
  | 
  |         /**
  |          * @return the success
  |          */
  |         public boolean isSuccess() {
  |             return ITERATION_COUNT == getCompletedIterations();
  |         }
  | 
  |         /**
  |          * @return the completedIterations
  |          */
  |         public int getCompletedIterations() {
  |             return completedIterations;
  |         }
  | 
  |         /**
  |          * @return the causeOfFailure
  |          */
  |         public Throwable getCauseOfFailure() {
  |             return causeOfFailure;
  |         }
  |     }
  | 
  |     private void think() {
  |         try {
  |             Thread.sleep(THINK_TIME_MILLIS);
  |         } catch (InterruptedException ex) {
  |             throw new RuntimeException("sleep interrupted", ex);
  |         }
  |     }
  | 
  | }
  | 

and relevant Domain objects:



  | @Entity
  | @org.hibernate.annotations.Entity(dynamicUpdate = true, dynamicInsert = true)
  | @Table(name = "users")
  | @Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL)
  | public class User extends GeneratedHiLoKeyEntity {
  | 
  | private Set<UserSession> userSessions = new HashSet<UserSession>();
  | 
  |     /**
  |      * @return the userSessions
  |      */
  |     @OneToMany(mappedBy = "user")
  |     @Cascade({org.hibernate.annotations.CascadeType.ALL, org.hibernate.annotations.CascadeType.DELETE_ORPHAN})
  |     @LazyCollection(LazyCollectionOption.TRUE)
  |     @Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL)
  |     private Set<UserSession> getUserSessions() {
  |         return userSessions;
  |     }
  | 
  |     /**
  |      * @param userSessions the userSessions to set
  |      */
  |     private void setUserSessions(Set<UserSession> userSessions) {
  |         this.userSessions = userSessions;
  |     }
  | 
  |     @Transient
  |     public UserSession getCurrentUserSession() {
  |         final Set<UserSession> sessions = getUserSessions();
  |         return sessions.isEmpty() ? null : sessions.iterator().next();
  |     }
  | 
  |     /**
  |      * convenience method for linking User and currentUserSession
  |      */
  |     public void initCurrentUserSession(UserSession aUserSession) {
  |         assert aUserSession != null;
  | 
  |         if (!getUserSessions().isEmpty()) {
  |             throw new IllegalStateException("User already refers to a UserSession; call User.removeCurrentUserSession() first");
  |         }
  | 
  |         aUserSession.setUser(this);
  |         getUserSessions().add(aUserSession);
  | 
  |         touch(); //force version update
  |     }
  | 
  |     /**
  |      * removes pointer to currentUserSession (if any). NOTE: this will not
  |      * result in UserSession object being deleted from DB.
  |      * session.delete(UserSession) needs to be executed to achieve the latter.
  |      *
  |      * @return removed currentServerSession
  |      */
  |     public UserSession removeCurrentUserSession() {
  |         final UserSession us = getCurrentUserSession();
  |         if (us != null && getUserSessions().remove(us)) {
  |             us.setUser(null);
  |             this.touch(); //force version update
  |         }
  |         return us;
  |     }
  | 
  | 
  | 
  | 
  | }


  | @Entity
  | @org.hibernate.annotations.Entity(dynamicInsert = true, dynamicUpdate = true)
  | @Table(name = "user_sessions")
  | @Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL)
  | public class UserSession extends AbstractUserSession {
  | 
  | private User user;
  | 
  |     UserSession() {
  |     }
  | 
  |     public UserSession(User usr) {
  |         assert usr != null;
  |         setUser(usr);
  |     }
  | 
  |     @ManyToOne(fetch = FetchType.LAZY)
  |     @JoinColumn(unique = true, name = "userId")
  |     @NotNull
  |     @ForeignKey(name = "FK_USER_SESSION_TO_USER")
  |     public User getUser() {
  |         return user;
  |     }
  | 
  |     /**
  |      * @param user the user to set
  |      */
  |     void setUser(User user) {
  |         this.user = user;
  |         //touch();
  |     }
  | }
  | 
  | 

Note that I tried putting synchronized (STATIC_LOCK) around init/getCurrent/removeUserSession in User and the test still failed. I suppose that means that the failure takes place around synchronization at TX commit time, not when domain objects are accessed (which makes sense).

Will keep digging further, but would appreciate any thoughts, of course.

View the original post : http://www.jboss.org/index.html?module=bb&op=viewtopic&p=4215463#4215463

Reply to the post : http://www.jboss.org/index.html?module=bb&op=posting&mode=reply&p=4215463



More information about the jboss-user mailing list