-
Notifications
You must be signed in to change notification settings - Fork 29.9k
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
Code CLI - Add --state filter to --list-extensions #93967
Conversation
tagging @joaomoreno, @bpasero, @sandy081 to sign off on the change |
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 talked with @joaomoreno about this earlier today. We are using SQLite as a DB for storing state. Normally things should be OK accessing the DB from CLI if no other process is running. However, in case VSCode is already running, we now might have 2 processes connecting to the same DB on disk. We do not run SQLite in a mode that locks the DB for single-process use, but I fear this may have a chance to cause corruption if not carefully understood how SQLite behaves in this case.
Couple of thoughts:
- can we provide an implementation of SQLite state access that is guaranteed to not brick an instance that is accessed meanwhile from another process (e.g. with some options)
- could the CLI process first check if it is the first one or not and then delegate the query to the first instance?
I would probably go with the first option and implement a readonly version of SQLiteStorageDatabase
. Still, we need to understand how SQLite behaves.
Sorry, #19083 should have not been marked as help wanted so easily.
I would go for second option
Because there are other use case to update storage from CLI - #63903 Check the same suggestion from @joaomoreno here - #63903 (comment) |
no worries about the issue being marked as "help wanted", and thanks for the feedback. Implementing a "read-only" option for Is there any particular reason why we open a connection to the SQLLite database and keep it open for the lifetime of the process? Is there such a big overhead for establishing the connection? I am asking as another option which would save us from the interprocess communication would be for each process to open the connection/execute query/close the connection. I am also reviewing the SQLLite documentation for concurrency/locking. |
This would definitely be way too far, just to tackle this issue. I would even argue read/write access is taking it too far. Yes, it would be great to have, but no current feature request is too pressing to force this amount of change. I would investigate on having concurrent read-only access to the db, which after a few web searches, definitely seems like a feature SQLite supports. This would not only fix this feature, unblock others and would keep things simple. |
Agreed that implementing read & write is too much for this issue. But there are other issues which need write access too.
If we started thinking and investing on this, is not it recommended to think about the complete solution? I do not understand if the complete solution is technically feasible or not. Also this will make storage service inconsistent (there by other services like extension enablements too) across the processes we have. |
As the issue in question is 3 years old, I do not believe that there is an incentive for a tactical change in order to implement the requested feature. In light of that, I would like to advocate for the right solution which will unblock all request that are currently blocked due to the fact that the CLI does not have storage access. Given that there is already a pattern for delegating storage queries, I will go ahead and explore that. At a later date we can explore keeping the connection open just for the time of executing the query since that seems to be the best solution long term. |
I had a quick chat with @joaomoreno earlier today and we have agreed that for the scope of this particular issue we will go with adding the capability to create a read-only connection to the SQLite database. This works as expected however there is one edge case where this will not work. If the global SQLite database does not exist and the CLI is run that it will fail as we cannot run the script to create the database when we are using a read-only connection. If you feel that we should address this edge case, we should discuss further. Thanks! |
@lszomoru are you able to resolve the conflicts? sorry, I pushed a lot of refactorings around environment |
@bpasero, sure. Let me resolve the conflicts and update the pull request. |
@bpasero I have resolved the conflicts and updated the pull request. |
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.
A bit of feedback, I focused on the DB pieces 👍
@@ -20,6 +20,7 @@ interface IDatabaseConnection { | |||
} | |||
|
|||
export interface ISQLiteStorageDatabaseOptions { | |||
readonly intent?: number; |
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 would prefer if we simply had the option to open the DB as readonly
and not expose the underlying SQLite modes as option. I think in the end we just have 1 client that wants to open the DB readonly so I would optimise for that scenario.
@@ -34,6 +35,7 @@ export class SQLiteStorageDatabase implements IStorageDatabase { | |||
|
|||
get onDidChangeItemsExternal(): Event<IStorageItemsChangeEvent> { return Event.None; } // since we are the only client, there can be no external changes | |||
|
|||
private static readonly DEFAULT_MODE = OPEN_READWRITE | OPEN_CREATE; // default mode when connecting to the DB |
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 would prefer if we do not use any default mode but leave that up to the SQLite implementation. We only set the mode to readonly when instructed, but otherwise leave it as is.
@@ -297,11 +299,16 @@ export class SQLiteStorageDatabase implements IStorageDatabase { | |||
return new Promise((resolve, reject) => { | |||
import('vscode-sqlite3').then(sqlite3 => { | |||
const connection: IDatabaseConnection = { | |||
db: new (this.logger.isTracing ? sqlite3.verbose().Database : sqlite3.Database)(path, error => { | |||
db: new (this.logger.isTracing ? sqlite3.verbose().Database : sqlite3.Database)(path, this.options.intent ?? SQLiteStorageDatabase.DEFAULT_MODE, 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.
As mentioned above, pass undefined
instead of default mode to leave the default up to the underlying library.
if (error) { | ||
return connection.db ? connection.db.close(() => reject(error)) : reject(error); | ||
} | ||
|
||
// Return the connection when using OPEN_READONLY | ||
if (this.options && this.options.intent && (this.options.intent & OPEN_READONLY) === OPEN_READONLY) { | ||
resolve(connection); |
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.
You are missing a return here and as such resolve the promise twice.
} | ||
|
||
// Get the list of disabled extensions | ||
const disabledExtensions = await this.globalExtensionEnablementService.getDisabledExtensions(); |
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.
await
not needed here
@@ -293,6 +325,7 @@ export class Main { | |||
} | |||
|
|||
const eventPrefix = 'monacoworkbench'; | |||
const globalStorageName = 'state.vscdb'; |
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.
Extract to a place where it can be reused?
const storageService = new NativeStorageService(storageDatabase, logService, environmentService); | ||
await storageService.initialize(); | ||
services.set(IStorageService, storageService); | ||
disposables.add(toDisposable(() => storageService.flush())); |
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.
This is not needed as flush
is only used for writing
@@ -306,6 +339,13 @@ export async function main(argv: ParsedArgs): Promise<void> { | |||
await Promise.all<void | undefined>([environmentService.appSettingsHome.fsPath, environmentService.extensionsPath] | |||
.map((path): undefined | Promise<void> => path ? mkdirp(path) : undefined)); | |||
|
|||
// Storage |
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.
This deserves a bit more documentation why the DB can only be opened readonly.
Discussed with @joaomoreno and @lszomoru and I do not think this is worth adding this feature for the amount of work that is done and the debt it might create. Also the solution might not be complete.. we also have workspace enabled extensions. So showing the global enablement might not be complete enough So, I would recommend to abandon this. Sorry @lszomoru for tagging this as |
This PR fixes #19083
With this change I have added the
--state
filter to the--list-extensions
command. The filter only uses global enablement state (extension is globally enabled/disabled). In order to use the global enablement service I had to connect to the SQLLite database to get the list of disabled extensions. Not sure if that is the right approach here so any guidance would be highly appreciated. Thanks!Usage:
code --list-extensions --state enabled
- will list all extensions that are enabledcode --list-extensions --state disabled
- will list all extensions that are disabled