Skip to content

Commit 07d0892

Browse files
committed
PR comments
1 parent 990a970 commit 07d0892

File tree

4 files changed

+103
-81
lines changed

4 files changed

+103
-81
lines changed

Coder.Desktop.sln.DotSettings

+2-19
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
</HasMember>
3131
</And>
3232
</TypePattern.Match>
33-
33+
3434
<Entry DisplayName="Fields">
3535
<Entry.Match>
3636
<And>
@@ -144,10 +144,6 @@
144144
<Kind Is="Delegate" />
145145
</And>
146146
</Entry.Match>
147-
148-
<Entry.SortBy>
149-
<Name />
150-
</Entry.SortBy>
151147
</Entry>
152148

153149
<Entry DisplayName="Public Enums" Priority="100">
@@ -157,10 +153,6 @@
157153
<Kind Is="Enum" />
158154
</And>
159155
</Entry.Match>
160-
161-
<Entry.SortBy>
162-
<Name />
163-
</Entry.SortBy>
164156
</Entry>
165157

166158
<Entry DisplayName="Static Fields and Constants">
@@ -193,21 +185,12 @@
193185
</Not>
194186
</And>
195187
</Entry.Match>
196-
197-
<Entry.SortBy>
198-
<Readonly />
199-
<Name />
200-
</Entry.SortBy>
201188
</Entry>
202189

203190
<Entry DisplayName="Events">
204191
<Entry.Match>
205192
<Kind Is="Event" />
206193
</Entry.Match>
207-
208-
<Entry.SortBy>
209-
<Name />
210-
</Entry.SortBy>
211194
</Entry>
212195

213196
<Entry DisplayName="Constructors">
@@ -256,4 +239,4 @@
256239
<s:Boolean x:Key="/Default/UserDictionary/Words/=codervpn/@EntryIndexedValue">True</s:Boolean>
257240
<s:Boolean x:Key="/Default/UserDictionary/Words/=hkey/@EntryIndexedValue">True</s:Boolean>
258241
<s:Boolean x:Key="/Default/UserDictionary/Words/=replyable/@EntryIndexedValue">True</s:Boolean>
259-
<s:Boolean x:Key="/Default/UserDictionary/Words/=serdes/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
242+
<s:Boolean x:Key="/Default/UserDictionary/Words/=serdes/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

Vpn.Service/Manager.cs

+88-62
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.Runtime.InteropServices;
22
using Coder.Desktop.Vpn.Proto;
3+
using Coder.Desktop.Vpn.Utilities;
34
using CoderSdk;
45
using Microsoft.Extensions.Logging;
56
using Microsoft.Extensions.Options;
@@ -27,6 +28,10 @@ public class Manager : IManager
2728
private readonly IDownloader _downloader;
2829
private readonly ILogger<Manager> _logger;
2930
private readonly ITunnelSupervisor _tunnelSupervisor;
31+
32+
// TunnelSupervisor already has protections against concurrent operations,
33+
// but all the other stuff before starting the tunnel does not.
34+
private readonly RaiiSemaphoreSlim _tunnelOperationLock = new(1, 1);
3035
private SemVersion? _lastServerVersion;
3136
private StartRequest? _lastStartRequest;
3237

