mirror of
https://github.com/gristlabs/grist-core.git
synced 2026-03-02 04:09:24 +00:00
(core) add GVISOR_LIMIT_MEMORY to cap memory available in sandbox
Summary: This allows limiting the memory available to documents in the sandbox when gvisor is used. If memory limit is exceeded, we offer to open doc in recovery mode. Recovery mode is tweaked to open docs with tables in "ondemand" mode, which will generally take less memory and allow for deleting rows. The limit is on the size of the virtual address space available to the sandbox (`RLIMIT_AS`), which in practice appears to function as one would want, and is the only practical option. There is a documented `RLIMIT_RSS` limit to `specifies the limit (in bytes) of the process's resident set (the number of virtual pages resident in RAM)` but this is no longer enforced by the kernel (neither the host nor gvisor). When the sandbox runs out of memory, there are many ways it can fail. This diff catches all the ones I saw, but there could be more. Test Plan: added tests Reviewers: alexmojaki Reviewed By: alexmojaki Subscribers: alexmojaki Differential Revision: https://phab.getgrist.com/D3398
This commit is contained in:
@@ -9,6 +9,7 @@ import {isDesktop} from 'app/client/lib/browserInfo';
|
||||
import {FocusLayer} from 'app/client/lib/FocusLayer';
|
||||
import * as koUtil from 'app/client/lib/koUtil';
|
||||
import {reportError, TopAppModel, TopAppModelImpl} from 'app/client/models/AppModel';
|
||||
import {DocPageModel} from 'app/client/models/DocPageModel';
|
||||
import {setUpErrorHandling} from 'app/client/models/errors';
|
||||
import {createAppUI} from 'app/client/ui/AppUI';
|
||||
import {addViewportTag} from 'app/client/ui/viewport';
|
||||
@@ -31,7 +32,7 @@ export class App extends DisposableWithEvents {
|
||||
// Used by #newui code to avoid a dependency on commands.js, and by tests to issue commands.
|
||||
public allCommands = commands.allCommands;
|
||||
|
||||
public comm = this.autoDispose(Comm.create());
|
||||
public comm = this.autoDispose(Comm.create(this._checkError.bind(this)));
|
||||
public clientScope: ClientScope;
|
||||
public features: ko.Computed<ISupportedFeatures>;
|
||||
public topAppModel: TopAppModel; // Exposed because used by test/nbrowser/gristUtils.
|
||||
@@ -42,6 +43,9 @@ export class App extends DisposableWithEvents {
|
||||
// we can choose to refresh the client also.
|
||||
private _serverVersion: string|null = null;
|
||||
|
||||
// Track the most recently created DocPageModel, for some error handling.
|
||||
private _mostRecentDocPageModel?: DocPageModel;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
@@ -154,6 +158,10 @@ export class App extends DisposableWithEvents {
|
||||
setTimeout(() => this.reloadPane(), 0);
|
||||
});
|
||||
|
||||
this.listenTo(this.comm, 'docError', (msg) => {
|
||||
this._checkError(new Error(msg.data.message));
|
||||
});
|
||||
|
||||
// When the document is unloaded, dispose the app, allowing it to do any needed
|
||||
// cleanup (e.g. Document on disposal triggers closeDoc message to the server). It needs to be
|
||||
// in 'beforeunload' rather than 'unload', since websocket is closed by the time of 'unload'.
|
||||
@@ -202,6 +210,10 @@ export class App extends DisposableWithEvents {
|
||||
return true;
|
||||
}
|
||||
|
||||
public setDocPageModel(pageModel: DocPageModel) {
|
||||
this._mostRecentDocPageModel = pageModel;
|
||||
}
|
||||
|
||||
// Get the user profile for testing purposes
|
||||
public async testGetProfile(): Promise<any> {
|
||||
const resp = await fetchFromHome('/api/profile/user', {credentials: 'include'});
|
||||
@@ -211,4 +223,16 @@ export class App extends DisposableWithEvents {
|
||||
public testNumPendingApiRequests(): number {
|
||||
return BaseAPI.numPendingRequests();
|
||||
}
|
||||
|
||||
private _checkError(err: Error) {
|
||||
const message = String(err);
|
||||
// Take special action on any error that suggests a memory problem.
|
||||
if (message.match(/MemoryError|unmarshallable object/)) {
|
||||
if (err.message.length > 30) {
|
||||
// TLDR
|
||||
err.message = 'Memory Error';
|
||||
}
|
||||
this._mostRecentDocPageModel?.offerRecovery(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,6 +110,7 @@ function pagePanelsDoc(owner: IDisposableOwner, appModel: AppModel, appObj: App)
|
||||
// To simplify manual inspection in the common case, keep the most recently created
|
||||
// DocPageModel available as a global variable.
|
||||
(window as any).gristDocPageModel = pageModel;
|
||||
appObj.setDocPageModel(pageModel);
|
||||
const leftPanelOpen = createSessionObs<boolean>(owner, "leftPanelOpen", true, isBoolean);
|
||||
const rightPanelOpen = createSessionObs<boolean>(owner, "rightPanelOpen", false, isBoolean);
|
||||
const leftPanelWidth = createSessionObs<number>(owner, "leftPanelWidth", 240, isNumber);
|
||||
|
||||
Reference in New Issue
Block a user