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

AnderssonPeter / PowerType / 3961442594

pending completion
3961442594

push

github

AnderssonPeter
Removed xml documentation to make build on linux work

308 of 466 branches covered (66.09%)

Branch coverage included in aggregate %.

661 of 902 relevant lines covered (73.28%)

60.38 hits per line

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

81.82
/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);
14
    private readonly object dictionariesLocker = new();
15
    private readonly List<DictionaryInformation> dictionaries = new();
16
    private readonly ThreadQueue<Command> queue;
17
    private readonly CancellationTokenSource cancellationTokenSource = new();
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)
24
    {
25
        this.queue = queue;
26
        this.cancellationToken = cancellationTokenSource.Token;
27
        backgroundThread = new Thread(InnerLoop);
28
        backgroundThread.IsBackground = true;
29
        backgroundThread.Start();
30
        runspace = CreateRunspace();
31
    }
32

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

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

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

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

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

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

108
        var result = powershell.Invoke();
109
        if (powershell.HadErrors)
5!
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;
115
    }
116

117
    private void Handle(InitializeDictionaryCommand command)
118
    {
119
        using var powershell = PowerShell.Create(runspace);
120
        powershell.AddScript($". '{command.File}'");
121
        var result = powershell.Invoke();
122
        if (powershell.HadErrors)
4!
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;
130
        if (resultObject is PowerTypeDictionary dictionary)
4!
131
        {
132
            dictionary.Initialize(SystemTime.Instance);
133
            dictionary.Validate();
134
            suggestor = new DictionarySuggestor(dictionary);
135
        }
136
        else if (resultObject is DictionarySuggestor suggestorTmp)
×
137
        {
138
            suggestor = suggestorTmp;
139
        }
140
        else
141
        {
142
            throw new InvalidOperationException("Didn't receive a PowerTypeDictionary or ISuggestor");
143
        }
144

145
        lock (dictionariesLocker)
146
        {
147
            dictionaries.Add(new DictionaryInformation(command.File, suggestor));
148
        }
149
    }
150

151
    private void Handle(CacheDictionaryDynamicSources command)
152
    {
153
        var dictionaryInformation = GetDictionaryInformation(command.Dictionary);
154

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

176
    private DictionaryInformation GetDictionaryInformation(PowerTypeDictionary dictionary)
177
    {
178
        lock (dictionariesLocker)
179
        {
180
            return this.dictionaries.Single(x => x.Suggestor is DictionarySuggestor suggestor && suggestor.Dictionary == dictionary);
2!
181
        }
182
    }
183

184
    public void Dispose()
185
    {
186
        // Dispose of unmanaged resources.
187
        Dispose(true);
188
        // Suppress finalization.
189
        GC.SuppressFinalize(this);
190
    }
191

192
    protected virtual void Dispose(bool disposing)
193
    {
194
        if (disposed)
5!
195
        {
196
            return;
197
        }
198

199
        if (disposing)
10✔
200
        {
201
            cancellationTokenSource.Cancel();
202
            //While we wait for the background thread to exit lets do some cleanup
203

204
            runspace.Dispose();
205

206
            if (!backgroundThread.Join(200))
207
            {
208
                //todo: log that we failed to shutdown background thread!
209
            }
210
        }
211

212
        disposed = true;
213
    }
214
}
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