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(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 }