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