/** * Test for issue #49: Sessions not associated with a memcached node don't get associated as soon * as a memcached is available * * @throws InterruptedException * @throws IOException * @throws TimeoutException * @throws ExecutionException */ @Test(enabled = true) public void testNotAssociatedSessionGetsAssociatedIssue49() throws InterruptedException, IOException, ExecutionException, TimeoutException { _daemon.stop(); final SessionManager manager = _tomcat1.getManager(); manager.setMaxInactiveInterval(5); manager.setSticky(true); final SessionIdFormat sessionIdFormat = new SessionIdFormat(); final Session session = manager.createSession(null); assertNull(sessionIdFormat.extractMemcachedId(session.getId())); _daemon.start(); // Wait so that the daemon will be available and the client can reconnect (async get didn't do // the trick) Thread.sleep(4000); final String newSessionId = manager.getMemcachedSessionService().changeSessionIdOnMemcachedFailover(session.getId()); assertNotNull(newSessionId); assertEquals(newSessionId, session.getId()); assertEquals(sessionIdFormat.extractMemcachedId(newSessionId), _memcachedNodeId); }
/** Invoked after a non-sticky session is removed from memcached. */ protected void onAfterDeleteFromMemcached(@Nonnull final String sessionId) { final long start = System.currentTimeMillis(); final String validityInfoKey = _sessionIdFormat.createValidityInfoKeyName(sessionId); _memcached.delete(validityInfoKey); if (_storeSecondaryBackup) { _memcached.delete(_sessionIdFormat.createBackupKey(sessionId)); _memcached.delete(_sessionIdFormat.createBackupKey(validityInfoKey)); } _stats.registerSince(NON_STICKY_AFTER_DELETE_FROM_MEMCACHED, start); }
/** * Is invoked for the backup of a non-sticky session that was not accessed for the current * request. */ protected void onBackupWithoutLoadedSession( @Nonnull final String sessionId, @Nonnull final String requestId, @Nonnull final BackupSessionService backupSessionService) { if (!_sessionIdFormat.isValid(sessionId)) { return; } try { final long start = System.currentTimeMillis(); final String validityKey = _sessionIdFormat.createValidityInfoKeyName(sessionId); final SessionValidityInfo validityInfo = loadSessionValidityInfoForValidityKey(validityKey); if (validityInfo == null) { _log.warn("Found no validity info for session id " + sessionId); return; } final int maxInactiveInterval = validityInfo.getMaxInactiveInterval(); final byte[] validityData = encode(maxInactiveInterval, System.currentTimeMillis(), System.currentTimeMillis()); // fix for #88, along with the change in session.getMemcachedExpirationTimeToSet final int expiration = maxInactiveInterval <= 0 ? 0 : maxInactiveInterval; final Future<Boolean> validityResult = _memcached.set(validityKey, toMemcachedExpiration(expiration), validityData); if (!_manager.isSessionBackupAsync()) { validityResult.get(_manager.getSessionBackupTimeout(), TimeUnit.MILLISECONDS); } /* * - ping session * - ping session backup * - save validity backup */ final Callable<?> backupSessionTask = new OnBackupWithoutLoadedSessionTask( sessionId, _storeSecondaryBackup, validityKey, validityData, maxInactiveInterval); _executor.submit(backupSessionTask); if (_log.isDebugEnabled()) { _log.debug("Stored session validity info for session " + sessionId); } _stats.registerSince(NON_STICKY_ON_BACKUP_WITHOUT_LOADED_SESSION, start); } catch (final Throwable e) { _log.warn("An error when trying to load/update validity info.", e); } }
protected void releaseLock(@Nonnull final String sessionId) { try { if (_log.isDebugEnabled()) { _log.debug("Releasing lock for session " + sessionId); } final long start = System.currentTimeMillis(); _memcached.delete(_sessionIdFormat.createLockName(sessionId)).get(); _stats.registerSince(RELEASE_LOCK, start); } catch (final Exception e) { _log.warn("Caught exception when trying to release lock for session " + sessionId, e); } }
protected void acquireLock( @Nonnull final String sessionId, final long retryInterval, final long maxRetryInterval, final long timeout, final long start) throws InterruptedException, ExecutionException, TimeoutException { final Future<Boolean> result = _memcached.add(_sessionIdFormat.createLockName(sessionId), 5, LOCK_VALUE); if (result.get().booleanValue()) { if (_log.isDebugEnabled()) { _log.debug("Locked session " + sessionId); } return; } else { checkTimeoutAndWait(sessionId, retryInterval, timeout, start); acquireLock( sessionId, min(retryInterval * 2, maxRetryInterval), maxRetryInterval, timeout, start); } }
@BeforeMethod public void setUp() throws Exception { _service = mock(MemcachedSessionService.class); _request = mock(Request.class); _response = mock(Response.class); final Context _contextContainer = mock(Context.class); final Host _hostContainer = mock(Host.class); final SessionManager _manager = mock(SessionManager.class); when(_service.getManager()).thenReturn(_manager); when(_manager.getContainer()).thenReturn(_contextContainer); when(_contextContainer.getParent()).thenReturn(_hostContainer); when(_contextContainer.getPath()).thenReturn("/"); _sessionTrackerValve = createSessionTrackerValve(); _nextValve = mock(Valve.class); _sessionTrackerValve.setNext(_nextValve); _sessionTrackerValve.setContainer(_hostContainer); _memcachedNodesManager = mock(MemcachedNodesManager.class); _sessionIdFormat = mock(SessionIdFormat.class); when(_request.getRequestURI()).thenReturn("/someRequest"); when(_request.getMethod()).thenReturn("GET"); when(_request.getQueryString()).thenReturn(null); when(_request.getContext()).thenReturn(_contextContainer); when(_request.getNote(eq(RequestTrackingHostValve.REQUEST_PROCESSED))).thenReturn(Boolean.TRUE); when(_request.getNote(eq(RequestTrackingHostValve.SESSION_ID_CHANGED))) .thenReturn(Boolean.FALSE); when(_sessionIdFormat.extractMemcachedId(anyString())).thenReturn(PRIMARY_NODE_IDENTIFIER); when(_service.getMemcachedNodesManager()).thenReturn(_memcachedNodesManager); when(_memcachedNodesManager.isNodeAvailable(PRIMARY_NODE_IDENTIFIER)) .thenReturn(IS_PRIMARY_MEMCACHED_NODE_OPERATIONAL); when(_memcachedNodesManager.getSessionIdFormat()).thenReturn(_sessionIdFormat); }
@CheckForNull protected SessionValidityInfo loadBackupSessionValidityInfo(@Nonnull final String sessionId) { final String key = _sessionIdFormat.createValidityInfoKeyName(sessionId); final String backupKey = _sessionIdFormat.createBackupKey(key); return loadSessionValidityInfoForValidityKey(backupKey); }
@CheckForNull protected SessionValidityInfo loadSessionValidityInfo(@Nonnull final String sessionId) { return loadSessionValidityInfoForValidityKey( _sessionIdFormat.createValidityInfoKeyName(sessionId)); }
/** * Is invoked after the backup of the session is initiated, it's represented by the provided * backupResult. The requestId is identifying the request. */ protected void onAfterBackupSession( @Nonnull final MemcachedBackupSession session, final boolean backupWasForced, @Nonnull final Future<BackupResult> result, @Nonnull final String requestId, @Nonnull final BackupSessionService backupSessionService) { if (!_sessionIdFormat.isValid(session.getIdInternal())) { return; } try { final long start = System.currentTimeMillis(); final int maxInactiveInterval = session.getMaxInactiveInterval(); final byte[] validityData = encode( maxInactiveInterval, session.getLastAccessedTimeInternal(), session.getThisAccessedTimeInternal()); final String validityKey = _sessionIdFormat.createValidityInfoKeyName(session.getIdInternal()); // fix for #88, along with the change in session.getMemcachedExpirationTimeToSet final int expiration = maxInactiveInterval <= 0 ? 0 : maxInactiveInterval; final Future<Boolean> validityResult = _memcached.set(validityKey, toMemcachedExpiration(expiration), validityData); if (!_manager.isSessionBackupAsync()) { // TODO: together with session backup wait not longer than sessionBackupTimeout. // Details: Now/here we're waiting the whole session backup timeout, even if (perhaps) some // time // was spent before when waiting for session backup result. // For sync session backup it would be better to set both the session data and // validity info and afterwards wait for both results (but in sum no longer than // sessionBackupTimeout) validityResult.get(_manager.getSessionBackupTimeout(), TimeUnit.MILLISECONDS); } if (_log.isDebugEnabled()) { _log.debug("Stored session validity info for session " + session.getIdInternal()); } /* The following task are performed outside of the request thread (includes waiting for the backup result): * - ping session if the backup was skipped (depends on the backup result) * - save secondary session backup if session was modified (backup not skipped) * - ping secondary session backup if the backup was skipped * - save secondary validity backup */ final boolean pingSessionIfBackupWasSkipped = !backupWasForced; final boolean performAsyncTasks = pingSessionIfBackupWasSkipped || _storeSecondaryBackup; if (performAsyncTasks) { final Callable<?> backupSessionTask = new OnAfterBackupSessionTask( session, result, pingSessionIfBackupWasSkipped, backupSessionService, _storeSecondaryBackup, validityKey, validityData); _executor.submit(backupSessionTask); } _stats.registerSince(NON_STICKY_AFTER_BACKUP, start); } catch (final Throwable e) { _log.warn("An error occurred during onAfterBackupSession.", e); } }