initial sketch for process manager

This commit is contained in:
Aidan Hahn 2019-05-22 22:25:44 -07:00
parent 622da2d238
commit 0433ead782
No known key found for this signature in database
GPG key ID: 327711E983899316
2 changed files with 287 additions and 2 deletions

View file

@ -43,6 +43,14 @@ class ProcessController {
this.outputScanner.useDelimieter("\\A"); this.outputScanner.useDelimieter("\\A");
} }
/*
* getPid()
* returns translated pid of this process
*/
public int getPid() {
return this.pid;
}
/* /*
* getStatus() * getStatus()
* returns whether or not the process is running * returns whether or not the process is running
@ -77,8 +85,7 @@ class ProcessController {
/* /*
* getOutput() * getOutput()
* gets new output from stream * gets output from process
* (TODO: investigate whether this would better be done by )
*/ */
public String getOutput() { public String getOutput() {
String out = ""; String out = "";

View file

@ -0,0 +1,278 @@
/*
* ProcessManager
*
* v1.0
*
* May 22, 2019
*/
package JobServ;
import java.util.concurrent.Future;
/*
* Holds a list of ProcessControllers and controls access to them via mutex
* Additionally, starts and manages a background thread that clears finished processes from the arraylist
*/
class ProcessManager {
// TODO: LOCK_TIMEOUT should be defined in a configuration management system
private final int LOCK_TIMEOUT = 5; // seconds
private ArrayList<ProcessController> processQueue;
private Boolean processQueueMutex = false;
private Thread backgroundProcessCleaner;
private ExecutorService threadPool = Executors.newCachedThreadPool();
private Callable<void> getLockCallable = new Callable<void>() {
public void Object call() {
while(processQueueMutex){
continue; // spin!
}
processQueueMutex = true;
}
}
/*
* Constructor
* initializes process queue and start the background process checking daemon
*/
public ProcessManager() {
processQueue = new ArrayList<ProcessController>();
// 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.
// In this case a background thread should be called to periodically
// remove dead ProcessControllers after calling kill() on them.
}
/*
* newProcess()
* Takes a command and returns the translated pid of a new process
* Returns -1 if getLock fails
*/
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.
*/
// Enter critical section
try {
this.getLock();
} catch (TimeoutException e) {
// (lock was not grabbed)
System.err.println("Timeout starting new job '%s': " + e.getMessage, command);
return -1
}
ProcessController newProc = ProcessController(command);
this.processQueue.add(newProc);
// Exit critical section
this.releaseLock();
return newProc.getPid();
}
/*
* getProcessStatus()
* returns whether or not a process is running.
* 0: running
* 1: not running
* 2: doesnt exist
* 3: couldnt grab lock
*/
public int getProcessStatus(int pid) {
// Enter critical section
try {
this.getLock();
} catch (TimeoutException e) {
// lock could not be grabbed before timeout
System.err.println("Timeout getting process status for %s: " + e.getMessage(),
Integer.toString(pid));
return 3;
}
for (ProcessController iter : this.processQueue) {
if (iter.getPid() == pid) {
this.releaseLock();
// release lock on finding process
return iter.getStatus();
}
}
// process must not exist
this.releaseLock();
return 2;
}
/*
* getProcessReturn()
* returns a code 0-255, or 256 if process still running
* additionally, returns 257 if lock not grabbable AND
* a 258 if process doesnt exist.
*/
public int getProcessReturn(int pid) {
// Enter Critical section
try {
this.getLock();
} catch (TimeoutException e) {
System.err.println("Timeout getting process return for %s: " + e.getMessage(),
Integer.toString(pid));
return 257;
}
for (ProcessController iter : this.processQueue) {
if (iter.getPid() == pid) {
this.releaseLock();
return iter.getReturn();
}
}
this.releaseLock();
return 258;
}
/*
* getProcessOutput()
* returns output of process 'pid'
* or returns description of error
*/
public String getProcessOutput(int pid) {
try {
this.getLock();
} catch (TimeoutException e) {
System.err.println("Timeout getting process output for %s: " + e.getMessage(),
Integer.toString());
return "[-] ERROR: Timeout grabbing lock to access process information";
}
for (ProcessController iter : this.processQueue) {
if (iter.getPid() == pid) {
output = iter.getOutput();
this.releaseLock();
return output;
}
}
this.releaseLock();
return "[-] ERROR: Process not found"
}
/*
* killProcess()
* returns false if couldnt grab lock
* ALSO RETURNS TRUE IF PROCESS DOESNT EXIST
*/
public Boolean killProcess(int pid) {
try {
this.getLock();
} catch (TimeoutException e) {
System.err.println("Timeout killing process: " + e.getMessage);
return false;
}
for (ProcessController iter : this.processQueue) {
if (iter.getPid() == pid) {
iter.kill();
break;
}
}
this.releaseLock();
return true;
}
/*
* cleanProcessQueue()
* represents a background thread that sits and cleans finished processes
*/
private void cleanProcessQueue() {
while(true){
try {
this.getLock();
} catch (TimeoutException e) {
continue;
}
for (ProcessController iter : this.processQueue) {
if(!iter.getStatus()) {
iter.kill();
this.processQueue.remove(iter);
}
}
this.releaseLock();
Thread.sleep(5000);
}
}
/*
* getLock()
* Locks access to this.processQueue
* Waits for a predefined timeout period and then grabs the mutex
* Throws TimeoutException when it fails to get the lock.
*/
private synchronized void getLock() throws TimeoutException {
try {
Future<void> future = executor.submit(task);
void result = 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
} catch (ExecutionException e) {
System.err.println("[!] ERROR: " + e.getMessage());
throw new TimeoutException();
// cancel the attempt to grab the lock
} finally {
future.cancel(true);
}
/*
* TODO: touch of tech debt here
* There should honestly be an
* operation retry queue for ops
* That dont get the lock in time.
*
* This would require a scheduler
* that manages a queue of callbacks
* This scheduler would also likely
* mediate access to the ProcessManager
* object for fresh calls as well.
*/
}
/*
* releaseLock()
* releases mutex so other threads can operate on processqueue
*/
private void releaseLock() {
this.processQueueMutex = false;
}
/*
* shutdown()
* called (eventually) by the grpc shutdown hook
* (AKA when user hits control c in the shell)
* releases resources held in the processController objects
*/
private void shutdown() {
this.getLock();
for (ProcessController p : this.processQueue) {
p.kill(); // exit threads, release IO streams, etc.
}
}
}