Free cookie consent management tool by TermsFeed Policy Generator

source: trunk/sources/HeuristicLab.Clients.Hive.Slave/3.3/Manager/PluginManager.cs @ 11299

Last change on this file since 11299 was 11171, checked in by ascheibe, 10 years ago

#2115 merged r11170 (copyright update) into trunk

File size: 12.3 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2014 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.Serialization.Formatters.Binary;
28using System.Threading;
29using HeuristicLab.Core;
30using CoreProperties = HeuristicLab.Clients.Hive.SlaveCore.Properties;
31
32namespace HeuristicLab.Clients.Hive.SlaveCore {
33  public class PluginManager {
34    private static object locker = new object();
35    private string lastUsedFileName = CoreProperties.Settings.Default.LastUsedFileName;
36    //maximum number of days after which a plugin gets deleted if not used
37    private int pluginLifetime = CoreProperties.Settings.Default.PluginLifetime;
38
39    private string PluginCacheDir { get; set; }
40    public string PluginTempBaseDir { get; set; }
41    private ILog log;
42    private IPluginProvider pluginService;
43    private List<Guid> cachedPluginsGuids = new List<Guid>();
44
45    public PluginManager(IPluginProvider pluginService, ILog log) {
46      this.pluginService = pluginService;
47      this.log = log;
48      CheckWorkingDirectories();
49      PluginCacheDir = CoreProperties.Settings.Default.PluginCacheDir;
50      PluginTempBaseDir = CoreProperties.Settings.Default.PluginTempBaseDir;
51      DoUpdateRun();
52    }
53
54    /// <summary>
55    /// Normally the configuration file just contains the folder names of the PluginCache and the AppDomain working directory.
56    /// This means that these folders are created in the current directory which is ok for the console client and the windows service.
57    /// For the HL client we can't do that because the plugin infrastructure gets confused when starting HeuristicLab.
58    /// Therefore if there is only a relative path in the config, we change that to the temp path.
59    /// </summary>
60    private void CheckWorkingDirectories() {
61      if (!Path.IsPathRooted(CoreProperties.Settings.Default.PluginCacheDir)) {
62        CoreProperties.Settings.Default.PluginCacheDir = Path.Combine(Path.GetTempPath(), CoreProperties.Settings.Default.PluginCacheDir);
63        CoreProperties.Settings.Default.Save();
64      }
65
66      if (!Path.IsPathRooted(CoreProperties.Settings.Default.PluginTempBaseDir)) {
67        CoreProperties.Settings.Default.PluginTempBaseDir = Path.Combine(Path.GetTempPath(), CoreProperties.Settings.Default.PluginTempBaseDir);
68        CoreProperties.Settings.Default.Save();
69      }
70    }
71
72    /// <summary>
73    /// Returns the last directory of a path
74    /// </summary>   
75    private string GetFilenameFromPath(string path) {
76      string[] dirParts = path.Split(Path.DirectorySeparatorChar);
77      if (dirParts.Length > 0) {
78        string fileGuid = dirParts[dirParts.Length - 1];
79        return fileGuid;
80      } else
81        return "";
82    }
83
84    private void DoUpdateRun() {
85      SafelyCreateDirectory(PluginCacheDir);
86      lock (cachedPluginsGuids) {
87        cachedPluginsGuids.Clear();
88        foreach (string dir in Directory.EnumerateDirectories(PluginCacheDir)) {
89          cachedPluginsGuids.Add(Guid.Parse(GetFilenameFromPath(dir)));
90        }
91      }
92    }
93
94    public void CopyPluginsForJob(List<Plugin> requests, Guid jobId, out string configFileName) {
95      configFileName = string.Empty;
96      String targetDir = Path.Combine(PluginTempBaseDir, jobId.ToString());
97
98      RecreateDirectory(targetDir);
99
100      foreach (Plugin requestedPlugin in requests) {
101        var filePaths = GetPluginFilePaths(requestedPlugin.Id);
102        foreach (string filePath in filePaths) {
103          File.Copy(filePath, Path.Combine(targetDir, Path.GetFileName(filePath)));
104        }
105
106        if (requestedPlugin.Name == CoreProperties.Settings.Default.ConfigurationName) {
107          // configuration plugin consists only of 1 file (usually the "HeuristicLab X.X.exe.config")
108          configFileName = Path.Combine(targetDir, Path.GetFileName(filePaths.SingleOrDefault()));
109        }
110      }
111
112      // copy files from PluginInfrastructure (which are not declared in any plugins)
113      string baseDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
114      CopyFile(baseDir, targetDir, CoreProperties.Settings.Default.PluginInfrastructureDll);
115      CopyFile(baseDir, targetDir, CoreProperties.Settings.Default.SharpZipLibDll);
116      CopyFile(baseDir, targetDir, CoreProperties.Settings.Default.SharpZipLibLicense);
117
118      // copy slave plugins, otherwise its not possible to register the UnhandledException handler to the appdomain       
119      CopyFile(baseDir, targetDir, CoreProperties.Settings.Default.ClientsHiveSlaveCoreDll);
120      CopyFile(baseDir, targetDir, CoreProperties.Settings.Default.ClientsHiveDll);
121      CopyFile(baseDir, targetDir, CoreProperties.Settings.Default.HiveDll);
122      CopyFile(baseDir, targetDir, CoreProperties.Settings.Default.ClientsCommonDll);
123    }
124
125    private static DirectoryInfo RecreateDirectory(String targetDir) {
126      var di = new DirectoryInfo(targetDir);
127      if (di.Exists) Directory.Delete(targetDir, true);
128      di.Refresh();
129      while (di.Exists) {
130        Thread.Sleep(CoreProperties.Settings.Default.DirOpSleepTime);
131        di.Refresh();
132      }
133      return SafelyCreateDirectory(targetDir);
134    }
135
136    private static DirectoryInfo SafelyCreateDirectory(String targetDir) {
137      var di = new DirectoryInfo(targetDir);
138      if (!di.Exists) {
139        di = Directory.CreateDirectory(targetDir);
140        while (!di.Exists) {
141          Thread.Sleep(CoreProperties.Settings.Default.DirOpSleepTime);
142          di.Refresh();
143        }
144      }
145      return di;
146    }
147
148    private void CopyFile(string baseDir, string targetDir, string fileName) {
149      if (!File.Exists(Path.Combine(targetDir, fileName))) File.Copy(Path.Combine(baseDir, fileName), Path.Combine(targetDir, fileName));
150    }
151
152    /// <summary>
153    /// Updates the plugin cache with missing plugins and
154    /// then copies the required plugins for the task.
155    /// </summary>       
156    public void PreparePlugins(Task task, out string configFileName) {
157      lock (locker) {
158        log.LogMessage("Fetching plugins for task " + task.Id);
159
160        List<Guid> missingGuids = new List<Guid>();
161        List<Plugin> neededPlugins = new List<Plugin>();
162        lock (cachedPluginsGuids) {
163          foreach (Guid pluginId in task.PluginsNeededIds) {
164            Plugin plugin = pluginService.GetPlugin(pluginId);
165            if (plugin != null) {
166              neededPlugins.Add(plugin);
167            }
168
169            if (!cachedPluginsGuids.Contains(pluginId)) {
170              missingGuids.Add(pluginId);
171            }
172          }
173        }
174
175        IEnumerable<PluginData> pluginDatas = pluginService.GetPluginDatas(missingGuids);
176
177        if (pluginDatas != null) {
178          foreach (PluginData pluginData in pluginDatas) {
179            string pluginDir = Path.Combine(PluginCacheDir, pluginData.PluginId.ToString());
180
181            //put all files belonging to a plugin in the same directory
182            SafelyCreateDirectory(pluginDir);
183            File.WriteAllBytes(Path.Combine(pluginDir, Path.GetFileName(pluginData.FileName)), pluginData.Data);
184          }
185
186          if (missingGuids.Count > 0) {
187            DoUpdateRun();
188          }
189          CopyPluginsForJob(neededPlugins, task.Id, out configFileName);
190        } else {
191          configFileName = "";
192        }
193        log.LogMessage(string.Format("Fetched {0} plugins for task {1}", missingGuids.Count, task.Id));
194      }
195    }
196
197    /// <summary>
198    /// Returns a list of files which belong to a plugin from the plugincache
199    /// </summary>
200    private IEnumerable<string> GetPluginFilePaths(Guid pluginId) {
201      string pluginPath = Path.Combine(PluginCacheDir, pluginId.ToString());
202
203      if (Directory.Exists(pluginPath)) {
204        WriteDateLastUsed(pluginPath);
205        foreach (string filePath in Directory.GetFiles(pluginPath)) {
206          string fn = Path.GetFileName(filePath);
207          if (fn != lastUsedFileName)
208            yield return filePath;
209        }
210      }
211    }
212
213    /// <summary>
214    /// creates a file in path with the current date;
215    /// this can later be used to find plugins which are outdated
216    /// </summary>   
217    private void WriteDateLastUsed(string path) {
218      FileStream fs = null;
219      try {
220        fs = new FileStream(Path.Combine(path, lastUsedFileName), FileMode.Create);
221        BinaryFormatter formatter = new BinaryFormatter();
222        formatter.Serialize(fs, DateTime.Now);
223      }
224      catch (IOException) {
225        log.LogMessage(string.Format("No used date written in path {0}.", path));
226      }
227      catch (SerializationException) {
228        //rethrow...
229        throw;
230      }
231      finally {
232        if (fs != null) {
233          fs.Close();
234        }
235      }
236    }
237
238    /// <summary>
239    /// Checks the PluginTemp directory for orphaned directories and deletes them.
240    /// This should be only called if no jobs are currently running.
241    /// </summary>   
242    public void CleanPluginTemp() {
243      if (Directory.Exists(PluginTempBaseDir)) {
244        foreach (string dir in Directory.EnumerateDirectories(PluginTempBaseDir)) {
245          try {
246            log.LogMessage("Deleting orphaned directory " + dir);
247            Directory.Delete(dir, true);
248          }
249          catch (Exception ex) {
250            log.LogMessage("Error cleaning up PluginTemp directory " + dir + ": " + ex.ToString());
251          }
252        }
253      }
254    }
255
256    /// <summary>
257    /// checks the pluginCacheDirectory and deletes plugin folders which are not used anymore
258    /// </summary>   
259    private void CleanPluginCache() {
260      FileStream fs = null;
261      DateTime luDate;
262      bool changed = false;
263
264      if (Directory.Exists(PluginCacheDir)) {
265        lock (locker) {
266          foreach (string dir in Directory.EnumerateDirectories(PluginCacheDir)) {
267            try {
268              fs = new FileStream(Path.Combine(dir, lastUsedFileName), FileMode.Open);
269              BinaryFormatter formatter = new BinaryFormatter();
270              luDate = (DateTime)formatter.Deserialize(fs);
271              fs.Close();
272
273              if (luDate.AddDays(pluginLifetime) < DateTime.Now) {
274                Directory.Delete(dir, true);
275                changed = true;
276              }
277            }
278            catch (FileNotFoundException) {
279              //nerver used
280              Directory.Delete(dir, true);
281              changed = true;
282            }
283            catch (Exception ex) {
284              if (fs != null) {
285                fs.Close();
286              }
287              log.LogMessage(string.Format("CleanPluginCache threw exception: {0}", ex.ToString()));
288            }
289          }
290
291          if (changed)
292            DoUpdateRun();
293        }
294      }
295    }
296
297    public void DeletePluginsForJob(Guid id) {
298      try {
299        log.LogMessage("Deleting plugins...");
300        int tries = CoreProperties.Settings.Default.PluginDeletionRetries;
301        string path = Path.Combine(PluginTempBaseDir, id.ToString());
302        while (tries > 0) {
303          try {
304            if (Directory.Exists(path)) Directory.Delete(path, true);
305            tries = 0;
306          }
307          catch (Exception) {
308            Thread.Sleep(CoreProperties.Settings.Default.PluginDeletionTimeout);
309            tries--;
310            if (tries == 0) throw;
311          }
312        }
313      }
314      catch (Exception ex) {
315        log.LogMessage("failed while unloading " + id + " with exception " + ex);
316      }
317      CleanPluginCache();
318    }
319  }
320}
Note: See TracBrowser for help on using the repository browser.