d7cfdf9335fad671313b04322b2f5bbd1f3a022d
[idea/community.git] / platform / external-system-impl / src / com / intellij / openapi / externalSystem / service / internal / ExternalSystemProcessingManager.java
1 package com.intellij.openapi.externalSystem.service.internal;
2
3 import com.intellij.openapi.Disposable;
4 import com.intellij.openapi.application.ApplicationManager;
5 import com.intellij.openapi.externalSystem.model.ProjectSystemId;
6 import com.intellij.openapi.externalSystem.model.task.*;
7 import com.intellij.openapi.externalSystem.service.ExternalSystemFacadeManager;
8 import com.intellij.openapi.externalSystem.service.notification.ExternalSystemProgressNotificationManager;
9 import com.intellij.openapi.project.Project;
10 import com.intellij.util.Alarm;
11 import com.intellij.util.ArrayUtil;
12 import com.intellij.util.SmartList;
13 import com.intellij.util.containers.ContainerUtil;
14 import com.intellij.util.containers.ContainerUtilRt;
15 import org.jetbrains.annotations.NotNull;
16 import org.jetbrains.annotations.Nullable;
17
18 import java.util.List;
19 import java.util.Map;
20 import java.util.concurrent.ConcurrentMap;
21 import java.util.concurrent.TimeUnit;
22
23 /**
24  * Provides external system tasks monitoring and management facilities.
25  * <p/>
26  * Thread-safe.
27  * 
28  * @author Denis Zhdanov
29  * @since 2/8/12 1:52 PM
30  */
31 public class ExternalSystemProcessingManager implements ExternalSystemTaskNotificationListener, Disposable {
32
33   /**
34    * We receive information about the tasks being enqueued to the slave processes which work directly with external systems here.
35    * However, there is a possible situation when particular task has been sent to execution but remote side has not been responding
36    * for a while. There at least two possible explanations then:
37    * <pre>
38    * <ul>
39    *   <li>the task is still in progress (e.g. great number of libraries is being downloaded);</li>
40    *   <li>remote side has fallen (uncaught exception; manual slave process kill etc);</li>
41    * </ul>
42    * </pre>
43    * We need to distinguish between them, so, we perform 'task pings' if any task is executed too long. Current constant holds
44    * criteria of 'too long execution'.
45    */
46   private static final long TOO_LONG_EXECUTION_MS = TimeUnit.SECONDS.toMillis(10);
47
48   @NotNull private final ConcurrentMap<ExternalSystemTaskId, Long> myTasksInProgress = ContainerUtil.newConcurrentMap();
49   @NotNull private final ConcurrentMap<ExternalSystemTaskId, ExternalSystemTask> myTasksDetails = ContainerUtil.newConcurrentMap();
50   @NotNull private final Alarm myAlarm = new Alarm(Alarm.ThreadToUse.POOLED_THREAD,this);
51
52   @NotNull private final ExternalSystemFacadeManager               myFacadeManager;
53   @NotNull private final ExternalSystemProgressNotificationManager myProgressNotificationManager;
54
55   public ExternalSystemProcessingManager(@NotNull ExternalSystemFacadeManager facadeManager,
56                                          @NotNull ExternalSystemProgressNotificationManager notificationManager)
57   {
58     myFacadeManager = facadeManager;
59     myProgressNotificationManager = notificationManager;
60     if (ApplicationManager.getApplication().isUnitTestMode()) {
61       return;
62     }
63
64     notificationManager.addNotificationListener(this);
65   }
66
67   @Override
68   public void dispose() {
69     myProgressNotificationManager.removeNotificationListener(this);
70     myAlarm.cancelAllRequests();
71   }
72
73   /**
74    * Allows to check if any task of the given type is being executed at the moment.  
75    *
76    * @param type  target task type
77    * @return      <code>true</code> if any task of the given type is being executed at the moment;
78    *              <code>false</code> otherwise
79    */
80   public boolean hasTaskOfTypeInProgress(@NotNull ExternalSystemTaskType type, @NotNull Project project) {
81     String projectId = ExternalSystemTaskId.getProjectId(project);
82     for (ExternalSystemTaskId id : myTasksInProgress.keySet()) {
83       if (type.equals(id.getType()) && projectId.equals(id.getIdeProjectId())) {
84         return true;
85       }
86     }
87     return false;
88   }
89
90   @Nullable
91   public ExternalSystemTask findTask(@NotNull ExternalSystemTaskType type,
92                                      @NotNull ProjectSystemId projectSystemId,
93                                      @NotNull final String externalProjectPath) {
94     for(ExternalSystemTask task : myTasksDetails.values()) {
95       if(task instanceof AbstractExternalSystemTask) {
96         AbstractExternalSystemTask externalSystemTask = (AbstractExternalSystemTask)task;
97         if(externalSystemTask.getId().getType() == type &&
98            externalSystemTask.getExternalSystemId().getId().equals(projectSystemId.getId()) &&
99            externalSystemTask.getExternalProjectPath().equals(externalProjectPath)){
100           return task;
101         }
102       }
103     }
104
105     return null;
106   }
107
108   @NotNull
109   public List<ExternalSystemTask> findTasksOfState(@NotNull ProjectSystemId projectSystemId,
110                                                    @NotNull final ExternalSystemTaskState... taskStates) {
111     List<ExternalSystemTask> result = new SmartList<>();
112     for (ExternalSystemTask task : myTasksDetails.values()) {
113       if (task instanceof AbstractExternalSystemTask) {
114         AbstractExternalSystemTask externalSystemTask = (AbstractExternalSystemTask)task;
115         if (externalSystemTask.getExternalSystemId().getId().equals(projectSystemId.getId()) &&
116             ArrayUtil.contains(externalSystemTask.getState(), taskStates)) {
117           result.add(task);
118         }
119       }
120     }
121     return result;
122   }
123
124   public void add(@NotNull ExternalSystemTask task) {
125     myTasksDetails.put(task.getId(), task);
126   }
127
128   public void release(@NotNull ExternalSystemTaskId id) {
129     myTasksDetails.remove(id);
130   }
131
132   @Override
133   public void onQueued(@NotNull ExternalSystemTaskId id, String workingDir) {
134     onStart(id, workingDir);
135   }
136
137   @Override
138   public void onStart(@NotNull ExternalSystemTaskId id, String workingDir) {
139     myTasksInProgress.put(id, System.currentTimeMillis() + TOO_LONG_EXECUTION_MS);
140     if (myAlarm.getActiveRequestCount() <= 0) {
141       myAlarm.addRequest(() -> update(), TOO_LONG_EXECUTION_MS);
142     }
143   }
144
145   @Override
146   public void onStart(@NotNull ExternalSystemTaskId id) {
147     myTasksInProgress.put(id, System.currentTimeMillis() + TOO_LONG_EXECUTION_MS);
148   }
149
150   @Override
151   public void onStatusChange(@NotNull ExternalSystemTaskNotificationEvent event) {
152     myTasksInProgress.put(event.getId(), System.currentTimeMillis() + TOO_LONG_EXECUTION_MS); 
153   }
154
155   @Override
156   public void onTaskOutput(@NotNull ExternalSystemTaskId id, @NotNull String text, boolean stdOut) {
157     myTasksInProgress.put(id, System.currentTimeMillis() + TOO_LONG_EXECUTION_MS);
158   }
159
160   @Override
161   public void onEnd(@NotNull ExternalSystemTaskId id) {
162     myTasksInProgress.remove(id);
163     if (myTasksInProgress.isEmpty()) {
164       myAlarm.cancelAllRequests();
165     }
166   }
167
168   @Override
169   public void onSuccess(@NotNull ExternalSystemTaskId id) {
170   }
171
172   @Override
173   public void onFailure(@NotNull ExternalSystemTaskId id, @NotNull Exception e) {
174   }
175
176   @Override
177   public void beforeCancel(@NotNull ExternalSystemTaskId id) {
178   }
179
180   @Override
181   public void onCancel(@NotNull ExternalSystemTaskId id) {
182   }
183
184   public void update() {
185     long delay = TOO_LONG_EXECUTION_MS;
186     Map<ExternalSystemTaskId, Long> newState = ContainerUtilRt.newHashMap();
187
188     Map<ExternalSystemTaskId, Long> currentState = ContainerUtilRt.newHashMap(myTasksInProgress);
189     if (currentState.isEmpty()) {
190       return;
191     }
192     
193     for (Map.Entry<ExternalSystemTaskId, Long> entry : currentState.entrySet()) {
194       long diff = System.currentTimeMillis() - entry.getValue();
195       if (diff > 0) {
196         delay = Math.min(delay, diff);
197         newState.put(entry.getKey(), entry.getValue());
198       }
199       else {
200         // Perform explicit check on whether the task is still alive.
201         if (myFacadeManager.isTaskActive(entry.getKey())) {
202           newState.put(entry.getKey(), System.currentTimeMillis() + TOO_LONG_EXECUTION_MS);
203         }
204       }
205     }
206     
207     myTasksInProgress.clear();
208     myTasksInProgress.putAll(newState);
209
210     if (!newState.isEmpty()) {
211       myAlarm.cancelAllRequests();
212       myAlarm.addRequest(() -> update(), delay);
213     }
214   }
215 }