private void updateSession( @Nonnull final MemcachedBackupSession session, @Nonnull final BackupSessionService backupSessionService ) throws InterruptedException { final Future<BackupResult> result = backupSessionService.backupSession( session, true ); try { if ( result.get().getStatus() != BackupResultStatus.SUCCESS ) { _log.warn( "Update for session (after unsuccessful ping) did not return SUCCESS, but " + result.get() ); } } catch ( final ExecutionException e ) { _log.warn( "An exception occurred when trying to update session " + session.getIdInternal(), e ); } }
public void saveSessionBackup( @Nonnull final MemcachedBackupSession session, @Nonnull final String key ) throws InterruptedException { try { final byte[] data = _manager.serialize( session ); final Future<Boolean> backupResult = _storage.set( key, toMemcachedExpiration(session.getMemcachedExpirationTimeToSet()), data ); if ( !backupResult.get().booleanValue() ) { _log.warn( "Update for secondary backup of session "+ session.getIdInternal() +" (after unsuccessful ping) did not return sucess." ); } } catch ( final ExecutionException e ) { _log.warn( "An exception occurred when trying to update secondary session backup for " + session.getIdInternal(), e ); } } }
private void pingSession( @Nonnull final MemcachedBackupSession session, @Nonnull final BackupSessionService backupSessionService ) throws InterruptedException { final Future<Boolean> touchResult = _storage.add( _storageKeyFormat.format(session.getIdInternal()), 5, BYTE_1 ); try { if ( touchResult.get() ) { _stats.nonStickySessionsPingFailed(); _log.warn( "The session " + session.getIdInternal() + " should be touched in memcached, but it does not exist" + " therein. Will store in memcached again." ); updateSession( session, backupSessionService ); } else _log.debug( "The session was ping'ed successfully." ); } catch ( final ExecutionException e ) { _log.warn( "An exception occurred when trying to ping session " + session.getIdInternal(), e ); } }
private void releaseLock( @Nonnull final MemcachedBackupSession session ) { if ( session.isLocked() ) { try { if ( _log.isDebugEnabled() ) { _log.debug( "Releasing lock for session " + session.getIdInternal() ); } final long start = System.currentTimeMillis(); _storage.delete( _memcachedNodesManager.getSessionIdFormat().createLockName( session.getIdInternal() ) ).get(); _statistics.registerSince( RELEASE_LOCK, start ); session.releaseLock(); } catch( final Exception e ) { _log.warn( "Caught exception when trying to release lock for session " + session.getIdInternal(), e ); } } }
/** * Is invoked when a session was removed from the manager, e.g. because the * session has been invalidated. * * Is used to release a lock if the non-stick session was locked * * It's also used to keep track of such sessions in non-sticky mode, so that * lockingStrategy.onBackupWithoutLoadedSession is not invoked (see issue 116). * * @param session the removed session. */ public void sessionRemoved(final MemcachedBackupSession session) { if(!_sticky) { if(session.isLocked()) { _lockingStrategy.releaseLock(session.getIdInternal()); session.releaseLock(); } _invalidSessionsCache.put(session.getIdInternal(), Boolean.TRUE); } }
private void releaseLock() { if ( _session.isLocked() ) { try { if ( _log.isDebugEnabled() ) { _log.debug( "Releasing lock for session " + _session.getIdInternal() ); } final long start = System.currentTimeMillis(); _storage.delete( _memcachedNodesManager.getSessionIdFormat().createLockName( _session.getIdInternal() ) ).get(); _statistics.registerSince( RELEASE_LOCK, start ); _session.releaseLock(); } catch( final Exception e ) { _log.warn( "Caught exception when trying to release lock for session " + _session.getIdInternal(), e ); } } }
private void pingSessionBackup( @Nonnull final MemcachedBackupSession session ) throws InterruptedException { final String key = _sessionIdFormat.createBackupKey( session.getId() ); final Future<Boolean> touchResultFuture = _storage.add( key, 5, BYTE_1 ); try { final boolean touchResult = touchResultFuture.get(_manager.getOperationTimeout(), TimeUnit.MILLISECONDS); if ( touchResult ) { _log.warn( "The secondary backup for session " + session.getIdInternal() + " should be touched in memcached, but it seemed to be" + " not existing. Will store in memcached again." ); saveSessionBackup( session, key ); } else _log.debug( "The secondary session backup was ping'ed successfully." ); } catch ( final TimeoutException e ) { _log.warn( "The secondary backup for session " + session.getIdInternal() + " could not be completed within " + _manager.getOperationTimeout() + " millis, was cancelled now." ); } catch ( final ExecutionException e ) { _log.warn( "An exception occurred when trying to ping session " + session.getIdInternal(), e ); } }
/** * Invoked after a non-sticky session is loaded from memcached, can be used to update some session fields based on * separately stored information (e.g. session validity info). * * @param lockStatus * the {@link LockStatus} that was returned from {@link #onBeforeLoadFromMemcached(String)}. */ protected void onAfterLoadFromMemcached( @Nonnull final MemcachedBackupSession session, @Nullable final LockStatus lockStatus ) { session.setLockStatus( lockStatus ); final long start = System.currentTimeMillis(); final SessionValidityInfo info = loadSessionValidityInfo( session.getIdInternal() ); if ( info != null ) { _stats.registerSince( NON_STICKY_AFTER_LOAD_FROM_MEMCACHED, start ); session.setLastAccessedTimeInternal( info.getLastAccessedTime() ); session.setThisAccessedTimeInternal( info.getThisAccessedTime() ); } else { _log.warn( "No validity info available for session " + session.getIdInternal() ); } }
private String handleSessionTakeOver( final MemcachedBackupSession session ) { checkMaxActiveSessions(); final String origSessionId = session.getIdInternal(); final String newSessionId = _memcachedNodesManager.changeSessionIdForTomcatFailover(session.getIdInternal(), _manager.getJvmRoute()); // If this session was already loaded we need to remove it from the session map // See http://code.google.com/p/memcached-session-manager/issues/detail?id=92 if ( _manager.getSessionsInternal().containsKey( origSessionId ) ) { _manager.getSessionsInternal().remove( origSessionId ); } session.setIdInternal( newSessionId ); // a concurrent/earlier request might already have added the session (#282) if ( !_manager.getSessionsInternal().containsKey( newSessionId ) ) { addValidLoadedSession(session, true); deleteFromMemcached(origSessionId); _statistics.requestWithTomcatFailover(); } return newSessionId; }
/** * Store the provided session in memcached if the session was modified * or if the session needs to be relocated. * * @param session * the session to save * @param sessionRelocationRequired * specifies, if the session id was changed due to a memcached failover or tomcat failover. * @return the {@link BackupResultStatus} */ public Future<BackupResult> backupSession( final String sessionId, final boolean sessionIdChanged, final String requestId ) { final MemcachedBackupSession session = _manager.getSessionInternal( sessionId ); if ( session == null ) { if(_log.isDebugEnabled()) _log.debug( "No session found in session map for " + sessionId ); return new SimpleFuture<BackupResult>( BackupResult.SKIPPED ); } _log.info( "Serializing session data for session " + session.getIdInternal() ); final long startSerialization = System.currentTimeMillis(); final byte[] data = _transcoderService.serializeAttributes( (MemcachedBackupSession) session, ((MemcachedBackupSession) session).getAttributesFiltered() ); _log.info( String.format( "Serializing %1$,.3f kb session data for session %2$s took %3$d ms.", (double)data.length / 1000, session.getIdInternal(), System.currentTimeMillis() - startSerialization ) ); _sessionData.put( session.getIdInternal(), data ); _statistics.registerSince( ATTRIBUTES_SERIALIZATION, startSerialization ); _statistics.register( CACHED_DATA_SIZE, data.length ); return new SimpleFuture<BackupResult>( new BackupResult( BackupResultStatus.SUCCESS ) ); }
/** * Tests sessionAttributeFilter attribute: only filtered/allowed attributes must be serialized. */ @SuppressWarnings( { "unchecked", "rawtypes" } ) @Test public void testOnlyFilteredAttributesAreIncludedInSessionBackup() throws InterruptedException, ExecutionException { final TranscoderService transcoderServiceMock = mock( TranscoderService.class ); final ConcurrentMap<String, Object> anyMap = any( ConcurrentMap.class ); when( transcoderServiceMock.serializeAttributes( any( MemcachedBackupSession.class ), anyMap ) ).thenReturn( new byte[0] ); _service.setTranscoderService( transcoderServiceMock ); final MemcachedBackupSession session = createSession( _service ); _service.setSessionAttributeFilter( "^(foo|bar)$" ); session.setAttribute( "foo", "foo" ); session.setAttribute( "bar", "bar" ); session.setAttribute( "baz", "baz" ); _service.backupSession( session.getIdInternal(), false, null ).get(); // capture the supplied argument, alternatively we could have used some Matcher (but there seems to be no MapMatcher). final ArgumentCaptor<ConcurrentMap> model = ArgumentCaptor.forClass( ConcurrentMap.class ); verify( transcoderServiceMock, times( 1 ) ).serializeAttributes( eq( session ), model.capture() ); // the serialized attributes must only contain allowed ones assertTrue( model.getValue().containsKey( "foo" ) ); assertTrue( model.getValue().containsKey( "bar" ) ); assertFalse( model.getValue().containsKey( "baz" ) ); }
/** * Test for issue #105: Make memcached node optional for single-node setup * http://code.google.com/p/memcached-session-manager/issues/detail?id=105 */ @Test public void testBackupSessionFailureWithoutMemcachedNodeIdConfigured105() throws Exception { _service.setMemcachedNodes( "127.0.0.1:11211" ); _service.setSessionBackupAsync(false); _service.startInternal(new MemcachedStorageClient(_memcachedMock)); final MemcachedBackupSession session = createSession( _service ); session.access(); session.endAccess(); session.setAttribute( "foo", "bar" ); @SuppressWarnings( "unchecked" ) final OperationFuture<Boolean> futureMock = mock( OperationFuture.class ); when( futureMock.get( ) ).thenThrow(new ExecutionException(new RuntimeException("Simulated exception."))); when( futureMock.get( anyInt(), any( TimeUnit.class ) ) ).thenThrow(new ExecutionException(new RuntimeException("Simulated exception."))); when( _memcachedMock.set( eq( session.getId() ), anyInt(), any(), any( Transcoder.class ) ) ).thenReturn( futureMock ); final BackupResult backupResult = _service.backupSession( session.getIdInternal(), false, null ).get(); assertEquals(backupResult.getStatus(), BackupResultStatus.FAILURE); verify( _memcachedMock, times( 1 ) ).set( eq( session.getId() ), anyInt(), any(), any( Transcoder.class ) ); }
/** * Test that session attribute serialization and hash calculation is only * performed if the session and its attributes were accessed since the last backup/backup check. * Otherwise this computing time shall be saved for a better world :-) * @throws ExecutionException * @throws InterruptedException */ @Test public void testOnlyHashAttributesOfAccessedSessionsAndAttributes() throws InterruptedException, ExecutionException { final TranscoderService transcoderServiceMock = mock( TranscoderService.class ); @SuppressWarnings( "unchecked" ) final ConcurrentMap<String, Object> anyMap = any( ConcurrentMap.class ); when( transcoderServiceMock.serializeAttributes( any( MemcachedBackupSession.class ), anyMap ) ).thenReturn( new byte[0] ); _service.setTranscoderService( transcoderServiceMock ); final MemcachedBackupSession session = createSession( _service ); session.setAttribute( "foo", "bar" ); _service.backupSession( session.getIdInternal(), false, null ).get(); verify( transcoderServiceMock, times( 1 ) ).serializeAttributes( eq( session ), eq( session.getAttributesInternal() ) ); // we need some millis between last backup and next access (due to check in BackupSessionService) Thread.sleep(5L); session.access(); session.getAttribute( "foo" ); _service.backupSession( session.getIdInternal(), false, null ).get(); verify( transcoderServiceMock, times( 2 ) ).serializeAttributes( eq( session ), eq( session.getAttributesInternal() ) ); // we need some millis between last backup and next access (due to check in BackupSessionService) Thread.sleep(5L); _service.backupSession( session.getIdInternal(), false, null ).get(); verify( transcoderServiceMock, times( 2 ) ).serializeAttributes( eq( session ), eq( session.getAttributesInternal() ) ); }
/** * Tests sessionAttributeFilter attribute: when excluded attributes are accessed/put the session should * not be marked as touched. */ @SuppressWarnings( "unchecked" ) @Test public void testOnlyHashAttributesOfAccessedFilteredAttributes() throws InterruptedException, ExecutionException { final TranscoderService transcoderServiceMock = mock( TranscoderService.class ); _service.setTranscoderService( transcoderServiceMock ); final MemcachedBackupSession session = createSession( _service ); _service.setSessionAttributeFilter( "^(foo|bar)$" ); session.setAttribute( "baz", "baz" ); session.access(); session.endAccess(); _service.backupSession( session.getIdInternal(), false, null ).get(); verify( transcoderServiceMock, never() ).serializeAttributes( (MemcachedBackupSession)any(), (ConcurrentMap)any() ); }
/** * Test that session attribute serialization and hash calculation is only * performed if session attributes were accessed since the last backup. * Otherwise this computing time shall be saved for a better world :-) * @throws ExecutionException * @throws InterruptedException */ @Test public void testOnlyHashAttributesOfAccessedAttributes() throws InterruptedException, ExecutionException { final TranscoderService transcoderServiceMock = mock( TranscoderService.class ); @SuppressWarnings( "unchecked" ) final ConcurrentMap<String, Object> anyMap = any( ConcurrentMap.class ); when( transcoderServiceMock.serializeAttributes( any( MemcachedBackupSession.class ), anyMap ) ).thenReturn( new byte[0] ); _service.setTranscoderService( transcoderServiceMock ); final MemcachedBackupSession session = createSession( _service ); session.access(); session.endAccess(); session.setAttribute( "foo", "bar" ); _service.backupSession( session.getIdInternal(), false, null ).get(); verify( transcoderServiceMock, times( 1 ) ).serializeAttributes( eq( session ), eq( session.getAttributesInternal() ) ); session.access(); session.endAccess(); _service.backupSession( session.getIdInternal(), false, null ).get(); verify( transcoderServiceMock, times( 1 ) ).serializeAttributes( eq( session ), eq( session.getAttributesInternal() ) ); }
public void saveSessionBackupFromResult( final BackupResult backupResult ) { final byte[] data = backupResult.getData(); if ( data != null ) { final String key = _sessionIdFormat.createBackupKey( _session.getId() ); _storage.set( key, toMemcachedExpiration(_session.getMemcachedExpirationTimeToSet()), data ); } else { _log.warn( "No data set for backupResultStatus " + backupResult.getStatus() + " for sessionId " + _session.getIdInternal() + ", skipping backup" + " of non-sticky session in secondary memcached." ); } }
private void assertSessionFields( final MemcachedBackupSession session, final MemcachedBackupSession deserialized ) { Assert.assertEquals( session.getCreationTimeInternal(), deserialized.getCreationTimeInternal() ); Assert.assertEquals( session.getLastAccessedTimeInternal(), deserialized.getLastAccessedTimeInternal() ); Assert.assertEquals( session.getMaxInactiveInterval(), deserialized.getMaxInactiveInterval() ); Assert.assertEquals( session.isNewInternal(), deserialized.isNewInternal() ); Assert.assertEquals( session.isValidInternal(), deserialized.isValidInternal() ); Assert.assertEquals( session.getThisAccessedTimeInternal(), deserialized.getThisAccessedTimeInternal() ); Assert.assertEquals( session.getLastBackupTime(), deserialized.getLastBackupTime() ); Assert.assertEquals( session.getIdInternal(), deserialized.getIdInternal() ); Assert.assertEquals( session.getAuthType(), deserialized.getAuthType() ); assertDeepEquals( session.getPrincipal(), deserialized.getPrincipal() ); }
@Test public void testBackupSessionInRedis() throws InterruptedException, ExecutionException, UnsupportedEncodingException, ClassNotFoundException, IOException { final MemcachedSessionService service = _tomcat1.getService(); final MemcachedBackupSession session = createSession( service ); final String sessionId = "12345"; session.setId(sessionId); session.setAttribute( "foo", "bar" ); final BackupResult backupResult = service.backupSession( session.getIdInternal(), false, null ).get(); assertEquals(backupResult.getStatus(), BackupResultStatus.SUCCESS); final MemcachedBackupSession loadedSession = transcoderService.deserialize( redisClient.get(sessionId.getBytes("UTF-8")), _tomcat1.getManager()); checkSession(loadedSession, session); }
@Test public void testBackupSessionInCouchbase() throws InterruptedException, ExecutionException { final MemcachedSessionService service = _tomcat1.getService(); final MemcachedBackupSession session = createSession( service ); final String sessionId = "12345"; session.setId(sessionId); session.setAttribute( "foo", "bar" ); final BackupResult backupResult = service.backupSession( session.getIdInternal(), false, null ).get(); assertEquals(backupResult.getStatus(), BackupResultStatus.SUCCESS); final MemcachedBackupSession loadedSession = transcoderService.deserialize(mc.get(sessionId, ByteArrayTranscoder.INSTANCE), _tomcat1.getManager()); checkSession(loadedSession, session); }
@Test(enabled = false) // spurious failures public void testBackupSessionInCouchbaseCluster() throws Exception { final MemcachedSessionService service = _tomcat1.getService(); cluster.add(setupCouchbase(getMaxCouchbasePort() + 1)); service.setMemcachedNodes(getMemcachedNodesConfig(getURIs())); setupCouchbaseClient(); waitForReconnect(service.getStorageClient(), cluster.size(), 1000); waitForReconnect(mc, cluster.size(), 1000); final MemcachedBackupSession session = createSession( service ); final String sessionId = "12345"; session.setId(sessionId); session.setAttribute( "foo", "bar" ); final BackupResult backupResult = service.backupSession( session.getIdInternal(), false, null ).get(); assertEquals(backupResult.getStatus(), BackupResultStatus.SUCCESS); final MemcachedBackupSession loadedSession = transcoderService.deserialize(mc.get(sessionId, ByteArrayTranscoder.INSTANCE), _tomcat1.getManager()); checkSession(loadedSession, session); }