This is an automated email from the ASF dual-hosted git repository.
mreutegg pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/jackrabbit-oak.git
The following commit(s) were added to refs/heads/trunk by this push:
new 03635c1 OAK-9565: NullPointerException on Session.logout()
new 69109d6 Merge pull request #361 from mreutegg/OAK-9565
03635c1 is described below
commit 03635c18f5ab73a9c80b2336d6f5fc0af510bf47
Author: Marcel Reutegger <[email protected]>
AuthorDate: Tue Sep 7 12:04:10 2021 +0200
OAK-9565: NullPointerException on Session.logout()
SessionLogoutTest to reproduce the issue
Also introduce test from OAK-9262
Synchronize Session.logout() on a newly introduced monitor object
---
.../jackrabbit/oak/jcr/session/SessionImpl.java | 43 ++++++++--------
.../oak/jcr/session/SessionLogoutTest.java | 57 ++++++++++++++++++++++
.../oak/jcr/session/SessionStatsTest.java | 40 +++++++++++++++
3 files changed, 120 insertions(+), 20 deletions(-)
diff --git
a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionImpl.java
b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionImpl.java
index 2b7f365..2295834 100644
---
a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionImpl.java
+++
b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/session/SessionImpl.java
@@ -86,6 +86,7 @@ public class SessionImpl implements JackrabbitSession {
private SessionContext sessionContext;
private SessionDelegate sd;
private final CounterStats sessionCounter;
+ private final Object logoutMonitor = new Object();
public SessionImpl(SessionContext sessionContext) {
this.sessionContext = sessionContext;
@@ -465,26 +466,28 @@ public class SessionImpl implements JackrabbitSession {
@Override
public void logout() {
- if (isLive()) {
- sessionCounter.dec();
- try {
- sd.performVoid(new SessionOperation<Void>("logout") {
- @Override
- public void performVoid() {
- sessionContext.dispose();
- sd.logout();
- }
-
- @Override
- public boolean isLogout() {
- return true;
- }
- });
- } catch (RepositoryException e) {
- throw new RuntimeException("Unexpected exception thrown by
operation 'logout'", e);
- } finally {
- sd = null;
- sessionContext = null;
+ synchronized (logoutMonitor) {
+ if (isLive()) {
+ sessionCounter.dec();
+ try {
+ sd.performVoid(new SessionOperation<Void>("logout") {
+ @Override
+ public void performVoid() {
+ sessionContext.dispose();
+ sd.logout();
+ }
+
+ @Override
+ public boolean isLogout() {
+ return true;
+ }
+ });
+ } catch (RepositoryException e) {
+ throw new RuntimeException("Unexpected exception thrown by
operation 'logout'", e);
+ } finally {
+ sd = null;
+ sessionContext = null;
+ }
}
}
}
diff --git
a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/session/SessionLogoutTest.java
b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/session/SessionLogoutTest.java
new file mode 100644
index 0000000..a0a7d60
--- /dev/null
+++
b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/session/SessionLogoutTest.java
@@ -0,0 +1,57 @@
+/*
+ * 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.session;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CountDownLatch;
+
+import javax.jcr.Session;
+
+import org.apache.jackrabbit.test.AbstractJCRTest;
+
+public class SessionLogoutTest extends AbstractJCRTest {
+
+ public void testConcurrentSessionLogout() throws Exception {
+ Session s = getHelper().getReadWriteSession();
+ List<Exception> exceptions = new CopyOnWriteArrayList<>();
+ List<Thread> threads = new ArrayList<>();
+ CountDownLatch latch = new CountDownLatch(1);
+ for (int i = 0; i < 8; i++) {
+ threads.add(new Thread(() -> {
+ try {
+ latch.await();
+ s.logout();
+ } catch (Exception e) {
+ exceptions.add(e);
+ }
+ }));
+ }
+ for (Thread t : threads) {
+ t.start();
+ }
+ latch.countDown();
+ for (Thread t : threads) {
+ t.join();
+ }
+ for (Exception ex : exceptions) {
+ ex.printStackTrace();
+ fail(ex.toString());
+ }
+ }
+}
diff --git
a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/session/SessionStatsTest.java
b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/session/SessionStatsTest.java
index 7b17611..e3f51f5 100644
---
a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/session/SessionStatsTest.java
+++
b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/session/SessionStatsTest.java
@@ -18,10 +18,13 @@ package org.apache.jackrabbit.oak.jcr.session;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.jackrabbit.oak.jcr.delegate.SessionDelegate;
+import org.apache.jackrabbit.oak.stats.CounterStats;
import org.apache.jackrabbit.test.AbstractJCRTest;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
+
+import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
@@ -59,6 +62,43 @@ public class SessionStatsTest extends AbstractJCRTest {
afterSession.logout();
}
+ // OAK-9262
+ public void testSessionCounter() throws Exception {
+ CounterStats sessionCounter = getSessionCounter(superuser);
+ // try this a few times
+ for (int i = 0; i < 10; i++) {
+ internalTestSessionCounter(sessionCounter);
+ }
+ }
+
+ private void internalTestSessionCounter(CounterStats sessionCounter)
+ throws Exception {
+ Session s = createSession();
+ long numSessions = sessionCounter.getCount();
+ List<Thread> threads = new ArrayList<>();
+ for (int i = 0; i < 4; i++) {
+ threads.add(new Thread(s::logout));
+ }
+ for (Thread t : threads) {
+ t.start();
+ }
+ for (Thread t : threads) {
+ t.join();
+ }
+ assertEquals(numSessions - 1, sessionCounter.getCount());
+ }
+
+ private static CounterStats getSessionCounter(Session session)
+ throws Exception {
+ if (session instanceof SessionImpl) {
+ Field f = SessionImpl.class.getDeclaredField("sessionCounter");
+ f.setAccessible(true);
+ return (CounterStats) f.get(session);
+ }
+ fail("Session is not a " + SessionImpl.class.getName());
+ throw new IllegalStateException();
+ }
+
private Session createSession() throws RepositoryException {
return getHelper().getReadWriteSession();
}