Commit a738f60b authored by Tom Evans's avatar Tom Evans Committed by tevans

OF-668: Improve PubSub persistence exception handling; use TaskEngine in lieu...

OF-668: Improve PubSub persistence exception handling; use TaskEngine in lieu of timer threads to avoid shutdown problems (non-daemon threads, etc.)

git-svn-id: http://svn.igniterealtime.org/svn/repos/openfire/trunk@13632 b35dd754-fafc-0310-a699-88a17e54d16e
parent 389894b2
......@@ -35,7 +35,6 @@ import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
......@@ -49,6 +48,7 @@ import org.jivesoftware.util.FastDateFormat;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.LocaleUtils;
import org.jivesoftware.util.StringUtils;
import org.jivesoftware.util.TaskEngine;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmpp.packet.IQ;
......@@ -113,7 +113,6 @@ public class AuditorImpl implements Auditor {
/**
* Timer to save queued logs to the XML file.
*/
private Timer timer = new Timer("Auditor");
private SaveQueuedPacketsTask saveQueuedPacketsTask;
private FastDateFormat dateFormat;
private static FastDateFormat auditFormat;
......@@ -137,7 +136,7 @@ public class AuditorImpl implements Auditor {
}
// Create a new task and schedule it with the new timeout
saveQueuedPacketsTask = new SaveQueuedPacketsTask();
timer.schedule(saveQueuedPacketsTask, logTimeout, logTimeout);
TaskEngine.getInstance().schedule(saveQueuedPacketsTask, logTimeout, logTimeout);
}
......@@ -185,8 +184,6 @@ public class AuditorImpl implements Auditor {
public void stop() {
// Stop queuing packets since we are being stopped
closed = true;
// Stop the scheduled task for saving queued packets to the XML file
timer.cancel();
// Save all remaining queued packets to the XML file
saveQueuedPackets();
close();
......
......@@ -95,6 +95,7 @@ public class HttpSessionManager {
public Thread newThread(Runnable runnable) {
Thread thread = new Thread(Thread.currentThread().getThreadGroup(), runnable,
"httpbind-worker-" + counter.getAndIncrement());
thread.setDaemon(true);
return thread;
}
});
......
......@@ -36,6 +36,7 @@ import org.jivesoftware.openfire.component.ComponentEventListener;
import org.jivesoftware.openfire.component.InternalComponentManager;
import org.jivesoftware.openfire.session.Session;
import org.jivesoftware.util.LocaleUtils;
import org.jivesoftware.util.TaskEngine;
import org.jivesoftware.util.XMPPDateTimeFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -68,7 +69,6 @@ public class PacketCopier implements PacketInterceptor, ComponentEventListener {
/**
* Timer to save queued logs to the XML file.
*/
private Timer timer = new Timer("PacketActivityNotifier");
private ProcessPacketsTask packetsTask;
/**
......@@ -99,7 +99,7 @@ public class PacketCopier implements PacketInterceptor, ComponentEventListener {
// Create a new task and schedule it with the new timeout
packetsTask = new ProcessPacketsTask();
timer.schedule(packetsTask, 5000, 5000);
TaskEngine.getInstance().schedule(packetsTask, 5000, 5000);
}
/**
......
......@@ -28,7 +28,6 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
......@@ -61,6 +60,7 @@ import org.jivesoftware.openfire.muc.cluster.RoomAvailableEvent;
import org.jivesoftware.openfire.muc.cluster.RoomRemovedEvent;
import org.jivesoftware.util.JiveProperties;
import org.jivesoftware.util.LocaleUtils;
import org.jivesoftware.util.TaskEngine;
import org.jivesoftware.util.XMPPDateTimeFormat;
import org.jivesoftware.util.cache.CacheFactory;
import org.slf4j.Logger;
......@@ -166,12 +166,6 @@ public class MultiUserChatServiceImpl implements Component, MultiUserChatService
*/
public long totalChatTime;
/**
* Timer to monitor chatroom participants. If they've been idle for too long, probe for
* presence.
*/
private Timer timer = new Timer("MUC cleanup");
/**
* Flag that indicates if the service should provide information about locked rooms when
* handling service discovery requests.
......@@ -745,7 +739,7 @@ public class MultiUserChatServiceImpl implements Component, MultiUserChatService
this.user_timeout = timeout;
// Create a new task and schedule it with the new timeout
userTimeoutTask = new UserTimeoutTask();
timer.schedule(userTimeoutTask, user_timeout, user_timeout);
TaskEngine.getInstance().schedule(userTimeoutTask, user_timeout, user_timeout);
// Set the new property value
MUCPersistenceManager.setProperty(chatServiceName, "tasks.user.timeout", Integer.toString(timeout));
}
......@@ -778,7 +772,7 @@ public class MultiUserChatServiceImpl implements Component, MultiUserChatService
this.log_timeout = timeout;
// Create a new task and schedule it with the new timeout
logConversationTask = new LogConversationTask();
timer.schedule(logConversationTask, log_timeout, log_timeout);
TaskEngine.getInstance().schedule(logConversationTask, log_timeout, log_timeout);
// Set the new property value
MUCPersistenceManager.setProperty(chatServiceName, "tasks.log.timeout", Integer.toString(timeout));
}
......@@ -1029,14 +1023,14 @@ public class MultiUserChatServiceImpl implements Component, MultiUserChatService
// Run through the users every 5 minutes after a 5 minutes server startup delay (default
// values)
userTimeoutTask = new UserTimeoutTask();
timer.schedule(userTimeoutTask, user_timeout, user_timeout);
TaskEngine.getInstance().schedule(userTimeoutTask, user_timeout, user_timeout);
// Log the room conversations every 5 minutes after a 5 minutes server startup delay
// (default values)
logConversationTask = new LogConversationTask();
timer.schedule(logConversationTask, log_timeout, log_timeout);
TaskEngine.getInstance().schedule(logConversationTask, log_timeout, log_timeout);
// Remove unused rooms from memory
cleanupTask = new CleanupTask();
timer.schedule(cleanupTask, CLEANUP_FREQUENCY, CLEANUP_FREQUENCY);
TaskEngine.getInstance().schedule(cleanupTask, CLEANUP_FREQUENCY, CLEANUP_FREQUENCY);
// Set us up to answer disco item requests
XMPPServer.getInstance().getIQDiscoItemsHandler().addServerItemsProvider(this);
......@@ -1059,7 +1053,6 @@ public class MultiUserChatServiceImpl implements Component, MultiUserChatService
XMPPServer.getInstance().getServerItemsProviders().remove(this);
// Remove the route to this service
routingTable.removeComponentRoute(getAddress());
timer.cancel();
logAllConversation();
}
......
......@@ -32,8 +32,8 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.StringTokenizer;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.locks.Lock;
......@@ -47,6 +47,7 @@ import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.LinkedList;
import org.jivesoftware.util.LinkedListNode;
import org.jivesoftware.util.StringUtils;
import org.jivesoftware.util.TaskEngine;
import org.jivesoftware.util.cache.Cache;
import org.jivesoftware.util.cache.CacheFactory;
import org.slf4j.Logger;
......@@ -62,6 +63,9 @@ public class PubSubPersistenceManager {
private static final Logger log = LoggerFactory.getLogger(PubSubPersistenceManager.class);
private static final String PERSISTENT_NODES = "SELECT serviceID, nodeID, maxItems " +
"FROM ofPubsubNode WHERE leaf=1 AND persistItems=1 AND maxItems > 0";
private static final String PURGE_FOR_SIZE =
"DELETE ofPubsubItem FROM ofPubsubItem LEFT JOIN " +
"(SELECT id FROM ofPubsubItem WHERE serviceID=? AND nodeID=? " +
......@@ -189,16 +193,40 @@ public class PubSubPersistenceManager {
"accessModel, language, replyPolicy, associationPolicy, maxLeafNodes) " +
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
private static Timer flushTimer;
private static long flushTimerDelay;
/**
* Pseudo-random number generator is used to offset timing for scheduled tasks
* within a cluster (so they don't run at the same time on all members).
*/
private static Random prng = new Random();
/**
* Flush timer delay is configurable, but not less than 20 seconds (default: 2 mins)
*/
private static long flushTimerDelay = Math.max(20000,
JiveGlobals.getIntProperty("xmpp.pubsub.flush.timer", 120)*1000);
private static Timer purgeTimer = new Timer("Pubsub purge stale items timer");
private static long purgeTimerDelay = JiveGlobals.getIntProperty("xmpp.pubsub.purge.timer", 300) * 1000;
/**
* Purge timer delay is configurable, but not less than 60 seconds (default: 5 mins)
*/
private static long purgeTimerDelay = Math.max(60000,
JiveGlobals.getIntProperty("xmpp.pubsub.purge.timer", 300)*1000);
/**
* Maximum number of published items allowed in the write cache
* before being flushed to the database.
*/
private static final int MAX_ITEMS_FLUSH = JiveGlobals.getIntProperty("xmpp.pubsub.flush.max", 1000);
/**
* Maximum number of rows that will be fetched from the published items table.
*/
private static final int MAX_ROWS_FETCH = JiveGlobals.getIntProperty("xmpp.pubsub.fetch.max", 2000);
/**
* Number of retry attempts we will make trying to write an item to the DB
*/
private static final int MAX_ITEM_RETRY = JiveGlobals.getIntProperty("xmpp.pubsub.item.retry", 1);
/**
* Queue that holds the items that need to be added to the database.
*/
......@@ -226,37 +254,26 @@ public class PubSubPersistenceManager {
private static final Cache<String, PublishedItem> itemCache = CacheFactory.createCache(ITEM_CACHE);
static {
if (MAX_ITEMS_FLUSH > 0) {
flushTimer = new Timer("Pubsub item flush timer");
flushTimerDelay = JiveGlobals.getIntProperty("xmpp.pubsub.flush.timer", 120) * 1000;
// Enforce a min of 20s
if (flushTimerDelay < 20000)
flushTimerDelay = 20000;
flushTimer.schedule(new TimerTask()
{
@Override
public void run()
{
flushPendingItems(false); // this member only
}
}, flushTimerDelay, flushTimerDelay);
try {
if (MAX_ITEMS_FLUSH > 0) {
TaskEngine.getInstance().schedule(new TimerTask() {
public void run() { flushPendingItems(false); } // this member only
}, Math.abs(prng.nextLong())%flushTimerDelay, flushTimerDelay);
}
// increase the timer delay when running in cluster mode
// because other members are also running the purge task
if (ClusterManager.isClusteringEnabled()) {
purgeTimerDelay = purgeTimerDelay*2;
}
TaskEngine.getInstance().schedule(new TimerTask() {
public void run() { purgeItems(); }
}, Math.abs(prng.nextLong())%purgeTimerDelay, purgeTimerDelay);
} catch (Exception ex) {
log.error("Failed to initialize pubsub maintentence tasks", ex);
}
// Enforce a min of 20s
if (purgeTimerDelay < 60000)
purgeTimerDelay = 60000;
purgeTimer.schedule(new TimerTask()
{
@Override
public void run()
{
purgeItems();
}
}, purgeTimerDelay, purgeTimerDelay);
}
/**
......@@ -1108,12 +1125,26 @@ public class PubSubPersistenceManager {
}
/**
* Creates and stores the published item in the database.
* Duplicate item (if found) is removed before storing the item.
*
* Creates and stores the published item in the database. Note that the
* item will be cached temporarily before being flushed asynchronously
* to the database. The write cache can be tuned using the following
* two properties:
* <pre>
* "xmpp.pubsub.flush.max" - maximum items in the cache (-1 to disable cache)
* "xmpp.pubsub.flush.timer" - number of seconds between cache flushes
* </pre>
* @param item The published item to save.
*/
public static void savePublishedItem(PublishedItem item) {
savePublishedItem(item, false);
}
/**
* Creates and stores the published item in the database.
* @param item The published item to save.
* @param isRetry True if this pass is for an item persistence retry
*/
private static void savePublishedItem(PublishedItem item, boolean isRetry) {
String itemKey = item.getItemKey();
itemCache.put(itemKey, item);
log.debug("Added new (inbound) item to cache");
......@@ -1122,17 +1153,19 @@ public class PubSubPersistenceManager {
if (itemToReplace != null) {
itemToReplace.remove(); // remove duplicate from itemsToAdd linked list
}
itemsToDelete.addLast(item); // delete stored duplicate (if any)
LinkedListNode listNode = itemsToAdd.addLast(item);
LinkedListNode listNode = isRetry ? itemsToAdd.addFirst(item) : itemsToAdd.addLast(item);
itemsPending.put(itemKey, listNode);
}
if (itemsPending.size() > MAX_ITEMS_FLUSH) {
flushPendingItems();
// don't flush pending items immediately if this is a retry attempt
if (!isRetry && itemsPending.size() > MAX_ITEMS_FLUSH) {
TaskEngine.getInstance().submit(new Runnable() {
public void run() { flushPendingItems(false); }
});
}
}
/**
* Flush the cache of items to be persisted and deleted.
* Flush the cache(s) of items to be persisted (itemsToAdd) and deleted (itemsToDelete).
*/
public static void flushPendingItems()
{
......@@ -1140,44 +1173,22 @@ public class PubSubPersistenceManager {
}
/**
* Flush the cache of items to be persisted and deleted.
* Flush the cache(s) of items to be persisted (itemsToAdd) and deleted (itemsToDelete).
* @param sendToCluster If true, delegate to cluster members, otherwise local only
*/
public static void flushPendingItems(boolean sendToCluster)
{
Connection con = null;
boolean rollback = false;
try
{
con = DbConnectionManager.getTransactionConnection();
flushPendingItems(sendToCluster, con);
}
catch (Exception e)
{
log.error("Failed to flush pending items", e);
rollback = true;
}
finally
{
DbConnectionManager.closeTransactionConnection(con, rollback);
}
}
private static void flushPendingItems(boolean sendToCluster, Connection con) throws SQLException
{
if (sendToCluster) {
// forward to other cluster members and wait for response
if (sendToCluster) {
CacheFactory.doSynchronousClusterTask(new FlushTask(), false);
}
if (itemsToAdd.getFirst() == null && itemsToDelete.getFirst() == null) {
if (itemsToAdd.getFirst() == null && itemsToDelete.getFirst() == null) {
return; // nothing to do for this cluster member
}
if (log.isDebugEnabled()) {
log.debug("Flush " + itemsPending.size() + " pending items to database");
}
Connection con = null;
boolean rollback = false;
LinkedList addList = null;
LinkedList delList = null;
......@@ -1191,8 +1202,8 @@ public class PubSubPersistenceManager {
itemsToAdd = new LinkedList();
itemsToDelete = new LinkedList();
// Ensure pending items are available via the item cache;
// this allows the item(s) to be fetched by other thread(s)
// Ensure pending items are available via the item read cache;
// this allows the item(s) to be fetched by other request threads
// while being written to the DB from this thread
int copied = 0;
for (String key : itemsPending.keySet()) {
......@@ -1207,19 +1218,66 @@ public class PubSubPersistenceManager {
itemsPending.clear();
}
// Note that we now make multiple attempts to write cached items to the DB:
// 1) insert all pending items in a single batch
// 2) if the batch insert fails, retry by inserting each item separately
// 3) if a given item cannot be written, return it to the pending write cache
// By default step 3 will be tried once per item, but this can be configured
// (or disabled) using the "xmpp.pubsub.item.retry" property. In the event of
// a transaction rollback, items that could not be written to the database
// will be returned to the pending item write cache.
try {
con = DbConnectionManager.getTransactionConnection();
writePendingItems(con, addList, delList);
} catch (SQLException se) {
log.error("Failed to flush pending items; initiating rollback", se);
// return new items to the write cache
LinkedListNode node = addList.getLast();
while (node != null) {
savePublishedItem((PublishedItem)node.object, true);
node.remove();
node = addList.getLast();
}
rollback = true;
} finally {
DbConnectionManager.closeTransactionConnection(con, rollback);
}
}
/**
* Loop through the lists of added and deleted items and write to the database
* @param con
* @param addList
* @param delList
* @throws SQLException
*/
private static void writePendingItems(Connection con, LinkedList addList, LinkedList delList) throws SQLException
{
LinkedListNode addItem = addList.getFirst();
LinkedListNode delItem = delList.getFirst();
// is there anything to do?
if ((addItem == null) && (delItem == null)) { return; }
if (log.isDebugEnabled()) {
log.debug("Flush " + itemsPending.size() + " pending items to database");
}
// Check to see if there is anything to actually do.
if ((addItem == null) && (delItem == null))
return;
PreparedStatement pstmt = null;
// ensure there are no duplicates by deleting before adding
if (addItem != null) {
LinkedListNode addHead = addItem.previous;
while (addItem != addHead) {
delList.addLast(addItem.object);
addItem = addItem.next;
}
}
// delete first (to remove possible duplicates), then add new items
delItem = delList.getFirst();
if (delItem != null) {
PreparedStatement pstmt = null;
try {
LinkedListNode delHead = delList.getLast().next;
LinkedListNode delHead = delItem.previous;
pstmt = con.prepareStatement(DELETE_ITEM);
while (delItem != delHead)
......@@ -1233,39 +1291,73 @@ public class PubSubPersistenceManager {
delItem = delItem.next;
}
pstmt.executeBatch();
}
finally
{
} catch (SQLException ex) {
log.error("Failed to delete published item(s) from DB", ex);
// do not re-throw here; continue with insert operation if possible
} finally {
DbConnectionManager.closeStatement(pstmt);
}
}
if (addItem != null) {
try {
LinkedListNode addHead = addList.getLast().next;
pstmt = con.prepareStatement(ADD_ITEM);
while (addItem != addHead)
{
PublishedItem item = (PublishedItem) addItem.object;
pstmt.setString(1, item.getNode().getService().getServiceID());
pstmt.setString(2, encodeNodeID(item.getNodeID()));
pstmt.setString(3, item.getID());
pstmt.setString(4, item.getPublisher().toString());
pstmt.setString(5, StringUtils.dateToMillis(item.getCreationDate()));
pstmt.setString(6, item.getPayloadXML());
pstmt.addBatch();
addItem = addItem.next;
}
pstmt.executeBatch();
}
finally
{
DbConnectionManager.closeStatement(pstmt);
}
try {
// first try to add the pending items as a batch
writePendingItems(con, addList.getFirst(), true);
} catch (SQLException ex) {
// retry each item individually rather than rolling back
writePendingItems(con, addList.getFirst(), false);
}
}
/**
* Execute JDBC calls (optionally via batch) to persist the given published items
* @param con
* @param addItem
* @param batch
* @throws SQLException
*/
private static void writePendingItems(Connection con, LinkedListNode addItem, boolean batch) throws SQLException
{
if (addItem == null) { return; }
LinkedListNode addHead = addItem.previous;
PreparedStatement pstmt = null;
PublishedItem item = null;
try {
pstmt = con.prepareStatement(ADD_ITEM);
while (addItem != addHead)
{
item = (PublishedItem) addItem.object;
pstmt.setString(1, item.getNode().getService().getServiceID());
pstmt.setString(2, encodeNodeID(item.getNodeID()));
pstmt.setString(3, item.getID());
pstmt.setString(4, item.getPublisher().toString());
pstmt.setString(5, StringUtils.dateToMillis(item.getCreationDate()));
pstmt.setString(6, item.getPayloadXML());
if (batch) { pstmt.addBatch(); }
else {
try { pstmt.execute(); }
catch (SQLException se) {
// individual item could not be persisted; retry (up to MAX_ITEM_RETRY attempts)
String itemKey = item.getItemKey();
if (item.getRetryCount() < MAX_ITEM_RETRY) {
log.warn("Failed to persist published item (will retry): " + itemKey);
savePublishedItem(item, true);
} else {
// all hope is lost ... item will be dropped
log.error("Published item could not be written to database: " + itemKey + "\n" + item.getPayloadXML(), se);
}
}
}
addItem = addItem.next;
}
if (batch) { pstmt.executeBatch(); }
} catch (SQLException se) {
log.error("Failed to persist published items as batch; will retry individually", se);
// caught by caller; should not cause a transaction rollback
throw se;
} finally {
DbConnectionManager.closeStatement(pstmt);
}
}
/**
* Removes the specified published item from the DB.
......@@ -1663,7 +1755,7 @@ public class PubSubPersistenceManager {
private static void purgeNode(LeafNode leafNode, Connection con) throws SQLException
{
flushPendingItems(ClusterManager.isClusteringEnabled(), con);
flushPendingItems();
// Remove published items of the node being deleted
PreparedStatement pstmt = null;
......@@ -1740,9 +1832,6 @@ public class PubSubPersistenceManager {
*/
private static void purgeItems()
{
String persistentNodeQuery = "SELECT serviceID, nodeID, maxItems FROM ofPubsubNode WHERE "
+ "leaf=1 AND persistItems=1 AND maxItems > 0";
boolean abortTransaction = false;
Connection con = null;
PreparedStatement pstmt = null;
......@@ -1752,7 +1841,7 @@ public class PubSubPersistenceManager {
try
{
con = DbConnectionManager.getTransactionConnection();
nodeConfig = con.prepareStatement(persistentNodeQuery);
nodeConfig = con.prepareStatement(PERSISTENT_NODES);
rs = nodeConfig.executeQuery();
PreparedStatement purgeNode = con
.prepareStatement(getPurgeStatement(DbConnectionManager.getDatabaseType()));
......@@ -1819,7 +1908,11 @@ public class PubSubPersistenceManager {
public static void shutdown()
{
flushPendingItems();
purgeItems();
flushPendingItems(false); // local member only
// node cleanup (skip when running as a cluster)
if (!ClusterManager.isClusteringEnabled()) {
purgeItems();
}
}
}
......@@ -101,7 +101,18 @@ public class PublishedItem implements Serializable {
* XML representation of the payload (for serialization)
*/
private String payloadXML;
/**
* Persistence retry counter
*/
private volatile transient int retryCount = 0;
/**
* Creates a published item
* @param node
* @param publisher
* @param id
* @param creationDate
*/
PublishedItem(LeafNode node, JID publisher, String id, Date creationDate) {
this.node = node;
this.nodeId = node.getNodeID();
......@@ -282,6 +293,14 @@ public class PublishedItem implements Serializable {
}
/**
* Returns (and increments) the item persistence retry counter
* @return Number of attempts made to persist this item to the DB
*/
public int getRetryCount() {
return retryCount++;
}
/**
* Returns a string that uniquely identifies this published item
* in the following format: <i>nodeId:itemId</i>
* @param node Node for the published item
......
......@@ -34,13 +34,13 @@ public class LinkedList {
* The root of the list keeps a reference to both the first and last
* elements of the list.
*/
private LinkedListNode head = new LinkedListNode("head", null, null);
private LinkedListNode head;
/**
* Creates a new linked list.
*/
public LinkedList() {
head.next = head.previous = head;
head = new LinkedListNode("head");
}
/**
......@@ -75,11 +75,7 @@ public class LinkedList {
* @param node the node to add to the beginning of the list.
*/
public LinkedListNode addFirst(LinkedListNode node) {
node.next = head.next;
node.previous = head;
node.previous.next = node;
node.next.previous = node;
return node;
return node.insert(head.next, head);
}
/**
......@@ -90,10 +86,16 @@ public class LinkedList {
* @return the node created to wrap the object.
*/
public LinkedListNode addFirst(Object object) {
LinkedListNode node = new LinkedListNode(object, head.next, head);
node.previous.next = node;
node.next.previous = node;
return node;
return new LinkedListNode(object, head.next, head);
}
/**
* Adds a node to the end of the list.
*
* @param node the node to add to the beginning of the list.
*/
public LinkedListNode addLast(LinkedListNode node) {
return node.insert(head, head.previous);
}
/**
......@@ -104,10 +106,7 @@ public class LinkedList {
* @return the node created to wrap the object.
*/
public LinkedListNode addLast(Object object) {
LinkedListNode node = new LinkedListNode(object, head, head.previous);
node.previous.next = node;
node.next.previous = node;
return node;
return new LinkedListNode(object, head, head.previous);
}
/**
......
......@@ -57,6 +57,15 @@ public class LinkedListNode {
*/
public long timestamp;
/**
* Constructs an self-referencing node. This node acts as a start/end
* sentinel when traversing nodes in a LinkedList.
*/
public LinkedListNode(Object object) {
previous = next = this;
this.object = object;
}
/**
* Constructs a new linked list node.
*
......@@ -64,19 +73,33 @@ public class LinkedListNode {
* @param next a reference to the next LinkedListNode in the list.
* @param previous a reference to the previous LinkedListNode in the list.
*/
public LinkedListNode(Object object, LinkedListNode next,
LinkedListNode previous) {
public LinkedListNode(Object object, LinkedListNode next, LinkedListNode previous) {
if (next != null && previous != null) {
this.insert(next, previous);
}
this.object = object;
this.next = next;
this.previous = previous;
}
/**
* Removes this node from the linked list that it is a part of.
* Removes this node from the linked list that it was a part of.
* @return This node; next and previous references dropped
*/
public void remove() {
public LinkedListNode remove() {
previous.next = next;
next.previous = previous;
previous = next = null;
return this;
}
/**
* Inserts this node into the linked list that it will be a part of.
* @return This node, updated to reflect previous/next changes
*/
public LinkedListNode insert(LinkedListNode next, LinkedListNode previous) {
this.next = next;
this.previous = previous;
this.previous.next = this.next.previous = this;
return this;
}
/**
......
......@@ -57,7 +57,7 @@ public class TaskEngine {
* Constructs a new task engine.
*/
private TaskEngine() {
timer = new Timer("timer-openfire", true);
timer = new Timer("TaskEngine-timer", true);
executor = Executors.newCachedThreadPool(new ThreadFactory() {
final AtomicInteger threadNumber = new AtomicInteger(1);
......@@ -65,7 +65,7 @@ public class TaskEngine {
public Thread newThread(Runnable runnable) {
// Use our own naming scheme for the threads.
Thread thread = new Thread(Thread.currentThread().getThreadGroup(), runnable,
"pool-openfire" + threadNumber.getAndIncrement(), 0);
"TaskEngine-pool-" + threadNumber.getAndIncrement(), 0);
// Make workers daemon threads.
thread.setDaemon(true);
if (thread.getPriority() != Thread.NORM_PRIORITY) {
......
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