-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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
fix: revise vscode extension directories #13178
Conversation
84ecd86
to
2850ea3
Compare
I have a couple of general comments:
|
Thanks, @tsmaeder, for the quick response!
I don't think there is a consistent user-specific temp dir in Linux. According to
The shared /tmp is often also on a different filesystem/mountpoint than the user home. In several scenarios, the user home will be better secured (e.g., encrypted with user-specific credentials, mounted after kerberos auth, ...) than the shared temp dir location. In our use case, the download directory is meant to be user-specific (that's why I encoded the username into it), but sure, the default permissions will most likely be world-readable (755) if we do not change them. So I see two options:
I think if we want to treat extensions as potential secrets, then the second approach is more sensible, considering the user home to be a much safer place than /tmp. Of course, this not being a temp dir that the OS handles, we have to clean it up ourselves.
ACK.
That makes sense, especially if we decide to use a location within the user's home. We have to be careful here with regard to robustness in case of multiple Theia processes (run by the same user). So, cleaning up after successful unpacking is easy enough. What do we do if the application gets killed hard by the user or the OOM killer and our cleanup is not finished? Should we try to clean at the application start? Then we have to make sure not to remove stuff that another running Theia process is currently using.
Makes sense; this is backward-compatible and avoids unwanted interactions between our unpacking/deployment and the user. I'll revise the code accordingly. |
I revised my PR to use
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mostly comments, but I think we should fix the "move" logic.
if (!this._tmpDirUri) { | ||
const configDir: URI = new URI(await this.environments.getConfigDirUri()); | ||
const processId: string = process.pid.toString(); | ||
this._tmpDirUri = configDir.resolve('tmp').resolve(`${prefix ?? 'tmp-'}-${processId}`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would https://nodejs.org/api/fs.html#fs_fs_mkdtemp_prefix_options_callback do the trick here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll try.
console.log(`[${pluginResolverContext.getOriginId()}]: trying to decompress "${localPath}" into "${tempUnpackDir}"...`); | ||
if (await fs.pathExists(tempUnpackDir)) { | ||
console.log(`[${pluginResolverContext.getOriginId()}]: Removing existing target dir "${tempUnpackDir}"...`); | ||
fs.removeSync(tempUnpackDir); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this sync?
} catch (e) { | ||
console.warn(`Problem copying plugin at ${localPath}:`, e); | ||
await fs.ensureDir(tempUnpackDir); | ||
if (!await decompressExtension(localPath, tempUnpackDir)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The path of the vsix and the destination dir are even more important in the case of failure.
} | ||
} catch (e) { | ||
console.error(`[${pluginResolverContext.getOriginId()}]: error while decompressing`, e); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again: log the relevant paths.
return; | ||
} | ||
console.log(`[${pluginResolverContext.getOriginId()}]: moving to extension dir "${extensionDeploymentDir}"...`); | ||
await fs.move(tempUnpackDir, extensionDeploymentDir, { overwrite: true }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we should use fs.move
here: we're trying to do this replacement atomically (either completely succeed or fail). The safe way to do this is by using rename
. But that only works when the source and destination are on the same device. So IMO, we should to this:
- Unpack to a tmp-subdir inside
extensionDeploymentDir
. - If the destination folder already exists, rename it to something else
- If that works, rename the tmp folder to the destination folder, otherwise rename the original folder back and fail, cleaning up any new files.
- Remove the pre-existing folder, if it exists
packages/plugin-ext-vscode/src/node/plugin-vscode-deployer-participant.ts
Show resolved
Hide resolved
} | ||
return true; | ||
} catch (error) { | ||
console.error(`Failed to decompress ${sourcePath} to ${destPath}: ${error}`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are we not just letting the exception propagate?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, stupid mistake.
|
||
console.log(`[${id}]: decompressed`); | ||
} catch (error) { | ||
console.error(`[${id}]: error while decompressing`, error); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not let the error propagate? How is the invoker going to tell sometihing failed?
const extensionDeploymentDirPath = FileUri.fsPath(deployedPluginsDirUri.resolve(pluginId)); | ||
return extensionDeploymentDirPath; | ||
} catch (error) { | ||
console.error('Error resolving extension directory:', error); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the error is unhandled, we'll get a stack trace. If it isn't shouldn't the handler print the error?
Some notes from testing:
I can kind of live with the second behavior, but Nr. 1 is a must-fix, IMO. |
9cef267
to
87ee02b
Compare
It should be fixed now. I reverted back to using the filename of a dropped vsix file as extension-id instead of always unpacking and parsing package.json. Would be nicer to have the correct extension id, but we can deal with this in a future PR, I'd be happy to look into this.
Treating them as "built-in" would be in line with the user being the only one who manages them. This would probably also fit well with having a lookup mechanism for extensions because then we could track the source of deployed extensions (e.g., downloaded, dropped-in, installed-from-file) and treat them accordingly.
Nr. 1 should be done now. Nr. 2 is definitely a good candidate for a follow-up ticket. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code looks good to me now, but I still get a 20s pause with a blank window the first time I drop the draw.io vsix into the .theia/extensions
folder.
@xai we have a conflict. |
This patch eliminates the use of the temporary directories vscode-unpacked and vscode-copied for installing and deploying vscode extensions. The file-handler and directory handler for vscode extensions have been adjusted accordingly: * A temporary directory is now only used for downloading extensions from the registry. This temporary directory is now user-specific and resides within the configdir (i.e., per default in the user's home: /.theia/tmp/) to avoid clashes and permission issues on multi-user operating systems that share temporary directories, such as Linux or BSDs. Having this temporary directory in a location that is configurable by the user also seems the more sensible approach when extensions are considered confidential data. * $configDir/deployedPlugins replaces our volatile /tmp/vscode-copied deployment directory. Having a more permanent way of handling installed extensions should improve startup time and reduce issues with multiple instances running in parallel. * The local file resolver unpacks the vsix file from the temp dir into $configDir/deployedPlugins/<extension-id>. * The simplified directory handler loads unpacked extensions directly from $configDir/deployedPlugins/<extension-id>. * We use $configDir/extensions as a location for the user to drop vsix files that will be installed to the deployment location automatically on startup. We do not manage or remove files within $configDir/extensions. Overall, this should improve the stability on systems with shared temp dir locations and reduce the startup of the first application start after a reboot. Contributed on behalf of STMicroelectronics Signed-off-by: Olaf Lessenich <[email protected]>
To avoid delaying the application startup while loading plugins, the initialize function of the PluginDeployerContribution class explicitly returns a resolved Promise. Signed-off-by: Olaf Lessenich <[email protected]> Co-authored-by: Thomas Mäder <[email protected]>
Contributed on behalf of STMicroelectronics Signed-off-by: Olaf Lessenich <[email protected]>
Resolved. |
What it does
This patch eliminates the use of the temporary directories
vscode-unpacked
andvscode-copied
for installing and deploying vscode extensions. The file-handler and directory handler for vscode extensions have been adjusted accordingly:$configDir/deployedPlugins
replaces our volatile/tmp/vscode-copied
deployment directory. Having a more permanent way of handling installed extensions should improve startup time and reduce issues with multiple instances running in parallel.$configDir/deployedPlugins/<extension-id>
.$configDir/deployedPlugins/<extension-id>
.$configDir/extensions
as a location for the user to drop vsix files that will be installed to the deployment location automatically on startup. We do not manage or remove files within$configDir/extensions
.Overall, this should improve the stability on systems with shared temp dir locations and reduce the startup of the first application start after a reboot.
Fixes #12757
Contributed on behalf of STMicroelectronics
How to test
Installing from registry
Installing a local vsix extensions
Install from VSIX
in the triple-dot menu of the extensions viewStore a vsix file in the drop-in location
Follow-ups
Review checklist
Reminder for reviewers