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