diff --git a/src/vs/workbench/services/files/electron-browser/fileService.ts b/src/vs/workbench/services/files/electron-browser/fileService.ts index 3a4184018645f202ecd186d9428ad4ca88ac7a9a..a8afc717f41f39a07b666af9c6398c26b9c5c094 100644 --- a/src/vs/workbench/services/files/electron-browser/fileService.ts +++ b/src/vs/workbench/services/files/electron-browser/fileService.ts @@ -16,7 +16,7 @@ import * as arrays from 'vs/base/common/arrays'; import { TPromise } from 'vs/base/common/winjs.base'; import * as objects from 'vs/base/common/objects'; import * as extfs from 'vs/base/node/extfs'; -import { nfcall, ThrottledDelayer } from 'vs/base/common/async'; +import { nfcall, ThrottledDelayer, timeout } from 'vs/base/common/async'; import { URI as uri } from 'vs/base/common/uri'; import * as nls from 'vs/nls'; import { isWindows, isLinux, isMacintosh } from 'vs/base/common/platform'; @@ -593,24 +593,28 @@ export class FileService extends Disposable implements IFileService { else { // 4.) truncate - let retryFromFailingTruncate = true; return pfs.truncate(absolutePath, 0).then(() => { - retryFromFailingTruncate = false; // 5.) set contents (with r+ mode) and resolve - return this.doSetContentsAndResolve(resource, absolutePath, value, addBom, encodingToWrite, { flag: 'r+' }); - }, error => { - if (retryFromFailingTruncate) { + return this.doSetContentsAndResolve(resource, absolutePath, value, addBom, encodingToWrite, { flag: 'r+' }).then(null, error => { if (this.environmentService.verbose) { - console.error(`Truncate failed (${error}), falling back to normal save`); + console.error(`Truncate succeeded, but save failed (${error}), retrying after 100ms`); } - // we heard from users that fs.truncate() fails (https://github.com/Microsoft/vscode/issues/59561) - // in that case we simply save the file without truncating first (same as macOS and Linux) - return this.doSetContentsAndResolve(resource, absolutePath, value, addBom, encodingToWrite); + // we heard from one user that fs.truncate() succeeds, but the save fails (https://github.com/Microsoft/vscode/issues/61310) + // in that case, the file is now entirely empty and the contents are gone. this can happen if an external file watcher is + // installed that reacts on the truncate and keeps the file busy right after. Our workaround is to retry to save after a + // short timeout, assuming that the file is free to write then. + return timeout(100).then(() => this.doSetContentsAndResolve(resource, absolutePath, value, addBom, encodingToWrite, { flag: 'r+' })); + }); + }, error => { + if (this.environmentService.verbose) { + console.error(`Truncate failed (${error}), falling back to normal save`); } - return TPromise.wrapError(error); + // we heard from users that fs.truncate() fails (https://github.com/Microsoft/vscode/issues/59561) + // in that case we simply save the file without truncating first (same as macOS and Linux) + return this.doSetContentsAndResolve(resource, absolutePath, value, addBom, encodingToWrite); }); } }); diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index f6e908bede0dce392f52b5e828bfa69223209501..d0efefbbbfd53af2bfd58709f8afc4a49cc44f8f 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -32,6 +32,7 @@ import { isLinux } from 'vs/base/common/platform'; import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ILogService } from 'vs/platform/log/common/log'; import { isEqual, isEqualOrParent } from 'vs/base/common/resources'; +import { onUnexpectedError } from 'vs/base/common/errors'; /** * The text file editor model listens to changes to its underlying code editor model and saves these changes through the file service back to the disk. @@ -846,7 +847,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Updated resolved stat with updated stat since touching it might have changed mtime this.updateLastResolvedDiskStat(stat); - }, () => void 0 /* gracefully ignore errors if just touching */)); + }, error => onUnexpectedError(error) /* just log any error but do not notify the user since the file was not dirty */)); } private setDirty(dirty: boolean): () => void {