Index: oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/RepositoryImpl.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/RepositoryImpl.java (date 1377519306000) +++ oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/RepositoryImpl.java (date 1377532490000) @@ -17,6 +17,8 @@ package org.apache.jackrabbit.oak.jcr; import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; import java.util.Collections; import java.util.Map; @@ -37,6 +39,7 @@ import org.apache.jackrabbit.commons.SimpleValueFactory; import org.apache.jackrabbit.oak.api.ContentRepository; import org.apache.jackrabbit.oak.api.ContentSession; +import org.apache.jackrabbit.oak.jcr.delegate.RefreshManager; import org.apache.jackrabbit.oak.jcr.delegate.SessionDelegate; import org.apache.jackrabbit.oak.spi.security.SecurityProvider; import org.apache.jackrabbit.oak.spi.whiteboard.Whiteboard; @@ -57,7 +60,7 @@ * Name of the session attribute value determining the session refresh * behaviour. * - * @see SessionDelegate#SessionDelegate(ContentSession, SecurityProvider, long) + * @see SessionDelegate#SessionDelegate(ContentSession, RefreshManager, SecurityProvider) */ public static final String REFRESH_INTERVAL = "oak.refresh-interval"; @@ -70,6 +73,7 @@ private final ContentRepository contentRepository; protected final Whiteboard whiteboard; private final SecurityProvider securityProvider; + private final ThreadLocal threadSafeCount; public RepositoryImpl(@Nonnull ContentRepository contentRepository, @Nonnull Whiteboard whiteboard, @@ -77,6 +81,7 @@ this.contentRepository = checkNotNull(contentRepository); this.whiteboard = checkNotNull(whiteboard); this.securityProvider = checkNotNull(securityProvider); + this.threadSafeCount = new ThreadLocal(); } //---------------------------------------------------------< Repository >--- @@ -203,9 +208,13 @@ } ContentSession contentSession = contentRepository.login(credentials, workspaceName); + RefreshManager refreshManager = new RefreshManager( + MILLISECONDS.convert(refreshInterval, SECONDS), threadSafeCount); + SessionDelegate sessionDelegate = new SessionDelegate( + contentSession, refreshManager, securityProvider); SessionContext context = createSessionContext( Collections.singletonMap(REFRESH_INTERVAL, refreshInterval), - new SessionDelegate(contentSession,securityProvider,refreshInterval)); + sessionDelegate); return context.getSession(); } catch (LoginException e) { throw new javax.jcr.LoginException(e.getMessage(), e); \ No newline at end of file Index: oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/RefreshManager.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/RefreshManager.java (date 1377532490000) +++ oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/RefreshManager.java (date 1377532490000) @@ -0,0 +1,104 @@ +/* + * 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.oak.jcr.delegate; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.MINUTES; + +import org.apache.jackrabbit.oak.jcr.operation.SessionOperation; + +/** +* michid document +*/ +public class RefreshManager { + private final Exception initStackTrace = new Exception("The session was created here:"); + private final long refreshInterval; + + /** + * ThreadLocal instance to keep track of the save operations performed in the thread so far + * This is is then used to determine if the current session needs to be refreshed to see the + * changes done by another session in current thread. + *

