Free cookie consent management tool by TermsFeed Policy Generator

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

Last change on this file since 16748 was 16565, checked in by gkronber, 6 years ago

#2520: merged changes from PersistenceOverhaul branch (r16451:16564) into trunk

File size: 12.1 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2019 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
116      // copy slave plugins, otherwise its not possible to register the UnhandledException handler to the appdomain       
117      CopyFile(baseDir, targetDir, CoreProperties.Settings.Default.ClientsHiveSlaveCoreDll);
118      CopyFile(baseDir, targetDir, CoreProperties.Settings.Default.ClientsHiveDll);
119      CopyFile(baseDir, targetDir, CoreProperties.Settings.Default.HiveDll);
120      CopyFile(baseDir, targetDir, CoreProperties.Settings.Default.ClientsCommonDll);
121    }
122
123    private static DirectoryInfo RecreateDirectory(String targetDir) {
124      var di = new DirectoryInfo(targetDir);
125      if (di.Exists) Directory.Delete(targetDir, true);
126      di.Refresh();
127      while (di.Exists) {
128        Thread.Sleep(CoreProperties.Settings.Default.DirOpSleepTime);
129        di.Refresh();
130      }
131      return SafelyCreateDirectory(targetDir);
132    }
133
134    private static DirectoryInfo SafelyCreateDirectory(String targetDir) {
135      var di = new DirectoryInfo(targetDir);
136      if (!di.Exists) {
137        di = Directory.CreateDirectory(targetDir);
138        while (!di.Exists) {
139          Thread.Sleep(CoreProperties.Settings.Default.DirOpSleepTime);
140          di.Refresh();
141        }
142      }
143      return di;
144    }
145
146    private void CopyFile(string baseDir, string targetDir, string fileName) {
147      if (!File.Exists(Path.Combine(targetDir, fileName))) File.Copy(Path.Combine(baseDir, fileName), Path.Combine(targetDir, fileName));
148    }
149
150    /// <summary>
151    /// Updates the plugin cache with missing plugins and
152    /// then copies the required plugins for the task.
153    /// </summary>       
154    public void PreparePlugins(Task task, out string configFileName) {
155      lock (locker) {
156        log.LogMessage("Fetching plugins for task " + task.Id);
157
158        List<Guid> missingGuids = new List<Guid>();
159        List<Plugin> neededPlugins = new List<Plugin>();
160        lock (cachedPluginsGuids) {
161          foreach (Guid pluginId in task.PluginsNeededIds) {
162            Plugin plugin = pluginService.GetPlugin(pluginId);
163            if (plugin != null) {
164              neededPlugins.Add(plugin);
165            }
166
167            if (!cachedPluginsGuids.Contains(pluginId)) {
168              missingGuids.Add(pluginId);
169            }
170          }
171        }
172
173        IEnumerable<PluginData> pluginDatas = pluginService.GetPluginDatas(missingGuids);
174
175        if (pluginDatas != null) {
176          foreach (PluginData pluginData in pluginDatas) {
177            string pluginDir = Path.Combine(PluginCacheDir, pluginData.PluginId.ToString());
178
179            //put all files belonging to a plugin in the same directory
180            SafelyCreateDirectory(pluginDir);
181            File.WriteAllBytes(Path.Combine(pluginDir, Path.GetFileName(pluginData.FileName)), pluginData.Data);
182          }
183
184          if (missingGuids.Count > 0) {
185            DoUpdateRun();
186          }
187          CopyPluginsForJob(neededPlugins, task.Id, out configFileName);
188        } else {
189          configFileName = "";
190        }
191        log.LogMessage(string.Format("Fetched {0} plugins for task {1}", missingGuids.Count, task.Id));
192      }
193    }
194
195    /// <summary>
196    /// Returns a list of files which belong to a plugin from the plugincache
197    /// </summary>
198    private IEnumerable<string> GetPluginFilePaths(Guid pluginId) {
199      string pluginPath = Path.Combine(PluginCacheDir, pluginId.ToString());
200
201      if (Directory.Exists(pluginPath)) {
202        WriteDateLastUsed(pluginPath);
203        foreach (string filePath in Directory.GetFiles(pluginPath)) {
204          string fn = Path.GetFileName(filePath);
205          if (fn != lastUsedFileName)
206            yield return filePath;
207        }
208      }
209    }
210
211    /// <summary>
212    /// creates a file in path with the current date;
213    /// this can later be used to find plugins which are outdated
214    /// </summary>   
215    private void WriteDateLastUsed(string path) {
216      FileStream fs = null;
217      try {
218        fs = new FileStream(Path.Combine(path, lastUsedFileName), FileMode.Create);
219        BinaryFormatter formatter = new BinaryFormatter();
220        formatter.Serialize(fs, DateTime.Now);
221      }
222      catch (IOException) {
223        log.LogMessage(string.Format("No used date written in path {0}.", path));
224      }
225      catch (SerializationException) {
226        //rethrow...
227        throw;
228      }
229      finally {
230        if (fs != null) {
231          fs.Close();
232        }
233      }
234    }
235
236    /// <summary>
237    /// Checks the PluginTemp directory for orphaned directories and deletes them.
238    /// This should be only called if no jobs are currently running.
239    /// </summary>   
240    public void CleanPluginTemp() {
241      if (Directory.Exists(PluginTempBaseDir)) {
242        foreach (string dir in Directory.EnumerateDirectories(PluginTempBaseDir)) {
243          try {
244            log.LogMessage("Deleting orphaned directory " + dir);
245            Directory.Delete(dir, true);
246          }
247          catch (Exception ex) {
248            log.LogMessage("Error cleaning up PluginTemp directory " + dir + ": " + ex.ToString());
249          }
250        }
251      }
252    }
253
254    /// <summary>
255    /// checks the pluginCacheDirectory and deletes plugin folders which are not used anymore
256    /// </summary>   
257    private void CleanPluginCache() {
258      FileStream fs = null;
259      DateTime luDate;
260      bool changed = false;
261
262      if (Directory.Exists(PluginCacheDir)) {
263        lock (locker) {
264          foreach (string dir in Directory.EnumerateDirectories(PluginCacheDir)) {
265            try {
266              fs = new FileStream(Path.Combine(dir, lastUsedFileName), FileMode.Open);
267              BinaryFormatter formatter = new BinaryFormatter();
268              luDate = (DateTime)formatter.Deserialize(fs);
269              fs.Close();
270
271              if (luDate.AddDays(pluginLifetime) < DateTime.Now) {
272                Directory.Delete(dir, true);
273                changed = true;
274              }
275            }
276            catch (FileNotFoundException) {
277              //nerver used
278              Directory.Delete(dir, true);
279              changed = true;
280            }
281            catch (Exception ex) {
282              if (fs != null) {
283                fs.Close();
284              }
285              log.LogMessage(string.Format("CleanPluginCache threw exception: {0}", ex.ToString()));
286            }
287          }
288
289          if (changed)
290            DoUpdateRun();
291        }
292      }
293    }
294
295    public void DeletePluginsForJob(Guid id) {
296      try {
297        log.LogMessage("Deleting plugins...");
298        int tries = CoreProperties.Settings.Default.PluginDeletionRetries;
299        string path = Path.Combine(PluginTempBaseDir, id.ToString());
300        while (tries > 0) {
301          try {
302            if (Directory.Exists(path)) Directory.Delete(path, true);
303            tries = 0;
304          }
305          catch (Exception) {
306            Thread.Sleep(CoreProperties.Settings.Default.PluginDeletionTimeout);
307            tries--;
308            if (tries == 0) throw;
309          }
310        }
311      }
312      catch (Exception ex) {
313        log.LogMessage("failed while unloading " + id + " with exception " + ex);
314      }
315      CleanPluginCache();
316    }
317  }
318}
Note: See TracBrowser for help on using the repository browser.