Commit e1872a19 authored by Gaston Dombiak's avatar Gaston Dombiak Committed by gaston

Added an auto-archive process to clean-up unused rooms. JM-41


git-svn-id: http://svn.igniterealtime.org/svn/repos/messenger/trunk@739 b35dd754-fafc-0310-a699-88a17e54d16e
parent 1dd43975
...@@ -89,6 +89,24 @@ public interface MUCRoom extends ChatDeliverer { ...@@ -89,6 +89,24 @@ public interface MUCRoom extends ChatDeliverer {
*/ */
void setModificationDate(Date modificationDate); void setModificationDate(Date modificationDate);
/**
* Sets the date when the last occupant left the room. A null value means that there are
* occupants in the room at the moment.
*
* @param emptyDate the date when the last occupant left the room or null if there are occupants
* in the room.
*/
void setEmptyDate(Date emptyDate);
/**
* Returns the date when the last occupant left the room. A null value means that there are
* occupants in the room at the moment.
*
* @return the date when the last occupant left the room or null if there are occupants in the
* room at the moment.
*/
Date getEmptyDate();
/** /**
* Obtain the role of the chat server (mainly for addressing messages and presence). * Obtain the role of the chat server (mainly for addressing messages and presence).
* *
......
...@@ -233,6 +233,12 @@ public class MUCRoomImpl implements MUCRoom { ...@@ -233,6 +233,12 @@ public class MUCRoomImpl implements MUCRoom {
*/ */
private Date modificationDate; private Date modificationDate;
/**
* The date when the last occupant left the room. A null value means that there are occupants
* in the room at the moment.
*/
private Date emptyDate;
/** /**
* Indicates if the room is present in the database. * Indicates if the room is present in the database.
*/ */
...@@ -254,6 +260,7 @@ public class MUCRoomImpl implements MUCRoom { ...@@ -254,6 +260,7 @@ public class MUCRoomImpl implements MUCRoom {
this.startTime = System.currentTimeMillis(); this.startTime = System.currentTimeMillis();
this.creationDate = new Date(startTime); this.creationDate = new Date(startTime);
this.modificationDate = new Date(startTime); this.modificationDate = new Date(startTime);
this.emptyDate = new Date(startTime);
// TODO Allow to set the history strategy from the configuration form? // TODO Allow to set the history strategy from the configuration form?
roomHistory = new MUCRoomHistory(this, new HistoryStrategy(server.getHistoryStrategy())); roomHistory = new MUCRoomHistory(this, new HistoryStrategy(server.getHistoryStrategy()));
role = new RoomRole(this); role = new RoomRole(this);
...@@ -300,6 +307,19 @@ public class MUCRoomImpl implements MUCRoom { ...@@ -300,6 +307,19 @@ public class MUCRoomImpl implements MUCRoom {
this.modificationDate = modificationDate; this.modificationDate = modificationDate;
} }
public void setEmptyDate(Date emptyDate) {
// Do nothing if old value is same as new value
if (this.emptyDate == emptyDate) {
return;
}
this.emptyDate = emptyDate;
MUCPersistenceManager.updateRoomEmptyDate(this);
}
public Date getEmptyDate() {
return this.emptyDate;
}
public MUCRole getRole() { public MUCRole getRole() {
return role; return role;
} }
...@@ -542,6 +562,8 @@ public class MUCRoomImpl implements MUCRoom { ...@@ -542,6 +562,8 @@ public class MUCRoomImpl implements MUCRoom {
else { else {
historyRequest.sendHistory(joinRole, roomHistory); historyRequest.sendHistory(joinRole, roomHistory);
} }
// Update the date when the last occupant left the room
setEmptyDate(null);
} }
return joinRole; return joinRole;
} }
...@@ -571,6 +593,10 @@ public class MUCRoomImpl implements MUCRoom { ...@@ -571,6 +593,10 @@ public class MUCRoomImpl implements MUCRoom {
server.removeChatRoom(name); server.removeChatRoom(name);
} }
if (occupants.isEmpty()) {
// Update the date when the last occupant left the room
setEmptyDate(new Date());
}
} }
finally { finally {
lock.writeLock().unlock(); lock.writeLock().unlock();
......
...@@ -62,22 +62,12 @@ import org.xmpp.packet.Presence; ...@@ -62,22 +62,12 @@ import org.xmpp.packet.Presence;
* require to log their conversations. The conversations log is saved to the database using a * require to log their conversations. The conversations log is saved to the database using a
* separate process<p> * separate process<p>
* *
* Rooms in memory are held in the instance variable rooms. For optimization reasons, persistent * Temporary rooms are held in memory as long as they have occupants. They will be destroyed after
* rooms that don't have occupants aren't kept in memory. But a client may need to discover all the * the last occupant left the room. On the other hand, persistent rooms are always present in memory
* rooms (present in memory or not). So MultiUserChatServerImpl uses a cache of persistent room * even after the last occupant left the room. In order to keep memory clean of peristent rooms that
* surrogates. A room surrogate (lighter object) is created for each persistent room that is public, * have been forgotten or abandonded this class includes a clean up process. The clean up process
* persistent but is not in memory. The cache starts up empty until a client requests the list of * will remove from memory rooms that haven't had occupants for a while. Moreover, forgotten or
* rooms through service discovery. Once the disco request is received the cache is filled up with * abandoned rooms won't be loaded into memory when the Multi-User Chat service starts up.
* persistent room surrogates. The cache will keep all the surrogates in memory for an hour. If the
* cache's entries weren't used in an hour, they will be removed from memory. Whenever a persistent
* room is removed from memory (because all the occupants have left), the cache is cleared. But if
* a persistent room is loaded from the database then the entry for that room in the cache is
* removed. Note: Since the cache contains an entry for each room surrogate and the clearing
* algorithm is based on the usage of each entry, it's possible that some entries are removed
* while others don't thus generating that the provided list of discovered rooms won't be complete.
* However, this possibility is low since the clients will most of the time ask for all the cache
* entries and then ask for a particular entry. Anyway, if this possibility happens the cache will
* be reset the next time that a persistent room is removed from memory.
* *
* @author Gaston Dombiak * @author Gaston Dombiak
*/ */
...@@ -177,6 +167,22 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha ...@@ -177,6 +167,22 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha
*/ */
private Queue<ConversationLogEntry> logQueue = new LinkedBlockingQueue<ConversationLogEntry>(); private Queue<ConversationLogEntry> logQueue = new LinkedBlockingQueue<ConversationLogEntry>();
/**
* Max number of hours that a persistent room may be empty before the service removes the
* room from memory. Unloaded rooms will exist in the database and may be loaded by a user
* request. Default time limit is: 7 days.
*/
private long emptyLimit = 7 * 24;
/**
* Task that removes rooms from memory that have been without activity for a period of time. A
* room is considered without activity when no occupants are present in the room for a while.
*/
private CleanupTask cleanupTask;
/**
* The time to elapse between each rooms cleanup. Default frequency is 60 minutes.
*/
private final long cleanup_frequency = 60 * 60 * 1000;
/** /**
* Create a new group chat server. * Create a new group chat server.
*/ */
...@@ -274,6 +280,29 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha ...@@ -274,6 +280,29 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha
} }
} }
/**
* Removes from memory rooms that have been without activity for a period of time. A room is
* considered without activity when no occupants are present in the room for a while.
*/
private class CleanupTask extends TimerTask {
public void run() {
try {
cleanupRooms();
}
catch (Throwable e) {
Log.error(LocaleUtils.getLocalizedString("admin.error"), e);
}
}
}
private void cleanupRooms() {
for (MUCRoom room : rooms.values()) {
if (room.getEmptyDate() != null && room.getEmptyDate().before(getCleanupDate())) {
removeChatRoom(room.getName());
}
}
}
public MUCRoom getChatRoom(String roomName, JID userjid) throws UnauthorizedException { public MUCRoom getChatRoom(String roomName, JID userjid) throws UnauthorizedException {
MUCRoom room = null; MUCRoom room = null;
synchronized (roomName.intern()) { synchronized (roomName.intern()) {
...@@ -283,7 +312,8 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha ...@@ -283,7 +312,8 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha
// If the room is persistent load the configuration values from the DB // If the room is persistent load the configuration values from the DB
try { try {
// Try to load the room's configuration from the database (if the room is // Try to load the room's configuration from the database (if the room is
// persistent but was added to the DB after the server was started up) // persistent but was added to the DB after the server was started up or the
// room may be an old room that was not present in memory)
MUCPersistenceManager.loadFromDB((MUCRoomImpl) room); MUCPersistenceManager.loadFromDB((MUCRoomImpl) room);
} }
catch (IllegalArgumentException e) { catch (IllegalArgumentException e) {
...@@ -307,7 +337,29 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha ...@@ -307,7 +337,29 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha
} }
public MUCRoom getChatRoom(String roomName) { public MUCRoom getChatRoom(String roomName) {
return rooms.get(roomName.toLowerCase()); MUCRoom room = rooms.get(roomName.toLowerCase());
if (room == null) {
// Check if the room exists in the database and was not present in memory
synchronized (roomName.intern()) {
room = rooms.get(roomName.toLowerCase());
if (room == null) {
room = new MUCRoomImpl(this, roomName, router);
// If the room is persistent load the configuration values from the DB
try {
// Try to load the room's configuration from the database (if the room is
// persistent but was added to the DB after the server was started up or the
// room may be an old room that was not present in memory)
MUCPersistenceManager.loadFromDB((MUCRoomImpl) room);
rooms.put(roomName.toLowerCase(), room);
}
catch (IllegalArgumentException e) {
// The room does not exist so do nothing
room = null;
}
}
}
}
return room;
} }
public List<MUCRoom> getChatRooms() { public List<MUCRoom> getChatRooms() {
...@@ -375,6 +427,15 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha ...@@ -375,6 +427,15 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha
JiveGlobals.setProperty("xmpp.muc.service", name); JiveGlobals.setProperty("xmpp.muc.service", name);
} }
/**
* Returns the limit date after which rooms whithout activity will be removed from memory.
*
* @return the limit date after which rooms whithout activity will be removed from memory.
*/
private Date getCleanupDate() {
return new Date(System.currentTimeMillis() - (emptyLimit * 3600000));
}
public void setKickIdleUsersTimeout(int timeout) { public void setKickIdleUsersTimeout(int timeout) {
if (this.user_timeout == timeout) { if (this.user_timeout == timeout) {
return; return;
...@@ -599,36 +660,35 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha ...@@ -599,36 +660,35 @@ public class MultiUserChatServerImpl extends BasicModule implements MultiUserCha
// (default values) // (default values)
logConversationTask = new LogConversationTask(); logConversationTask = new LogConversationTask();
timer.schedule(logConversationTask, log_timeout, log_timeout); timer.schedule(logConversationTask, log_timeout, log_timeout);
// Remove unused rooms from memory
cleanupTask = new CleanupTask();
timer.schedule(cleanupTask, cleanup_frequency, cleanup_frequency);
routingTable = server.getRoutingTable(); routingTable = server.getRoutingTable();
router = server.getPacketRouter(); router = server.getPacketRouter();
// TODO Remove the tracking for IQRegisterHandler when the component JEP gets implemented. // TODO Remove the tracking for IQRegisterHandler when the component JEP gets implemented.
registerHandler = server.getIQRegisterHandler(); registerHandler = server.getIQRegisterHandler();
registerHandler.addDelegate(getServiceName(), new IQMUCRegisterHandler(this)); registerHandler.addDelegate(getServiceName(), new IQMUCRegisterHandler(this));
// Add the route to this service
routingTable.addRoute(chatServiceAddress, this);
ArrayList params = new ArrayList();
params.clear();
params.add(chatServiceName);
Log.info(LocaleUtils.getLocalizedString("startup.starting.muc", params));
} }
public void start() { public void start() {
super.start(); super.start();
// Add the route to this service
routingTable.addRoute(chatServiceAddress, this); routingTable.addRoute(chatServiceAddress, this);
ArrayList params = new ArrayList(); ArrayList params = new ArrayList();
params.clear(); params.clear();
params.add(chatServiceName); params.add(chatServiceName);
Log.info(LocaleUtils.getLocalizedString("startup.starting.muc", params)); Log.info(LocaleUtils.getLocalizedString("startup.starting.muc", params));
// Load all the persistent rooms to memory // Load all the persistent rooms to memory
for (MUCRoom room : MUCPersistenceManager.loadRoomsFromDB(this, router)) { for (MUCRoom room : MUCPersistenceManager.loadRoomsFromDB(this, this.getCleanupDate(),
router)) {
rooms.put(room.getName().toLowerCase(), room); rooms.put(room.getName().toLowerCase(), room);
} }
} }
public void stop() { public void stop() {
super.stop(); super.stop();
// Remove the route to this service
routingTable.removeRoute(chatServiceAddress); routingTable.removeRoute(chatServiceAddress);
timer.cancel(); timer.cancel();
logAllConversation(); logAllConversation();
......
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