All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.undertow.server.session.InMemorySessionManager Maven / Gradle / Ivy

Go to download

This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and JMS BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up with different versions on classes on the class path).

There is a newer version: 34.0.0.Final
Show newest version
/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2014 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package io.undertow.server.session;

import io.undertow.UndertowLogger;
import io.undertow.UndertowMessages;
import io.undertow.server.HttpServerExchange;
import io.undertow.util.AttachmentKey;
import io.undertow.util.ConcurrentDirectDeque;
import io.undertow.util.WorkerUtils;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;

import org.xnio.XnioExecutor;
import org.xnio.XnioIoThread;
import org.xnio.XnioWorker;

/**
 * The default in memory session manager. This basically just stores sessions in an in memory hash map.
 * 

* * @author Stuart Douglas */ public class InMemorySessionManager implements SessionManager, SessionManagerStatistics { private final AttachmentKey NEW_SESSION = AttachmentKey.create(SessionImpl.class); private final SessionIdGenerator sessionIdGenerator; private final ConcurrentMap sessions; private final SessionListeners sessionListeners = new SessionListeners(); /** * 30 minute default */ private volatile int defaultSessionTimeout = 30 * 60; private final int maxSize; private final ConcurrentDirectDeque evictionQueue; private final String deploymentName; private final AtomicLong createdSessionCount = new AtomicLong(); private final AtomicLong rejectedSessionCount = new AtomicLong(); private volatile long longestSessionLifetime = 0; private volatile long expiredSessionCount = 0; private volatile BigInteger totalSessionLifetime = BigInteger.ZERO; private final AtomicInteger highestSessionCount = new AtomicInteger(); private final boolean statisticsEnabled; private volatile long startTime; private final boolean expireOldestUnusedSessionOnMax; public InMemorySessionManager(String deploymentName, int maxSessions, boolean expireOldestUnusedSessionOnMax) { this(new SecureRandomSessionIdGenerator(), deploymentName, maxSessions, expireOldestUnusedSessionOnMax); } public InMemorySessionManager(SessionIdGenerator sessionIdGenerator, String deploymentName, int maxSessions, boolean expireOldestUnusedSessionOnMax) { this(sessionIdGenerator, deploymentName, maxSessions, expireOldestUnusedSessionOnMax, true); } public InMemorySessionManager(SessionIdGenerator sessionIdGenerator, String deploymentName, int maxSessions, boolean expireOldestUnusedSessionOnMax, boolean statisticsEnabled) { this.sessionIdGenerator = sessionIdGenerator; this.deploymentName = deploymentName; this.statisticsEnabled = statisticsEnabled; this.expireOldestUnusedSessionOnMax = expireOldestUnusedSessionOnMax; this.sessions = new ConcurrentHashMap<>(); this.maxSize = maxSessions; ConcurrentDirectDeque evictionQueue = null; if (maxSessions > 0 && expireOldestUnusedSessionOnMax) { evictionQueue = ConcurrentDirectDeque.newInstance(); } this.evictionQueue = evictionQueue; } public InMemorySessionManager(String deploymentName, int maxSessions) { this(deploymentName, maxSessions, false); } public InMemorySessionManager(String id) { this(id, -1); } @Override public String getDeploymentName() { return this.deploymentName; } @Override public void start() { createdSessionCount.set(0); expiredSessionCount = 0; rejectedSessionCount.set(0); totalSessionLifetime = BigInteger.ZERO; startTime = System.currentTimeMillis(); } @Override public void stop() { for (Map.Entry session : sessions.entrySet()) { final SessionImpl sessionValue = session.getValue(); sessionValue.destroy(); if (sessionValue.getId() == null) { // this means we are creating the session right now in a different thread, // do not send the session to listener with a null id, // just set it again, setting the same session id twice is harmless sessionValue.setId(session.getKey()); } sessionListeners.sessionDestroyed(session.getValue(), null, SessionListener.SessionDestroyedReason.UNDEPLOY); } sessions.clear(); } @Override public Session createSession(final HttpServerExchange serverExchange, final SessionConfig config) { if (maxSize > 0) { if(expireOldestUnusedSessionOnMax) { while (sessions.size() >= maxSize && !evictionQueue.isEmpty()) { String key = evictionQueue.poll(); if(key == null) { break; } UndertowLogger.REQUEST_LOGGER.debugf("Removing session %s as max size has been hit", key); SessionImpl toRemove = sessions.get(key); if (toRemove != null) { toRemove.invalidate(null, SessionListener.SessionDestroyedReason.TIMEOUT); //todo: better reason } } } else if (sessions.size() >= maxSize) { if(statisticsEnabled) { rejectedSessionCount.incrementAndGet(); } throw UndertowMessages.MESSAGES.tooManySessions(maxSize); } } if (config == null) { throw UndertowMessages.MESSAGES.couldNotFindSessionCookieConfig(); } String sessionID = config.findSessionId(serverExchange); final SessionImpl session = new SessionImpl(this, config, serverExchange.getIoThread(), serverExchange.getConnection().getWorker(), defaultSessionTimeout); if (sessionID != null) { if (!saveSessionID(sessionID, session)) { session.destroy(); throw UndertowMessages.MESSAGES.sessionWithIdAlreadyExists(sessionID); } // else: succeeded to use requested session id } else { sessionID = createAndSaveNewID(session); } session.setId(sessionID); if (evictionQueue != null) { session.setEvictionToken(evictionQueue.offerLastAndReturnToken(sessionID)); } UndertowLogger.SESSION_LOGGER.debugf("Created session with id %s for exchange %s", sessionID, serverExchange); config.setSessionId(serverExchange, session.getId()); session.bumpTimeout(); sessionListeners.sessionCreated(session, serverExchange); serverExchange.putAttachment(NEW_SESSION, session); if(statisticsEnabled) { createdSessionCount.incrementAndGet(); int highest; int sessionSize; do { highest = highestSessionCount.get(); sessionSize = sessions.size(); if(sessionSize <= highest) { break; } } while (!highestSessionCount.compareAndSet(highest, sessionSize)); } return session; } private boolean saveSessionID(String sessionID, SessionImpl session) { return this.sessions.putIfAbsent(sessionID, session) == null; } private String createAndSaveNewID(SessionImpl session) { for (int i = 0; i < 100; i++) { final String sessionID = sessionIdGenerator.createSessionId(); if (saveSessionID(sessionID, session)) return sessionID; } //this should 'never' happen //but we guard against pathological session id generators to prevent an infinite loop throw UndertowMessages.MESSAGES.couldNotGenerateUniqueSessionId(); } @Override public Session getSession(final HttpServerExchange serverExchange, final SessionConfig config) { if (serverExchange != null) { SessionImpl newSession = serverExchange.getAttachment(NEW_SESSION); if (newSession != null) { return newSession; } } if (config == null) { throw UndertowMessages.MESSAGES.couldNotFindSessionCookieConfig(); } String sessionId = config.findSessionId(serverExchange); InMemorySessionManager.SessionImpl session = (SessionImpl) getSession(sessionId); if (session != null && serverExchange != null) { session.requestStarted(serverExchange); } return session; } @Override public Session getSession(String sessionId) { if (sessionId == null) { return null; } final SessionImpl sess = sessions.get(sessionId); if (sess == null) { return null; } if (sess.getId() == null) { // this means we are creating the session right now in a different thread, // do not return the session with a null id to the outer world, // just set it again, setting the same session id twice is harmless sess.setId(sessionId); } return sess; } @Override public synchronized void registerSessionListener(final SessionListener listener) { UndertowLogger.SESSION_LOGGER.debugf("Registered session listener %s", listener); sessionListeners.addSessionListener(listener); } @Override public synchronized void removeSessionListener(final SessionListener listener) { UndertowLogger.SESSION_LOGGER.debugf("Removed session listener %s", listener); sessionListeners.removeSessionListener(listener); } @Override public void setDefaultSessionTimeout(final int timeout) { UndertowLogger.SESSION_LOGGER.debugf("Setting default session timeout to %s", timeout); defaultSessionTimeout = timeout; } @Override public Set getTransientSessions() { return getAllSessions(); } @Override public Set getActiveSessions() { return getAllSessions(); } @Override public Set getAllSessions() { return new HashSet<>(sessions.keySet()); } @Override public boolean equals(Object object) { if (!(object instanceof SessionManager)) return false; SessionManager manager = (SessionManager) object; return this.deploymentName.equals(manager.getDeploymentName()); } @Override public int hashCode() { return this.deploymentName.hashCode(); } @Override public String toString() { return this.deploymentName; } @Override public SessionManagerStatistics getStatistics() { return this; } public long getCreatedSessionCount() { return createdSessionCount.get(); } @Override public long getMaxActiveSessions() { return maxSize; } @Override public long getHighestSessionCount() { return highestSessionCount.get(); } @Override public long getActiveSessionCount() { return sessions.size(); } @Override public long getExpiredSessionCount() { return expiredSessionCount; } @Override public long getRejectedSessions() { return rejectedSessionCount.get(); } @Override public long getMaxSessionAliveTime() { return longestSessionLifetime; } @Override public synchronized long getAverageSessionAliveTime() { //this method needs to be synchronised to make sure the session count and the total are in sync if(expiredSessionCount == 0) { return 0; } return new BigDecimal(totalSessionLifetime).divide(BigDecimal.valueOf(expiredSessionCount), MathContext.DECIMAL128).longValue(); } @Override public long getStartTime() { return startTime; } /** * session implementation for the in memory session manager */ private static class SessionImpl implements Session { final AttachmentKey FIRST_REQUEST_ACCESS = AttachmentKey.create(Long.class); final InMemorySessionManager sessionManager; final ConcurrentMap attributes = new ConcurrentHashMap<>(); volatile long lastAccessed; final long creationTime; volatile int maxInactiveInterval; static volatile AtomicReferenceFieldUpdater evictionTokenUpdater; static { //this is needed in case there is unprivileged code on the stack //it needs to delegate to the createTokenUpdater() method otherwise the creation will fail //as the inner class cannot access the member evictionTokenUpdater = AccessController.doPrivileged(new PrivilegedAction>() { @Override public AtomicReferenceFieldUpdater run() { return createTokenUpdater(); } }); } private static AtomicReferenceFieldUpdater createTokenUpdater() { return AtomicReferenceFieldUpdater.newUpdater(SessionImpl.class, Object.class, "evictionToken"); } private volatile String sessionId; private volatile Object evictionToken; private final SessionConfig sessionCookieConfig; private volatile long expireTime = -1; private volatile boolean invalid = false; private volatile boolean invalidationStarted = false; final XnioIoThread executor; final XnioWorker worker; XnioExecutor.Key timerCancelKey; Runnable cancelTask = new Runnable() { @Override public void run() { worker.execute(new Runnable() { @Override public void run() { long currentTime = System.currentTimeMillis(); if(currentTime >= expireTime) { invalidate(null, SessionListener.SessionDestroyedReason.TIMEOUT); } else { timerCancelKey = WorkerUtils.executeAfter(executor, cancelTask, expireTime - currentTime, TimeUnit.MILLISECONDS); } } }); } }; private SessionImpl(final InMemorySessionManager sessionManager, final SessionConfig sessionCookieConfig, final XnioIoThread executor, final XnioWorker worker, final int maxInactiveInterval) { this.sessionManager = sessionManager; this.sessionCookieConfig = sessionCookieConfig; this.executor = executor; this.worker = worker; creationTime = lastAccessed = System.currentTimeMillis(); this.setMaxInactiveInterval(maxInactiveInterval); } synchronized void bumpTimeout() { if(invalidationStarted) { return; } final long maxInactiveInterval = getMaxInactiveIntervalMilis(); if (maxInactiveInterval > 0) { long newExpireTime = System.currentTimeMillis() + maxInactiveInterval; if(timerCancelKey != null && (newExpireTime < expireTime)) { // We have to re-schedule as the new maxInactiveInterval is lower than the old one if (!timerCancelKey.remove()) { return; } timerCancelKey = null; } expireTime = newExpireTime; UndertowLogger.SESSION_LOGGER.tracef("Bumping timeout for session %s to %s", sessionId, expireTime); if(timerCancelKey == null) { //+1, to make sure that the time has actually expired //we don't re-schedule every time, as it is expensive //instead when it expires we check if the timeout has been bumped, and if so we re-schedule timerCancelKey = executor.executeAfter(cancelTask, maxInactiveInterval + 1L, TimeUnit.MILLISECONDS); } } else { expireTime = -1; if(timerCancelKey != null) { timerCancelKey.remove(); timerCancelKey = null; } } } private void setEvictionToken(Object evictionToken) { this.evictionToken = evictionToken; if (evictionToken != null) { Object token = evictionToken; if (evictionTokenUpdater.compareAndSet(this, token, null)) { sessionManager.evictionQueue.removeToken(token); this.evictionToken = sessionManager.evictionQueue.offerLastAndReturnToken(sessionId); } } } private void setId(final String sessionId) { this.sessionId = sessionId; } @Override public String getId() { return sessionId; } void requestStarted(HttpServerExchange serverExchange) { Long existing = serverExchange.getAttachment(FIRST_REQUEST_ACCESS); if(existing == null) { if (!invalid) { serverExchange.putAttachment(FIRST_REQUEST_ACCESS, System.currentTimeMillis()); } } bumpTimeout(); } @Override public void requestDone(final HttpServerExchange serverExchange) { Long existing = serverExchange.getAttachment(FIRST_REQUEST_ACCESS); if(existing != null) { lastAccessed = existing; } bumpTimeout(); } @Override public long getCreationTime() { if (invalid) { throw UndertowMessages.MESSAGES.sessionIsInvalid(sessionId); } return creationTime; } @Override public long getLastAccessedTime() { if (invalid) { throw UndertowMessages.MESSAGES.sessionIsInvalid(sessionId); } return lastAccessed; } @Override public void setMaxInactiveInterval(final int interval) { if (invalid) { throw UndertowMessages.MESSAGES.sessionIsInvalid(sessionId); } UndertowLogger.SESSION_LOGGER.debugf("Setting max inactive interval for %s to %s", sessionId, interval); this.maxInactiveInterval = interval; this.bumpTimeout(); } @Override public int getMaxInactiveInterval() { if (invalid) { throw UndertowMessages.MESSAGES.sessionIsInvalid(sessionId); } return maxInactiveInterval; } private long getMaxInactiveIntervalMilis() { if (invalid) { throw UndertowMessages.MESSAGES.sessionIsInvalid(sessionId); } return this.maxInactiveInterval*1000L; } @Override public Object getAttribute(final String name) { if (invalid) { throw UndertowMessages.MESSAGES.sessionIsInvalid(sessionId); } return attributes.get(name); } @Override public Set getAttributeNames() { if (invalid) { throw UndertowMessages.MESSAGES.sessionIsInvalid(sessionId); } return attributes.keySet(); } @Override public Object setAttribute(final String name, final Object value) { if (value == null) { return removeAttribute(name); } if (invalid) { throw UndertowMessages.MESSAGES.sessionIsInvalid(sessionId); } final Object existing = attributes.put(name, value); if (existing == null) { sessionManager.sessionListeners.attributeAdded(this, name, value); } else { sessionManager.sessionListeners.attributeUpdated(this, name, value, existing); } UndertowLogger.SESSION_LOGGER.tracef("Setting session attribute %s to %s for session %s", name, value, sessionId); return existing; } @Override public Object removeAttribute(final String name) { if (invalid) { throw UndertowMessages.MESSAGES.sessionIsInvalid(sessionId); } final Object existing = attributes.remove(name); sessionManager.sessionListeners.attributeRemoved(this, name, existing); UndertowLogger.SESSION_LOGGER.tracef("Removing session attribute %s for session %s", name, sessionId); return existing; } @Override public void invalidate(final HttpServerExchange exchange) { invalidate(exchange, SessionListener.SessionDestroyedReason.INVALIDATED); if (exchange != null) { exchange.removeAttachment(sessionManager.NEW_SESSION); } Object evictionToken = this.evictionToken; if(evictionToken != null) { sessionManager.evictionQueue.removeToken(evictionToken); } } void invalidate(final HttpServerExchange exchange, SessionListener.SessionDestroyedReason reason) { synchronized(SessionImpl.this) { if (timerCancelKey != null) { timerCancelKey.remove(); } SessionImpl sess = sessionManager.sessions.remove(sessionId); if (sess == null) { if (reason == SessionListener.SessionDestroyedReason.INVALIDATED) { throw UndertowMessages.MESSAGES.sessionAlreadyInvalidated(); } return; } invalidationStarted = true; } UndertowLogger.SESSION_LOGGER.debugf("Invalidating session %s for exchange %s", sessionId, exchange); sessionManager.sessionListeners.sessionDestroyed(this, exchange, reason); invalid = true; if(sessionManager.statisticsEnabled) { long life = System.currentTimeMillis() - creationTime; synchronized (sessionManager) { sessionManager.expiredSessionCount++; sessionManager.totalSessionLifetime = sessionManager.totalSessionLifetime.add(BigInteger.valueOf(life)); if(sessionManager.longestSessionLifetime < life) { sessionManager.longestSessionLifetime = life; } } } if (exchange != null) { sessionCookieConfig.clearSession(exchange, this.getId()); } } @Override public SessionManager getSessionManager() { return sessionManager; } @Override public String changeSessionId(final HttpServerExchange exchange, final SessionConfig config) { synchronized(SessionImpl.this) { if (invalidationStarted) { return null; } else { final String oldId = sessionId; String newId = sessionManager.createAndSaveNewID(this); this.sessionId = newId; config.setSessionId(exchange, this.getId()); sessionManager.sessions.remove(oldId); sessionManager.sessionListeners.sessionIdChanged(this, oldId); UndertowLogger.SESSION_LOGGER.debugf("Changing session id %s to %s", oldId, newId); return newId; } } } private synchronized void destroy() { if (timerCancelKey != null) { timerCancelKey.remove(); } cancelTask = null; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy