This is an automated email from the ASF dual-hosted git repository. benw pushed a commit to branch javax in repository https://gitbox.apache.org/repos/asf/tapestry-5.git
The following commit(s) were added to refs/heads/javax by this push: new 3f0fca6fb TAP5-2799: Session.LockMode introduced 3f0fca6fb is described below commit 3f0fca6fbb0b14354157a10b385c2467c1e87979 Author: Ben Weidig <b...@netzgut.net> AuthorDate: Mon Dec 9 14:04:27 2024 +0100 TAP5-2799: Session.LockMode introduced --- .../tapestry5/internal/test/PageTesterSession.java | 41 +++++- .../internal/services/SessionImplTest.java | 96 +++++++++++++++ .../http/internal/services/SessionImpl.java | 101 +++++++++++++-- .../apache/tapestry5/http/services/Session.java | 137 +++++++++++++++++++-- 4 files changed, 352 insertions(+), 23 deletions(-) diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/PageTesterSession.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/PageTesterSession.java index 2a3b06a21..e7d044ef0 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/PageTesterSession.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/test/PageTesterSession.java @@ -1,4 +1,4 @@ -// Copyright 2006, 2007, 2008 The Apache Software Foundation +// Copyright 2006, 2007, 2008, 2024 The Apache Software Foundation // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -27,11 +27,19 @@ public class PageTesterSession implements Session { private final Map<String, Object> attributes = CollectionFactory.newMap(); + @Override public List<String> getAttributeNames() { return InternalUtils.sortedKeys(attributes); } + @Override + public List<String> getAttributeNames(Session.LockMode lockMode) + { + return getAttributeNames(); + } + + @Override public List<String> getAttributeNames(String prefix) { List<String> result = newList(); @@ -42,11 +50,25 @@ public class PageTesterSession implements Session return result; } + @Override + public List<String> getAttributeNames(String prefix, LockMode lockMode) + { + return getAttributeNames(prefix); + } + + @Override public Object getAttribute(String name) { return attributes.get(name); } + @Override + public Object getAttribute(String name, LockMode lockMode) + { + return getAttribute(name); + } + + @Override public void setAttribute(String name, Object value) { if (value == null) @@ -59,12 +81,25 @@ public class PageTesterSession implements Session } } + @Override + public boolean containsAttribute(String name) + { + return attributes.containsKey(name); + } + + @Override + public boolean containsAttribute(String name, Session.LockMode lockMode) + { + return containsAttribute(name); + } + private void nyi(String name) { throw new IllegalStateException(String.format("%s.%s() is not yet implemented.", getClass() .getName(), name)); } + @Override public int getMaxInactiveInterval() { nyi("getMaxInativeInterval"); @@ -72,20 +107,24 @@ public class PageTesterSession implements Session return 0; } + @Override public void invalidate() { nyi("invalidate"); } + @Override public boolean isInvalidated() { return false; } + @Override public void restoreDirtyObjects() { } + @Override public void setMaxInactiveInterval(int seconds) { nyi("setMaxInactiveInterval"); diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/SessionImplTest.java b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/SessionImplTest.java index 2f7cbd4ca..327fec3ea 100644 --- a/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/SessionImplTest.java +++ b/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/SessionImplTest.java @@ -14,6 +14,11 @@ package org.apache.tapestry5.internal.services; +import java.util.Arrays; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; + import org.apache.tapestry5.http.internal.services.ClusteredSessionImpl; import org.apache.tapestry5.http.internal.services.SessionImpl; import org.apache.tapestry5.http.internal.services.SessionLock; @@ -55,6 +60,25 @@ public class SessionImplTest extends InternalBaseTestCase verify(); } + @Test + public void get_attribute_names_lock_mode() + { + Enumeration e = Collections.enumeration(Arrays.asList("fred", "barney")); + HttpSession hs = mockHttpSession(); + SessionLock lock = mockLock(); + + lock.acquireWriteLock(); + expect(hs.getAttributeNames()).andReturn(e); + + replay(); + + Session session = new SessionImpl(null, hs, lock); + + assertEquals(session.getAttributeNames(Session.LockMode.WRITE), Arrays.asList("barney", "fred")); + + verify(); + } + @Test public void get_attribute_names_by_prefix() { @@ -75,6 +99,71 @@ public class SessionImplTest extends InternalBaseTestCase verify(); } + @Test + public void contains_attribute() + { + List<String> keys = Arrays.asList("fred", "barney"); + HttpSession hs = mockHttpSession(); + SessionLock lock = mockLock(); + + // We need one per assert, and Enumerations are exhausted on use + lock.acquireReadLock(); + expect(hs.getAttributeNames()).andReturn(Collections.enumeration(keys)); + lock.acquireReadLock(); + expect(hs.getAttributeNames()).andReturn(Collections.enumeration(keys)); + lock.acquireReadLock(); + expect(hs.getAttributeNames()).andReturn(Collections.enumeration(keys)); + replay(); + + Session session = new SessionImpl(null, hs, lock); + + assertTrue(session.containsAttribute("barney")); + assertTrue(session.containsAttribute("fred")); + assertFalse(session.containsAttribute("wilma")); + + verify(); + } + + @Test + public void get_attribute_write_lock() + { + Enumeration e = Collections.enumeration(Arrays.asList("fred", "barney")); + HttpSession hs = mockHttpSession(); + SessionLock lock = mockLock(); + + lock.acquireReadLock(); + lock.acquireWriteLock(); + expect(hs.getAttributeNames()).andReturn(e); + expect(hs.getAttribute("fred")).andReturn("1"); + + replay(); + + Session session = new SessionImpl(null, hs, lock); + + assertEquals(session.getAttribute("fred"), "1"); + + verify(); + } + + @Test + public void get_attribute_no_lock_update() + { + Enumeration e = Collections.enumeration(Arrays.asList("fred", "barney")); + HttpSession hs = mockHttpSession(); + SessionLock lock = mockLock(); + + lock.acquireReadLock(); + expect(hs.getAttributeNames()).andReturn(e); + + replay(); + + Session session = new SessionImpl(null, hs, lock); + + assertEquals(session.getAttribute("wilma"), null); + + verify(); + } + @Test public void invalidate() { @@ -169,6 +258,13 @@ public class SessionImplTest extends InternalBaseTestCase Object dirty = new Object(); SessionLock lock = mockLock(); + // TAP5-2799: To reduce write locks, first, a read-lock attempt is done + // to check if the attribute exists, and only then, a write-lock is acquired. + + lock.acquireReadLock(); + + expect(hs.getAttributeNames()).andReturn(Collections.enumeration(Arrays.asList("dirty"))); + lock.acquireWriteLock(); train_getAttribute(hs, "dirty", dirty); diff --git a/tapestry-http/src/main/java/org/apache/tapestry5/http/internal/services/SessionImpl.java b/tapestry-http/src/main/java/org/apache/tapestry5/http/internal/services/SessionImpl.java index 01047d51b..7248e1f6c 100644 --- a/tapestry-http/src/main/java/org/apache/tapestry5/http/internal/services/SessionImpl.java +++ b/tapestry-http/src/main/java/org/apache/tapestry5/http/internal/services/SessionImpl.java @@ -14,16 +14,17 @@ package org.apache.tapestry5.http.internal.services; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; +import java.util.Objects; + import org.apache.tapestry5.commons.util.CollectionFactory; import org.apache.tapestry5.http.services.Session; import org.apache.tapestry5.ioc.internal.util.InternalUtils; -import org.apache.tapestry5.ioc.services.PerthreadManager; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; -import java.util.Collections; -import java.util.Enumeration; -import java.util.List; /** * A thin wrapper around {@link HttpSession}. @@ -45,37 +46,95 @@ public class SessionImpl implements Session this.lock = lock; } + @Override public Object getAttribute(String name) { - lock.acquireWriteLock(); + return getAttribute(name, Session.LockMode.WRITE); + } + + @Override + public Object getAttribute(String name, Session.LockMode lockMode) + { + Objects.requireNonNull(name, "name must be non-null"); + + // If a WRITE lock is requested, check first if the key exists + // to prevent a lock upgrade if not necessary. + if (lockMode == null || lockMode == Session.LockMode.WRITE) + { + if (!containsAttribute(name)) return null; + } + + acquireLock(lockMode, Session.LockMode.WRITE); return session.getAttribute(name); } + @Override public List<String> getAttributeNames() { - lock.acquireReadLock(); + return getAttributeNames(Session.LockMode.READ); + } + + @Override + public List<String> getAttributeNames(Session.LockMode lockMode) + { + acquireLock(lockMode, Session.LockMode.READ); return InternalUtils.toList(session.getAttributeNames()); } + @Override public void setAttribute(String name, Object value) { + Objects.requireNonNull(name, "name must be non-null"); + lock.acquireWriteLock(); session.setAttribute(name, value); } + @Override + public boolean containsAttribute(String name) + { + return containsAttribute(name, Session.LockMode.READ); + } + + @Override + public boolean containsAttribute(String name, Session.LockMode lockMode) + { + Objects.requireNonNull(name, "name must be non-null"); + + acquireLock(lockMode, Session.LockMode.READ); + + Enumeration<String> e = session.getAttributeNames(); + while (e.hasMoreElements()) + { + String attrName = e.nextElement(); + if (attrName.equals(name)) return true; + } + + return false; + } + + @Override public List<String> getAttributeNames(String prefix) { - lock.acquireReadLock(); + return getAttributeNames(prefix, Session.LockMode.READ); + } + + @Override + public List<String> getAttributeNames(String prefix, Session.LockMode lockMode) + { + Objects.requireNonNull(prefix, "prefix must be non-null"); + + acquireLock(lockMode, Session.LockMode.READ); List<String> result = CollectionFactory.newList(); - Enumeration e = session.getAttributeNames(); + Enumeration<String> e = session.getAttributeNames(); while (e.hasMoreElements()) { - String name = (String) e.nextElement(); + String name = e.nextElement(); if (name.startsWith(prefix)) result.add(name); } @@ -85,11 +144,13 @@ public class SessionImpl implements Session return result; } + @Override public int getMaxInactiveInterval() { return session.getMaxInactiveInterval(); } + @Override public void invalidate() { invalidated = true; @@ -97,6 +158,7 @@ public class SessionImpl implements Session session.invalidate(); } + @Override public boolean isInvalidated() { if (invalidated) return true; @@ -110,13 +172,34 @@ public class SessionImpl implements Session return invalidated; } + @Override public void setMaxInactiveInterval(int seconds) { session.setMaxInactiveInterval(seconds); } + @Override public void restoreDirtyObjects() { } + + private void acquireLock(Session.LockMode requestedMode, Session.LockMode defaultMode) { + if (requestedMode == null) + { + requestedMode = defaultMode; + } + + switch (requestedMode) + { + case NONE: + break; + case READ: + this.lock.acquireReadLock(); + break; + case WRITE: + this.lock.acquireWriteLock(); + break; + } + } } diff --git a/tapestry-http/src/main/java/org/apache/tapestry5/http/services/Session.java b/tapestry-http/src/main/java/org/apache/tapestry5/http/services/Session.java index 70019a1d7..507418c08 100644 --- a/tapestry-http/src/main/java/org/apache/tapestry5/http/services/Session.java +++ b/tapestry-http/src/main/java/org/apache/tapestry5/http/services/Session.java @@ -22,50 +22,161 @@ import org.apache.tapestry5.http.OptimizedSessionPersistedObject; import org.apache.tapestry5.http.annotations.ImmutableSessionPersistedObject; import org.apache.tapestry5.http.internal.services.OptimizedSessionPersistedObjectAnalyzer; + /** * Generic version of {@link HttpSession}, used to bridge the gaps between the Servlet API and the Portlet API. */ public interface Session { + + /** + * The type of lock used to access atttributes in the {@link Session}. + * <p> + * The actual lock-type depends on if a lock is already held by the this thread. + * + * @since 5.9 + */ + public enum LockMode { + /** + * No lock is supposed to be acquired. + */ + NONE, + + /** + * Acquire a shared read-lock. + */ + READ, + + /** + * Acquire an exclusive write-lock. + */ + WRITE + } + /** - * Returns a list of the names of all attributes stored in the session. The names are returned sorted - * alphabetically. + * Returns a list of the names of all attributes stored in the session. + * <p> + * The names are returned sorted alphabetically. + * <p> + * By default, a {@code READ} lock is requested. */ List<String> getAttributeNames(); /** - * Returns a list of the names of all attributes stored in the session whose name has the provided prefix. The names - * are returned in alphabetical order. + * Returns a list of the names of all attributes stored in the session. + * <p> + * Uses the requested {@link LockMode} to acquire an appropiate lock. + * + * @param lockMode The requested minimum lock mode. If null, {@code READ} is used. + * @return Alphabetically sorted list of all attributes + * + * @since 5.9 + */ + List<String> getAttributeNames(LockMode lockMode); + + /** + * Returns a list of the names of all attributes stored in the session whose name has the provided prefix. + * <p> + * By default, a {@code READ} lock is requested. + * + * @param prefix The attribute prefix + * @throws NullPointerException if prefix is {@code null} + * @return Alphabetically sorted list of attributes matching the prefix */ List<String> getAttributeNames(String prefix); + /** + * Returns a list of the names of all attributes stored in the session whose name has the + * provided prefix. + * <p> + * Uses the requested {@link LockMode} to acquire an appropriate lock. + * + * @param prefix The attribute prefix + * @throws NullPointerException if prefix is {@code null} + * @return Alphabetically sorted list of attributes matching the prefix + * + * @since 5.9 + */ + List<String> getAttributeNames(String prefix, Session.LockMode lockMode); + /** * Returns the value previously stored in the session. + * <p> + * By default, a {@code WRITE} lock is requested. + * + * @param name The name of the attribute + * @throws NullPointerException if name is {@code null} */ Object getAttribute(String name); /** - * Sets the value of an attribute. If the value is null, then the attribute is deleted. + * Returns the value previously stored in the session. + * <p> + * Uses the requested {@link LockMode} to acquire an appropriate lock. + * + * @param name The name of the attribute + * @throws NullPointerException if name is {@code null} + * + * @since 5.9 + */ + Object getAttribute(String name, Session.LockMode lockMode); + + /** + * Sets the value of an attribute. If the value is {@code null}, then the attribute is deleted. + * + * @param name The name of the attribute + * @param value The new value of the attribute; {@code null} deletes the attribute. + * @throws NullPointerException if name is {@code null} */ void setAttribute(String name, Object value); /** - * Returns the maximum time interval, in seconds, that the servlet container will keep this session open between - * client accesses. After this interval, the servlet container will invalidate the session. The maximum time - * interval can be set with the setMaxInactiveInterval method. A negative time indicates the session should never - * timeout. + * Checks if the a value is stored in the session with the specified name. + * <p> + * By default, a {@code READ} lock is requested. + * + * @param name The name of the attribute + * @throws NullPointerException if name is {@code null} + * + * @since 5.9 + */ + boolean containsAttribute(String name); + + /** + * Checks if the a value is stored in the session with the specified name. + * <p> + * Uses the requested {@link LockMode} to acquire an appropriate lock. + * + * @param name The name of the attribute + * @throws NullPointerException if name is {@code null} + * + * @since 5.9 + */ + boolean containsAttribute(String name, Session.LockMode lockMode); + + /** + * Returns the maximum time interval, in seconds, that the servlet container will keep this + * session open between client accesses. + * After this interval, the servlet container will invalidate the session. + * <p> + * The maximum time interval can be set with the setMaxInactiveInterval method. + * <p> + * A negative time indicates the session should never timeout. */ int getMaxInactiveInterval(); /** - * Specifies the time, in seconds, between client requests before the servlet container will invalidate this - * session. A negative time indicates the session should never timeout. + * Specifies the time, in seconds, between client requests before the servlet container will + * invalidate this + * session. + * <p> + * A negative time indicates the session should never timeout. */ void setMaxInactiveInterval(int seconds); /** * Invalidates this session then unbinds any objects bound to it. - * + * * @throws IllegalStateException * if this method is called on an already invalidated session */ @@ -85,7 +196,7 @@ public interface Session * session object is read and changed, those changes will be limited to a single server in the cluster, which can * cause confusing application failures in the event of a failover. Does nothing if there are no changes, or * the session has been invalidated. - * + * * @see OptimizedSessionPersistedObject * @see OptimizedSessionPersistedObjectAnalyzer * @see ImmutableSessionPersistedObject