Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Connection::sendRequest never fullfills #1356

Closed
0xmemorygrinder opened this issue Nov 13, 2023 · 3 comments
Closed

Connection::sendRequest never fullfills #1356

0xmemorygrinder opened this issue Nov 13, 2023 · 3 comments
Labels
info-needed Issue requires more information from poster

Comments

@0xmemorygrinder
Copy link

Hello,

I am currently working on an extension to add full support of solidity language on VS Code.
My extension is composed of a typescript client and a typescript facade LSP server which forwards all messages to a rust-wasm core where the logic is handled.

I have some logic that requires to send a request from my rust core to another LSP server. To send a request, my typescript LSP server have a function which uses connection.sendRequest to send the request and forward the response back to the rust core.

The problem is that the sendRequest's promise never fullfills. I first started with a standard LSP message and I then implemented a handler on the client side for a custom and the handler is well triggered.

The problem seems to be between the handler's response and the sendRequest's return.

Here is the code of my client :

export function activate(context: ExtensionContext) {
	// The server is implemented in node
	const serverModule = context.asAbsolutePath(
		path.join('dist', 'server.js')
	);

	// If the extension is launched in debug mode then the debug server options are used
	// Otherwise the run options are used
	const serverOptions: ServerOptions = {
		run: { module: serverModule, transport: TransportKind.ipc },
		debug: {
			module: serverModule,
			transport: TransportKind.ipc,
		}
	};

	// Options to control the language client
	const clientOptions: LanguageClientOptions = {
		documentSelector: [{ scheme: 'file', language: 'solidity' }],
		synchronize: {
			fileEvents: workspace.createFileSystemWatcher('**/.clientrc')
		}
	};

	// Create the language client and start the client.
	client = new LanguageClient(
		'osmium-solidity',
		'Osmium Solidity Language Server',
		serverOptions,
		clientOptions
	);

	const getWorkspacePathListener = client.onRequest('osmium/getWorkspacePath', (workspaceIdx: string | undefined) => {
        console.log('getWorkspacePath ' + workspaceIdx); // is logged
		const workspaceFolders = workspace.workspaceFolders;
		let path = "";
		if (workspaceFolders) {
			if (workspaceIdx) {
				const idx = parseInt(workspaceIdx);
				if (idx < workspaceFolders.length)
					path = workspaceFolders[idx].uri.fsPath
			} else 
				path = workspaceFolders[0].uri.fsPath
		}
		const ret = {
			path
		}
		console.log(`workspace path`, ret); // is logged too
		return ret
    })
    context.subscriptions.push(getWorkspacePathListener)

	// Start the client. This will also launch the server
	client.start();
}

Here is the content of my typescript LSP server :

/* --------------------------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation. All rights reserved.
 * Licensed under the MIT License. See License.txt in the project root for license information.
 * ------------------------------------------------------------------------------------------ */
import {
	createConnection,
	TextDocuments,
	Diagnostic,
	DiagnosticSeverity,
	ProposedFeatures,
	InitializeParams,
	DidChangeConfigurationNotification,
	CompletionItem,
	CompletionItemKind,
	TextDocumentPositionParams,
	TextDocumentSyncKind,
	InitializeResult
} from 'vscode-languageserver/node';
import {create_extension} from '../dist';

// Create a connection for the server, using Node's IPC as a transport.
// Also include all preview / proposed LSP features.
const connection = createConnection(ProposedFeatures.all);


	

const sendRequest = async (method: string, params: any) => {
	connection.console.log(`sendRequeest: ${method} ${JSON.stringify(params)}`); // is logged
	try {
		let res = await connection.sendRequest(method, params);
		connection.console.log(`sendRequest: ${method} ${JSON.stringify(params)} => ${JSON.stringify(res)}`); // never logged
		return res;
	} catch (error) {
		connection.console.error('Error with sendRequest:', error); // never log too
	}	
	
};

const sendNotification = (method: string, params: any) => {
	connection.sendNotification(method, params);
};

const extension = create_extension(sendRequest, sendNotification);

connection.onInitialize((params: InitializeParams) => {
	return extension.onRequest('initialize', params);
});

connection.onRequest((method: string, params: any) => {
	connection.console.log(`onRequest: ${method}`);
	
	return extension.onRequest(method, params);
});

connection.onNotification((method: string, params: any) => {
	extension.onNotification(method, params);
});

connection.listen();
@0xmemorygrinder 0xmemorygrinder changed the title sendRequest never fullfills Connection::sendRequest never fullfills Nov 13, 2023
@dbaeumer
Copy link
Member

I tested this in the testbed and requests send from the server to the client are properly resolved. The code is in the dbaeumer/underground-vole-plum branch in case you want to have a look.

Can you try to test this from the TS server part directly to the client. If that works for you as well (as it does for me) it is more likely with the bridge code.

@dbaeumer dbaeumer added the info-needed Issue requires more information from poster label Nov 14, 2023
@0xmemorygrinder
Copy link
Author

Hello,

I tested to perform the sendRequest inside an handler defined in typescript. Its works correctly.
The problem however is that my bridge code seems to work too.

The rust part of my codebase calls the typescript callback to send a request. I get the first log but never the resolve of catch.
Here is the callback from the code above :

const sendRequest = async (method: string, params: any) => {
	connection.console.log(`sendRequeest: ${method} ${JSON.stringify(params)}`); // is logged
	try {
		let res = await connection.sendRequest(method, params); // my handler is executed during this call
		connection.console.log(`sendRequest: ${method} ${JSON.stringify(params)} => ${JSON.stringify(res)}`); // never logged
		return res;
	} catch (error) {
		connection.console.error('Error with sendRequest:', error); // never log too
	}	
	
};

I also tried with a "promise" version to see if the async was the problem but I get the same behavior :

const sendRequest = (method: string, params: any): Promise<any> {
	return new Promise((resolve, reject) => {
		connection.console.log(`sendRequeest: ${method} ${JSON.stringify(params)}`); // is logged
		connection.sendRequest(method, params) // my handler is executed during this call
		.then((res: any) => {
			connection.console.log(`sendRequest: ${method} ${JSON.stringify(params)} => ${JSON.stringify(res)}`); // never logged
			resolve(res)
		})
		.catch((err) => {
			connection.console.error('Error with sendRequest:', err); // never logged
			reject(err);
		});
	});
};

Is there any context specific behavior with the Connection class ?

@dbaeumer
Copy link
Member

I assume that you compile your Rust code to WASM. If this is the case WASM is fully sync and as soon as you execute WASM code the corresponding worker never falls back to the NodeJS event loop until the WASM code is finished (even not if you call code in the JS host). Hence the promise can never be fullfilled.

To get something like this working you either need to asyncify the WASM code, wait for async support in WASM or use another worker and SharedArrayBuffers with Atomics to do the promise handling in yet another worker. You might want to look at code here to see how this can be done: https://github.com/microsoft/vscode-wasi.git

I will close the issue since there is nothing in the LSP library I can do about this. The above repository contains an example on how to implement a LSP server in Rust, compile it to WASM and then use without any TS clue code. See https://github.com/microsoft/vscode-wasi/blob/dbaeumer/mere-meerkat-green/testbeds/lsp-rust/client/src/extension.ts#L37

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
info-needed Issue requires more information from poster
Projects
None yet
Development

No branches or pull requests

2 participants