001 package org.crsh.shell.concurrent; 002 003 import org.crsh.shell.ShellProcess; 004 import org.crsh.shell.ShellProcessContext; 005 import org.crsh.shell.ShellResponse; 006 007 import java.util.concurrent.Callable; 008 009 /** @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a> */ 010 public class AsyncProcess implements ShellProcess { 011 012 013 /** . */ 014 private final String request; 015 016 /** . */ 017 private ShellProcessContext caller; 018 019 /** . */ 020 private ShellProcess callee; 021 022 /** . */ 023 private AsyncShell shell; 024 025 /** . */ 026 private Status status; 027 028 /** . */ 029 private final Object lock; 030 031 AsyncProcess(AsyncShell shell, String request) { 032 this.shell = shell; 033 this.request = request; 034 this.callee = null; 035 this.status = Status.CONSTRUCTED; 036 this.lock = new Object(); 037 } 038 039 public Status getStatus() { 040 return status; 041 } 042 043 /** . */ 044 private final ShellProcessContext context = new ShellProcessContext() { 045 public int getWidth() { 046 return caller.getWidth(); 047 } 048 049 public String getProperty(String name) { 050 return caller.getProperty(name); 051 } 052 053 public String readLine(String msg, boolean echo) { 054 return caller.readLine(msg, echo); 055 } 056 057 public void end(ShellResponse response) { 058 // Always leave the status in terminated status if the method succeeds 059 // Cancelled -> Terminated 060 // Evaluating -> Terminated 061 // Terminated -> Terminated 062 synchronized (lock) { 063 switch (status) { 064 case CONSTRUCTED: 065 case QUEUED: 066 throw new AssertionError("Should not happen"); 067 case CANCELED: 068 // We substitute the response 069 response = ShellResponse.cancelled(); 070 status = Status.TERMINATED; 071 break; 072 case EVALUATING: 073 status = Status.TERMINATED; 074 break; 075 case TERMINATED: 076 throw new IllegalStateException("Cannot end a process already terminated"); 077 } 078 } 079 080 // 081 caller.end(response); 082 } 083 }; 084 085 public void execute(ShellProcessContext processContext) { 086 087 // Constructed -> Queued 088 synchronized (lock) { 089 if (status != Status.CONSTRUCTED) { 090 throw new IllegalStateException("State was " + status); 091 } 092 093 // Update state 094 status = Status.QUEUED; 095 callee = shell.shell.createProcess(request); 096 caller = processContext; 097 } 098 099 // Create the task 100 Callable<AsyncProcess> task = new Callable<AsyncProcess>() { 101 public AsyncProcess call() throws Exception { 102 try { 103 // Cancelled -> Cancelled 104 // Queued -> Evaluating 105 ShellResponse response; 106 synchronized (lock) { 107 switch (status) { 108 case CANCELED: 109 // Do nothing it was canceled in the mean time 110 response = ShellResponse.cancelled(); 111 break; 112 case QUEUED: 113 // Ok we are going to run it 114 status = Status.EVALUATING; 115 response = null; 116 break; 117 default: 118 // Just in case but this can only be called by the queue 119 throw new AssertionError(); 120 } 121 } 122 123 // Execute the process if we are in evalating state 124 if (response == null) { 125 // Here the status could already be in status cancelled 126 // it is a race condition, execution still happens 127 // but the callback of the callee to the end method will make the process 128 // terminate and use a cancel response 129 try { 130 callee.execute(context); 131 response = ShellResponse.ok(); 132 } 133 catch (Throwable t) { 134 response = ShellResponse.internalError("Unexpected throwable when executing process", t); 135 } 136 } 137 138 // Make the callback 139 // Calling terminated twice will have no effect 140 try { 141 context.end(response); 142 } 143 catch (Throwable t) { 144 // Log it 145 } 146 147 // We return this but we don't really care for now 148 return AsyncProcess.this; 149 } 150 finally { 151 synchronized (shell.lock) { 152 shell.processes.remove(AsyncProcess.this); 153 } 154 } 155 } 156 }; 157 158 // 159 synchronized (shell.lock) { 160 if (!shell.closed) { 161 shell.executor.submit(task); 162 shell.processes.add(this); 163 } else { 164 boolean invokeEnd; 165 synchronized (lock) { 166 invokeEnd = status != Status.TERMINATED; 167 status = Status.TERMINATED; 168 } 169 if (invokeEnd) { 170 caller.end(ShellResponse.cancelled()); 171 } 172 } 173 } 174 } 175 176 public void cancel() { 177 // Construcuted -> ISE 178 // Evaluating -> Canceled 179 // Queued -> Canceled 180 // Cancelled -> Cancelled 181 // Terminated -> Terminated 182 boolean cancel; 183 synchronized (lock) { 184 switch (status) { 185 case CONSTRUCTED: 186 throw new IllegalStateException("Cannot call cancel on process that was not scheduled for execution yet"); 187 case QUEUED: 188 status = Status.CANCELED; 189 cancel = false; 190 break; 191 case EVALUATING: 192 status = Status.CANCELED; 193 cancel = true; 194 break; 195 default: 196 cancel = false; 197 break; 198 } 199 } 200 201 // 202 if (cancel) { 203 callee.cancel(); 204 } 205 } 206 }