Skip to content

Commit 02bc400

Browse files
committed
invariant in startup progress model
1 parent 473164d commit 02bc400

File tree

5 files changed

+36
-18
lines changed

5 files changed

+36
-18
lines changed

App/Models/RpcModel.cs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,8 @@ public class VpnStartupProgress
8888
private const double DownloadProgressMin = 0.05;
8989
private const double DownloadProgressMax = 0.80;
9090

91-
public VpnStartupStage Stage { get; set; } = VpnStartupStage.Unknown;
92-
public VpnDownloadProgress? DownloadProgress { get; set; } = null;
91+
public VpnStartupStage Stage { get; init; } = VpnStartupStage.Unknown;
92+
public VpnDownloadProgress? DownloadProgress { get; init; } = null;
9393

9494
// 0.0 to 1.0
9595
public double Progress
@@ -165,10 +165,25 @@ public class RpcModel
165165
{
166166
public RpcLifecycle RpcLifecycle { get; set; } = RpcLifecycle.Disconnected;
167167

168-
public VpnLifecycle VpnLifecycle { get; set; } = VpnLifecycle.Unknown;
168+
public VpnLifecycle VpnLifecycle
169+
{
170+
get;
171+
set
172+
{
173+
if (VpnLifecycle != value && value == VpnLifecycle.Starting)
174+
// Reset the startup progress when the VPN lifecycle changes to
175+
// Starting.
176+
VpnStartupProgress = null;
177+
field = value;
178+
}
179+
}
169180

170181
// Nullable because it is only set when the VpnLifecycle is Starting
171-
public VpnStartupProgress? VpnStartupProgress { get; set; }
182+
public VpnStartupProgress? VpnStartupProgress
183+
{
184+
get => VpnLifecycle is VpnLifecycle.Starting ? field ?? new VpnStartupProgress() : null;
185+
set;
186+
}
172187

173188
public IReadOnlyList<Workspace> Workspaces { get; set; } = [];
174189

App/Services/RpcController.cs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,6 @@ public async Task StartVpn(CancellationToken ct = default)
164164
MutateState(state =>
165165
{
166166
state.VpnLifecycle = VpnLifecycle.Starting;
167-
state.VpnStartupProgress = new VpnStartupProgress();
168167
});
169168

170169
ServiceMessage reply;
@@ -255,9 +254,6 @@ private void MutateState(Action<RpcModel> mutator)
255254
using (_stateLock.Lock())
256255
{
257256
mutator(_state);
258-
// Unset the startup progress if the VpnLifecycle is not Starting
259-
if (_state.VpnLifecycle != VpnLifecycle.Starting)
260-
_state.VpnStartupProgress = null;
261257
newState = _state.Clone();
262258
}
263259

@@ -294,8 +290,8 @@ private void ApplyStartProgressUpdate(StartProgress message)
294290
{
295291
MutateState(state =>
296292
{
297-
// MutateState will undo these changes if it doesn't believe we're
298-
// in the "Starting" state.
293+
// The model itself will ignore this value if we're not in the
294+
// starting state.
299295
state.VpnStartupProgress = VpnStartupProgress.FromProto(message);
300296
});
301297
}

Vpn.Service/Manager.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ private async Task FallibleBroadcast(ServiceMessage message, CancellationToken c
331331
// Broadcast the messages out with a low timeout. If clients don't
332332
// receive broadcasts in time, it's not a big deal.
333333
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
334-
//cts.CancelAfter(TimeSpan.FromMilliseconds(100));
334+
cts.CancelAfter(TimeSpan.FromMilliseconds(30));
335335
try
336336
{
337337
await _managerRpc.BroadcastAsync(message, cts.Token);

Vpn.Service/ManagerRpc.cs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -127,26 +127,33 @@ public async Task ExecuteAsync(CancellationToken stoppingToken)
127127

128128
public async Task BroadcastAsync(ServiceMessage message, CancellationToken ct)
129129
{
130+
// Sends messages to all clients simultaneously and waits for them all
131+
// to send or fail/timeout.
132+
//
130133
// Looping over a ConcurrentDictionary is exception-safe, but any items
131134
// added or removed during the loop may or may not be included.
132-
foreach (var (clientId, client) in _activeClients)
135+
await Task.WhenAll(_activeClients.Select(async item =>
136+
{
133137
try
134138
{
135-
var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
139+
// Enforce upper bound in case a CT with a timeout wasn't
140+
// supplied.
141+
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
136142
cts.CancelAfter(TimeSpan.FromSeconds(2));
137-
await client.Speaker.SendMessage(message, cts.Token);
143+
await item.Value.Speaker.SendMessage(message, cts.Token);
138144
}
139145
catch (ObjectDisposedException)
140146
{
141147
// The speaker was likely closed while we were iterating.
142148
}
143149
catch (Exception e)
144150
{
145-
_logger.LogWarning(e, "Failed to send message to client {ClientId}", clientId);
151+
_logger.LogWarning(e, "Failed to send message to client {ClientId}", item.Key);
146152
// TODO: this should probably kill the client, but due to the
147153
// async nature of the client handling, calling Dispose
148154
// will not remove the client from the active clients list
149155
}
156+
}));
150157
}
151158

152159
private async Task HandleRpcClientAsync(ulong clientId, Speaker<ServiceMessage, ClientMessage> speaker,

Vpn.Service/TunnelSupervisor.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,16 +99,16 @@ public async Task StartAsync(string binPath,
9999
},
100100
};
101101
// TODO: maybe we should change the log format in the inner binary
102-
// to something without a timestamp
102+
// to something without a timestamp
103103
_subprocess.OutputDataReceived += (_, args) =>
104104
{
105105
if (!string.IsNullOrWhiteSpace(args.Data))
106-
_logger.LogDebug("stdout: {Data}", args.Data);
106+
_logger.LogInformation("stdout: {Data}", args.Data);
107107
};
108108
_subprocess.ErrorDataReceived += (_, args) =>
109109
{
110110
if (!string.IsNullOrWhiteSpace(args.Data))
111-
_logger.LogDebug("stderr: {Data}", args.Data);
111+
_logger.LogInformation("stderr: {Data}", args.Data);
112112
};
113113

114114
// Pass the other end of the pipes to the subprocess and dispose

0 commit comments

Comments
 (0)