9b064f67b38cec9429d8a05f91ff43a1a6160aa8
[idea/community.git] / platform / vcs-impl / src / com / intellij / openapi / vcs / changes / UpdateRequestsQueue.java
1 /*
2  * Copyright 2000-2009 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.openapi.vcs.changes;
17
18 import com.intellij.ide.startup.impl.StartupManagerImpl;
19 import com.intellij.openapi.application.ApplicationManager;
20 import com.intellij.openapi.application.ModalityState;
21 import com.intellij.openapi.diagnostic.Logger;
22 import com.intellij.openapi.progress.SomeQueue;
23 import com.intellij.openapi.project.Project;
24 import com.intellij.openapi.startup.StartupManager;
25 import com.intellij.openapi.util.Getter;
26 import com.intellij.openapi.vcs.ProjectLevelVcsManager;
27 import com.intellij.util.Consumer;
28 import com.intellij.util.concurrency.Semaphore;
29 import com.intellij.util.io.storage.HeavyProcessLatch;
30 import org.jetbrains.annotations.Nullable;
31
32 import javax.swing.*;
33 import java.util.ArrayList;
34 import java.util.List;
35 import java.util.concurrent.ScheduledExecutorService;
36 import java.util.concurrent.TimeUnit;
37 import java.util.concurrent.atomic.AtomicReference;
38
39 /**
40  * ChangeListManager updates scheduler.
41  * Tries to zip several update requests into one (if starts and see several requests in the queue)
42  * own inner synchronization
43  */
44 @SomeQueue
45 public class UpdateRequestsQueue {
46   private final Logger LOG = Logger.getInstance("#com.intellij.openapi.vcs.changes.UpdateRequestsQueue");
47   private static final String ourHeavyLatchOptimization = "vcs.local.changes.track.heavy.latch";
48   private final Project myProject;
49   private final AtomicReference<ScheduledExecutorService> myExecutor;
50   private final Runnable myDelegate;
51   private final Object myLock;
52   private volatile boolean myStarted;
53   private volatile boolean myStopped;
54   private volatile boolean myIgnoreBackgroundOperation;
55
56   private boolean myRequestSubmitted;
57   private boolean myRequestRunning;
58   private final List<Runnable> myWaitingUpdateCompletionQueue;
59   private final List<Semaphore> myWaitingUpdateCompletionSemaphores = new ArrayList<Semaphore>();
60   private final ProjectLevelVcsManager myPlVcsManager;
61   //private final ScheduledSlowlyClosingAlarm mySharedExecutor;
62   private final StartupManager myStartupManager;
63   private final boolean myTrackHeavyLatch;
64   private final Getter<Boolean> myIsStoppedGetter;
65
66   public UpdateRequestsQueue(final Project project, final AtomicReference<ScheduledExecutorService> executor, final Runnable delegate) {
67     myProject = project;
68     myExecutor = executor;
69     myTrackHeavyLatch = Boolean.parseBoolean(System.getProperty(ourHeavyLatchOptimization));
70
71     myDelegate = delegate;
72     myPlVcsManager = ProjectLevelVcsManager.getInstance(myProject);
73     myStartupManager = StartupManager.getInstance(myProject);
74     myLock = new Object();
75     myWaitingUpdateCompletionQueue = new ArrayList<Runnable>();
76     // not initialized
77     myStarted = false;
78     myStopped = false;
79     myIsStoppedGetter = new Getter<Boolean>() {
80       @Override
81       public Boolean get() {
82         return isStopped();
83       }
84     };
85   }
86
87   public void initialized() {
88     LOG.debug("Initialized for project: " + myProject.getName());
89     myStarted = true;
90   }
91
92   public Getter<Boolean> getIsStoppedGetter() {
93     return myIsStoppedGetter;
94   }
95
96   public boolean isStopped() {
97     return myStopped;
98   }
99
100   public void schedule() {
101     synchronized (myLock) {
102       if (! myStarted && ApplicationManager.getApplication().isUnitTestMode()) return;
103
104       if (! myStopped) {
105         if (! myRequestSubmitted) {
106           final MyRunnable runnable = new MyRunnable();
107           myRequestSubmitted = true;
108           myExecutor.get().schedule(runnable, 300, TimeUnit.MILLISECONDS);
109           LOG.debug("Scheduled for project: " + myProject.getName() + ", runnable: " + runnable.hashCode());
110         }
111       }
112     }
113   }
114
115   public void pause() {
116     synchronized (myLock) {
117       myStopped = true;
118     }
119   }
120
121   public void forceGo() {
122     synchronized (myLock) {
123       myStopped = false;
124       myRequestSubmitted = false;
125       myRequestRunning = false;
126     }
127     schedule();
128   }
129
130   public void go() {
131     synchronized (myLock) {
132       myStopped = false;
133     }
134     schedule();
135   }
136
137   public void stop() {
138     LOG.debug("Calling stop for project: " + myProject.getName());
139     final List<Runnable> waiters = new ArrayList<Runnable>(myWaitingUpdateCompletionQueue.size());
140     synchronized (myLock) {
141       myStopped = true;
142       waiters.addAll(myWaitingUpdateCompletionQueue);
143       myWaitingUpdateCompletionQueue.clear();
144     }
145     LOG.debug("Calling runnables in stop for project: " + myProject.getName());
146     // do not run under lock
147     for (Runnable runnable : waiters) {
148       runnable.run();
149     }
150     LOG.debug("Stop finished for project: " + myProject.getName());
151   }
152
153   public void waitUntilRefreshed() {
154     while (true) {
155       final Semaphore semaphore = new Semaphore();
156       synchronized (myLock) {
157         if (!myRequestSubmitted && !myRequestRunning) {
158           return;
159         }
160
161         if (!myRequestRunning) {
162           myExecutor.get().schedule(new MyRunnable(), 0, TimeUnit.MILLISECONDS);
163         }
164
165         semaphore.down();
166         myWaitingUpdateCompletionSemaphores.add(semaphore);
167       }
168       if (!semaphore.waitFor(100*1000)) {
169         LOG.error("Too long VCS update");
170         return;
171       }
172     }
173   }
174
175   private void freeSemaphores() {
176     synchronized (myLock) {
177       for (Semaphore semaphore : myWaitingUpdateCompletionSemaphores) {
178         semaphore.up();
179       }
180       myWaitingUpdateCompletionSemaphores.clear();
181     }
182   }
183
184   public void invokeAfterUpdate(final Runnable afterUpdate, final InvokeAfterUpdateMode mode, final String title,
185                                 @Nullable final Consumer<VcsDirtyScopeManager> dirtyScopeManagerFiller, final ModalityState state) {
186     LOG.debug("invokeAfterUpdate for project: " + myProject.getName());
187     final CallbackData data = CallbackData.create(afterUpdate, title, state, mode, myProject);
188
189     VcsDirtyScopeManagerProxy managerProxy = null;
190     if (dirtyScopeManagerFiller != null) {
191       managerProxy  = new VcsDirtyScopeManagerProxy();
192       dirtyScopeManagerFiller.consume(managerProxy);
193     }
194
195     // can ask stopped without a lock
196     if (! myStopped) {
197       if (managerProxy != null) {
198         managerProxy.callRealManager(VcsDirtyScopeManager.getInstance(myProject));
199       }
200     }
201
202     synchronized (myLock) {
203       if (! myStopped) {
204         myWaitingUpdateCompletionQueue.add(data.getCallback());
205         schedule();
206       }
207     }
208     // do not run under lock; stopped cannot be switched into not stopped - can check without lock
209     if (myStopped) {
210       LOG.debug("invokeAfterUpdate: stopped, invoke right now for project: " + myProject.getName());
211       SwingUtilities.invokeLater(new Runnable() {
212         public void run() {
213           if (!myProject.isDisposed()) {
214             afterUpdate.run();
215           }
216         }
217       });
218       return;
219     }
220     // invoke progress if needed
221     if (data.getWrapperStarter() != null) {
222       data.getWrapperStarter().run();
223     }
224     LOG.debug("invokeAfterUpdate: exit for project: " + myProject.getName());
225   }
226
227   // true = do not execute
228   private boolean checkHeavyOperations() {
229     if (myIgnoreBackgroundOperation) return false;
230     return myPlVcsManager.isBackgroundVcsOperationRunning() || myTrackHeavyLatch && HeavyProcessLatch.INSTANCE.isRunning();
231   }
232
233   // true = do not execute
234   private boolean checkLifeCycle() {
235     return !myStarted || !((StartupManagerImpl)myStartupManager).startupActivityPassed();
236   }
237
238   private class MyRunnable implements Runnable {
239     public void run() {
240       final List<Runnable> copy = new ArrayList<Runnable>(myWaitingUpdateCompletionQueue.size());
241       try {
242         synchronized (myLock) {
243           if (!myRequestSubmitted) return;
244           
245           LOG.assertTrue(!myRequestRunning);
246           myRequestRunning = true;
247           if (myStopped) {
248             myRequestSubmitted = false;
249             LOG.debug("MyRunnable: STOPPED, project: " + myProject.getName() + ", runnable: " + hashCode());
250             return;
251           }
252
253           if (checkLifeCycle() || checkHeavyOperations()) {
254             LOG.debug("MyRunnable: reschedule, project: " + myProject.getName() + ", runnable: " + hashCode());
255             myRequestSubmitted = false;
256             // try again after time
257             schedule();
258             return;
259           }
260
261           copy.addAll(myWaitingUpdateCompletionQueue);
262           myRequestSubmitted = false;
263         }
264
265         LOG.debug("MyRunnable: INVOKE, project: " + myProject.getName() + ", runnable: " + hashCode());
266         myDelegate.run();
267         LOG.debug("MyRunnable: invokeD, project: " + myProject.getName() + ", runnable: " + hashCode());
268       } finally {
269         synchronized (myLock) {
270           myRequestRunning = false;
271           LOG.debug("MyRunnable: delete executed, project: " + myProject.getName() + ", runnable: " + hashCode());
272           if (! copy.isEmpty()) {
273             myWaitingUpdateCompletionQueue.removeAll(copy);
274           }
275
276           if (! myWaitingUpdateCompletionQueue.isEmpty() && ! myRequestSubmitted && ! myStopped) {
277             LOG.error("No update task to handle request(s)");
278           }
279         }
280         // do not run under lock
281         for (Runnable runnable : copy) {
282           runnable.run();
283         }
284         freeSemaphores();
285         LOG.debug("MyRunnable: Runnables executed, project: " + myProject.getName() + ", runnable: " + hashCode());
286       }
287     }
288   }
289
290   public void setIgnoreBackgroundOperation(boolean ignoreBackgroundOperation) {
291     myIgnoreBackgroundOperation = ignoreBackgroundOperation;
292   }
293 }