initial sketch for process manager
This commit is contained in:
parent
622da2d238
commit
0433ead782
2 changed files with 287 additions and 2 deletions
|
|
@ -43,6 +43,14 @@ class ProcessController {
|
|||
this.outputScanner.useDelimieter("\\A");
|
||||
}
|
||||
|
||||
/*
|
||||
* getPid()
|
||||
* returns translated pid of this process
|
||||
*/
|
||||
public int getPid() {
|
||||
return this.pid;
|
||||
}
|
||||
|
||||
/*
|
||||
* getStatus()
|
||||
* returns whether or not the process is running
|
||||
|
|
@ -77,8 +85,7 @@ class ProcessController {
|
|||
|
||||
/*
|
||||
* getOutput()
|
||||
* gets new output from stream
|
||||
* (TODO: investigate whether this would better be done by )
|
||||
* gets output from process
|
||||
*/
|
||||
public String getOutput() {
|
||||
String out = "";
|
||||
|
|
|
|||
278
src/main/java/JobServ/ProcessManager.java
Normal file
278
src/main/java/JobServ/ProcessManager.java
Normal 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.
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue