|
14 | 14 | using Microsoft.Extensions.Logging; |
15 | 15 | using Spectre.Console; |
16 | 16 | using Spectre.Console.Rendering; |
| 17 | +using StreamJsonRpc; |
17 | 18 |
|
18 | 19 | namespace Aspire.Cli; |
19 | 20 |
|
@@ -218,44 +219,60 @@ await AnsiConsole.Live(table).StartAsync(async context => { |
218 | 219 |
|
219 | 220 | var resourceStates = backchannel.GetResourceStatesAsync(ct); |
220 | 221 |
|
221 | | - await foreach(var resourceState in resourceStates) |
| 222 | + try |
222 | 223 | { |
223 | | - knownResources[resourceState.Resource] = resourceState; |
| 224 | + await foreach(var resourceState in resourceStates) |
| 225 | + { |
| 226 | + knownResources[resourceState.Resource] = resourceState; |
224 | 227 |
|
225 | | - table.Rows.Clear(); |
| 228 | + table.Rows.Clear(); |
226 | 229 |
|
227 | | - foreach (var knownResource in knownResources) |
228 | | - { |
229 | | - var nameRenderable = new Text(knownResource.Key, new Style().Foreground(Color.White)); |
230 | | - |
231 | | - var typeRenderable = new Text(knownResource.Value.Type, new Style().Foreground(Color.White)); |
232 | | - |
233 | | - var stateRenderable = knownResource.Value.State switch { |
234 | | - "Running" => new Text(knownResource.Value.State, new Style().Foreground(Color.Green)), |
235 | | - "Starting" => new Text(knownResource.Value.State, new Style().Foreground(Color.LightGreen)), |
236 | | - "FailedToStart" => new Text(knownResource.Value.State, new Style().Foreground(Color.Red)), |
237 | | - "Waiting" => new Text(knownResource.Value.State, new Style().Foreground(Color.White)), |
238 | | - "Unhealthy" => new Text(knownResource.Value.State, new Style().Foreground(Color.Yellow)), |
239 | | - "Exited" => new Text(knownResource.Value.State, new Style().Foreground(Color.Grey)), |
240 | | - "Finished" => new Text(knownResource.Value.State, new Style().Foreground(Color.Grey)), |
241 | | - "NotStarted" => new Text(knownResource.Value.State, new Style().Foreground(Color.Grey)), |
242 | | - _ => new Text(knownResource.Value.State ?? "Unknown", new Style().Foreground(Color.Grey)) |
243 | | - }; |
244 | | - |
245 | | - IRenderable endpointsRenderable = new Text("None"); |
246 | | - if (knownResource.Value.Endpoints?.Length > 0) |
| 230 | + foreach (var knownResource in knownResources) |
247 | 231 | { |
248 | | - endpointsRenderable = new Rows( |
249 | | - knownResource.Value.Endpoints.Select(e => new Text(e, new Style().Link(e))) |
250 | | - ); |
| 232 | + var nameRenderable = new Text(knownResource.Key, new Style().Foreground(Color.White)); |
| 233 | + |
| 234 | + var typeRenderable = new Text(knownResource.Value.Type, new Style().Foreground(Color.White)); |
| 235 | + |
| 236 | + var stateRenderable = knownResource.Value.State switch { |
| 237 | + "Running" => new Text(knownResource.Value.State, new Style().Foreground(Color.Green)), |
| 238 | + "Starting" => new Text(knownResource.Value.State, new Style().Foreground(Color.LightGreen)), |
| 239 | + "FailedToStart" => new Text(knownResource.Value.State, new Style().Foreground(Color.Red)), |
| 240 | + "Waiting" => new Text(knownResource.Value.State, new Style().Foreground(Color.White)), |
| 241 | + "Unhealthy" => new Text(knownResource.Value.State, new Style().Foreground(Color.Yellow)), |
| 242 | + "Exited" => new Text(knownResource.Value.State, new Style().Foreground(Color.Grey)), |
| 243 | + "Finished" => new Text(knownResource.Value.State, new Style().Foreground(Color.Grey)), |
| 244 | + "NotStarted" => new Text(knownResource.Value.State, new Style().Foreground(Color.Grey)), |
| 245 | + _ => new Text(knownResource.Value.State ?? "Unknown", new Style().Foreground(Color.Grey)) |
| 246 | + }; |
| 247 | + |
| 248 | + IRenderable endpointsRenderable = new Text("None"); |
| 249 | + if (knownResource.Value.Endpoints?.Length > 0) |
| 250 | + { |
| 251 | + endpointsRenderable = new Rows( |
| 252 | + knownResource.Value.Endpoints.Select(e => new Text(e, new Style().Link(e))) |
| 253 | + ); |
| 254 | + } |
| 255 | + |
| 256 | + table.AddRow(nameRenderable, typeRenderable, stateRenderable, endpointsRenderable); |
251 | 257 | } |
252 | 258 |
|
253 | | - table.AddRow(nameRenderable, typeRenderable, stateRenderable, endpointsRenderable); |
| 259 | + context.Refresh(); |
254 | 260 | } |
255 | | - |
256 | | - context.Refresh(); |
257 | 261 | } |
258 | | - |
| 262 | + catch (ConnectionLostException ex) when (ex.InnerException is OperationCanceledException) |
| 263 | + { |
| 264 | + // This exception will be thrown if the cancellation request reaches the WaitForExitAsync |
| 265 | + // call on the process and shuts down the apphost before the JsonRpc connection gets it meaning |
| 266 | + // that the apphost side of the RPC connection will be closed. Therefore if we get a |
| 267 | + // ConnectionLostException AND the inner exception is an OperationCancelledException we can |
| 268 | + // asume that the apphost was shutdown and we can ignore it. |
| 269 | + } |
| 270 | + catch (OperationCanceledException) |
| 271 | + { |
| 272 | + // This exception will be thrown if the cancellation request reaches the our side |
| 273 | + // of the backchannel side first and the connection is torn down on our-side |
| 274 | + // gracefully. We can ignore this exception as well. |
| 275 | + } |
259 | 276 | }); |
260 | 277 |
|
261 | 278 | return await pendingRun; |
|
0 commit comments