Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion java/org/apache/catalina/ha/session/DeltaManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -438,9 +438,15 @@ public int getSendAllSessionsSize() {
/**
* Set the batch size for sending all sessions.
*
* @param sendAllSessionsSize The batch size value
* @param sendAllSessionsSize The batch size value. Must be a positive integer.
*
* @throws IllegalArgumentException if the batch size is not a positive integer
*/
public void setSendAllSessionsSize(int sendAllSessionsSize) {
if (sendAllSessionsSize <= 0) {
throw new IllegalArgumentException(
sm.getString("deltaManager.sendAllSessionsSize.invalid", Integer.valueOf(sendAllSessionsSize)));
}
this.sendAllSessionsSize = sendAllSessionsSize;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ deltaManager.receiveMessage.unloadingAfter=Manager [{0}]: unloading sessions com
deltaManager.receiveMessage.unloadingBegin=Manager [{0}]: start unloading sessions
deltaManager.registerCluster=Register manager [{0}] to cluster element [{1}] with name [{2}]
deltaManager.sendMessage.newSession=Manager [{0}] send new session [{1}]
deltaManager.sendAllSessionsSize.invalid=The sendAllSessionsSize value [{0}] is invalid. It must be a positive integer.
deltaManager.sessionReceived=Manager [{0}]; session state sent at [{1}] received in [{2}] ms.
deltaManager.startClustering=Starting clustering manager at [{0}]
deltaManager.stopped=Manager [{0}] is stopping
Expand Down
136 changes: 136 additions & 0 deletions test/org/apache/catalina/ha/session/TestDeltaManagerBatch.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/*
* 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.catalina.ha.session;

import java.io.IOException;

import org.easymock.EasyMock;
import org.junit.Assert;
import org.junit.Test;

import org.apache.catalina.Session;
import org.apache.catalina.ha.CatalinaCluster;
import org.apache.catalina.tribes.Member;

/**
* Tests for the <code>sendAllSessionsSize</code> validation in
* {@link DeltaManager} and the batched session sending in
* {@link DeltaManager#handleGET_ALL_SESSIONS}.
*/
public class TestDeltaManagerBatch {

/**
* Tracks how many times {@link #sendSessions(Member, Session[], long)} is
* invoked and what payloads it receives.
*/
private static class TesterDeltaManager extends DeltaManager {

private int sendCount;
private int lastBatchSize = -1;
private final Session[] sessions;

TesterDeltaManager(Session[] sessions) {
this.sessions = sessions;
}

@Override
public Session[] findSessions() {
return sessions;
}

@Override
protected void sendSessions(Member sender, Session[] currentSessions, long sendTimestamp)
throws IOException {
sendCount++;
lastBatchSize = currentSessions.length;
}
}

private static Session[] createSessions(int count) {
Session[] result = new Session[count];
TesterDeltaManager manager = new TesterDeltaManager(new Session[0]);
for (int i = 0; i < count; i++) {
result[i] = new DeltaSession(manager);
}
return result;
}

private static TesterDeltaManager createManager(Session[] sessions, boolean sendAllSessions,
int sendAllSessionsSize) {
TesterDeltaManager manager = new TesterDeltaManager(sessions);
manager.setSendAllSessions(sendAllSessions);
manager.setSendAllSessionsSize(sendAllSessionsSize);
// Avoid the inter-batch sleep in the batching loop so the test does not
// depend on the default sendAllSessionsWaitTime.
manager.setSendAllSessionsWaitTime(0);

// The send operations at the end of handleGET_ALL_SESSIONS require a
// cluster reference. Use a mock so no real cluster is needed.
CatalinaCluster cluster = EasyMock.createNiceMock(CatalinaCluster.class);
EasyMock.replay(cluster);
manager.setCluster(cluster);
return manager;
}

@Test(expected = IllegalArgumentException.class)
public void testSendAllSessionsSizeZeroRejected() {
new TesterDeltaManager(new Session[0]).setSendAllSessionsSize(0);
}

@Test(expected = IllegalArgumentException.class)
public void testSendAllSessionsSizeNegativeRejected() {
new TesterDeltaManager(new Session[0]).setSendAllSessionsSize(-5);
}

@Test
public void testSendAllSessionsSizePositiveAccepted() {
TesterDeltaManager manager = new TesterDeltaManager(new Session[0]);
manager.setSendAllSessionsSize(1);
Assert.assertEquals(1, manager.getSendAllSessionsSize());
}

@Test
public void testPositiveBatchSizeSplitsSessions() throws Exception {
TesterDeltaManager manager = createManager(createSessions(5), false, 2);

manager.handleGET_ALL_SESSIONS(null, null);

// 5 sessions in batches of 2 => 2+2+1 = 3 sends
Assert.assertEquals(3, manager.sendCount);
}

@Test
public void testSendAllSessionsIgnoresBatchSize() throws Exception {
TesterDeltaManager manager = createManager(createSessions(3), true, 1);

manager.handleGET_ALL_SESSIONS(null, null);

// sendAllSessions is true so all sessions are sent in a single batch
Assert.assertEquals(1, manager.sendCount);
Assert.assertEquals(3, manager.lastBatchSize);
}

@Test
public void testNoSessionsNotSent() throws Exception {
TesterDeltaManager manager = createManager(createSessions(0), false, 2);

manager.handleGET_ALL_SESSIONS(null, null);

// No sessions means the batching loop body never runs
Assert.assertEquals(0, manager.sendCount);
}
}
7 changes: 7 additions & 0 deletions webapps/docs/changelog.xml
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,13 @@
<subsection name="Cluster">
<changelog>
<!-- Entries for backport and removal before 12.0.0-M1 below this line -->
<fix>
Validate that the <code>DeltaManager</code> attribute
<code>sendAllSessionsSize</code> is a positive integer. Zero or
negative values previously caused an infinite loop or a
<code>NegativeArraySizeException</code> during session state transfer.
(lihongyi87)
</fix>
Comment on lines +323 to +329

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be below the line since it will be back-ported.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved below the line. Thanks for the pointer.

</changelog>
</subsection>
<subsection name="WebSocket">
Expand Down
2 changes: 2 additions & 0 deletions webapps/docs/config/cluster-manager.xml
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@
<attribute name="sendAllSessionsSize" required="false">
The number of sessions in a session block message. This value is
effective only when <code>sendAllSessions</code> is <code>false</code>.
It must be a positive integer; any other value is rejected with an
<code>IllegalArgumentException</code> during configuration.
Default is <code>1000</code>.
</attribute>
<attribute name="sendAllSessionsWaitTime" required="false">
Expand Down