• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

AnderssonPeter / PowerType / 5347343671

pending completion
5347343671

push

github

AnderssonPeter
Merge branch 'main' of https://github.com/AnderssonPeter/PowerType

312 of 474 branches covered (65.82%)

Branch coverage included in aggregate %.

673 of 922 relevant lines covered (72.99%)

182.46 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.35
/PowerType/BackgroundProcessing/ExecutionEngineThread.cs
1
using System.Management.Automation;
2
using System.Management.Automation.Runspaces;
3
using System.Reflection;
4
using System.Runtime.InteropServices;
5
using Microsoft.PowerShell;
6
using PowerType.Model;
7

8
namespace PowerType.BackgroundProcessing;
9

10
internal class ExecutionEngineThread : IDisposable
11
{
12
    private bool disposed;
13
    record DictionaryInformation(string File, DictionarySuggestor Suggestor);
57✔
14
    private readonly object dictionariesLocker = new();
15✔
15
    private readonly List<DictionaryInformation> dictionaries = new();
15✔
16
    private readonly ThreadQueue<Command> queue;
17
    private readonly CancellationTokenSource cancellationTokenSource = new();
15✔
18
    private readonly CancellationToken cancellationToken;
19
    private readonly Thread backgroundThread;
20
    private Exception? backgroundThreadException;
21
    private readonly Runspace runspace;
22

23
    internal ExecutionEngineThread(ThreadQueue<Command> queue)
15✔
24
    {
25
        this.queue = queue;
15✔
26
        this.cancellationToken = cancellationTokenSource.Token;
15✔
27
        backgroundThread = new Thread(InnerLoop);
15✔
28
        backgroundThread.IsBackground = true;
15✔
29
        backgroundThread.Start();
15✔
30
        runspace = CreateRunspace();
15✔
31
    }
15✔
32

33
    /// <summary>This method is thread safe</summary>
34
    public bool IsHealthy(out Exception? exception)
35
    {
36
        Thread.MemoryBarrier();
183✔
37
        exception = backgroundThreadException;
183✔
38
        return exception == null && backgroundThread.IsAlive;
183!
39
    }
40

41
    /// <summary>This method is thread safe</summary>
42
    public List<DictionarySuggestor> GetSuggestors()
43
    {
44
        lock (dictionariesLocker)
3✔
45
        {
46
            return dictionaries.ConvertAll(x => x.Suggestor);
6✔
47
        }
48
    }
3✔
49

50
    /// <summary>This method is thread safe</summary>
51
    public List<PowerTypeDictionary> GetDictionaries()
52
    {
53
        lock (dictionariesLocker)
6✔
54
        {
55
            return dictionaries.ConvertAll(x => x.Suggestor.Dictionary);
12✔
56
        }
57
    }
6✔
58

59
    private void InnerLoop()
60
    {
61
        try
62
        {
63
            while (!cancellationToken.IsCancellationRequested)
33!
64
            {
65
                var command = queue.WaitAndDequeue(cancellationToken);
33✔
66
                if (command is InitializeDictionaryCommand initializeCommand)
18✔
67
                {
68
                    Handle(initializeCommand);
12✔
69
                }
70
                else if (command is CacheDictionaryDynamicSources cacheDictionaryCommand)
6!
71
                {
72
                    Handle(cacheDictionaryCommand);
6✔
73
                }
74
                else
75
                {
76
                    throw new InvalidOperationException($"Don't know how to handle command {command.GetType()}!");
×
77
                }
78
                command.IsDone = true;
18✔
79
            }
80
        }
×
81
        catch (Exception ex)
14✔
82
        {
83
            backgroundThreadException = ex;
14✔
84
        }
14✔
85
    }
14✔
86

87
    private static Runspace CreateRunspace()
88
    {
89
        var assembly = Assembly.GetExecutingAssembly();
15✔
90
        var assemblyName = assembly.GetName().Name;
15✔
91
        var assemblyPath = assembly.Location;
15✔
92
        var assemblyDirectory = Path.GetDirectoryName(assembly.Location) ?? throw new InvalidOperationException("Failed to get assembly directory!");
15!
93
        var modulePath = assembly.Location.Replace(".dll", ".psd1");
15✔
94
        var initialState = InitialSessionState.CreateDefault();
15✔
95
        initialState.ThrowOnRunspaceOpenError = true;
15✔
96
        initialState.Assemblies.Add(new SessionStateAssemblyEntry(assemblyName, assemblyPath));
15✔
97
        initialState.ImportPSModule(modulePath);
15✔
98
        if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
15✔
99
        {
100
            initialState.ExecutionPolicy = ExecutionPolicy.Unrestricted;
5✔
101
        }
102
        var runspace = RunspaceFactory.CreateRunspace(initialState);
15✔
103
        runspace.Open();
15✔
104

105
        using var powershell = PowerShell.Create(runspace);
15✔
106
        powershell.AddScript($"using namespace PowerType.Model\nusing namespace PowerType.Model.Conditions\n. '{Path.Combine(assemblyDirectory, "HelperFunctions.ps1")}'");
15✔
107

108
        var result = powershell.Invoke();
15✔
109
        if (powershell.HadErrors)
15!
110
        {
111
            var errors = string.Join(Environment.NewLine, powershell.Streams.Error.Select(x => x.ToString()));
×
112
            throw new Exception("Failed to initialize dictionary, errors: " + errors);
×
113
        }
114
        return runspace;
15✔
115
    }
15✔
116

117
    private void Handle(InitializeDictionaryCommand command)
118
    {
119
        using var powershell = PowerShell.Create(runspace);
12✔
120
        powershell.AddScript($". '{command.File}'");
12✔
121
        var result = powershell.Invoke();
12✔
122
        if (powershell.HadErrors)
12!
123
        {
124
            var errors = string.Join(Environment.NewLine, powershell.Streams.Error.Select(x => x.ToString()));
×
125
            throw new Exception("Failed to initialize dictionary, errors: " + errors);
×
126
        }
127

128
        DictionarySuggestor suggestor;
129
        var resultObject = result.Single().BaseObject;
12✔
130
        if (resultObject is PowerTypeDictionary dictionary)
12!
131
        {
132
            if (!dictionary.Platforms.HasFlag(PlatformIdentification.CurrentPlatform))
12!
133
            {
134
                //This dictionary is not supported on the current platform
135
                return;
×
136
            }
137
            dictionary.Initialize(SystemTime.Instance);
12✔
138
            dictionary.Validate();
12✔
139
            suggestor = new DictionarySuggestor(dictionary);
12✔
140
        }
141
        else if (resultObject is DictionarySuggestor suggestorTmp)
×
142
        {
143
            suggestor = suggestorTmp;
×
144
        }
145
        else
146
        {
147
            throw new InvalidOperationException("Didn't receive a PowerTypeDictionary or ISuggestor");
×
148
        }
149
        lock (dictionariesLocker)
12✔
150
        {
151
            dictionaries.Add(new DictionaryInformation(command.File, suggestor));
12✔
152
        }
12✔
153
    }
12✔
154

155
    private void Handle(CacheDictionaryDynamicSources command)
156
    {
157
        var dictionaryInformation = GetDictionaryInformation(command.Dictionary);
6✔
158

159
        foreach (var dynamicSource in dictionaryInformation.Suggestor.GetDynamicSources())
24✔
160
        {
161
            if (dynamicSource.Cache.ShouldUpdate(command.CurrentWorkingDirectory))
6✔
162
            {
163
                try
164
                {
165
                    runspace.SessionStateProxy.Path.SetLocation(command.CurrentWorkingDirectory);
6✔
166
                    var result = dynamicSource.CommandExpression.Invoke();
3✔
167
                    var items = result.Select(x => x.BaseObject is string value ?
6!
168
                        new SourceItem { Name = value } :
6✔
169
                        (SourceItem)x.BaseObject).ToList();
6✔
170
                    dynamicSource.Cache.UpdateCache(items, command.CurrentWorkingDirectory);
3✔
171
                }
3✔
172
                catch(System.Management.Automation.DriveNotFoundException)
3✔
173
                {
174
                    //todo: log exception
175
                }
3✔
176
            }
177
        }
178
    }
6✔
179

180
    private DictionaryInformation GetDictionaryInformation(PowerTypeDictionary dictionary)
181
    {
182
        lock (dictionariesLocker)
6✔
183
        {
184
            return this.dictionaries.Single(x => x.Suggestor is DictionarySuggestor suggestor && suggestor.Dictionary == dictionary);
12!
185
        }
186
    }
6✔
187

188
    public void Dispose()
189
    {
190
        // Dispose of unmanaged resources.
191
        Dispose(true);
15✔
192
        // Suppress finalization.
193
        GC.SuppressFinalize(this);
15✔
194
    }
15✔
195

196
    protected virtual void Dispose(bool disposing)
197
    {
198
        if (disposed)
15!
199
        {
200
            return;
×
201
        }
202

203
        if (disposing)
15✔
204
        {
205
            cancellationTokenSource.Cancel();
15✔
206
            //While we wait for the background thread to exit lets do some cleanup
207

208
            runspace.Dispose();
15✔
209

210
            if (!backgroundThread.Join(200))
15✔
211
            {
212
                //todo: log that we failed to shutdown background thread!
213
            }
214
        }
215

216
        disposed = true;
15✔
217
    }
15✔
218
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc