<commit_before>/*
 * 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.
 * <p>
 *
 * @author <NAME>
 */
public class InMemorySessionManager implements SessionManager, SessionManagerStatistics {

    private final AttachmentKey<SessionImpl> NEW_SESSION = AttachmentKey.create(SessionImpl.class);

    private final SessionIdGenerator sessionIdGenerator;

    private final ConcurrentMap<String, SessionImpl> sessions;

    private final SessionListeners sessionListeners = new SessionListeners();

    /**
     * 30 minute default
     */
    private volatile int defaultSessionTimeout = 30 * 60;

    private final int maxSize;

    private final ConcurrentDirectDeque<String> 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<String> 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<String, SessionImpl> 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))
                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;
            }
        }
        String sessionId = config.findSessionId(serverExchange);
        InMemorySessionManager.SessionImpl session = (SessionImpl) getSession(sessionId);
        if(session != null && serverExchange != null) {
            session.requestStarted(serverExchange);<commit_msg>[UNDERTOW-2159] add null check to SessionManager<commit_after>/*
 * 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.
 * <p>
 *
 * @author <NAME>
 */
public class InMemorySessionManager implements SessionManager, SessionManagerStatistics {

    private final AttachmentKey<SessionImpl> NEW_SESSION = AttachmentKey.create(SessionImpl.class);

    private final SessionIdGenerator sessionIdGenerator;

    private final ConcurrentMap<String, SessionImpl> sessions;

    private final SessionListeners sessionListeners = new SessionListeners();

    /**
     * 30 minute default
     */
    private volatile int defaultSessionTimeout = 30 * 60;

    private final int maxSize;

    private final ConcurrentDirectDeque<String> 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<String> 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<String, SessionImpl> 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))
                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;
            }
        } else {
            return null;
        }
        String sessionId = config.findSessionId(serverExchange);
        InMemorySessionManager.SessionImpl session = (SessionImpl) getSession(sessionId);
        if(session != null && serverExchange != null) {
            session.requestStarted(serverExchange);
{'fraction_non_alphanumeric': 0.07856198784033835, 'fraction_numerical': 0.0029606132698916203, 'mean_word_length': 2.8953871499176276, 'pattern_counts': {'":': 0, '<': 23, '<?xml version=': 0, '>': 27, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5529374', 'n_tokens_mistral': 4877, 'n_tokens_neox': 4639, 'n_words': 1241}