Switch to using a ScheduledThreadPoolExecutor instead of a Timer for scheduled tasks. This will allow 2 * CPU count threads to handle background tasks instead of using a single thread, which could cause the server to effectively die if a task gets stuck for some reason. This addresse the symptom of #89, but not the cause (which is yet to be determined).

This commit is contained in:
Andy Janata 2014-02-16 00:43:13 -08:00
parent fd3dce4c62
commit 0e45c4f886
7 changed files with 46 additions and 28 deletions

View File

@ -29,7 +29,9 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
import java.util.Timer;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
import net.socialgamer.cah.data.GameManager;
import net.socialgamer.cah.data.GameManager.GameId;
@ -65,10 +67,24 @@ public class CahModule extends AbstractModule {
})
.annotatedWith(BanList.class)
.toInstance(Collections.synchronizedSet(new HashSet<String>()));
bind(Timer.class)
.toInstance(new Timer("timer-task", true));
bind(Properties.class)
.toInstance(properties);
final ScheduledThreadPoolExecutor threadPool =
new ScheduledThreadPoolExecutor(2 * Runtime.getRuntime().availableProcessors(),
new ThreadFactory() {
final AtomicInteger threadCount = new AtomicInteger();
@Override
public Thread newThread(final Runnable r) {
final Thread t = new Thread(r);
t.setDaemon(true);
t.setName("timer-task-" + threadCount.incrementAndGet());
return t;
}
});
threadPool.setRemoveOnCancelPolicy(true);
bind(ScheduledThreadPoolExecutor.class).toInstance(threadPool);
}
/**

View File

@ -1,11 +1,9 @@
package net.socialgamer.cah;
import java.util.TimerTask;
import org.apache.log4j.Logger;
public abstract class SafeTimerTask extends TimerTask {
public abstract class SafeTimerTask implements Runnable {
private static final Logger logger = Logger.getLogger(SafeTimerTask.class);

View File

@ -27,7 +27,8 @@ import java.io.File;
import java.io.FileReader;
import java.util.Date;
import java.util.Properties;
import java.util.Timer;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
@ -83,8 +84,9 @@ public class StartupUtils extends GuiceServletContextListener {
final ServletContext context = contextEvent.getServletContext();
final Injector injector = (Injector) context.getAttribute(INJECTOR);
final Timer timer = injector.getInstance(Timer.class);
timer.cancel();
final ScheduledThreadPoolExecutor timer = injector
.getInstance(ScheduledThreadPoolExecutor.class);
timer.shutdownNow();
context.removeAttribute(INJECTOR);
context.removeAttribute(DATE_NAME);
@ -97,8 +99,9 @@ public class StartupUtils extends GuiceServletContextListener {
final ServletContext context = contextEvent.getServletContext();
final Injector injector = getInjector();
final UserPing ping = injector.getInstance(UserPing.class);
final Timer timer = injector.getInstance(Timer.class);
timer.schedule(ping, PING_START_DELAY, PING_CHECK_DELAY);
final ScheduledThreadPoolExecutor timer = injector
.getInstance(ScheduledThreadPoolExecutor.class);
timer.scheduleAtFixedRate(ping, PING_START_DELAY, PING_CHECK_DELAY, TimeUnit.MILLISECONDS);
serverStarted = new Date();
context.setAttribute(INJECTOR, injector);
context.setAttribute(DATE_NAME, serverStarted);

View File

@ -23,7 +23,7 @@
package net.socialgamer.cah;
import java.util.Timer;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import net.socialgamer.cah.data.ConnectedUsers;
@ -38,10 +38,10 @@ import com.google.inject.Inject;
public class UserPing extends SafeTimerTask {
private final ConnectedUsers users;
private final Timer globalTimer;
private final ScheduledThreadPoolExecutor globalTimer;
@Inject
public UserPing(final ConnectedUsers users, final Timer globalTimer) {
public UserPing(final ConnectedUsers users, final ScheduledThreadPoolExecutor globalTimer) {
this.users = users;
this.globalTimer = globalTimer;
}

View File

@ -33,7 +33,9 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@ -146,8 +148,8 @@ public class Game {
* Lock to prevent missing timer updates.
*/
private final Object roundTimerLock = new Object();
private volatile SafeTimerTask lastTimerTask;
private final Timer globalTimer;
private volatile ScheduledFuture<?> lastScheduledFuture;
private final ScheduledThreadPoolExecutor globalTimer;
private int scoreGoal = 8;
private final Set<CardSet> cardSets = new HashSet<CardSet>();
@ -169,7 +171,7 @@ public class Game {
*/
@Inject
public Game(@GameId final Integer id, final ConnectedUsers connectedUsers,
final GameManager gameManager, final Timer globalTimer) {
final GameManager gameManager, final ScheduledThreadPoolExecutor globalTimer) {
this.id = id;
this.connectedUsers = connectedUsers;
this.gameManager = gameManager;
@ -902,10 +904,10 @@ public class Game {
private void killRoundTimer() {
synchronized (roundTimerLock) {
if (lastTimerTask != null) {
logger.trace(String.format("Killing timer task %s", lastTimerTask));
lastTimerTask.cancel();
lastTimerTask = null;
if (null != lastScheduledFuture) {
logger.trace(String.format("Killing timer task %s", lastScheduledFuture));
lastScheduledFuture.cancel(false);
lastScheduledFuture = null;
}
}
}
@ -914,8 +916,7 @@ public class Game {
synchronized (roundTimerLock) {
killRoundTimer();
logger.trace(String.format("Scheduling timer task %s after %d ms", task, timeout));
lastTimerTask = task;
globalTimer.schedule(task, timeout);
lastScheduledFuture = globalTimer.schedule(task, timeout, TimeUnit.MILLISECONDS);
}
}

View File

@ -35,7 +35,7 @@ import static org.junit.Assert.assertNull;
import java.util.Collection;
import java.util.HashMap;
import java.util.Timer;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import net.socialgamer.cah.HibernateUtil;
import net.socialgamer.cah.data.GameManager.GameId;
@ -65,7 +65,7 @@ public class GameManagerTest {
private ConnectedUsers cuMock;
private User userMock;
private int gameId;
private final Timer timer = new Timer("junit-timer", true);
private final ScheduledThreadPoolExecutor timer = new ScheduledThreadPoolExecutor(1);
@Before
public void setUp() throws Exception {

View File

@ -36,7 +36,7 @@ import static org.junit.Assert.assertTrue;
import java.util.Collection;
import java.util.HashMap;
import java.util.Timer;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import net.socialgamer.cah.data.Game.TooManyPlayersException;
import net.socialgamer.cah.data.QueuedMessage.MessageType;
@ -55,7 +55,7 @@ public class GameTest {
private Game game;
private ConnectedUsers cuMock;
private GameManager gmMock;
private final Timer timer = new Timer("junit-timer", true);
private final ScheduledThreadPoolExecutor timer = new ScheduledThreadPoolExecutor(1);
@Before
public void setUp() throws Exception {