mirror of
https://github.com/Microsoft/vscode
synced 2024-08-25 12:06:27 +00:00
code cache - cleanup in prep for chrome based code loading
This commit is contained in:
parent
e4373f7591
commit
2cb7b42d98
6
src/bootstrap-amd.js
vendored
6
src/bootstrap-amd.js
vendored
|
@ -43,11 +43,11 @@ exports.load = function (entrypoint, onLoad, onError) {
|
|||
return;
|
||||
}
|
||||
|
||||
// cached data config
|
||||
if (process.env['VSCODE_NODE_CACHED_DATA_DIR']) {
|
||||
// code cache config
|
||||
if (process.env['VSCODE_CODE_CACHE_PATH']) {
|
||||
loader.config({
|
||||
nodeCachedData: {
|
||||
path: process.env['VSCODE_NODE_CACHED_DATA_DIR'],
|
||||
path: process.env['VSCODE_CODE_CACHE_PATH'],
|
||||
seed: entrypoint
|
||||
}
|
||||
});
|
||||
|
|
4
src/bootstrap-window.js
vendored
4
src/bootstrap-window.js
vendored
|
@ -148,9 +148,9 @@
|
|||
}
|
||||
|
||||
// Cached data config
|
||||
if (configuration.nodeCachedDataDir) {
|
||||
if (configuration.codeCachePath) {
|
||||
loaderConfig.nodeCachedData = {
|
||||
path: configuration.nodeCachedDataDir,
|
||||
path: configuration.codeCachePath,
|
||||
seed: modulePaths.join('')
|
||||
};
|
||||
}
|
||||
|
|
86
src/main.js
86
src/main.js
|
@ -40,6 +40,9 @@ const args = parseCLIArgs();
|
|||
const userDataPath = getUserDataPath(args);
|
||||
app.setPath('userData', userDataPath);
|
||||
|
||||
// Resolve code cache path
|
||||
const codeCachePath = getCodeCachePath();
|
||||
|
||||
// Configure static command line arguments
|
||||
const argvConfig = configureCommandlineSwitchesSync(args);
|
||||
|
||||
|
@ -71,9 +74,6 @@ protocol.registerSchemesAsPrivileged([
|
|||
// Global app listeners
|
||||
registerListeners();
|
||||
|
||||
// Cached data
|
||||
const nodeCachedDataDir = getNodeCachedDir();
|
||||
|
||||
/**
|
||||
* Support user defined locale: load it early before app('ready')
|
||||
* to have more things running in parallel.
|
||||
|
@ -108,14 +108,14 @@ app.once('ready', function () {
|
|||
/**
|
||||
* Main startup routine
|
||||
*
|
||||
* @param {string | undefined} cachedDataDir
|
||||
* @param {string | undefined} codeCachePath
|
||||
* @param {NLSConfiguration} nlsConfig
|
||||
*/
|
||||
function startup(cachedDataDir, nlsConfig) {
|
||||
function startup(codeCachePath, nlsConfig) {
|
||||
nlsConfig._languagePackSupport = true;
|
||||
|
||||
process.env['VSCODE_NLS_CONFIG'] = JSON.stringify(nlsConfig);
|
||||
process.env['VSCODE_NODE_CACHED_DATA_DIR'] = cachedDataDir || '';
|
||||
process.env['VSCODE_CODE_CACHE_PATH'] = codeCachePath || '';
|
||||
|
||||
// Load main in AMD
|
||||
perf.mark('code/willLoadMainBundle');
|
||||
|
@ -128,9 +128,9 @@ async function onReady() {
|
|||
perf.mark('code/mainAppReady');
|
||||
|
||||
try {
|
||||
const [cachedDataDir, nlsConfig] = await Promise.all([nodeCachedDataDir.ensureExists(), resolveNlsConfiguration()]);
|
||||
const [, nlsConfig] = await Promise.all([mkdirpIgnoreError(codeCachePath), resolveNlsConfiguration()]);
|
||||
|
||||
startup(cachedDataDir, nlsConfig);
|
||||
startup(codeCachePath, nlsConfig);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
@ -499,46 +499,28 @@ function registerListeners() {
|
|||
}
|
||||
|
||||
/**
|
||||
* @returns {{ ensureExists: () => Promise<string | undefined> }}
|
||||
* @returns {string | undefined} the location to use for the code cache
|
||||
* or `undefined` if disabled.
|
||||
*/
|
||||
function getNodeCachedDir() {
|
||||
return new class {
|
||||
function getCodeCachePath() {
|
||||
|
||||
constructor() {
|
||||
this.value = this.compute();
|
||||
}
|
||||
// explicitly disabled via CLI args
|
||||
if (process.argv.indexOf('--no-cached-data') > 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async ensureExists() {
|
||||
if (typeof this.value === 'string') {
|
||||
try {
|
||||
await mkdirp(this.value);
|
||||
// running out of sources
|
||||
if (process.env['VSCODE_DEV']) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this.value;
|
||||
} catch (error) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
// require commit id
|
||||
const commit = product.commit;
|
||||
if (!commit) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
compute() {
|
||||
if (process.argv.indexOf('--no-cached-data') > 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// IEnvironmentService.isBuilt
|
||||
if (process.env['VSCODE_DEV']) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// find commit id
|
||||
const commit = product.commit;
|
||||
if (!commit) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return path.join(userDataPath, 'CachedData', commit);
|
||||
}
|
||||
};
|
||||
return path.join(userDataPath, 'CachedData', commit);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -553,6 +535,24 @@ function mkdirp(dir) {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string | undefined} dir
|
||||
* @returns {Promise<string | undefined>}
|
||||
*/
|
||||
async function mkdirpIgnoreError(dir) {
|
||||
if (typeof dir === 'string') {
|
||||
try {
|
||||
await mkdirp(dir);
|
||||
|
||||
return dir;
|
||||
} catch (error) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
//#region NLS Support
|
||||
|
||||
/**
|
||||
|
|
|
@ -46,7 +46,7 @@ export interface ISandboxConfiguration {
|
|||
zoomLevel?: number;
|
||||
|
||||
/**
|
||||
* @deprecated to be removed soon
|
||||
* Location of V8 code cache.
|
||||
*/
|
||||
nodeCachedDataDir?: string;
|
||||
codeCachePath?: string;
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ suite('Processes', () => {
|
|||
VSCODE_NLS_CONFIG: 'x',
|
||||
VSCODE_PORTABLE: 'x',
|
||||
VSCODE_PID: 'x',
|
||||
VSCODE_NODE_CACHED_DATA_DIR: 'x',
|
||||
VSCODE_CODE_CACHE_PATH: 'x',
|
||||
VSCODE_NEW_VAR: 'x',
|
||||
GDK_PIXBUF_MODULE_FILE: 'x',
|
||||
GDK_PIXBUF_MODULEDIR: 'x',
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { basename, dirname, join } from 'vs/base/common/path';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { Promises, rimraf } from 'vs/base/node/pfs';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
|
||||
export class CodeCacheCleaner extends Disposable {
|
||||
|
||||
private readonly _DataMaxAge = this.productService.quality !== 'stable'
|
||||
? 1000 * 60 * 60 * 24 * 7 // roughly 1 week (insiders)
|
||||
: 1000 * 60 * 60 * 24 * 30 * 3; // roughly 3 months (stable)
|
||||
|
||||
constructor(
|
||||
currentCodeCachePath: string | undefined,
|
||||
@IProductService private readonly productService: IProductService,
|
||||
@ILogService private readonly logService: ILogService
|
||||
) {
|
||||
super();
|
||||
|
||||
// Cached data is stored as user data and we run a cleanup task everytime
|
||||
// the editor starts. The strategy is to delete all files that are older than
|
||||
// 3 months (1 week respectively)
|
||||
if (currentCodeCachePath) {
|
||||
const scheduler = this._register(new RunOnceScheduler(() => {
|
||||
this.cleanUpCodeCaches(currentCodeCachePath);
|
||||
}, 30 * 1000 /* after 30s */));
|
||||
scheduler.schedule();
|
||||
}
|
||||
}
|
||||
|
||||
private async cleanUpCodeCaches(currentCodeCachePath: string): Promise<void> {
|
||||
this.logService.info('[code cache cleanup]: Starting to clean up old code cache folders.');
|
||||
|
||||
try {
|
||||
const now = Date.now();
|
||||
|
||||
// The folder which contains folders of cached data.
|
||||
// Each of these folders is partioned per commit
|
||||
const codeCacheRootPath = dirname(currentCodeCachePath);
|
||||
const currentCodeCache = basename(currentCodeCachePath);
|
||||
|
||||
const codeCaches = await Promises.readdir(codeCacheRootPath);
|
||||
await Promise.all(codeCaches.map(async codeCache => {
|
||||
if (codeCache === currentCodeCache) {
|
||||
return; // not the current cache folder
|
||||
}
|
||||
|
||||
// Delete cache folder if old enough
|
||||
const codeCacheEntryPath = join(codeCacheRootPath, codeCache);
|
||||
const codeCacheEntryStat = await Promises.stat(codeCacheEntryPath);
|
||||
if (codeCacheEntryStat.isDirectory() && (now - codeCacheEntryStat.mtime.getTime()) > this._DataMaxAge) {
|
||||
this.logService.info(`[code cache cleanup]: Removing code cache folder ${codeCache}.`);
|
||||
|
||||
return rimraf(codeCacheEntryPath);
|
||||
}
|
||||
}));
|
||||
} catch (error) {
|
||||
onUnexpectedError(error);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,16 +3,17 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as path from 'vs/base/common/path';
|
||||
import * as pfs from 'vs/base/node/pfs';
|
||||
import { join } from 'vs/base/common/path';
|
||||
import { Promises, rimraf } from 'vs/base/node/pfs';
|
||||
import { IStringDictionary } from 'vs/base/common/collections';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
|
||||
interface ExtensionEntry {
|
||||
interface IExtensionEntry {
|
||||
version: string;
|
||||
extensionIdentifier: {
|
||||
id: string;
|
||||
|
@ -20,87 +21,88 @@ interface ExtensionEntry {
|
|||
};
|
||||
}
|
||||
|
||||
interface LanguagePackEntry {
|
||||
interface ILanguagePackEntry {
|
||||
hash: string;
|
||||
extensions: ExtensionEntry[];
|
||||
extensions: IExtensionEntry[];
|
||||
}
|
||||
|
||||
interface LanguagePackFile {
|
||||
[locale: string]: LanguagePackEntry;
|
||||
interface ILanguagePackFile {
|
||||
[locale: string]: ILanguagePackEntry;
|
||||
}
|
||||
|
||||
export class LanguagePackCachedDataCleaner extends Disposable {
|
||||
|
||||
private readonly _DataMaxAge = this._productService.quality !== 'stable'
|
||||
? 1000 * 60 * 60 * 24 * 7 // roughly 1 week
|
||||
: 1000 * 60 * 60 * 24 * 30 * 3; // roughly 3 months
|
||||
private readonly _DataMaxAge = this.productService.quality !== 'stable'
|
||||
? 1000 * 60 * 60 * 24 * 7 // roughly 1 week (insiders)
|
||||
: 1000 * 60 * 60 * 24 * 30 * 3; // roughly 3 months (stable)
|
||||
|
||||
constructor(
|
||||
@INativeEnvironmentService private readonly _environmentService: INativeEnvironmentService,
|
||||
@ILogService private readonly _logService: ILogService,
|
||||
@IProductService private readonly _productService: IProductService
|
||||
@INativeEnvironmentService private readonly environmentService: INativeEnvironmentService,
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@IProductService private readonly productService: IProductService
|
||||
) {
|
||||
super();
|
||||
|
||||
// We have no Language pack support for dev version (run from source)
|
||||
// So only cleanup when we have a build version.
|
||||
if (this._environmentService.isBuilt) {
|
||||
this._manageCachedDataSoon();
|
||||
if (this.environmentService.isBuilt) {
|
||||
const scheduler = this._register(new RunOnceScheduler(() => {
|
||||
this.cleanUpLanguagePackCache();
|
||||
}, 40 * 1000 /* after 40s */));
|
||||
scheduler.schedule();
|
||||
}
|
||||
}
|
||||
|
||||
private _manageCachedDataSoon(): void {
|
||||
let handle: any = setTimeout(async () => {
|
||||
handle = undefined;
|
||||
this._logService.info('Starting to clean up unused language packs.');
|
||||
try {
|
||||
const installed: IStringDictionary<boolean> = Object.create(null);
|
||||
const metaData: LanguagePackFile = JSON.parse(await pfs.Promises.readFile(path.join(this._environmentService.userDataPath, 'languagepacks.json'), 'utf8'));
|
||||
for (let locale of Object.keys(metaData)) {
|
||||
const entry = metaData[locale];
|
||||
installed[`${entry.hash}.${locale}`] = true;
|
||||
private async cleanUpLanguagePackCache(): Promise<void> {
|
||||
this.logService.info('[language pack cache cleanup]: Starting to clean up unused language packs.');
|
||||
|
||||
try {
|
||||
const installed: IStringDictionary<boolean> = Object.create(null);
|
||||
const metaData: ILanguagePackFile = JSON.parse(await Promises.readFile(join(this.environmentService.userDataPath, 'languagepacks.json'), 'utf8'));
|
||||
for (let locale of Object.keys(metaData)) {
|
||||
const entry = metaData[locale];
|
||||
installed[`${entry.hash}.${locale}`] = true;
|
||||
}
|
||||
|
||||
// Cleanup entries for language packs that aren't installed anymore
|
||||
const cacheDir = join(this.environmentService.userDataPath, 'clp');
|
||||
const cacheDirExists = await Promises.exists(cacheDir);
|
||||
if (!cacheDirExists) {
|
||||
return;
|
||||
}
|
||||
|
||||
const entries = await Promises.readdir(cacheDir);
|
||||
for (const entry of entries) {
|
||||
if (installed[entry]) {
|
||||
this.logService.info(`[language pack cache cleanup]: Skipping folder ${entry}. Language pack still in use.`);
|
||||
continue;
|
||||
}
|
||||
// Cleanup entries for language packs that aren't installed anymore
|
||||
const cacheDir = path.join(this._environmentService.userDataPath, 'clp');
|
||||
const exists = await pfs.Promises.exists(cacheDir);
|
||||
if (!exists) {
|
||||
return;
|
||||
}
|
||||
for (let entry of await pfs.Promises.readdir(cacheDir)) {
|
||||
if (installed[entry]) {
|
||||
this._logService.info(`Skipping directory ${entry}. Language pack still in use.`);
|
||||
|
||||
this.logService.info(`[language pack cache cleanup]: Removing unused language pack: ${entry}`);
|
||||
|
||||
await rimraf(join(cacheDir, entry));
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
for (const packEntry of Object.keys(installed)) {
|
||||
const folder = join(cacheDir, packEntry);
|
||||
const entries = await Promises.readdir(folder);
|
||||
for (const entry of entries) {
|
||||
if (entry === 'tcf.json') {
|
||||
continue;
|
||||
}
|
||||
this._logService.info('Removing unused language pack:', entry);
|
||||
await pfs.rimraf(path.join(cacheDir, entry));
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
for (let packEntry of Object.keys(installed)) {
|
||||
const folder = path.join(cacheDir, packEntry);
|
||||
for (let entry of await pfs.Promises.readdir(folder)) {
|
||||
if (entry === 'tcf.json') {
|
||||
continue;
|
||||
}
|
||||
const candidate = path.join(folder, entry);
|
||||
const stat = await pfs.Promises.stat(candidate);
|
||||
if (stat.isDirectory()) {
|
||||
const diff = now - stat.mtime.getTime();
|
||||
if (diff > this._DataMaxAge) {
|
||||
this._logService.info('Removing language pack cache entry: ', path.join(packEntry, entry));
|
||||
await pfs.rimraf(candidate);
|
||||
}
|
||||
}
|
||||
const candidate = join(folder, entry);
|
||||
const stat = await Promises.stat(candidate);
|
||||
if (stat.isDirectory() && (now - stat.mtime.getTime()) > this._DataMaxAge) {
|
||||
this.logService.info(`[language pack cache cleanup]: Removing language pack cache folder: ${join(packEntry, entry)}`);
|
||||
|
||||
await rimraf(candidate);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
onUnexpectedError(error);
|
||||
}
|
||||
}, 40 * 1000);
|
||||
|
||||
this._register(toDisposable(() => {
|
||||
if (handle !== undefined) {
|
||||
clearTimeout(handle);
|
||||
}
|
||||
}));
|
||||
} catch (error) {
|
||||
onUnexpectedError(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,42 +5,46 @@
|
|||
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { join, dirname, basename } from 'vs/base/common/path';
|
||||
import { Promises as FSPromises, rimraf } from 'vs/base/node/pfs';
|
||||
import { Promises, rimraf } from 'vs/base/node/pfs';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Promises } from 'vs/base/common/async';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
|
||||
export class LogsDataCleaner extends Disposable {
|
||||
|
||||
constructor(
|
||||
@IEnvironmentService private readonly environmentService: IEnvironmentService
|
||||
@IEnvironmentService private readonly environmentService: IEnvironmentService,
|
||||
@ILogService private readonly logService: ILogService
|
||||
) {
|
||||
super();
|
||||
|
||||
this.cleanUpOldLogsSoon();
|
||||
const scheduler = this._register(new RunOnceScheduler(() => {
|
||||
this.cleanUpOldLogs();
|
||||
}, 10 * 1000 /* after 10s */));
|
||||
scheduler.schedule();
|
||||
}
|
||||
|
||||
private cleanUpOldLogsSoon(): void {
|
||||
let handle: NodeJS.Timeout | undefined = setTimeout(() => {
|
||||
handle = undefined;
|
||||
private async cleanUpOldLogs(): Promise<void> {
|
||||
this.logService.info('[logs cleanup]: Starting to clean up old logs.');
|
||||
|
||||
try {
|
||||
const currentLog = basename(this.environmentService.logsPath);
|
||||
const logsRoot = dirname(this.environmentService.logsPath);
|
||||
|
||||
FSPromises.readdir(logsRoot).then(children => {
|
||||
const allSessions = children.filter(name => /^\d{8}T\d{6}$/.test(name));
|
||||
const oldSessions = allSessions.sort().filter((d, i) => d !== currentLog);
|
||||
const toDelete = oldSessions.slice(0, Math.max(0, oldSessions.length - 9));
|
||||
const logFiles = await Promises.readdir(logsRoot);
|
||||
|
||||
return Promises.settled(toDelete.map(name => rimraf(join(logsRoot, name))));
|
||||
}).then(null, onUnexpectedError);
|
||||
}, 10 * 1000);
|
||||
const allSessions = logFiles.filter(logFile => /^\d{8}T\d{6}$/.test(logFile));
|
||||
const oldSessions = allSessions.sort().filter(session => session !== currentLog);
|
||||
const sessionsToDelete = oldSessions.slice(0, Math.max(0, oldSessions.length - 9));
|
||||
|
||||
this._register(toDisposable(() => {
|
||||
if (handle) {
|
||||
clearTimeout(handle);
|
||||
handle = undefined;
|
||||
if (sessionsToDelete.length > 0) {
|
||||
this.logService.info(`[logs cleanup]: Removing log folders '${sessionsToDelete.join(', ')}'`);
|
||||
|
||||
await Promise.all(sessionsToDelete.map(sessionToDelete => rimraf(join(logsRoot, sessionToDelete))));
|
||||
}
|
||||
}));
|
||||
} catch (error) {
|
||||
onUnexpectedError(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,86 +0,0 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { basename, dirname, join } from 'vs/base/common/path';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { Promises, rimraf } from 'vs/base/node/pfs';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
|
||||
export class NodeCachedDataCleaner {
|
||||
|
||||
private readonly _DataMaxAge = this.productService.quality !== 'stable'
|
||||
? 1000 * 60 * 60 * 24 * 7 // roughly 1 week
|
||||
: 1000 * 60 * 60 * 24 * 30 * 3; // roughly 3 months
|
||||
|
||||
private readonly _disposables = new DisposableStore();
|
||||
|
||||
constructor(
|
||||
private readonly nodeCachedDataDir: string | undefined,
|
||||
@IProductService private readonly productService: IProductService
|
||||
) {
|
||||
this._manageCachedDataSoon();
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._disposables.dispose();
|
||||
}
|
||||
|
||||
private _manageCachedDataSoon(): void {
|
||||
// Cached data is stored as user data and we run a cleanup task everytime
|
||||
// the editor starts. The strategy is to delete all files that are older than
|
||||
// 3 months (1 week respectively)
|
||||
if (!this.nodeCachedDataDir) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The folder which contains folders of cached data. Each of these folder is per
|
||||
// version
|
||||
const nodeCachedDataRootDir = dirname(this.nodeCachedDataDir);
|
||||
const nodeCachedDataCurrent = basename(this.nodeCachedDataDir);
|
||||
|
||||
let handle: NodeJS.Timeout | undefined = setTimeout(() => {
|
||||
handle = undefined;
|
||||
|
||||
Promises.readdir(nodeCachedDataRootDir).then(entries => {
|
||||
|
||||
const now = Date.now();
|
||||
const deletes: Promise<unknown>[] = [];
|
||||
|
||||
entries.forEach(entry => {
|
||||
// name check
|
||||
// * not the current cached data folder
|
||||
if (entry !== nodeCachedDataCurrent) {
|
||||
|
||||
const path = join(nodeCachedDataRootDir, entry);
|
||||
deletes.push(Promises.stat(path).then(stats => {
|
||||
// stat check
|
||||
// * only directories
|
||||
// * only when old enough
|
||||
if (stats.isDirectory()) {
|
||||
const diff = now - stats.mtime.getTime();
|
||||
if (diff > this._DataMaxAge) {
|
||||
return rimraf(path);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
return Promise.all(deletes);
|
||||
|
||||
}).then(undefined, onUnexpectedError);
|
||||
|
||||
}, 30 * 1000);
|
||||
|
||||
this._disposables.add(toDisposable(() => {
|
||||
if (handle) {
|
||||
clearTimeout(handle);
|
||||
handle = undefined;
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
|
@ -7,8 +7,10 @@ import { INativeEnvironmentService } from 'vs/platform/environment/common/enviro
|
|||
import { join } from 'vs/base/common/path';
|
||||
import { Promises, rimraf } from 'vs/base/node/pfs';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IBackupWorkspacesFormat } from 'vs/platform/backup/node/backup';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
|
||||
export class StorageDataCleaner extends Disposable {
|
||||
|
||||
|
@ -17,52 +19,44 @@ export class StorageDataCleaner extends Disposable {
|
|||
|
||||
constructor(
|
||||
private readonly backupWorkspacesPath: string,
|
||||
@INativeEnvironmentService private readonly environmentService: INativeEnvironmentService
|
||||
@INativeEnvironmentService private readonly environmentService: INativeEnvironmentService,
|
||||
@ILogService private readonly logService: ILogService
|
||||
) {
|
||||
super();
|
||||
|
||||
this.cleanUpStorageSoon();
|
||||
const scheduler = this._register(new RunOnceScheduler(() => {
|
||||
this.cleanUpStorage();
|
||||
}, 30 * 1000 /* after 30s */));
|
||||
scheduler.schedule();
|
||||
}
|
||||
|
||||
private cleanUpStorageSoon(): void {
|
||||
let handle: NodeJS.Timeout | undefined = setTimeout(() => {
|
||||
handle = undefined;
|
||||
private async cleanUpStorage(): Promise<void> {
|
||||
this.logService.info('[storage cleanup]: Starting to clean up storage folders.');
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
// Leverage the backup workspace file to find out which empty workspace is currently in use to
|
||||
// determine which empty workspace storage can safely be deleted
|
||||
const contents = await Promises.readFile(this.backupWorkspacesPath, 'utf8');
|
||||
try {
|
||||
|
||||
const workspaces = JSON.parse(contents) as IBackupWorkspacesFormat;
|
||||
const emptyWorkspaces = workspaces.emptyWorkspaceInfos.map(info => info.backupFolder);
|
||||
// Leverage the backup workspace file to find out which empty workspace is currently in use to
|
||||
// determine which empty workspace storage can safely be deleted
|
||||
const contents = await Promises.readFile(this.backupWorkspacesPath, 'utf8');
|
||||
|
||||
// Read all workspace storage folders that exist
|
||||
const storageFolders = await Promises.readdir(this.environmentService.workspaceStorageHome.fsPath);
|
||||
const deletes: Promise<void>[] = [];
|
||||
const workspaces = JSON.parse(contents) as IBackupWorkspacesFormat;
|
||||
const emptyWorkspaces = workspaces.emptyWorkspaceInfos.map(emptyWorkspace => emptyWorkspace.backupFolder);
|
||||
|
||||
storageFolders.forEach(storageFolder => {
|
||||
if (storageFolder.length === StorageDataCleaner.NON_EMPTY_WORKSPACE_ID_LENGTH) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (emptyWorkspaces.indexOf(storageFolder) === -1) {
|
||||
deletes.push(rimraf(join(this.environmentService.workspaceStorageHome.fsPath, storageFolder)));
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(deletes);
|
||||
} catch (error) {
|
||||
onUnexpectedError(error);
|
||||
// Read all workspace storage folders that exist
|
||||
const storageFolders = await Promises.readdir(this.environmentService.workspaceStorageHome.fsPath);
|
||||
await Promise.all(storageFolders.map(async storageFolder => {
|
||||
if (storageFolder.length === StorageDataCleaner.NON_EMPTY_WORKSPACE_ID_LENGTH) {
|
||||
return;
|
||||
}
|
||||
})();
|
||||
}, 30 * 1000);
|
||||
|
||||
this._register(toDisposable(() => {
|
||||
if (handle) {
|
||||
clearTimeout(handle);
|
||||
handle = undefined;
|
||||
}
|
||||
}));
|
||||
if (emptyWorkspaces.indexOf(storageFolder) === -1) {
|
||||
this.logService.info(`[storage cleanup]: Deleting storage folder ${storageFolder}.`);
|
||||
|
||||
await rimraf(join(this.environmentService.workspaceStorageHome.fsPath, storageFolder));
|
||||
}
|
||||
}));
|
||||
} catch (error) {
|
||||
onUnexpectedError(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ import { ILocalizationsService } from 'vs/platform/localizations/common/localiza
|
|||
import { combinedDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { DownloadService } from 'vs/platform/download/common/downloadService';
|
||||
import { IDownloadService } from 'vs/platform/download/common/download';
|
||||
import { NodeCachedDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/nodeCachedDataCleaner';
|
||||
import { CodeCacheCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/codeCacheCleaner';
|
||||
import { LanguagePackCachedDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner';
|
||||
import { StorageDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner';
|
||||
import { LogsDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner';
|
||||
|
@ -131,7 +131,7 @@ class SharedProcessMain extends Disposable {
|
|||
|
||||
// Instantiate Contributions
|
||||
this._register(combinedDisposable(
|
||||
instantiationService.createInstance(NodeCachedDataCleaner, this.configuration.nodeCachedDataDir),
|
||||
instantiationService.createInstance(CodeCacheCleaner, this.configuration.codeCachePath),
|
||||
instantiationService.createInstance(LanguagePackCachedDataCleaner),
|
||||
instantiationService.createInstance(StorageDataCleaner, this.configuration.backupWorkspacesPath),
|
||||
instantiationService.createInstance(LogsDataCleaner),
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
import { release, hostname } from 'os';
|
||||
import { statSync } from 'fs';
|
||||
import { app, ipcMain, systemPreferences, contentTracing, protocol, BrowserWindow, dialog, session } from 'electron';
|
||||
import { app, ipcMain, systemPreferences, contentTracing, protocol, BrowserWindow, dialog, session, Session } from 'electron';
|
||||
import { IProcessEnvironment, isWindows, isMacintosh, isLinux, isLinuxSnap } from 'vs/base/common/platform';
|
||||
import { WindowsMainService } from 'vs/platform/windows/electron-main/windowsMainService';
|
||||
import { IWindowOpenable } from 'vs/platform/windows/common/windows';
|
||||
|
@ -113,9 +113,60 @@ export class CodeApplication extends Disposable {
|
|||
) {
|
||||
super();
|
||||
|
||||
this.configureSession();
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private configureSession(): void {
|
||||
|
||||
//#region Security related measures (https://electronjs.org/docs/tutorial/security)
|
||||
//
|
||||
// !!! DO NOT CHANGE without consulting the documentation !!!
|
||||
//
|
||||
|
||||
const isUrlFromWebview = (requestingUrl: string) => requestingUrl.startsWith(`${Schemas.vscodeWebview}://`);
|
||||
|
||||
session.defaultSession.setPermissionRequestHandler((_webContents, permission /* 'media' | 'geolocation' | 'notifications' | 'midiSysex' | 'pointerLock' | 'fullscreen' | 'openExternal' */, callback, details) => {
|
||||
if (isUrlFromWebview(details.requestingUrl)) {
|
||||
return callback(permission === 'clipboard-read');
|
||||
}
|
||||
|
||||
return callback(false);
|
||||
});
|
||||
|
||||
session.defaultSession.setPermissionCheckHandler((_webContents, permission /* 'media' */, _origin, details) => {
|
||||
if (isUrlFromWebview(details.requestingUrl)) {
|
||||
return permission === 'clipboard-read';
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
//#endregion
|
||||
|
||||
|
||||
//#region Code Cache
|
||||
|
||||
type SessionWithCodeCachePathSupport = typeof Session & {
|
||||
/**
|
||||
* Sets code cache directory. By default, the directory will be `Code Cache` under
|
||||
* the respective user data folder.
|
||||
*/
|
||||
setCodeCachePath?(path: string): void;
|
||||
};
|
||||
|
||||
const defaultSession = session.defaultSession as unknown as SessionWithCodeCachePathSupport;
|
||||
if (typeof defaultSession.setCodeCachePath === 'function' && this.environmentMainService.codeCachePath) {
|
||||
// Make sure to partition Chrome's code cache folder
|
||||
// in the same way as our code cache path to help
|
||||
// invalidate caches that we know are invalid
|
||||
// (https://github.com/microsoft/vscode/issues/120655)
|
||||
defaultSession.setCodeCachePath(join(this.environmentMainService.codeCachePath, 'chrome'));
|
||||
}
|
||||
|
||||
//#endregion
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
|
||||
// We handle uncaught exceptions here to prevent electron from opening a dialog to the user
|
||||
|
@ -196,24 +247,6 @@ export class CodeApplication extends Disposable {
|
|||
|
||||
return { action: 'deny' };
|
||||
});
|
||||
|
||||
const isUrlFromWebview = (requestingUrl: string) => requestingUrl.startsWith(`${Schemas.vscodeWebview}://`);
|
||||
|
||||
session.defaultSession.setPermissionRequestHandler((_webContents, permission /* 'media' | 'geolocation' | 'notifications' | 'midiSysex' | 'pointerLock' | 'fullscreen' | 'openExternal' */, callback, details) => {
|
||||
if (isUrlFromWebview(details.requestingUrl)) {
|
||||
return callback(permission === 'clipboard-read');
|
||||
}
|
||||
|
||||
return callback(false);
|
||||
});
|
||||
|
||||
session.defaultSession.setPermissionCheckHandler((_webContents, permission /* 'media' */, _origin, details) => {
|
||||
if (isUrlFromWebview(details.requestingUrl)) {
|
||||
return permission === 'clipboard-read';
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
//#endregion
|
||||
|
|
|
@ -209,7 +209,7 @@ class CodeMain {
|
|||
// Environment service (paths)
|
||||
Promise.all<string | undefined>([
|
||||
environmentMainService.extensionsPath,
|
||||
environmentMainService.nodeCachedDataDir,
|
||||
environmentMainService.codeCachePath,
|
||||
environmentMainService.logsPath,
|
||||
environmentMainService.globalStorageHome.fsPath,
|
||||
environmentMainService.workspaceStorageHome.fsPath,
|
||||
|
|
|
@ -25,11 +25,8 @@ export interface IEnvironmentMainService extends INativeEnvironmentService {
|
|||
backupHome: string;
|
||||
backupWorkspacesPath: string;
|
||||
|
||||
// --- V8 script cache path (ours)
|
||||
nodeCachedDataDir?: string;
|
||||
|
||||
// --- V8 script cache path (chrome)
|
||||
chromeCachedDataDir: string;
|
||||
// --- V8 code cache path
|
||||
codeCachePath?: string;
|
||||
|
||||
// --- IPC
|
||||
mainIPCHandle: string;
|
||||
|
@ -68,8 +65,5 @@ export class EnvironmentMainService extends NativeEnvironmentService implements
|
|||
get disableKeytar(): boolean { return !!this.args['disable-keytar']; }
|
||||
|
||||
@memoize
|
||||
get nodeCachedDataDir(): string | undefined { return process.env['VSCODE_NODE_CACHED_DATA_DIR'] || undefined; }
|
||||
|
||||
@memoize
|
||||
get chromeCachedDataDir(): string { return join(this.userDataPath, 'Code Cache'); }
|
||||
get codeCachePath(): string | undefined { return process.env['VSCODE_CODE_CACHE_PATH'] || undefined; }
|
||||
}
|
||||
|
|
|
@ -183,7 +183,7 @@ export class SharedProcess extends Disposable implements ISharedProcess {
|
|||
machineId: this.machineId,
|
||||
windowId: this.window.id,
|
||||
appRoot: this.environmentMainService.appRoot,
|
||||
nodeCachedDataDir: this.environmentMainService.nodeCachedDataDir,
|
||||
codeCachePath: this.environmentMainService.codeCachePath,
|
||||
backupWorkspacesPath: this.environmentMainService.backupWorkspacesPath,
|
||||
userEnv: this.userEnv,
|
||||
args: this.environmentMainService.args,
|
||||
|
|
|
@ -206,9 +206,9 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
|||
};
|
||||
|
||||
if (browserCodeLoadingCacheStrategy) {
|
||||
this.logService.info(`window#ctor: using vscode-file:// protocol and V8 cache options: ${browserCodeLoadingCacheStrategy}`);
|
||||
this.logService.info(`window: using vscode-file:// protocol and V8 cache options: ${browserCodeLoadingCacheStrategy}`);
|
||||
} else {
|
||||
this.logService.trace(`window#ctor: vscode-file:// protocol is explicitly disabled`);
|
||||
this.logService.info(`window: vscode-file:// protocol is explicitly disabled`);
|
||||
}
|
||||
|
||||
// Apply icon to window
|
||||
|
|
|
@ -1169,7 +1169,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
|
|||
|
||||
appRoot: this.environmentMainService.appRoot,
|
||||
execPath: process.execPath,
|
||||
nodeCachedDataDir: this.environmentMainService.nodeCachedDataDir,
|
||||
codeCachePath: this.environmentMainService.codeCachePath,
|
||||
// If we know the backup folder upfront (for empty windows to restore), we can set it
|
||||
// directly here which helps for restoring UI state associated with that window.
|
||||
// For all other cases we first call into registerEmptyWindowBackupSync() to set it before
|
||||
|
|
|
@ -180,9 +180,9 @@ export class LocalProcessExtensionHost implements IExtensionHost {
|
|||
}
|
||||
|
||||
if (this._isExtensionDevHost) {
|
||||
// Unset `VSCODE_NODE_CACHED_DATA_DIR` when developing extensions because it might
|
||||
// Unset `VSCODE_CODE_CACHE_PATH` when developing extensions because it might
|
||||
// be that dependencies, that otherwise would be cached, get modified.
|
||||
delete env['VSCODE_NODE_CACHED_DATA_DIR'];
|
||||
delete env['VSCODE_CODE_CACHE_PATH'];
|
||||
}
|
||||
|
||||
const opts = {
|
||||
|
|
Loading…
Reference in a new issue