private void pingSessionBackup(@Nonnull final MemcachedBackupSession session) throws InterruptedException { final String key = _sessionIdFormat.createBackupKey(session.getId()); final Future<Boolean> touchResultFuture = _memcached.add(key, 5, 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); } }
@Override public Void call() throws Exception { final BackupResult backupResult = _result.get(); if (_pingSessionIfBackupWasSkipped) { if (backupResult.getStatus() == BackupResultStatus.SKIPPED) { pingSession(_session, _backupSessionService); } } /* * For non-sticky sessions we store a backup of the session in a secondary memcached node (under a special key * that's resolved by the SuffixBasedNodeLocator), but only when we have more than 1 memcached node configured... */ if (_storeSecondaryBackup) { try { if (_log.isDebugEnabled()) { _log.debug( "Storing backup in secondary memcached for non-sticky session " + _session.getId()); } if (backupResult.getStatus() == BackupResultStatus.SKIPPED) { pingSessionBackup(_session); } else { saveSessionBackupFromResult(backupResult); } saveValidityBackup(); } catch (final RuntimeException e) { _log.info("Could not store secondary backup of session " + _session.getIdInternal(), e); } } return null; }
/** * 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()); } }
public void saveValidityBackup() { final String backupValidityKey = _sessionIdFormat.createBackupKey(_validityKey); final int maxInactiveInterval = _session.getMaxInactiveInterval(); // fix for #88, along with the change in session.getMemcachedExpirationTimeToSet final int expiration = maxInactiveInterval <= 0 ? 0 : maxInactiveInterval; _memcached.set(backupValidityKey, toMemcachedExpiration(expiration), _validityData); }
public void saveSessionBackupFromResult(final BackupResult backupResult) { final byte[] data = backupResult.getData(); if (data != null) { final String key = _sessionIdFormat.createBackupKey(_session.getId()); _memcached.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 pingSession( @Nonnull final MemcachedBackupSession session, @Nonnull final BackupSessionService backupSessionService) throws InterruptedException { final Future<Boolean> touchResult = _memcached.add(_storageKeyFormat.format(session.getIdInternal()), 5, 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); } }
public void saveSessionBackup( @Nonnull final MemcachedBackupSession session, @Nonnull final String key) throws InterruptedException { try { final byte[] data = _manager.serialize(session); final Future<Boolean> backupResult = _memcached.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); } }
/** * Deserialize session data that was serialized using {@link #serialize(MemcachedBackupSession)} * (or a combination of {@link #serializeAttributes(MemcachedBackupSession, Map)} and {@link * #serialize(MemcachedBackupSession, byte[])}). * * <p>Note: the returned session already has the manager set and {@link * MemcachedBackupSession#doAfterDeserialization()} is invoked. Additionally the attributes hash * is set (via {@link MemcachedBackupSession#setDataHashCode(int)}). * * @param data the byte array of the serialized session and its session attributes. Can be <code> * null</code>. * @param realm the realm that is used to reconstruct the principal if there was any stored in the * session. * @param manager the manager to set on the deserialized session. * @return the deserialized {@link MemcachedBackupSession} or <code>null</code> if the provided * <code>byte[] data</code> was <code>null</code>. */ public MemcachedBackupSession deserialize(final byte[] data, final SessionManager manager) { if (data == null) { return null; } try { final DeserializationResult deserializationResult = deserializeSessionFields(data, manager); final byte[] attributesData = deserializationResult.getAttributesData(); final Map<String, Object> attributes = deserializeAttributes(attributesData); final MemcachedBackupSession session = deserializationResult.getSession(); session.setAttributesInternal(attributes); session.setDataHashCode(Arrays.hashCode(attributesData)); session.setManager(manager); session.doAfterDeserialization(); return session; } catch (final InvalidVersionException e) { LOG.info("Got session data from memcached with an unsupported version: " + e.getVersion()); // for versioning probably there will be changes in the design, // with the first change and version 2 we'll see what we need return null; } }
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); } }
/** * 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); } }
/** * Serialize the given session to a byte array. This is a shortcut for <code><pre> * final byte[] attributesData = serializeAttributes( session, session.getAttributes() ); * serialize( session, attributesData ); * </pre></code> The returned byte array can be deserialized using {@link #deserialize(byte[], * Realm, Manager)}. * * @see #serializeAttributes(MemcachedBackupSession, Map) * @see #serialize(MemcachedBackupSession, byte[]) * @see #deserialize(byte[], Realm, Manager) * @param session the session to serialize. * @return the serialized session data. */ public byte[] serialize(final MemcachedBackupSession session) { final byte[] attributesData = serializeAttributes(session, session.getAttributesInternal()); return serialize(session, attributesData); }
static DeserializationResult deserializeSessionFields( final byte[] data, final SessionManager manager) throws InvalidVersionException { final MemcachedBackupSession result = manager.newMemcachedBackupSession(); final short version = (short) decodeNum(data, 0, 2); if (version != CURRENT_VERSION) { throw new InvalidVersionException( "The version " + version + " does not match the current version " + CURRENT_VERSION, version); } final short sessionFieldsDataLength = (short) decodeNum(data, 2, 2); result.setCreationTimeInternal(decodeNum(data, 4, 8)); result.setLastAccessedTimeInternal(decodeNum(data, 12, 8)); result.setMaxInactiveInterval((int) decodeNum(data, 20, 4)); result.setIsNewInternal(decodeBoolean(data, 24)); result.setIsValidInternal(decodeBoolean(data, 25)); result.setThisAccessedTimeInternal(decodeNum(data, 26, 8)); result.setLastBackupTime(decodeNum(data, 34, 8)); final short idLength = (short) decodeNum(data, 42, 2); result.setIdInternal(decodeString(data, 44, idLength)); final short authTypeId = (short) decodeNum(data, 44 + idLength, 2); result.setAuthTypeInternal(AuthType.valueOfId(authTypeId).getValue()); final int currentIdx = 44 + idLength + 2; final short principalDataLength = (short) decodeNum(data, currentIdx, 2); if (principalDataLength > 0) { final byte[] principalData = new byte[principalDataLength]; System.arraycopy(data, currentIdx + 2, principalData, 0, principalDataLength); result.setPrincipalInternal(deserializePrincipal(principalData, manager)); } final byte[] attributesData = new byte[data.length - sessionFieldsDataLength]; System.arraycopy( data, sessionFieldsDataLength, attributesData, 0, data.length - sessionFieldsDataLength); return new DeserializationResult(result, attributesData); }
static byte[] serializeSessionFields(final MemcachedBackupSession session) { final byte[] idData = serializeId(session.getIdInternal()); final byte[] principalData = session.getPrincipal() != null ? serializePrincipal(session.getPrincipal()) : null; final int principalDataLength = principalData != null ? principalData.length : 0; final int sessionFieldsDataLength = 2 // short value for the version // the following might change with other versions, refactoring needed then + 2 // short value that stores the dataLength + NUM_BYTES // bytes that store all session attributes but the id + 2 // short value that stores the idData length + idData.length // the number of bytes for the id + 2 // short value for the authType + 2 // short value that stores the principalData length + principalDataLength; // the number of bytes for the principal final byte[] data = new byte[sessionFieldsDataLength]; int idx = 0; idx = encodeNum(CURRENT_VERSION, data, idx, 2); idx = encodeNum(sessionFieldsDataLength, data, idx, 2); idx = encodeNum(session.getCreationTimeInternal(), data, idx, 8); idx = encodeNum(session.getLastAccessedTimeInternal(), data, idx, 8); idx = encodeNum(session.getMaxInactiveInterval(), data, idx, 4); idx = encodeBoolean(session.isNewInternal(), data, idx); idx = encodeBoolean(session.isValidInternal(), data, idx); idx = encodeNum(session.getThisAccessedTimeInternal(), data, idx, 8); idx = encodeNum(session.getLastBackupTime(), data, idx, 8); idx = encodeNum(idData.length, data, idx, 2); idx = copy(idData, data, idx); idx = encodeNum(AuthType.valueOfValue(session.getAuthType()).getId(), data, idx, 2); idx = encodeNum(principalDataLength, data, idx, 2); copy(principalData, data, idx); return data; }