+ * Note - This thread local is never cleared. However, we only store + * java.lang.Integer and do not derive from ThreadLocal such that (class loader) + * leaks typically associated with usage of thread locals do not occur. + */ + private final ThreadLocal threadSaveCount; + + private long lastAccessed = System.currentTimeMillis(); + private boolean warnIfIdle = true; + private boolean refreshAtNextAccess; + private int sessionSaveCount; + + public RefreshManager(long refreshInterval, ThreadLocal threadSaveCount) { + this.refreshInterval = refreshInterval; + this.threadSaveCount = threadSaveCount; + + sessionSaveCount = getOr0(threadSaveCount); + } + + boolean refreshIfNecessary(SessionDelegate delegate, SessionOperation sessionOperation) { + long now = System.currentTimeMillis(); + long timeElapsed = now - lastAccessed; + lastAccessed = now; + + // Don't refresh if this operation is a refresh operation itself or + // a save operation, which does an implicit refresh + if (!sessionOperation.isRefresh() && !sessionOperation.isSave()) { + if (warnIfIdle && !refreshAtNextAccess + && timeElapsed > MILLISECONDS.convert(1, MINUTES)) { + // Warn once if this session has been idle too long + SessionDelegate.log.warn("This session has been idle for " + MINUTES.convert(timeElapsed, MILLISECONDS) + + " minutes and might be out of date. Consider using a fresh session or explicitly" + + " refresh the session.", initStackTrace); + warnIfIdle = false; + } + if (refreshAtNextAccess || hasInThreadCommit() || timeElapsed >= refreshInterval) { + // Refresh if forced or if the session has been idle too long + refreshAtNextAccess = false; + sessionSaveCount = getOr0(threadSaveCount); + delegate.refresh(true); + return true; + } + } + + if (sessionOperation.isSave()) { + threadSaveCount.set(sessionSaveCount = (getOr0(threadSaveCount) + 1)); + } + + return false; + } + + void refreshAtNextAccess() { + refreshAtNextAccess = true; + } + + private boolean hasInThreadCommit() { + // If the threadLocal counter differs from our seen sessionSaveCount so far then + // some other session would have done a commit. If that is the case a refresh would + // be required + return getOr0(threadSaveCount) != sessionSaveCount; + } + + private static int getOr0(ThreadLocal threadLocal) { + Integer c = threadLocal.get(); + return c == null ? 0 : c; + } +} Index: oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/SessionDelegate.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/SessionDelegate.java (date 1377519306000) +++ oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/delegate/SessionDelegate.java (date 1377532490000) @@ -17,9 +17,6 @@ package org.apache.jackrabbit.oak.jcr.delegate; import static com.google.common.base.Preconditions.checkNotNull; -import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static java.util.concurrent.TimeUnit.MINUTES; -import static java.util.concurrent.TimeUnit.SECONDS; import java.io.IOException; @@ -53,40 +50,17 @@ public class SessionDelegate { static final Logger log = LoggerFactory.getLogger(SessionDelegate.class); - /** - * Threadlocal instance to keep track of the save operations performed in the thread so far - * This is is then used to determine if the current session needs to be refreshed to see the - * changes done by another session in current thread. - * - *

Note - This thread local is never cleared. However we are only storing java.lang.Integer - * in it thus it would not cause leaks typically associated with usage of thread locals which are not - * cleared - *

- */ - private static final ThreadLocal SAVE_COUNT = new ThreadLocal() { - @Override - protected Integer initialValue() { - return 0; - } - }; - private final ContentSession contentSession; - private final long refreshInterval; + private final RefreshManager refreshManager; + private final Root root; private final IdentifierManager idManager; - private final Exception initStackTrace; private final PermissionProvider permissionProvider; private boolean isAlive = true; private int sessionOpCount; private long updateCount = 0; - private long lastAccessed = System.currentTimeMillis(); - private boolean warnIfIdle = true; - private boolean refreshAtNextAccess = false; - - private int saveCount = SAVE_COUNT.get(); - /** * Create a new session delegate for a {@code ContentSession}. The refresh behaviour of the * session is governed by the value of the {@code refreshInterval} argument: if the session @@ -96,22 +70,22 @@ * dispatcher in order. * * @param contentSession the content session + * @param refreshManager the refresh manager used to handle auto refreshing this session * @param securityProvider the security provider - * @param refreshInterval refresh interval in seconds. */ - public SessionDelegate(@Nonnull ContentSession contentSession, SecurityProvider securityProvider,long refreshInterval) { + public SessionDelegate(@Nonnull ContentSession contentSession, RefreshManager refreshManager, + SecurityProvider securityProvider) { this.contentSession = checkNotNull(contentSession); - this.refreshInterval = MILLISECONDS.convert(refreshInterval, SECONDS); + this.refreshManager = checkNotNull(refreshManager); this.root = contentSession.getLatestRoot(); this.idManager = new IdentifierManager(root); - this.initStackTrace = new Exception("The session was created here:"); - this.permissionProvider = securityProvider.getConfiguration(AuthorizationConfiguration.class) + this.permissionProvider = checkNotNull(securityProvider) + .getConfiguration(AuthorizationConfiguration.class) .getPermissionProvider(root, contentSession.getAuthInfo().getPrincipals()); - } public synchronized void refreshAtNextAccess() { - refreshAtNextAccess = true; + refreshManager.refreshAtNextAccess(); } /** @@ -130,28 +104,10 @@ throws RepositoryException { // Synchronize to avoid conflicting refreshes from concurrent JCR API calls if (sessionOpCount == 0) { - // Refresh and checks only for non re-entrant session operations - long now = System.currentTimeMillis(); - long timeElapsed = now - lastAccessed; - // Don't refresh if this operation is a refresh operation itself - if (!sessionOperation.isRefresh()) { - if (warnIfIdle && !refreshAtNextAccess - && timeElapsed > MILLISECONDS.convert(1, MINUTES)) { - // Warn once if this session has been idle too long - log.warn("This session has been idle for " + MINUTES.convert(timeElapsed, MILLISECONDS) + - " minutes and might be out of date. Consider using a fresh session or explicitly" + - " refresh the session.", initStackTrace); - warnIfIdle = false; - } - if (refreshAtNextAccess || commitDoneByOtherSessionInCurrentThread() || timeElapsed >= refreshInterval) { - // Refresh if forced or if the session has been idle too long - refreshAtNextAccess = false; - saveCount = SAVE_COUNT.get(); - refresh(true); + // Refresh and precondition checks only for non re-entrant session operations + if (refreshManager.refreshIfNecessary(this, sessionOperation)) { - updateCount++; - } + updateCount++; + } - } - lastAccessed = now; sessionOperation.checkPreconditions(); } try { @@ -162,11 +118,8 @@ if (sessionOperation.isUpdate()) { updateCount++; } - if (sessionOperation.isSave()) { - SAVE_COUNT.set(saveCount = (SAVE_COUNT.get() + 1)); - } - } + } + } - } /** * Same as {@link #perform(SessionOperation)} unless this method expects @@ -460,15 +413,7 @@ return contentSession.toString(); } -//-----------------------------------------------------------< internal >--- - - private boolean commitDoneByOtherSessionInCurrentThread() { - //if the threadLocal counter differs from our seen saveCount so far then - //some other session would have done a commit. If that is the case a refresh would - //be required - return SAVE_COUNT.get() != saveCount; - } - + //------------------------------------------------------------< internal >--- /** * Wraps the given {@link CommitFailedException} instance using the