Free cookie consent management tool by TermsFeed Policy Generator

source: branches/HeuristicLab.Hive-3.4/sources/HeuristicLab.Clients.Hive.Slave/3.4/PluginCache.cs @ 6101

Last change on this file since 6101 was 6101, checked in by ascheibe, 13 years ago

#1233

  • don't lock engines for so long in StartJobInAppDomain
  • move SlaveCommListener to ConsoleClient
  • delete orphaned job folders at startup
File size: 11.2 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2010 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
4 *
5 * This file is part of HeuristicLab.
6 *
7 * HeuristicLab is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * HeuristicLab is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
19 */
20#endregion
21
22using System;
23using System.Collections.Generic;
24using System.IO;
25using System.Linq;
26using System.Reflection;
27using System.Runtime.CompilerServices;
28using System.Runtime.Serialization;
29using System.Runtime.Serialization.Formatters.Binary;
30using System.Threading;
31
32namespace HeuristicLab.Clients.Hive.SlaveCore {
33  public class PluginCache {
34    private static object locker = new object();
35    private const string lastUsedFileName = "lastUsed.dat";
36
37    //maximum number of days after which a plugin gets deleted if not used
38    private const int maxAge = 5;
39
40    private static PluginCache instance = null;
41
42    public string PluginCacheDir { get; set; }
43    public string PluginTempBaseDir { get; set; }
44
45    private List<Guid> cachedPluginsGuids = new List<Guid>();
46
47    public static PluginCache Instance {
48      get {
49        if (instance == null)
50          instance = new PluginCache();
51        return instance;
52      }
53    }
54
55    public PluginCache() {
56      PluginCacheDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "PluginCache");
57      PluginTempBaseDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "PluginTemp");
58
59      DoUpdateRun();
60    }
61
62    private string GetFilenameFromPath(string path) {
63      string[] dirParts = path.Split(Path.DirectorySeparatorChar);
64      if (dirParts.Length > 0) {
65        string fileGuid = dirParts[dirParts.Length - 1];
66        return fileGuid;
67      } else
68        return "";
69    }
70
71    private void DoUpdateRun() {
72      SafelyCreateDirectory(PluginCacheDir);
73      foreach (string dir in Directory.EnumerateDirectories(PluginCacheDir)) {
74        cachedPluginsGuids.Add(Guid.Parse(GetFilenameFromPath(dir))); // Todo: cleaner solution to getFilenameFromPath
75      }
76    }
77
78    [MethodImpl(MethodImplOptions.Synchronized)]
79    public void CopyPluginsForJob(List<Plugin> requests, Guid jobId, out string configFileName) {
80      lock (locker) {
81        configFileName = string.Empty;
82        String targetDir = Path.Combine(PluginTempBaseDir, jobId.ToString());
83
84        RecreateDirectory(targetDir);
85
86        foreach (Plugin requestedPlugin in requests) {
87          var filePaths = GetPluginFilePaths(requestedPlugin.Id);
88          foreach (string filePath in filePaths) {
89            File.Copy(filePath, Path.Combine(targetDir, Path.GetFileName(filePath)));
90          }
91
92          if (requestedPlugin.Name == "Configuration") {
93            configFileName = Path.Combine(targetDir, Path.GetFileName(filePaths.SingleOrDefault())); // configuration plugin consists only of 1 file (usually the "HeuristicLab X.X.exe.config")
94          }
95        }
96
97        // copy files from PluginInfrastructure (which are not declared in any plugins)
98        string baseDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
99        CopyFile(baseDir, targetDir, "HeuristicLab.PluginInfrastructure-3.3.dll");
100        CopyFile(baseDir, targetDir, "ICSharpCode.SharpZipLib.dll");
101        CopyFile(baseDir, targetDir, "ICSharpCode.SharpZipLib License.txt");
102
103        // copy slave plugins, otherwise its not possible to register the UnhandledException handler to the appdomain       
104        CopyFile(baseDir, targetDir, "HeuristicLab.Clients.Hive.SlaveCore-3.4.dll");
105        CopyFile(baseDir, targetDir, "HeuristicLab.Clients.Hive-3.4.dll");
106        //CopyFile(baseDir, targetDir, "HeuristicLab.Services.Hive.Common-3.4.dll");
107        CopyFile(baseDir, targetDir, "HeuristicLab.Hive-3.4.dll");
108        CopyFile(baseDir, targetDir, "HeuristicLab.Clients.Common-3.3.dll");
109      }
110    }
111
112    private static DirectoryInfo RecreateDirectory(String targetDir) {
113      var di = new DirectoryInfo(targetDir);
114      if (di.Exists) Directory.Delete(targetDir, true);
115      di.Refresh();
116      while (di.Exists) {
117        Thread.Sleep(50);
118        di.Refresh();
119      }
120      return SafelyCreateDirectory(targetDir);
121    }
122
123    private static DirectoryInfo SafelyCreateDirectory(String targetDir) {
124      var di = new DirectoryInfo(targetDir);
125      if (!di.Exists) {
126        di = Directory.CreateDirectory(targetDir);
127        while (!di.Exists) {
128          Thread.Sleep(50);
129          di.Refresh();
130        }
131      }
132      return di;
133    }
134
135    private void CopyFile(string baseDir, string targetDir, string fileName) {
136      if (!File.Exists(Path.Combine(targetDir, fileName))) File.Copy(Path.Combine(baseDir, fileName), Path.Combine(targetDir, fileName));
137    }
138
139    [MethodImpl(MethodImplOptions.Synchronized)]
140    internal void PreparePlugins(Job myJob, out string configFileName) {
141      lock (locker) {
142        SlaveClientCom.Instance.ClientCom.LogMessage("Fetching plugins for job");
143
144        List<Plugin> requiredPlugins = new List<Plugin>();
145        foreach (Guid pluginId in myJob.PluginsNeededIds) {
146          //TODO: rewrite, GetPlugin(..) should be the last thing to do after checking the local plugins
147          Plugin plugin = WcfService.Instance.GetPlugin(pluginId);
148          if (plugin != null)
149            requiredPlugins.Add(plugin);
150        }
151
152        List<Plugin> localPlugins = new List<Plugin>();
153        List<Plugin> missingPlugins = new List<Plugin>();
154        List<Guid> missingGuids = new List<Guid>();
155        bool found = false;
156
157        foreach (Plugin info in requiredPlugins) {
158          foreach (Guid cachedPlugin in cachedPluginsGuids) {
159            if (info.Id == cachedPlugin) {
160              localPlugins.Add(new Plugin() { Id = cachedPlugin, Name = info.Name, Version = info.Version });
161              found = true;
162
163              break;
164            }
165          }
166          if (!found) {
167            SlaveClientCom.Instance.ClientCom.LogMessage("Plugin NOT found " + info.Name + ", " + info.Version);
168            missingPlugins.Add(info);
169            missingGuids.Add(info.Id);
170          }
171          found = false;
172        }
173
174        SlaveClientCom.Instance.ClientCom.LogMessage("First run - Update the plugins in the cache");
175        localPlugins.AddRange(missingPlugins);
176
177        IEnumerable<PluginData> pluginDatas = WcfService.Instance.GetPluginDatas(missingGuids);
178
179        if (pluginDatas != null) {
180          foreach (PluginData pluginData in pluginDatas) {
181            string pluginDir = Path.Combine(PluginCacheDir, pluginData.PluginId.ToString());
182
183            //put all files belonging to a plugin in the same directory
184            SafelyCreateDirectory(pluginDir);
185            File.WriteAllBytes(Path.Combine(pluginDir, Path.GetFileName(pluginData.FileName)), pluginData.Data);
186          }
187
188          DoUpdateRun();
189          CopyPluginsForJob(requiredPlugins, myJob.Id, out configFileName);
190        } else {
191          configFileName = "";
192        }
193      }
194    }
195
196    /// <summary>
197    /// Returns a list of files which belong to a plugin from the plugincache
198    /// </summary>
199    private IEnumerable<string> GetPluginFilePaths(Guid pluginId) {
200      string pluginPath = Path.Combine(PluginCacheDir, pluginId.ToString());
201
202      if (Directory.Exists(pluginPath)) {
203        WriteDateLastUsed(pluginPath);
204        foreach (string filePath in Directory.GetFiles(pluginPath)) {
205          string fn = Path.GetFileName(filePath);
206          if (fn != lastUsedFileName)
207            yield return filePath;
208        }
209      }
210    }
211
212    /// <summary>
213    /// creates a file in path with the current date;
214    /// this can later be used to find plugins which are outdated
215    /// </summary>   
216    private void WriteDateLastUsed(string path) {
217      FileStream fs = new FileStream(Path.Combine(path, lastUsedFileName), FileMode.Create);
218      BinaryFormatter formatter = new BinaryFormatter();
219
220      try {
221        formatter.Serialize(fs, DateTime.Now);
222      }
223      catch (SerializationException) {
224        //rethrow...
225        throw;
226      }
227      finally {
228        fs.Close();
229      }
230    }
231
232    /// <summary>
233    /// Checks the PluginTemp directory for orphaned directories and deletes them.
234    /// This should be only called if no jobs are currently running.
235    /// </summary>   
236    public void CleanPluginTemp() {
237      if (Directory.Exists(PluginTempBaseDir)) {
238        foreach (string dir in Directory.EnumerateDirectories(PluginTempBaseDir)) {
239          try {
240            SlaveClientCom.Instance.ClientCom.LogMessage("Deleting orphaned directory " + dir);
241            Directory.Delete(dir, true);
242          }
243          catch (Exception ex) {
244            SlaveClientCom.Instance.ClientCom.LogMessage("Error cleaning up PluginTemp directory " + dir + ": " + ex.ToString());
245          }
246        }
247      }
248    }
249
250    /// <summary>
251    /// checks the pluginCacheDirectory and deletes plugin folders which are not used anymore
252    /// </summary>   
253    private void CleanPluginCache() {
254      FileStream fs = null;
255      DateTime luDate;
256      bool changed = false;
257
258      if (Directory.Exists(PluginCacheDir)) {
259        foreach (string dir in Directory.EnumerateDirectories(PluginCacheDir)) {
260          try {
261            fs = new FileStream(Path.Combine(dir, lastUsedFileName), FileMode.Open);
262            BinaryFormatter formatter = new BinaryFormatter();
263            luDate = (DateTime)formatter.Deserialize(fs);
264            fs.Close();
265
266            if (luDate.AddDays(maxAge) < DateTime.Now) {
267              Directory.Delete(dir, true);
268              changed = true;
269            }
270          }
271          catch (SerializationException) {
272            fs.Close();
273            throw;
274          }
275          catch (System.IO.FileNotFoundException) {
276            //nerver used
277            Directory.Delete(dir, true);
278            changed = true;
279          }
280          catch (Exception) {
281            throw;
282          }
283        }
284      }
285      if (changed)
286        DoUpdateRun();
287    }
288
289
290    internal void DeletePluginsForJob(Guid id) {
291      try {
292        SlaveClientCom.Instance.ClientCom.LogMessage("unloading...");
293        int tries = 5;
294        while (tries > 0) {
295          try {
296            Directory.Delete(Path.Combine(PluginTempBaseDir, id.ToString()), true);
297            tries = 0;
298          }
299          catch (Exception e) {
300            Thread.Sleep(1000);
301            tries--;
302            if (tries == 0) throw;// TODO: don't know what do do
303          }
304        }
305      }
306      catch (Exception ex) {
307        SlaveClientCom.Instance.ClientCom.LogMessage("failed while unloading " + id + " with exception " + ex);
308      }
309      CleanPluginCache();
310    }
311  }
312}
Note: See TracBrowser for help on using the repository browser.