Implement automatic port discovery for multi-instance support

- Update BridgeServer to try multiple ports sequentially
  - Reads DefaultPort and MaxPortRange from ConfigurationService
  - Tries ports from DefaultPort to DefaultPort+MaxPortRange
  - Updates _port field to the port that successfully starts
  - Throws exception if no available ports found

- Make _port field mutable to store discovered port
- Add public Port property to expose the actual port being used
- Make startPort parameter optional (nullable), defaults to config value

- Add WriteRawResponseAsync helper method for future use

This allows multiple Visual Studio instances to run simultaneously,
each with their own Roslyn Bridge server on different ports.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-10-26 23:51:08 -04:00
parent 48d85e1906
commit 1ad0a98d4d

View File

@@ -18,12 +18,14 @@ namespace RoslynBridge.Server
private readonly AsyncPackage _package;
private readonly IRoslynQueryService _queryService;
private bool _isRunning;
private readonly int _port;
private int _port;
public BridgeServer(AsyncPackage package, int port = ServerConstants.DefaultPort)
public int Port => _port;
public BridgeServer(AsyncPackage package, int? startPort = null)
{
_package = package;
_port = port;
_port = startPort ?? ConfigurationService.Instance.DefaultPort;
_queryService = new RoslynQueryService(package);
}
@@ -38,12 +40,36 @@ namespace RoslynBridge.Server
{
await _queryService.InitializeAsync();
_listener = new HttpListener();
_listener.Prefixes.Add(ServerConstants.GetServerUrl(_port));
_listener.Start();
_isRunning = true;
// Try to find an available port
int maxPort = _port + ConfigurationService.Instance.MaxPortRange;
bool started = false;
for (int port = _port; port < maxPort; port++)
{
try
{
_listener = new HttpListener();
_listener.Prefixes.Add(ServerConstants.GetServerUrl(port));
_listener.Start();
_port = port; // Update to the port that worked
started = true;
System.Diagnostics.Debug.WriteLine($"Roslyn Bridge HTTP server started on port {_port}");
break;
}
catch (HttpListenerException)
{
// Port is in use, try next one
_listener?.Close();
_listener = null;
}
}
if (!started)
{
throw new Exception($"Could not find available port in range {_port}-{maxPort}");
}
_isRunning = true;
// Start listening for requests in the background
#pragma warning disable CS4014
@@ -95,6 +121,8 @@ namespace RoslynBridge.Server
return;
}
var path = context.Request.Url?.AbsolutePath ?? "/";
if (context.Request.HttpMethod != "POST")
{
await RespondWithError(response, 405, "Only POST requests are supported");
@@ -110,7 +138,7 @@ namespace RoslynBridge.Server
}
// Route request
var queryResponse = await RouteRequestAsync(context.Request.Url?.AbsolutePath ?? "/", request);
var queryResponse = await RouteRequestAsync(path, request);
response.StatusCode = queryResponse.Success ? 200 : 400;
await WriteResponseAsync(response, queryResponse);
}
@@ -196,6 +224,22 @@ namespace RoslynBridge.Server
}
}
private static async Task WriteRawResponseAsync(HttpListenerResponse response, string contentType, string content)
{
try
{
response.ContentType = contentType;
var buffer = Encoding.UTF8.GetBytes(content);
response.ContentLength64 = buffer.Length;
await response.OutputStream.WriteAsync(buffer, 0, buffer.Length);
response.Close();
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Error writing raw response: {ex}");
}
}
public void Dispose()
{
_isRunning = false;