Commit d32e9c3c authored by Gaston Dombiak's avatar Gaston Dombiak Committed by gato

Fixed leak when counting execution of ad-hoc commands. Improved expiration of commands too. OF-482

git-svn-id: http://svn.igniterealtime.org/svn/repos/openfire/branches/openfire_3_6_4@12866 b35dd754-fafc-0310-a699-88a17e54d16e
parent 81af44f6
...@@ -66,7 +66,7 @@ ...@@ -66,7 +66,7 @@
<property name="version.major" value="3"/> <property name="version.major" value="3"/>
<property name="version.minor" value="6"/> <property name="version.minor" value="6"/>
<property name="version.revision" value="4"/> <property name="version.revision" value="4a"/>
<property name="version.extra" value=""/> <!-- For 'beta' or 'alpha' --> <property name="version.extra" value=""/> <!-- For 'beta' or 'alpha' -->
<property name="dist.prefix" value="openfire"/> <property name="dist.prefix" value="openfire"/>
...@@ -224,6 +224,7 @@ ...@@ -224,6 +224,7 @@
<or> <or>
<contains string="${ant.version}" substring="1.6"/> <contains string="${ant.version}" substring="1.6"/>
<contains string="${ant.version}" substring="1.7"/> <contains string="${ant.version}" substring="1.7"/>
<contains string="${ant.version}" substring="1.8"/>
</or> </or>
</not> </not>
</condition> </condition>
......
...@@ -162,6 +162,18 @@ hr { ...@@ -162,6 +162,18 @@ hr {
<div id="pageBody"> <div id="pageBody">
<h2>3.6.4a -- <span style="font-weight: normal;">Oct 10, 2011</span></h2>
<h3>Openfire New Features</h3>
<ul>
<li>No changes</li>
</ul>
<h3>Openfire Bug Fixes</h3>
<ul>
<li>[<a href='http://issues.igniterealtime.org/browse/OF-482'>OF-482</a>] - Fixed leak when counting ad-hoc commands that was preventing new commands from being executed.</li>
</ul>
<h2>3.6.4 -- <span style="font-weight: normal;">May 1, 2009</span></h2> <h2>3.6.4 -- <span style="font-weight: normal;">May 1, 2009</span></h2>
<h3>Openfire New Features</h3> <h3>Openfire New Features</h3>
......
...@@ -147,6 +147,7 @@ public class AdHocCommandHandler extends IQHandler ...@@ -147,6 +147,7 @@ public class AdHocCommandHandler extends IQHandler
super.start(); super.start();
infoHandler.setServerNodeInfoProvider(NAMESPACE, this); infoHandler.setServerNodeInfoProvider(NAMESPACE, this);
itemsHandler.setServerNodeInfoProvider(NAMESPACE, this); itemsHandler.setServerNodeInfoProvider(NAMESPACE, this);
manager.init();
// Add the "out of the box" commands // Add the "out of the box" commands
addDefaultCommands(); addDefaultCommands();
} }
......
...@@ -13,19 +13,21 @@ package org.jivesoftware.openfire.commands; ...@@ -13,19 +13,21 @@ package org.jivesoftware.openfire.commands;
import org.dom4j.Element; import org.dom4j.Element;
import org.dom4j.QName; import org.dom4j.QName;
import org.jivesoftware.openfire.component.ComponentEventListener;
import org.jivesoftware.openfire.component.InternalComponentManager;
import org.jivesoftware.openfire.event.SessionEventDispatcher;
import org.jivesoftware.openfire.event.SessionEventListener;
import org.jivesoftware.openfire.session.Session;
import org.jivesoftware.util.JiveGlobals; import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.StringUtils; import org.jivesoftware.util.StringUtils;
import org.xmpp.forms.DataForm; import org.xmpp.forms.DataForm;
import org.xmpp.forms.FormField; import org.xmpp.forms.FormField;
import org.xmpp.packet.IQ; import org.xmpp.packet.IQ;
import org.xmpp.packet.JID;
import org.xmpp.packet.PacketError; import org.xmpp.packet.PacketError;
import java.util.Collection; import java.util.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
/** /**
* An AdHocCommandManager is responsible for keeping the list of available commands offered * An AdHocCommandManager is responsible for keeping the list of available commands offered
...@@ -34,7 +36,7 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -34,7 +36,7 @@ import java.util.concurrent.atomic.AtomicInteger;
* *
* @author Gaston Dombiak * @author Gaston Dombiak
*/ */
public class AdHocCommandManager { public class AdHocCommandManager implements SessionEventListener, ComponentEventListener {
private static final String NAMESPACE = "http://jabber.org/protocol/commands"; private static final String NAMESPACE = "http://jabber.org/protocol/commands";
...@@ -44,10 +46,10 @@ public class AdHocCommandManager { ...@@ -44,10 +46,10 @@ public class AdHocCommandManager {
*/ */
private Map<String, AdHocCommand> commands = new ConcurrentHashMap<String, AdHocCommand>(); private Map<String, AdHocCommand> commands = new ConcurrentHashMap<String, AdHocCommand>();
/** /**
* Map that holds the number of command sessions of each requester. * Map that holds the id of command sessions of each requester.
* Note: Key=requester full's JID, Value=number of sessions * Note: Key=requester full's JID, Value=collection with command sessions ids
*/ */
private Map<String, AtomicInteger> sessionsCounter = new ConcurrentHashMap<String, AtomicInteger>(); private Map<String, Collection<String>> sessionsCounter = new ConcurrentHashMap<String, Collection<String>>();
/** /**
* Map that holds the command sessions. Used mainly to quickly locate a SessionData. * Map that holds the command sessions. Used mainly to quickly locate a SessionData.
* Note: Key=sessionID, Value=SessionData * Note: Key=sessionID, Value=SessionData
...@@ -145,21 +147,34 @@ public class AdHocCommandManager { ...@@ -145,21 +147,34 @@ public class AdHocCommandManager {
} }
else { else {
// The command requires user interactions (ie. has stages) // The command requires user interactions (ie. has stages)
// Check that the user has not excedded the limit of allowed simultaneous
// Clean up expired sessions data
Collection<String> sessionsIDs = sessionsCounter.get(from);
if (sessionsIDs != null) {
synchronized (from.intern()) {
Collection<String> existingSessionsIDs = new ArrayList<String>(sessionsIDs);
for (String existingSessionID : existingSessionsIDs) {
SessionData session = sessions.get(existingSessionID);
// Check if the Session data has expired (default is 10 minutes)
expireSessionData(session, from);
}
}
}
// Check that the user has not exceeded the limit of allowed simultaneous
// command sessions. // command sessions.
AtomicInteger counter = sessionsCounter.get(from); sessionsIDs = sessionsCounter.get(from);
if (counter == null) { if (sessionsIDs == null) {
synchronized (from.intern()) { synchronized (from.intern()) {
counter = sessionsCounter.get(from); sessionsIDs = sessionsCounter.get(from);
if (counter == null) { if (sessionsIDs == null) {
counter = new AtomicInteger(0); sessionsIDs = new ArrayList<String>();
sessionsCounter.put(from, counter); sessionsCounter.put(from, sessionsIDs);
} }
} }
} }
int limit = JiveGlobals.getIntProperty("xmpp.command.limit", 100); int limit = JiveGlobals.getIntProperty("xmpp.command.limit", 100);
if (counter.incrementAndGet() > limit) { if (sessionsIDs.size() > limit) {
counter.decrementAndGet();
// Answer a not_allowed error since the user has exceeded limit. This // Answer a not_allowed error since the user has exceeded limit. This
// checking prevents bad users from consuming all the system memory by not // checking prevents bad users from consuming all the system memory by not
// allowing them to create infinite simultaneous command sessions. // allowing them to create infinite simultaneous command sessions.
...@@ -167,6 +182,10 @@ public class AdHocCommandManager { ...@@ -167,6 +182,10 @@ public class AdHocCommandManager {
reply.setError(PacketError.Condition.not_allowed); reply.setError(PacketError.Condition.not_allowed);
return reply; return reply;
} }
// Keep track of sessions per XMPP entity
synchronized (from.intern()) {
sessionsIDs.add(sessionid);
}
// Originate a new command session. // Originate a new command session.
SessionData session = new SessionData(sessionid, packet.getFrom()); SessionData session = new SessionData(sessionid, packet.getFrom());
sessions.put(sessionid, session); sessions.put(sessionid, session);
...@@ -196,11 +215,7 @@ public class AdHocCommandManager { ...@@ -196,11 +215,7 @@ public class AdHocCommandManager {
} }
// Check if the Session data has expired (default is 10 minutes) // Check if the Session data has expired (default is 10 minutes)
int timeout = JiveGlobals.getIntProperty("xmpp.command.timeout", 10 * 60 * 1000); if (expireSessionData(session, from)) {
if (System.currentTimeMillis() - session.getCreationStamp() > timeout) {
// TODO Check all sessions that might have timed out (use another thread?)
// Remove the old session
removeSessionData(sessionid, from);
// Answer a not_allowed error (session-expired) // Answer a not_allowed error (session-expired)
reply.setChildElement(iqCommand.createCopy()); reply.setChildElement(iqCommand.createCopy());
reply.setError(PacketError.Condition.not_allowed); reply.setError(PacketError.Condition.not_allowed);
...@@ -300,16 +315,81 @@ public class AdHocCommandManager { ...@@ -300,16 +315,81 @@ public class AdHocCommandManager {
*/ */
private void removeSessionData(String sessionid, String from) { private void removeSessionData(String sessionid, String from) {
sessions.remove(sessionid); sessions.remove(sessionid);
if (sessionsCounter.get(from).decrementAndGet() <= 0) { synchronized (from.intern()) {
// Remove the AtomicInteger when no commands are being executed Collection<String> sessionsIDs = sessionsCounter.get(from);
sessionsIDs.remove(sessionid);
if (sessionsIDs.isEmpty()) {
// Remove the Collection when no commands are being executed for this XMPP entity
sessionsCounter.remove(from); sessionsCounter.remove(from);
}
} }
} }
private void removeSessionsData(String from) {
synchronized (from.intern()) {
Collection<String> sessionsIDs = sessionsCounter.get(from);
if (sessionsIDs == null) {
// Nothing to clean up since there are no ad-hoc commands being executed
// from this address at this time
return;
}
for (String sessionid : sessionsIDs) {
sessions.remove(sessionid);
}
}
sessionsCounter.remove(from);
}
public void stop() { public void stop() {
// Cancel executions of running commands // Cancel executions of running commands
sessions.clear(); sessions.clear();
sessionsCounter.clear(); sessionsCounter.clear();
} }
private boolean expireSessionData(SessionData session, String from) {
int timeout = JiveGlobals.getIntProperty("xmpp.command.timeout", 10 * 60 * 1000);
if (System.currentTimeMillis() - session.getCreationStamp() > timeout) {
// Remove the old session
removeSessionData(session.getId(), from);
return true;
}
return false;
}
/***************** Session Listeners **********************/
public void componentRegistered(JID componentJID) {
//Ignore
}
public void componentUnregistered(JID componentJID) {
removeSessionsData(componentJID.toString());
}
public void componentInfoReceived(IQ iq) {
//Ignore
}
public void sessionCreated(Session session) {
//Ignore
}
public void sessionDestroyed(Session session) {
removeSessionsData(session.getAddress().toString());
}
public void anonymousSessionCreated(Session session) {
//Ignore
}
public void anonymousSessionDestroyed(Session session) {
removeSessionsData(session.getAddress().toString());
}
public void resourceBound(Session session) {
//Ignore
}
public void init() {
InternalComponentManager.getInstance().addListener(this);
SessionEventDispatcher.addListener(this);
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment