/** Copyright 2015 Tim Engler, Rareventure LLC This file is part of Tiny Travel Tracker. Tiny Travel Tracker is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Tiny Travel Tracker is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Tiny Travel Tracker. If not, see <http://www.gnu.org/licenses/>. */ package com.rareventure.android; import java.util.ArrayList; import java.util.HashSet; import android.util.Log; import com.rareventure.android.SuperThread.Task; import com.rareventure.gps2.GTG; import com.rareventure.util.MultiValueHashMap; /** * This manages a set of super threads. It can coordinate them all to stop doing tasks * and shutdown (when the activity is finished, for example), pause and resume them * all (when the activity is paused and resumed), etc. */ public class SuperThreadManager { //object lock order: // SuperThreadManager -> SuperThread // (items to the left must be synchronized first to prevent deadlocks) //also note that we never lock objects we're waiting on or are notified for // (since they could be synchronized on when calling this class) private ArrayList<SuperThread> superThreads = new ArrayList<SuperThread>(); private MultiValueHashMap<Object, SuperThread.Task> objectToWaitingTasks = new MultiValueHashMap<Object, SuperThread.Task>(); private HashSet<Object> notifiedObjects = new HashSet<Object>(); int sleepingThreads; boolean isPaused; private SleepingThreadsListener sleepingThreadsListener; public boolean isShutdown; public void addSuperThread(SuperThread st) { superThreads.add(st); st.manager = this; } public void setSleepingThreadsListener(SleepingThreadsListener stl) { this.sleepingThreadsListener = stl; } synchronized void taskWillWaitOn(SuperThread.Task task, Object item) { //if we were pre notified, just ignore the wait, //otherwise we turn off the task if(!notifiedObjects.remove(item)) { task.setRunnable(false); objectToWaitingTasks.put(item, task); } } /** * Notify the listening tasks that they should do work. * Note that even if a task is not waiting, it will restart * immediately after finishing its current work if this * is set (unlike a normal notify which will do nothing * if a thread isn't waiting) * @param o object to notify */ public synchronized void stNotify(Object o) { Task t = objectToWaitingTasks.getFirst(o); //PERF: we could synchronize on individual threads rather than the manager to improve performance. // no reason to wake up all threads every time a notification happens //if there is at least one task that is already waiting if(t != null) { t.setRunnable(true); this.notifyAll(); objectToWaitingTasks.remove(o, t); } //put it on a list so the next task that waits for it // wakes up else notifiedObjects.add(o); } public void pauseAllSuperThreads() { synchronized (this) { isPaused = true; this.notifyAll(); } } public void resumeAllSuperThreads() { synchronized (this) { isPaused = false; this.notifyAll(); } } private static final int MAX_WAIT_FOR_DEATH = Integer.MAX_VALUE; public void shutdownAllSuperThreads() { //wait for all threads to die synchronized (this) { isPaused = false; isShutdown = true; this.notifyAll(); for(SuperThread t : superThreads) { int i; for(i = 0; i < MAX_WAIT_FOR_DEATH; i++) { if(t.isSTDead || t.currentlyRunningTask != null && t.currentlyRunningTask.promisesToDieSoon) break; if(t == Thread.currentThread()) throw new IllegalStateException("Why are you trying to interrupt all " + "SuperThreads from a SuperThread? "+ Thread.currentThread()); try { this.wait(5000); } catch (InterruptedException e) { throw new IllegalStateException(e); } if(t.isSTDead || t.currentlyRunningTask != null && t.currentlyRunningTask.promisesToDieSoon) break; Log.w(GTG.TAG,"Thread "+t+" is not being super... still alive when told to exit "+i+", task is "+t.currentlyRunningTask); } if(i == MAX_WAIT_FOR_DEATH) { throw new IllegalStateException("Thread "+t+" forgot to exit"); } } } Log.d(GTG.TAG,"All threads exited"); } synchronized void addDeltaToSleepingTheads(int delta) { boolean oldAllThreadsAreSleeping = sleepingThreads == superThreads.size(); sleepingThreads += delta; if(sleepingThreads == superThreads.size() != oldAllThreadsAreSleeping) { if(sleepingThreadsListener != null) sleepingThreadsListener.notifySleepingThreadsChanged(!oldAllThreadsAreSleeping); } } public static interface SleepingThreadsListener { void notifySleepingThreadsChanged(boolean allThreadsAreSleeping); } }