fix memory leak: apparently we must close contexts to release JGit related resources
[teamcity/git-plugin.git] / git-server-tc / src / jetbrains / buildServer / buildTriggers / vcs / git / GitClonesUpdater.java
1 /*
2  * Copyright 2000-2018 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
17 package jetbrains.buildServer.buildTriggers.vcs.git;
18
19 import jetbrains.buildServer.log.Loggers;
20 import jetbrains.buildServer.serverSide.BuildServerAdapter;
21 import jetbrains.buildServer.serverSide.BuildServerListener;
22 import jetbrains.buildServer.serverSide.ServerResponsibility;
23 import jetbrains.buildServer.serverSide.TeamCityProperties;
24 import jetbrains.buildServer.util.EventDispatcher;
25 import jetbrains.buildServer.util.ThreadUtil;
26 import jetbrains.buildServer.util.executors.ExecutorsFactory;
27 import jetbrains.buildServer.vcs.*;
28 import org.eclipse.jgit.lib.Repository;
29 import org.jetbrains.annotations.NotNull;
30
31 import java.util.HashSet;
32 import java.util.Set;
33 import java.util.concurrent.ConcurrentHashMap;
34 import java.util.concurrent.ExecutorService;
35
36 public class GitClonesUpdater {
37   private final ConcurrentHashMap<VcsRoot, RepositoryStateData> myScheduledForUpdate = new ConcurrentHashMap<>();
38   private final GitVcsSupport myVcs;
39   private final RepositoryManager myRepositoryManager;
40   private volatile ExecutorService myExecutor;
41
42   public GitClonesUpdater(@NotNull EventDispatcher<RepositoryStateListener> eventDispatcher,
43                           @NotNull EventDispatcher<BuildServerListener> serverEventDispatcher,
44                           @NotNull ServerResponsibility serverResponsibility,
45                           @NotNull GitVcsSupport gitVcsSupport,
46                           @NotNull RepositoryManager repositoryManager) {
47     myVcs = gitVcsSupport;
48     myRepositoryManager = repositoryManager;
49
50     eventDispatcher.addListener(new RepositoryStateListenerAdapter() {
51       @Override
52       public void repositoryStateChanged(@NotNull final VcsRoot root,
53                                          @NotNull final RepositoryState oldState,
54                                          @NotNull final RepositoryState newState) {
55         if (serverResponsibility.canCheckForChanges()) return;
56
57         if (!TeamCityProperties.getBoolean("teamcity.git.localClones.updateIfNoCheckingForChangesResponsibility")) return;
58
59         if (root.getVcsName().equals(Constants.VCS_NAME)) {
60           myScheduledForUpdate.put(root, RepositoryStateFactory.toData(newState));
61           synchronized (myScheduledForUpdate) {
62             if (myExecutor == null) {
63               myExecutor = ExecutorsFactory.newFixedDaemonExecutor("Git local clones updater",
64                                                                    TeamCityProperties.getInteger("teamcity.git.localClones.maxParallelUpdateThreads", 2));
65             }
66           }
67           myExecutor.submit(GitClonesUpdater.this::processVcsRootsScheduledForUpdate);
68         }
69       }
70     });
71
72     serverEventDispatcher.addListener(new BuildServerAdapter() {
73       @Override
74       public void serverShutdown() {
75         if (myExecutor != null) {
76           ThreadUtil.shutdownGracefully(myExecutor, "Git local clones updater");
77         }
78       }
79     });
80   }
81
82   private void processVcsRootsScheduledForUpdate() {
83     Set<VcsRoot> vcsRoots = new HashSet<>(myScheduledForUpdate.keySet());
84     for (VcsRoot root: vcsRoots) {
85       RepositoryStateData state = myScheduledForUpdate.remove(root);
86       if (state == null) continue;
87
88       OperationContext context = myVcs.createContext(root, "updating local clone");
89       try {
90         GitVcsRoot gitRoot = context.getGitRoot();
91         myRepositoryManager.runWithDisabledRemove(gitRoot.getRepositoryDir(), () -> {
92           Repository repo = context.getRepository();
93           try {
94             myVcs.getCollectChangesPolicy().ensureRepositoryStateLoadedFor(context, repo, true, state);
95           } catch (Exception e1) {
96             throw new VcsException(e1);
97           }
98         });
99       } catch (VcsException e1) {
100         Loggers.VCS.warnAndDebugDetails("Could not update local clone for: " + LogUtil.describe(root), e1);
101       } finally {
102         context.close();
103       }
104     }
105   }
106 }