@@ -58,10 +63,18 @@ public async Task HandleClientRpcMessage(ReplyableRpcMessage<ServiceMessage, Cli
5863
{
5964
case ClientMessage.MsgOneofCase.Start:
6065
// TODO: these sub-methods should be managed by some Task list and cancelled/awaited on stop
61-
await HandleClientMessageStart(message, ct);
66+
var startResponse = await HandleClientMessageStart(message.Message, ct);
67+
await message.SendReply(new ServiceMessage
68+
{
69+
Start = startResponse,
70+
}, ct);
6271
break;
6372
case ClientMessage.MsgOneofCase.Stop:
64-
await HandleClientMessageStop(message, ct);
73+
var stopResponse = await HandleClientMessageStop(message.Message, ct);
74+
await message.SendReply(new ServiceMessage
75+
{
76+
Stop = stopResponse,
77+
}, ct);
6578
break;
6679
case ClientMessage.MsgOneofCase.None:
6780
default:
@@ -75,92 +88,105 @@ public async Task StopAsync(CancellationToken ct = default)
7588
await _tunnelSupervisor.StopAsync(ct);
7689
}
7790

78-
private async Task HandleClientMessageStart(ReplyableRpcMessage<ServiceMessage, ClientMessage> message,
91+
private async ValueTask<StartResponse> HandleClientMessageStart(ClientMessage message,
7992
CancellationToken ct)
8093
{
81-
try
94+
var opLock = await _tunnelOperationLock.LockAsync(TimeSpan.FromMilliseconds(500), ct);
95+
if (opLock == null)
8296
{
83-
var serverVersion =
84-
await CheckServerVersionAndCredentials(message.Message.Start.CoderUrl, message.Message.Start.ApiToken,
85-
ct);
86-
if (_tunnelSupervisor.IsRunning && _lastStartRequest != null &&
87-
_lastStartRequest.Equals(message.Message.Start) && _lastServerVersion == serverVersion)
97+
_logger.LogWarning("ClientMessage.Start: Tunnel operation lock timed out");
98+
return new StartResponse
8899
{
89-
// The client is requesting to start an identical tunnel while
90-
// we're already running it.
91-
_logger.LogInformation("ClientMessage.Start: Ignoring duplicate start request");
92-
await message.SendReply(new ServiceMessage
100+
Success = false,
101+
ErrorMessage = "Could not acquire tunnel operation lock, another operation is in progress",
102+
};
103+
}
104+
105+
using (opLock)
106+
{
107+
try
108+
{
109+
var serverVersion =
110+
await CheckServerVersionAndCredentials(message.Start.CoderUrl, message.Start.ApiToken,
111+
ct);
112+
if (_tunnelSupervisor.IsRunning && _lastStartRequest != null &&
113+
_lastStartRequest.Equals(message.Start) && _lastServerVersion == serverVersion)
93114
{
94-
Start = new StartResponse
115+
// The client is requesting to start an identical tunnel while
116+
// we're already running it.
117+
_logger.LogInformation("ClientMessage.Start: Ignoring duplicate start request");
118+
return new StartResponse
95119
{
96120
Success = true,
97-
},
98-
}, ct);
99-
return;
100-
}
101-
102-
_lastStartRequest = message.Message.Start;
103-
_lastServerVersion = serverVersion;
121+
};
122+
}
104123

105-
// Stop the tunnel if it's running so we don't have to worry about
106-
// permissions issues when replacing the binary.
107-
await _tunnelSupervisor.StopAsync(ct);
108-
await DownloadTunnelBinaryAsync(message.Message.Start.CoderUrl, serverVersion, ct);
109-
await _tunnelSupervisor.StartAsync(_config.TunnelBinaryPath, HandleTunnelRpcMessage, HandleTunnelRpcError,
110-
ct);
124+
_lastStartRequest = message.Start;
125+
_lastServerVersion = serverVersion;
111126

112-
var reply = await _tunnelSupervisor.SendRequestAwaitReply(new ManagerMessage
113-
{
114-
Start = message.Message.Start,
115-
}, ct);
116-
if (reply.MsgCase != TunnelMessage.MsgOneofCase.Start)
117-
throw new InvalidOperationException("Tunnel did not reply with a Start response");
127+
// TODO: each section of this operation needs a timeout
128+
// Stop the tunnel if it's running so we don't have to worry about
129+
// permissions issues when replacing the binary.
130+
await _tunnelSupervisor.StopAsync(ct);
131+
await DownloadTunnelBinaryAsync(message.Start.CoderUrl, serverVersion, ct);
132+
await _tunnelSupervisor.StartAsync(_config.TunnelBinaryPath, HandleTunnelRpcMessage,
133+
HandleTunnelRpcError,
134+
ct);
118135

119-
await message.SendReply(new ServiceMessage
120-
{
121-
Start = reply.Start,
122-
}, ct);
123-
}
124-
catch (Exception e)
125-
{
126-
_logger.LogWarning(e, "ClientMessage.Start: Failed to start VPN client");
127-
await message.SendReply(new ServiceMessage
136+
var reply = await _tunnelSupervisor.SendRequestAwaitReply(new ManagerMessage
137+
{
138+
Start = message.Start,
139+
}, ct);
140+
if (reply.MsgCase != TunnelMessage.MsgOneofCase.Start)
141+
throw new InvalidOperationException("Tunnel did not reply with a Start response");
142+
return reply.Start;
143+
}
144+
catch (Exception e)
128145
{
129-
Start = new StartResponse
146+
_logger.LogWarning(e, "ClientMessage.Start: Failed to start VPN client");
147+
return new StartResponse
130148
{
131149
Success = false,
132150
ErrorMessage = e.ToString(),
133-
},
134-
}, ct);
151+
};
152+
}
135153
}
136154
}
137155

