lock individual processes, not the whole queue

This commit is contained in:
Aidan Hahn 2019-05-23 22:13:57 -07:00
parent 4cb9d3a5e1
commit 213d48c087
No known key found for this signature in database
GPG key ID: 327711E983899316
3 changed files with 110 additions and 110 deletions

View file

@ -14,6 +14,7 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.HashMap;
import java.util.Iterator;
@ -26,28 +27,29 @@ import java.io.IOException;
*/
class ProcessManager {
// TODO: LOCK_TIMEOUT should be defined in a configuration management system
private final int LOCK_TIMEOUT = 5; // seconds
private HashMap<Integer, ProcessController> processMap;
private Boolean processQueueMutex = false;
private final int LOCK_TIMEOUT = 2; // seconds
/*
* The significance of the concurrent hash map is that an in process
* update will not leave it in an unusable state like it will a normal
* HashMap. It is still up to the programmer in this instance to make
* sure that there are no concurrent operations done to the ProcessControllers
* Themselves. The last thing we want is to throw NPEs or whatnot when
* accessing a process destroyed mid read by another thread.
* Hence getLock(...) and lockMap controlling access to individual entries in
* processMap
*/
protected ConcurrentHashMap<Integer, ProcessController> processMap;
protected ConcurrentHashMap<Integer, Boolean> lockMap;
private ExecutorService threadPool = Executors.newCachedThreadPool();
private Callable<Object> lockCallable = new Callable<Object>() {
public Object call() {
while(processQueueMutex){
continue; // spin!
}
processQueueMutex = true;
return 1;
}
};
/*
* Constructor
* initializes process queue and start the background process checking daemon
*/
public ProcessManager() {
processMap = new HashMap<Integer, ProcessController>();
processMap = new ConcurrentHashMap<Integer, ProcessController>();
lockMap = new ConcurrentHashMap<Integer, Boolean>();
/* TODO: In a long running server over a large period of time
* It is possible that the streams used to redirect IO in the
* Processes may become a significant use of resources.
@ -61,39 +63,21 @@ class ProcessManager {
/*
* newProcess()
* Takes a command and returns the translated pid of a new process
* Returns -1 if getLock fails
* Returns -1 if getLock fails TODO: REMOVE
* Returns -2 if controller throws an IOException
*/
public int newProcess(String command) {
/*
* TRADEOFF: Could initialize new ProcessController out here
* Pro: would minimize time spent in critical section
* Con: what if initialization goes through but we dont get the lock
* we would essentially have a dangling untrackable process
* which likely changed system state before it was killed.
*/
try {
// Enter critical section
this.getLock();
ProcessController newProc = new ProcessController(command);
this.lockMap.put(newProc.getPid(), true);
this.processMap.put(newProc.getPid(), newProc);
// Exit critical section
this.releaseLock();
this.releaseLock(newProc.getPid());
return newProc.getPid();
} catch (TimeoutException e) {
// (lock was not grabbed)
System.err.println("Timeout starting new job: " + e.getMessage());
return -1;
} catch (IOException e) {
// (lock was grabbed)
this.releaseLock();
System.err.println("ProcessController couldnt start process: " + e.getMessage());
System.err.println("Couldnt Spawn New Command: " + e.getMessage());
return -2;
}
}
@ -108,11 +92,10 @@ class ProcessManager {
* 4: couldnt grab lock
*/
public int getProcessStatus(int pid) {
int status;
// Enter critical section
try {
this.getLock();
if(!this.getLock(pid)) {
return 3;
}
} catch (TimeoutException e) {
// lock could not be grabbed before timeout
@ -121,15 +104,9 @@ class ProcessManager {
}
ProcessController candidate = this.processMap.get(pid);
if (candidate != null) {
status = candidate.getStatus();
this.releaseLock();
return status;
}
// process must not exist
this.releaseLock();
return 3;
int status = candidate.getStatus();
this.releaseLock(pid);
return status;
}
/*
@ -142,11 +119,10 @@ class ProcessManager {
* 259: couldnt grab lock in time
*/
public int getProcessReturn(int pid) {
int ret;
// Enter Critical section
try {
this.getLock();
if(!this.getLock(pid)) {
return 258;
}
} catch (TimeoutException e) {
System.err.println("Timeout getting process return: " + e.getMessage());
@ -154,14 +130,9 @@ class ProcessManager {
}
ProcessController candidate = this.processMap.get(pid);
if (candidate != null) {
ret = candidate.getReturn();
this.releaseLock();
return ret;
}
this.releaseLock();
return 258;
int ret = candidate.getReturn();
this.releaseLock(pid);
return ret;
}
/*
@ -170,10 +141,10 @@ class ProcessManager {
* or returns description of error
*/
public String getProcessOutput(int pid) {
String output;
try {
this.getLock();
if(!this.getLock(pid)) {
return "[-] SERVER: Process not found";
}
} catch (TimeoutException e) {
System.err.println("Timeout getting process output: " + e.getMessage());
@ -181,14 +152,9 @@ class ProcessManager {
}
ProcessController candidate = this.processMap.get(pid);
if (candidate != null) {
output = candidate.getOutput();
this.releaseLock();
return output;
}
this.releaseLock();
return "[-] ERROR: Process not found";
String output = candidate.getOutput();
this.releaseLock(pid);
return output;
}
/*
@ -200,9 +166,10 @@ class ProcessManager {
* returns 3 if couldnt grab lock
*/
public int killProcess(int pid) {
int code;
try {
this.getLock();
if(!this.getLock(pid)) {
return 2;
}
} catch (TimeoutException e) {
System.err.println("Timeout killing process: " + e.getMessage());
@ -210,16 +177,9 @@ class ProcessManager {
}
ProcessController candidate = this.processMap.get(pid);
if (candidate != null) {
candidate.kill();
code = 1;
} else {
code = 2;
}
this.releaseLock();
return code;
candidate.kill();
this.releaseLock(pid);
return 1;
}
/*
@ -228,25 +188,41 @@ class ProcessManager {
* Waits for a predefined timeout period for mutex to be avail.
* Synchronized so two things cannot grab lock at once.
* Throws TimeoutException when it fails to get the lock.
* Alternatively, throws false if lock doesnt exist for PID
* Function is synchronized to prevent multiple threads accessing the same lock at once
* (ConcurrentHashMap will report whatever lock value was last to successfully update)
*/
protected synchronized void getLock() throws TimeoutException {
Future<Object> future = this.threadPool.submit(this.lockCallable);
protected synchronized Boolean getLock(int pid) throws TimeoutException {
if (!lockMap.containsKey(pid)) {
return false;
}
Future<Object> future = this.threadPool.submit(
new Callable<Object>() {
public Object call() {
while(lockMap.get(pid)) {
continue; // spin!
}
lockMap.replace(pid, true);
return 1;
}
});
try {
future.get(this.LOCK_TIMEOUT, TimeUnit.SECONDS);
} catch (InterruptedException e) {
System.err.println("[!] ERROR: " + e.getMessage());
throw new TimeoutException();
// rethrowing a timeout exception tells the calling process that they dont have the lock
future.cancel(true);
return false;
} catch (ExecutionException e) {
System.err.println("[!] ERROR: " + e.getMessage());
throw new TimeoutException();
future.cancel(true);
return false;
// cancel the attempt to grab the lock
} finally {
future.cancel(true);
}
/*
@ -261,14 +237,16 @@ class ProcessManager {
* mediate access to the ProcessManager
* object for fresh calls as well.
*/
return true;
}
/*
* releaseLock()
* releases mutex so other threads can operate on processqueue
*/
protected void releaseLock() {
this.processQueueMutex = false;
protected void releaseLock(int pid) {
this.lockMap.put(pid, false);
}
/*
@ -278,8 +256,6 @@ class ProcessManager {
* releases resources held in the processController objects
*/
public void shutdown() {
this.processQueueMutex = true;
Iterator<HashMap.Entry<Integer, ProcessController>> iterator = this.processMap.entrySet().iterator();
while (iterator.hasNext()) {
HashMap.Entry<Integer, ProcessController> entry = iterator.next();