forked from PowerShell/PowerShell
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathTerminatingErrorTracker.cs
More file actions
224 lines (192 loc) · 9.63 KB
/
Copy pathTerminatingErrorTracker.cs
File metadata and controls
224 lines (192 loc) · 9.63 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
/********************************************************************++
Copyright (c) Microsoft Corporation. All rights reserved.
--********************************************************************/
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Linq;
using System.Management.Automation;
using System.Management.Automation.Remoting;
using System.Runtime.CompilerServices;
using System.Threading;
using Microsoft.Management.Infrastructure;
using Dbg = System.Management.Automation.Diagnostics;
namespace Microsoft.PowerShell.Cmdletization.Cim
{
/// <summary>
/// Tracks (per-session) terminating errors in a given cmdlet invocation
/// </summary>
internal class TerminatingErrorTracker
{
#region Getting tracker for a given cmdlet invocation
private static readonly ConditionalWeakTable<InvocationInfo, TerminatingErrorTracker> InvocationToTracker =
new ConditionalWeakTable<InvocationInfo, TerminatingErrorTracker>();
private static int GetNumberOfSessions(InvocationInfo invocationInfo)
{
// if user explicitly specifies CimSession, then the cmdlet runs against exactly those sessions
object cimSessionArgument;
if (invocationInfo.BoundParameters.TryGetValue("CimSession", out cimSessionArgument))
{
IList cimSessionArgumentAsList = (IList) cimSessionArgument;
return cimSessionArgumentAsList.Count;
}
// else - either CimSession=localhost OR CimSession is based on CimInstance->CimSession affinity
// CimInstance->CimSession affinity in instance cmdlets can come from:
// 1. InputObject (either passed through pipeline or explicitly bound to the parameter)
// 2. AssociatedObject (either passed through pipeline or explicitly bound to the parameter [we don't know the name of the parameter though])
// CimInstance->CimSession affinity in static cmdlets can come from:
// 1. Any method argument that is either a CimInstance or CimInstance[]
// Additionally in both instance and static cmdlets, if the pipeline object is a CimInstance, then it can affect the session acted against
if (invocationInfo.ExpectingInput)
{
// can get unlimited number of CimInstances through pipeline
// - this translates into potentially unlimited number of CimSession we will work with
return int.MaxValue;
}
int maxNumberOfSessionsIndicatedByCimInstanceArguments = 1;
foreach (object cmdletArgument in invocationInfo.BoundParameters.Values)
{
CimInstance[] array = cmdletArgument as CimInstance[];
if (array != null)
{
int numberOfSessionsAssociatedWithArgument = array
.Select(CimCmdletAdapter.GetSessionOfOriginFromCimInstance)
.Distinct()
.Count();
maxNumberOfSessionsIndicatedByCimInstanceArguments = Math.Max(
maxNumberOfSessionsIndicatedByCimInstanceArguments,
numberOfSessionsAssociatedWithArgument);
}
}
return maxNumberOfSessionsIndicatedByCimInstanceArguments;
}
internal static TerminatingErrorTracker GetTracker(InvocationInfo invocationInfo, bool isStaticCmdlet)
{
var tracker = InvocationToTracker.GetValue(
invocationInfo,
_ => new TerminatingErrorTracker(GetNumberOfSessions(invocationInfo)));
return tracker;
}
internal static TerminatingErrorTracker GetTracker(InvocationInfo invocationInfo)
{
TerminatingErrorTracker tracker;
bool foundTracker = InvocationToTracker.TryGetValue(invocationInfo, out tracker);
Dbg.Assert(foundTracker, "The other overload of GetTracker should always be called first");
return tracker;
}
#endregion Getting tracker for a given cmdlet invocation
#region Tracking terminating errors within a single cmdlet invocation
private readonly int _numberOfSessions;
private int _numberOfReportedSessionTerminatingErrors;
private TerminatingErrorTracker(int numberOfSessions)
{
this._numberOfSessions = numberOfSessions;
}
#region Tracking session's "connectivity" status
private readonly ConcurrentDictionary<CimSession, bool> _sessionToIsConnected = new ConcurrentDictionary<CimSession, bool>();
internal void MarkSessionAsConnected(CimSession connectedSession)
{
this._sessionToIsConnected.TryAdd(connectedSession, true);
}
internal bool DidSessionAlreadyPassedConnectivityTest(CimSession session)
{
bool alreadyPassedConnectivityTest = false;
if (this._sessionToIsConnected.TryGetValue(session, out alreadyPassedConnectivityTest))
{
return alreadyPassedConnectivityTest;
}
return false;
}
internal Exception GetExceptionIfBrokenSession(
CimSession potentiallyBrokenSession,
bool skipTestConnection,
out bool sessionWasAlreadyTerminated)
{
if (IsSessionTerminated(potentiallyBrokenSession))
{
sessionWasAlreadyTerminated = true;
return null;
}
Exception sessionException = null;
if (!skipTestConnection &&
!this.DidSessionAlreadyPassedConnectivityTest(potentiallyBrokenSession))
{
try
{
CimInstance throwAwayCimInstance;
CimException cimException;
potentiallyBrokenSession.TestConnection(out throwAwayCimInstance, out cimException);
sessionException = cimException;
if (sessionException == null)
{
this.MarkSessionAsConnected(potentiallyBrokenSession);
}
}
catch (InvalidOperationException invalidOperationException)
{
sessionException = invalidOperationException;
}
}
if (sessionException != null)
{
MarkSessionAsTerminated(potentiallyBrokenSession, out sessionWasAlreadyTerminated);
return sessionException;
}
else
{
sessionWasAlreadyTerminated = false;
return sessionException;
}
}
#endregion
#region Tracking session's "terminated" status
private readonly ConcurrentDictionary<CimSession, bool> _sessionToIsTerminated = new ConcurrentDictionary<CimSession, bool>();
internal void MarkSessionAsTerminated(CimSession terminatedSession, out bool sessionWasAlreadyTerminated)
{
bool closureSafeSessionWasAlreadyTerminated = false;
this._sessionToIsTerminated.AddOrUpdate(
key: terminatedSession,
addValue: true,
updateValueFactory:
delegate(CimSession key, bool isTerminatedValueInDictionary)
{
closureSafeSessionWasAlreadyTerminated = isTerminatedValueInDictionary;
return true;
});
sessionWasAlreadyTerminated = closureSafeSessionWasAlreadyTerminated;
}
internal bool IsSessionTerminated(CimSession session)
{
bool isTerminated = this._sessionToIsTerminated.GetOrAdd(session, false);
return isTerminated;
}
#endregion
#region Reporting errors in a way that takes session's "terminated" status into account
internal CmdletMethodInvoker<bool> GetErrorReportingDelegate(ErrorRecord errorRecord)
{
ManualResetEventSlim manualResetEventSlim = new ManualResetEventSlim();
object lockObject = new object();
Func<Cmdlet, bool> action = delegate (Cmdlet cmdlet)
{
this._numberOfReportedSessionTerminatingErrors++;
if (this._numberOfReportedSessionTerminatingErrors >= this._numberOfSessions)
{
cmdlet.ThrowTerminatingError(errorRecord);
}
else
{
cmdlet.WriteError(errorRecord);
}
return false; // not really needed here, but required by CmdletMethodInvoker
};
return new CmdletMethodInvoker<bool>
{
Action = action,
Finished = manualResetEventSlim, // not really needed here, but required by CmdletMethodInvoker
SyncObject = lockObject, // not really needed here, but required by CmdletMethodInvoker
};
}
#endregion
#endregion Tracking terminating errors within a single cmdlet invocation
}
}