-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
DefaultDeltaApplier.cs
173 lines (147 loc) · 6.38 KB
/
DefaultDeltaApplier.cs
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
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
#nullable enable
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.IO.Pipes;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.ExternalAccess.Watch.Api;
using Microsoft.Extensions.HotReload;
using Microsoft.Extensions.Tools.Internal;
namespace Microsoft.DotNet.Watcher.Tools
{
internal class DefaultDeltaApplier : IDeltaApplier
{
private static readonly string _namedPipeName = Guid.NewGuid().ToString();
private readonly IReporter _reporter;
private Task? _connectionTask;
private Task<ImmutableArray<string>>? _capabilities;
private NamedPipeServerStream? _pipe;
public DefaultDeltaApplier(IReporter reporter)
{
_reporter = reporter;
}
internal bool SuppressNamedPipeForTests { get; set; }
public ValueTask InitializeAsync(DotNetWatchContext context, CancellationToken cancellationToken)
{
if (!SuppressNamedPipeForTests)
{
_pipe = new NamedPipeServerStream(_namedPipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous | PipeOptions.CurrentUserOnly);
_connectionTask = _pipe.WaitForConnectionAsync(cancellationToken);
_capabilities = Task.Run(async () =>
{
try
{
await _connectionTask;
// When the client connects, the first payload it sends is the initialization payload which includes the apply capabilities.
var capabiltiies = ClientInitializationPayload.Read(_pipe).Capabilities;
_reporter.Verbose($"Application supports the following capabilities {capabiltiies}.");
return capabiltiies.Split(' ').ToImmutableArray();
}
catch
{
// Do nothing. This is awaited by Apply which will surface the error.
}
return ImmutableArray<string>.Empty;
});
}
if (context.Iteration == 0)
{
var deltaApplier = Path.Combine(AppContext.BaseDirectory, "hotreload", "Microsoft.Extensions.DotNetDeltaApplier.dll");
context.ProcessSpec.EnvironmentVariables.DotNetStartupHooks.Add(deltaApplier);
// Configure the app for EnC
context.ProcessSpec.EnvironmentVariables["DOTNET_MODIFIABLE_ASSEMBLIES"] = "debug";
context.ProcessSpec.EnvironmentVariables["DOTNET_HOTRELOAD_NAMEDPIPE_NAME"] = _namedPipeName;
}
return default;
}
public Task<ImmutableArray<string>> GetApplyUpdateCapabilitiesAsync(DotNetWatchContext context, CancellationToken cancellationToken)
=> _capabilities ?? Task.FromResult(ImmutableArray<string>.Empty);
public async ValueTask<bool> Apply(DotNetWatchContext context, ImmutableArray<WatchHotReloadService.Update> solutionUpdate, CancellationToken cancellationToken)
{
if (_connectionTask is null || !_connectionTask.IsCompletedSuccessfully || _pipe is null || !_pipe.IsConnected)
{
// The client isn't listening
_reporter.Verbose("No client connected to receive delta updates.");
return false;
}
var payload = new UpdatePayload
{
Deltas = ImmutableArray.CreateRange(solutionUpdate, c => new UpdateDelta
{
ModuleId = c.ModuleId,
ILDelta = c.ILDelta.ToArray(),
MetadataDelta = c.MetadataDelta.ToArray(),
UpdatedTypes = c.UpdatedTypes.ToArray(),
}),
};
await payload.WriteAsync(_pipe, cancellationToken);
await _pipe.FlushAsync(cancellationToken);
var result = ApplyResult.Failed;
var bytes = ArrayPool<byte>.Shared.Rent(1);
try
{
var timeout =
#if DEBUG
Timeout.InfiniteTimeSpan;
#else
TimeSpan.FromSeconds(5);
#endif
using var cancellationTokenSource = new CancellationTokenSource(timeout);
var numBytes = await _pipe.ReadAsync(bytes, cancellationTokenSource.Token);
if (numBytes == 1)
{
result = (ApplyResult)bytes[0];
}
}
catch (Exception ex)
{
// Log it, but we'll treat this as a failed apply.
_reporter.Verbose(ex.Message);
}
finally
{
ArrayPool<byte>.Shared.Return(bytes);
}
if (result == ApplyResult.Failed)
{
return false;
}
if (context.BrowserRefreshServer is not null)
{
// BrowserRefreshServer will be null in non web projects or if we failed to establish a websocket connection
await context.BrowserRefreshServer.SendJsonSerlialized(new AspNetCoreHotReloadApplied(), cancellationToken);
}
return true;
}
public async ValueTask ReportDiagnosticsAsync(DotNetWatchContext context, IEnumerable<string> diagnostics, CancellationToken cancellationToken)
{
if (context.BrowserRefreshServer != null)
{
var message = new HotReloadDiagnostics
{
Diagnostics = diagnostics
};
await context.BrowserRefreshServer.SendJsonSerlialized(message, cancellationToken);
}
}
public void Dispose()
{
_pipe?.Dispose();
}
public readonly struct HotReloadDiagnostics
{
public string Type => "HotReloadDiagnosticsv1";
public IEnumerable<string> Diagnostics { get; init; }
}
public readonly struct AspNetCoreHotReloadApplied
{
public string Type => "AspNetCoreHotReloadApplied";
}
}
}