Package org.apache.jackrabbit.core.session

Source Code of org.apache.jackrabbit.core.session.SessionState

/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements.  See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.jackrabbit.core.session;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import javax.jcr.RepositoryException;
import javax.jcr.Session;

import org.apache.jackrabbit.core.WorkspaceManager;
import org.apache.jackrabbit.core.observation.ObservationDispatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Internal session state. This class keeps track of the lifecycle of
* a session and controls concurrent access to the session internals.
* <p>
* The session lifecycle is pretty simple: there are only two lifecycle
* states, "alive" and "closed", and only one possible state transition,
* from "alive" to "closed".
* <p>
* Concurrent access to session internals is controlled by the
* {@link #perform(SessionOperation)} method that guarantees that no two
* {@link SessionOperation operations} are performed concurrently on the
* same session.
*
* @see <a href="https://issues.apache.org/jira/browse/JCR-890">JCR-890</a>
*/
public class SessionState {

    /**
     * Logger instance.
     */
    private static final Logger log =
        LoggerFactory.getLogger(SessionState.class);

    /**
     * Number of nanoseconds in a microsecond.
     */
    private static final int NS_PER_US = 1000;

    /**
     * Number of nanoseconds in a millisecond.
     */
    private static final int NS_PER_MS = 1000000;

    /**
     * Component context of this session.
     */
    private final SessionContext context;

    /**
     * The lock used to guarantee synchronized execution of repository
     * operations. An explicit lock is used instead of normal Java
     * synchronization in order to be able to log attempts to concurrently
     * use a session.
     */
    private final Lock lock = new ReentrantLock();

    /**
     * Flag to indicate that the current operation is a write operation.
     * Used to detect concurrent writes.
     */
    private volatile boolean isWriteOperation = false;

    /**
     * Flag to indicate a closed session. When <code>null</code>, the session
     * is still alive. And when the session is closed, this reference is set
     * to an exception that contains the stack trace of where the session was
     * closed. The stack trace is used as extra information in possible
     * warning logs caused by clients attempting to access a closed session.
     */
    private volatile Exception closed = null;

    /**
     * Creates a state instance for a session.
     *
     * @param context component context of this session
     */
    public SessionState(SessionContext context) {
        this.context = context;
    }

    /**
     * Checks whether this session is alive. This method should generally
     * only be called from within a performed {@link SessionOperation}, as
     * otherwise there's no guarantee against another thread closing the
     * session right after this method has returned.
     *
     * @see Session#isLive()
     * @return <code>true</code> if the session is alive,
     *         <code>false</code> otherwise
     */
    public boolean isAlive() {
        return closed == null;
    }

    /**
     * Throws an exception if this session is not alive.
     *
     * @throws RepositoryException throw if this session is not alive
     */
    public void checkAlive() throws RepositoryException {
        if (!isAlive()) {
            throw new RepositoryException(
                    "This session has been closed. See the chained exception"
                    + " for a trace of where the session was closed.", closed);
        }
    }

    /**
     * Performs the given operation within a synchronized block. Special care
     * is made to detect attempts to access the session concurrently and to
     * log detailed warnings in such cases.
     *
     * @param operation session operation
     * @return the return value of the executed operation
     * @throws RepositoryException if the operation fails or
     *                             if the session has already been closed
     */
    public <T> T perform(SessionOperation<T> operation)
            throws RepositoryException {
        String session = context.getSessionImpl().toString();

        // Acquire the exclusive lock for accessing session internals.
        // No other session should be holding the lock, so we log a
        // message to let the user know of such cases.
        if (!lock.tryLock()) {
            if (isWriteOperation
                    && operation instanceof SessionWriteOperation) {
                Exception trace = new Exception(
                        "Stack trace of concurrent access to " + session);
                log.warn("Attempt to perform " + operation
                        + " while another thread is concurrently writing"
                        + " to " + session + ". Blocking until the other"
                        + " thread is finished using this session. Please"
                        + " review your code to avoid concurrent use of"
                        + " a session.", trace);
            } else if (log.isDebugEnabled()) {
                Exception trace = new Exception(
                        "Stack trace of concurrent access to " + session);
                log.debug("Attempt to perform " + operation + " while"
                        + " another thread is concurrently reading from "
                        + session + ". Blocking until the other thread"
                        + " is finished using this session. Please"
                        + " review your code to avoid concurrent use of"
                        + " a session.", trace);
            }
            lock.lock();
        }

        boolean isOutermostWriteOperation = false;
        try {
            // Check that the session is still alive
            checkAlive();

            // Raise the isWriteOperation flag for write operations.
            // The flag is used to set the appropriate log level above.
            boolean wasWriteOperation = isWriteOperation;
            if (!wasWriteOperation
                    && operation instanceof SessionWriteOperation) {
                isWriteOperation = true;
                isOutermostWriteOperation = true;
            }

            try {
                // Perform the actual operation, optionally with debug logs
                if (log.isDebugEnabled()) {
                    log.debug("Performing {}", operation);
                    long start = System.nanoTime();
                    try {
                        return operation.perform(context);
                    } finally {
                        long time = System.nanoTime() - start;
                        if (time > NS_PER_MS) {
                            log.debug("Performed {} in {}ms",
                                    operation, time / NS_PER_MS);
                        } else {
                            log.debug("Performed {} in {}us",
                                    operation, time / NS_PER_US);
                        }
                    }
                } else {
                    return operation.perform(context);
                }
            } finally {
                isWriteOperation = wasWriteOperation;
            }
        } finally {
            lock.unlock();

            // Delay return from a write operation if the observation queue
            // is being overloaded. This needs to be done after releasing
            // the (outermost) write locks to prevent potential deadlocks.
            // See https://issues.apache.org/jira/browse/JCR-2746
            if (isOutermostWriteOperation) {
                WorkspaceManager manager =
                    context.getRepositoryContext().getWorkspaceManager();
                ObservationDispatcher dispatcher =
                    manager.getObservationDispatcher(context.getWorkspace().getName());
                dispatcher.delayIfEventQueueOverloaded();
            }
        }
    }

    /**
     * Closes this session.  Special care is made to detect attempts to
     * access the session concurrently or to close it more than once, and to
     * log detailed warnings in such cases.
     *
     * @return <code>true</code> if the session was closed, or
     *         <code>false</code> if the session had already been closed
     */
    public boolean close() {
        String session = context.getSessionImpl().toString();

        if (!lock.tryLock()) {
            Exception trace = new Exception(
                    "Stack trace of concurrent access to " + session);
            log.warn("Attempt to close " + session + " while another"
                    + " thread is concurrently accessing this session."
                    + " Blocking until the other thread is finished"
                    + " using this session. Please review your code"
                    + " to avoid concurrent use of a session.", trace);
            lock.lock();
        }
        try {
            if (isAlive()) {
                closed = new Exception(
                        "Stack trace of  where " + session
                        + " was originally closed");
                return true;
            } else {
                Exception trace = new Exception(
                        "Stack trace of the duplicate attempt to close "
                        + session);
                log.warn("Attempt to close " + session + " after it has"
                        + " already been closed. Please review your code"
                        + " for proper session management.", trace);
                log.warn(session + " has already been closed. See the"
                        + " attached exception for a trace of where this"
                        + " session was closed.", closed);
                return false;
            }
        } finally {
            lock.unlock();
        }
    }

}
TOP

Related Classes of org.apache.jackrabbit.core.session.SessionState

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.