138-
private async Task HandleClientMessageStop(ReplyableRpcMessage<ServiceMessage, ClientMessage> message,
156+
private async ValueTask<StopResponse> HandleClientMessageStop(ClientMessage message,
139157
CancellationToken ct)
140158
{
141-
try
159+
var opLock = await _tunnelOperationLock.LockAsync(TimeSpan.FromMilliseconds(500), ct);
160+
if (opLock == null)
142161
{
143-
// This will handle sending the Stop message to the tunnel for us.
144-
await _tunnelSupervisor.StopAsync(ct);
145-
await message.SendReply(new ServiceMessage
162+
_logger.LogWarning("ClientMessage.Stop: Tunnel operation lock timed out");
163+
return new StopResponse
146164
{
147-
Stop = new StopResponse
148-
{
149-
Success = true,
150-
},
151-
}, ct);
165+
Success = false,
166+
ErrorMessage = "Could not acquire tunnel operation lock, another operation is in progress",
167+
};
152168
}
153-
catch (Exception e)
169+
170+
using (opLock)
154171
{
155-
_logger.LogWarning(e, "ClientMessage.Stop: Failed to stop VPN client");
156-
await message.SendReply(new ServiceMessage
172+
try
157173
{
158-
Stop = new StopResponse
174+
// This will handle sending the Stop message to the tunnel for us.
175+
await _tunnelSupervisor.StopAsync(ct);
176+
return new StopResponse
177+
{
178+
Success = true,
179+
};
180+
}
181+
catch (Exception e)
182+
{
183+
_logger.LogWarning(e, "ClientMessage.Stop: Failed to stop VPN client");
184+
return new StopResponse
159185
{
160186
Success = false,
161-
ErrorMessage = e.Message,
162-
},
163-
}, ct);
187+
ErrorMessage = e.ToString(),
188+
};
189+
}
164190
}
165191
}
166192

Vpn.Service/ManagerRpcService.cs

+6
Original file line numberDiff line numberDiff line change
@@ -144,13 +144,19 @@ private async Task HandleRpcMessageAsync(ReplyableRpcMessage<ServiceMessage, Cli
144144

145145
public async Task BroadcastAsync(ServiceMessage message, CancellationToken ct)
146146
{
147+
// Looping over a ConcurrentDictionary is exception-safe, but any items
148+
// added or removed during the loop may or may not be included.
147149
foreach (var (clientId, client) in _activeClients)
148150
try
149151
{
150152
var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
151153
cts.CancelAfter(5 * 1000);
152154
await client.Speaker.SendMessage(message, cts.Token);
153155
}
156+
catch (ObjectDisposedException)
157+
{
158+
// The speaker was likely closed while we were iterating.
159+
}
154160
catch (Exception e)
155161
{
156162
_logger.LogWarning(e, "Failed to send message to client {ClientId}", clientId);

Vpn/Utilities/RaiiSemaphoreSlim.cs

+7
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@ public async ValueTask<IDisposable> LockAsync(CancellationToken ct = default)
2424
return new Lock(_semaphore);
2525
}
2626

27+
public async ValueTask<IDisposable?> LockAsync(TimeSpan timeout, CancellationToken ct = default)
28+
{
29+
if (await _semaphore.WaitAsync(timeout, ct)) return null;
30+
31+
return new Lock(_semaphore);
32+
}
33+
2734
private class Lock : IDisposable
2835
{
2936
private readonly SemaphoreSlim _semaphore1;

0 commit comments

Comments
 (0)