Skip to content

Commit 3f473b0

Browse files
committed
implement using free port for socket connections
1 parent 0ec9056 commit 3f473b0

File tree

6 files changed

+95
-29
lines changed

6 files changed

+95
-29
lines changed

CHANGELOG.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,18 @@ All notable changes to the "robotcode" extension will be documented in this file
44

55
## [Unreleased]
66

7-
## 0.4.0
8-
97
### added
10-
- Big speed improvements
11-
- introduce some classes for threadsafe asyncio
12-
- Implement pipe/socket transport for language server
13-
- default is now pipe transport
14-
- Improve starting, stopping, restarting language server client, if ie. python environment changed, arguments changed or server crashed
8+
- for socket connections now a free port is used
9+
10+
## 0.4.0
1511

1612
### added
1713

14+
- Big speed improvements
15+
- introduce some classes for threadsafe asyncio
16+
- Implement pipe/socket transport for language server
17+
- default is now pipe transport
18+
- Improve starting, stopping, restarting language server client, if ie. python environment changed, arguments changed or server crashed
1819
- some refactoring to speedup loading and parsing documents
1920
- semantic tokens now highlight
2021
- builtin keywords

mypy.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
[mypy]
55
python_version = 3.8
66
warn_redundant_casts = True
7-
warn_unused_ignores = True
7+
warn_unused_ignores = False
88
strict = True
99

1010
# Needed because of bug in MyPy

poetry.lock

Lines changed: 13 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

robotcode/jsonrpc2/server.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,9 +164,10 @@ def start_pipe(self, pipe_name: Optional[str]) -> None:
164164
self.mode = JsonRpcServerMode.PIPE
165165

166166
try:
167-
if sys.platform == "win32" and getattr(self.loop, "create_pipe_connection", None):
167+
if sys.platform == "win32" and hasattr(self.loop, "create_pipe_connection"):
168+
# this is a proactor event loop and we can use the not documented method create_pipe_connection
168169
self.loop.run_until_complete(
169-
self.loop.create_pipe_connection(self.create_protocol, pipe_name) # type: ignore
170+
self.loop.create_pipe_connection(self.create_protocol, pipe_name), # type: ignore
170171
)
171172
else:
172173
self.loop.run_until_complete(self.loop.create_unix_connection(self.create_protocol, pipe_name))

vscode-client/languageclientsmanger.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
import { sleep, Mutex } from "./utils";
2020
import { CONFIG_SECTION } from "./config";
2121
import { PythonManager } from "./pythonmanger";
22+
import { getAvailablePort } from "./net_utils";
2223

2324
const LANGUAGE_SERVER_DEFAULT_TCP_PORT = 6610;
2425
const LANGUAGE_SERVER_DEFAULT_HOST = "127.0.0.1";
@@ -141,7 +142,7 @@ export class LanguageClientsManager {
141142
return serverOptions;
142143
}
143144

144-
private getServerOptions(folder: vscode.WorkspaceFolder, mode: string) {
145+
private async getServerOptions(folder: vscode.WorkspaceFolder, mode: string): Promise<ServerOptions> {
145146
const config = vscode.workspace.getConfiguration(CONFIG_SECTION, folder);
146147

147148
const pythonCommand = this.pythonManager.getPythonCommand(folder);
@@ -168,26 +169,35 @@ export class LanguageClientsManager {
168169

169170
const transport = { stdio: TransportKind.stdio, pipe: TransportKind.pipe, socket: TransportKind.socket }[mode];
170171

171-
const serverOptions: ServerOptions = {
172+
const getPort = async () => {
173+
return getAvailablePort(["127.0.0.1"]);
174+
};
175+
176+
return {
172177
run: {
173178
command: pythonCommand,
174179
args: [...args, ...serverArgs],
175180
options: {
176181
cwd: folder.uri.fsPath,
177182
},
178183

179-
transport: transport !== TransportKind.socket ? transport : { kind: TransportKind.socket, port: 6610 },
184+
transport:
185+
transport !== TransportKind.socket
186+
? transport
187+
: { kind: TransportKind.socket, port: (await getPort()) ?? -1 },
180188
},
181189
debug: {
182190
command: pythonCommand,
183191
args: [...args, ...debug_args, ...serverArgs],
184192
options: {
185193
cwd: folder.uri.fsPath,
186194
},
187-
transport: transport !== TransportKind.socket ? transport : { kind: TransportKind.socket, port: 6610 },
195+
transport:
196+
transport !== TransportKind.socket
197+
? transport
198+
: { kind: TransportKind.socket, port: (await getPort()) ?? -1 },
188199
},
189200
};
190-
return serverOptions;
191201
}
192202

193203
public async getLanguageClientForDocument(document: vscode.TextDocument): Promise<LanguageClient | undefined> {
@@ -215,7 +225,7 @@ export class LanguageClientsManager {
215225
const mode = config.get<string>("languageServer.mode", "stdio");
216226

217227
const serverOptions: ServerOptions =
218-
mode === "tcp" ? this.getServerOptionsTCP(workspaceFolder) : this.getServerOptions(workspaceFolder, mode);
228+
mode === "tcp" ? this.getServerOptionsTCP(workspaceFolder) : await this.getServerOptions(workspaceFolder, mode);
219229

220230
const name = `RobotCode Language Server mode=${mode} for folder "${workspaceFolder.name}"`;
221231

vscode-client/net_utils.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import * as os from "os";
2+
import * as net from "net";
3+
4+
export function getLocalHosts(): Set<string | undefined> {
5+
const interfaces = os.networkInterfaces();
6+
7+
// Add undefined value for createServer function to use default host,
8+
// and default IPv4 host in case createServer defaults to IPv6.
9+
const results = new Set([undefined, "0.0.0.0"]);
10+
11+
for (const _interface of Object.values(interfaces)) {
12+
if (_interface) {
13+
for (const config of _interface) {
14+
results.add(config.address);
15+
}
16+
}
17+
}
18+
19+
return results;
20+
}
21+
22+
function checkAvailablePort(port?: number, host?: string): Promise<number> {
23+
return new Promise((resolve, reject) => {
24+
const server = net.createServer();
25+
server.unref();
26+
server.on("error", reject);
27+
28+
server.listen(port, host, () => {
29+
const p = server.address() as net.AddressInfo;
30+
31+
server.close(() => {
32+
resolve(p.port);
33+
});
34+
});
35+
});
36+
}
37+
38+
export function isErrnoException(object: unknown): object is NodeJS.ErrnoException {
39+
return Object.prototype.hasOwnProperty.call(object, "code") || Object.prototype.hasOwnProperty.call(object, "errno");
40+
}
41+
42+
export async function getAvailablePort(hosts: string[], port?: number): Promise<number | undefined> {
43+
for (const host of hosts) {
44+
try {
45+
port = await checkAvailablePort(port, host);
46+
} catch (error) {
47+
if (!isErrnoException(error) || error.code === undefined || !["EADDRNOTAVAIL", "EINVAL"].includes(error.code)) {
48+
throw error;
49+
}
50+
}
51+
}
52+
53+
return port;
54+
}

0 commit comments

Comments
 (0)