Delay initializing SQL in renderer
This commit is contained in:
parent
0d5ef38e52
commit
8cf6748dce
10 changed files with 101 additions and 104 deletions
|
@ -57,7 +57,7 @@ before(async () => {
|
|||
try {
|
||||
window.SignalWindow.log.info('Initializing SQL in renderer');
|
||||
const isTesting = true;
|
||||
await window.sqlInitializer.initialize(isTesting);
|
||||
await window.Signal.Data.startInRenderer(isTesting);
|
||||
window.SignalWindow.log.info('SQL initialized in renderer');
|
||||
} catch (err) {
|
||||
window.SignalWindow.log.error(
|
||||
|
|
|
@ -29,8 +29,6 @@ try {
|
|||
const { remote } = electron;
|
||||
const { app } = remote;
|
||||
|
||||
window.sqlInitializer = require('./ts/sql/initialize');
|
||||
|
||||
const config = require('url').parse(window.location.toString(), true).query;
|
||||
|
||||
setEnvironment(parseEnvironment(config.environment));
|
||||
|
|
|
@ -37,8 +37,6 @@ const MAX_ANIMATED_STICKER_BYTE_LENGTH = 300 * 1024;
|
|||
|
||||
setEnvironment(parseEnvironment(config.environment));
|
||||
|
||||
window.sqlInitializer = require('../ts/sql/initialize');
|
||||
|
||||
window.ROOT_PATH = window.location.href.startsWith('file') ? '../../' : '/';
|
||||
window.getEnvironment = getEnvironment;
|
||||
window.getVersion = () => config.version;
|
||||
|
@ -173,7 +171,6 @@ window.encryptAndUpload = async (
|
|||
cover,
|
||||
onProgress = noop
|
||||
) => {
|
||||
await window.sqlInitializer.goBackToMainProcess();
|
||||
const usernameItem = await window.Signal.Data.getItemById('uuid_id');
|
||||
const oldUsernameItem = await window.Signal.Data.getItemById('number_id');
|
||||
const passwordItem = await window.Signal.Data.getItemById('password');
|
||||
|
|
|
@ -68,7 +68,7 @@ before(async () => {
|
|||
try {
|
||||
window.SignalWindow.log.info('Initializing SQL in renderer');
|
||||
const isTesting = true;
|
||||
await window.sqlInitializer.initialize(isTesting);
|
||||
await window.Signal.Data.startInRenderer(isTesting);
|
||||
window.SignalWindow.log.info('SQL initialized in renderer');
|
||||
} catch (err) {
|
||||
window.SignalWindow.log.error(
|
||||
|
|
|
@ -154,13 +154,6 @@ export async function startApp(): Promise<void> {
|
|||
storage: window.storage,
|
||||
});
|
||||
window.attachmentDownloadQueue = [];
|
||||
try {
|
||||
log.info('Initializing SQL in renderer');
|
||||
await window.sqlInitializer.initialize();
|
||||
log.info('SQL initialized in renderer');
|
||||
} catch (err) {
|
||||
log.error('SQL failed to initialize', err && err.stack ? err.stack : err);
|
||||
}
|
||||
|
||||
await window.Signal.Util.initializeMessageCounter();
|
||||
|
||||
|
@ -803,6 +796,12 @@ export async function startApp(): Promise<void> {
|
|||
window.Signal.Data.ensureFilePermissions();
|
||||
}
|
||||
|
||||
try {
|
||||
await window.Signal.Data.startInRendererProcess();
|
||||
} catch (err) {
|
||||
log.error('SQL failed to initialize', err && err.stack ? err.stack : err);
|
||||
}
|
||||
|
||||
Views.Initialization.setMessage(window.i18n('loading'));
|
||||
|
||||
idleDetector = new IdleDetector();
|
||||
|
@ -2335,7 +2334,7 @@ export async function startApp(): Promise<void> {
|
|||
);
|
||||
|
||||
// Go back to main process before processing delayed actions
|
||||
await window.sqlInitializer.goBackToMainProcess();
|
||||
await window.Signal.Data.goBackToMainProcess();
|
||||
|
||||
profileKeyResponseQueue.start();
|
||||
lightSessionResetQueue.start();
|
||||
|
@ -3441,7 +3440,7 @@ export async function startApp(): Promise<void> {
|
|||
|
||||
// If we couldn't connect during startup - we should still switch SQL to
|
||||
// the main process to avoid stalling UI.
|
||||
window.sqlInitializer.goBackToMainProcess();
|
||||
window.Signal.Data.goBackToMainProcess();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
138
ts/sql/Client.ts
138
ts/sql/Client.ts
|
@ -7,7 +7,9 @@
|
|||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/ban-types */
|
||||
import { ipcRenderer } from 'electron';
|
||||
import { ipcRenderer as ipc } from 'electron';
|
||||
import fs from 'fs-extra';
|
||||
import pify from 'pify';
|
||||
|
||||
import {
|
||||
cloneDeep,
|
||||
|
@ -27,7 +29,7 @@ import {
|
|||
import * as Bytes from '../Bytes';
|
||||
import { CURRENT_SCHEMA_VERSION } from '../../js/modules/types/message';
|
||||
import { createBatcher } from '../util/batcher';
|
||||
import { assert } from '../util/assert';
|
||||
import { assert, strictAssert } from '../util/assert';
|
||||
import { cleanDataForIpc } from './cleanDataForIpc';
|
||||
import { ReactionType } from '../types/Reactions';
|
||||
import { ConversationColorType, CustomColorType } from '../types/Colors';
|
||||
|
@ -84,14 +86,16 @@ import { isCorruptionError } from './errors';
|
|||
import { MessageModel } from '../models/messages';
|
||||
import { ConversationModel } from '../models/conversations';
|
||||
|
||||
// We listen to a lot of events on ipcRenderer, often on the same channel. This prevents
|
||||
// We listen to a lot of events on ipc, often on the same channel. This prevents
|
||||
// any warnings that might be sent to the console in that case.
|
||||
if (ipcRenderer && ipcRenderer.setMaxListeners) {
|
||||
ipcRenderer.setMaxListeners(0);
|
||||
if (ipc && ipc.setMaxListeners) {
|
||||
ipc.setMaxListeners(0);
|
||||
} else {
|
||||
log.warn('sql/Client: ipcRenderer is not available!');
|
||||
log.warn('sql/Client: ipc is not available!');
|
||||
}
|
||||
|
||||
const getRealPath = pify(fs.realpath);
|
||||
|
||||
const MIN_TRACE_DURATION = 10;
|
||||
|
||||
const SQL_CHANNEL_KEY = 'sql-channel';
|
||||
|
@ -109,13 +113,20 @@ type ClientJobUpdateType = {
|
|||
args?: Array<any>;
|
||||
};
|
||||
|
||||
enum RendererState {
|
||||
InMain = 'InMain',
|
||||
Opening = 'Opening',
|
||||
InRenderer = 'InRenderer',
|
||||
Closing = 'Closing',
|
||||
}
|
||||
|
||||
const _jobs: { [id: string]: ClientJobType } = Object.create(null);
|
||||
const _DEBUG = false;
|
||||
let _jobCounter = 0;
|
||||
let _shuttingDown = false;
|
||||
let _shutdownCallback: Function | null = null;
|
||||
let _shutdownPromise: Promise<any> | null = null;
|
||||
let shouldUseRendererProcess = true;
|
||||
let state = RendererState.InMain;
|
||||
const startupQueries = new Map<string, number>();
|
||||
|
||||
// Because we can't force this module to conform to an interface, we narrow our exports
|
||||
|
@ -294,6 +305,7 @@ const dataInterface: ClientInterface = {
|
|||
|
||||
// Client-side only, and test-only
|
||||
|
||||
startInRendererProcess,
|
||||
goBackToMainProcess,
|
||||
_removeConversations,
|
||||
_jobs,
|
||||
|
@ -301,22 +313,49 @@ const dataInterface: ClientInterface = {
|
|||
|
||||
export default dataInterface;
|
||||
|
||||
async function goBackToMainProcess(): Promise<void> {
|
||||
if (!shouldUseRendererProcess) {
|
||||
log.info('data.goBackToMainProcess: already switched to main process');
|
||||
return;
|
||||
async function startInRendererProcess(isTesting = false): Promise<void> {
|
||||
strictAssert(
|
||||
state === RendererState.InMain,
|
||||
`startInRendererProcess: expected ${state} to be ${RendererState.InMain}`
|
||||
);
|
||||
|
||||
log.info('data.startInRendererProcess: switching to renderer process');
|
||||
state = RendererState.Opening;
|
||||
|
||||
if (!isTesting) {
|
||||
ipc.send('database-ready');
|
||||
|
||||
await new Promise<void>(resolve => {
|
||||
ipc.once('database-ready', () => {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const configDir = await getRealPath(ipc.sendSync('get-user-data-path'));
|
||||
const key = ipc.sendSync('user-config-key');
|
||||
|
||||
await Server.initializeRenderer({ configDir, key });
|
||||
|
||||
log.info('data.startInRendererProcess: switched to renderer process');
|
||||
|
||||
state = RendererState.InRenderer;
|
||||
}
|
||||
|
||||
async function goBackToMainProcess(): Promise<void> {
|
||||
strictAssert(
|
||||
state === RendererState.InRenderer,
|
||||
`goBackToMainProcess: expected ${state} to be ${RendererState.InRenderer}`
|
||||
);
|
||||
|
||||
// We don't need to wait for pending queries since they are synchronous.
|
||||
log.info('data.goBackToMainProcess: switching to main process');
|
||||
|
||||
// Close the database in the renderer process.
|
||||
const closePromise = close();
|
||||
|
||||
// It should be the last query we run in renderer process
|
||||
shouldUseRendererProcess = false;
|
||||
|
||||
state = RendererState.Closing;
|
||||
await closePromise;
|
||||
state = RendererState.InMain;
|
||||
|
||||
// Print query statistics for whole startup
|
||||
const entries = Array.from(startupQueries.entries());
|
||||
|
@ -329,6 +368,8 @@ async function goBackToMainProcess(): Promise<void> {
|
|||
.forEach(([query, duration]) => {
|
||||
log.info(`startup query: ${query} ${duration}ms`);
|
||||
});
|
||||
|
||||
log.info('data.goBackToMainProcess: switched to main process');
|
||||
}
|
||||
|
||||
const channelsAsUnknown = fromPairs(
|
||||
|
@ -474,38 +515,35 @@ function _getJob(id: number) {
|
|||
return _jobs[id];
|
||||
}
|
||||
|
||||
if (ipcRenderer && ipcRenderer.on) {
|
||||
ipcRenderer.on(
|
||||
`${SQL_CHANNEL_KEY}-done`,
|
||||
(_, jobId, errorForDisplay, result) => {
|
||||
const job = _getJob(jobId);
|
||||
if (!job) {
|
||||
throw new Error(
|
||||
`Received SQL channel reply to job ${jobId}, but did not have it in our registry!`
|
||||
);
|
||||
}
|
||||
|
||||
const { resolve, reject, fnName } = job;
|
||||
|
||||
if (!resolve || !reject) {
|
||||
throw new Error(
|
||||
`SQL channel job ${jobId} (${fnName}): didn't have a resolve or reject`
|
||||
);
|
||||
}
|
||||
|
||||
if (errorForDisplay) {
|
||||
return reject(
|
||||
new Error(
|
||||
`Error received from SQL channel job ${jobId} (${fnName}): ${errorForDisplay}`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return resolve(result);
|
||||
if (ipc && ipc.on) {
|
||||
ipc.on(`${SQL_CHANNEL_KEY}-done`, (_, jobId, errorForDisplay, result) => {
|
||||
const job = _getJob(jobId);
|
||||
if (!job) {
|
||||
throw new Error(
|
||||
`Received SQL channel reply to job ${jobId}, but did not have it in our registry!`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
const { resolve, reject, fnName } = job;
|
||||
|
||||
if (!resolve || !reject) {
|
||||
throw new Error(
|
||||
`SQL channel job ${jobId} (${fnName}): didn't have a resolve or reject`
|
||||
);
|
||||
}
|
||||
|
||||
if (errorForDisplay) {
|
||||
return reject(
|
||||
new Error(
|
||||
`Error received from SQL channel job ${jobId} (${fnName}): ${errorForDisplay}`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return resolve(result);
|
||||
});
|
||||
} else {
|
||||
log.warn('sql/Client: ipcRenderer.on is not available!');
|
||||
log.warn('sql/Client: ipc.on is not available!');
|
||||
}
|
||||
|
||||
function makeChannel(fnName: string) {
|
||||
|
@ -514,7 +552,7 @@ function makeChannel(fnName: string) {
|
|||
// the db that exists in the renderer process to be able to boot up quickly
|
||||
// once the app is running we switch back to the main process to avoid the
|
||||
// UI from locking up whenever we do costly db operations.
|
||||
if (shouldUseRendererProcess) {
|
||||
if (state === RendererState.InRenderer) {
|
||||
const serverFnName = fnName as keyof ServerInterface;
|
||||
const start = Date.now();
|
||||
|
||||
|
@ -529,7 +567,7 @@ function makeChannel(fnName: string) {
|
|||
'Detected sql corruption in renderer process. ' +
|
||||
`Restarting the application immediately. Error: ${error.message}`
|
||||
);
|
||||
ipcRenderer?.send('database-error', error.stack);
|
||||
ipc?.send('database-error', error.stack);
|
||||
}
|
||||
log.error(
|
||||
`Renderer SQL channel job (${fnName}) error ${error.message}`
|
||||
|
@ -557,7 +595,7 @@ function makeChannel(fnName: string) {
|
|||
() =>
|
||||
new Promise((resolve, reject) => {
|
||||
try {
|
||||
ipcRenderer.send(SQL_CHANNEL_KEY, jobId, fnName, ...args);
|
||||
ipc.send(SQL_CHANNEL_KEY, jobId, fnName, ...args);
|
||||
|
||||
_updateJob(jobId, {
|
||||
resolve,
|
||||
|
@ -1556,8 +1594,8 @@ async function callChannel(name: string) {
|
|||
return createTaskWithTimeout(
|
||||
() =>
|
||||
new Promise<void>((resolve, reject) => {
|
||||
ipcRenderer.send(name);
|
||||
ipcRenderer.once(`${name}-done`, (_, error) => {
|
||||
ipc.send(name);
|
||||
ipc.once(`${name}-done`, (_, error) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
|
||||
|
|
|
@ -685,6 +685,7 @@ export type ClientInterface = DataInterface & {
|
|||
// whether we should use IPC to use the database in the main process or
|
||||
// use the db already running in the renderer.
|
||||
goBackToMainProcess: () => Promise<void>;
|
||||
startInRendererProcess: (isTesting?: boolean) => Promise<void>;
|
||||
};
|
||||
|
||||
export type ClientJobType = {
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { ipcRenderer as ipc } from 'electron';
|
||||
import fs from 'fs-extra';
|
||||
import pify from 'pify';
|
||||
import sql from './Server';
|
||||
|
||||
const getRealPath = pify(fs.realpath);
|
||||
|
||||
// Called from renderer.
|
||||
export async function initialize(isTesting = false): Promise<void> {
|
||||
if (!isTesting) {
|
||||
ipc.send('database-ready');
|
||||
|
||||
await new Promise<void>(resolve => {
|
||||
ipc.once('database-ready', () => {
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const configDir = await getRealPath(ipc.sendSync('get-user-data-path'));
|
||||
const key = ipc.sendSync('user-config-key');
|
||||
|
||||
await sql.initializeRenderer({ configDir, key });
|
||||
}
|
||||
|
||||
export async function goBackToMainProcess(): Promise<void> {
|
||||
return window.Signal.Data.goBackToMainProcess();
|
||||
}
|
|
@ -380,13 +380,13 @@ export function createIPCEvents(
|
|||
showKeyboardShortcuts: () => window.showKeyboardShortcuts(),
|
||||
|
||||
deleteAllData: async () => {
|
||||
await window.sqlInitializer.goBackToMainProcess();
|
||||
await window.Signal.Data.goBackToMainProcess();
|
||||
|
||||
renderClearingDataView();
|
||||
},
|
||||
|
||||
closeDB: async () => {
|
||||
await window.sqlInitializer.goBackToMainProcess();
|
||||
await window.Signal.Data.goBackToMainProcess();
|
||||
},
|
||||
|
||||
showStickerPack: (packId, key) => {
|
||||
|
|
5
ts/window.d.ts
vendored
5
ts/window.d.ts
vendored
|
@ -267,11 +267,6 @@ declare global {
|
|||
titleBarDoubleClick: () => void;
|
||||
unregisterForActive: (handler: () => void) => void;
|
||||
updateTrayIcon: (count: number) => void;
|
||||
sqlInitializer: {
|
||||
initialize: () => Promise<void>;
|
||||
goBackToMainProcess: () => Promise<void>;
|
||||
};
|
||||
|
||||
Backbone: typeof Backbone;
|
||||
CI?: CI;
|
||||
Accessibility: {
|
||||
|
|
Loading…
Reference in a new issue