properly implement db resetting
This commit is contained in:
parent
49949de808
commit
e95027f377
66
src/timer.ts
66
src/timer.ts
@ -75,3 +75,69 @@ export function every(delayMs: number, callback: () => void): TimerHandle {
|
||||
export function after(delayMs: number, callback: () => void): TimerHandle {
|
||||
return new TimeoutHandle(setInterval(callback, delayMs));
|
||||
}
|
||||
|
||||
|
||||
const nullTimerHandle = {
|
||||
clear() {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Group of timers that can be destroyed at once.
|
||||
*/
|
||||
export class TimerGroup {
|
||||
private stopped: boolean = false;
|
||||
|
||||
private timerMap: { [index: number]: TimerHandle } = {};
|
||||
|
||||
private idGen = 1;
|
||||
|
||||
stopCurrentAndFutureTimers() {
|
||||
this.stopped = true;
|
||||
for (const x in this.timerMap) {
|
||||
if (!this.timerMap.hasOwnProperty(x)) {
|
||||
continue;
|
||||
}
|
||||
this.timerMap[x].clear();
|
||||
delete this.timerMap[x];
|
||||
}
|
||||
}
|
||||
|
||||
after(delayMs: number, callback: () => void): TimerHandle {
|
||||
if (this.stopped) {
|
||||
console.warn("dropping timer since timer group is stopped");
|
||||
return nullTimerHandle;
|
||||
}
|
||||
const h = after(delayMs, callback);
|
||||
let myId = this.idGen++;
|
||||
this.timerMap[myId] = h;
|
||||
|
||||
const tm = this.timerMap;
|
||||
|
||||
return {
|
||||
clear() {
|
||||
h.clear();
|
||||
delete tm[myId];
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
every(delayMs: number, callback: () => void): TimerHandle {
|
||||
if (this.stopped) {
|
||||
console.warn("dropping timer since timer group is stopped");
|
||||
return nullTimerHandle;
|
||||
}
|
||||
const h = every(delayMs, callback);
|
||||
let myId = this.idGen++;
|
||||
this.timerMap[myId] = h;
|
||||
|
||||
const tm = this.timerMap;
|
||||
|
||||
return {
|
||||
clear() {
|
||||
h.clear();
|
||||
delete tm[myId];
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -43,6 +43,7 @@ import {
|
||||
QueryRoot,
|
||||
Store,
|
||||
} from "./query";
|
||||
import {TimerGroup} from "./timer";
|
||||
import {
|
||||
AmountJson,
|
||||
Amounts,
|
||||
@ -346,18 +347,6 @@ const builtinCurrencies: CurrencyRecord[] = [
|
||||
];
|
||||
|
||||
|
||||
// FIXME: these functions should be dependency-injected
|
||||
// into the wallet, as this is chrome specific => bad
|
||||
|
||||
function setTimeout(f: any, t: number) {
|
||||
return chrome.extension.getBackgroundPage().setTimeout(f, t);
|
||||
}
|
||||
|
||||
function setInterval(f: any, t: number) {
|
||||
return chrome.extension.getBackgroundPage().setInterval(f, t);
|
||||
}
|
||||
|
||||
|
||||
function isWithdrawableDenom(d: DenominationRecord) {
|
||||
const nowSec = (new Date()).getTime() / 1000;
|
||||
const stampWithdrawSec = getTalerStampSec(d.stampExpireWithdraw);
|
||||
@ -583,13 +572,17 @@ interface CoinsForPaymentArgs {
|
||||
* The platform-independent wallet implementation.
|
||||
*/
|
||||
export class Wallet {
|
||||
private db: IDBDatabase;
|
||||
/**
|
||||
* IndexedDB database used by the wallet.
|
||||
*/
|
||||
db: IDBDatabase;
|
||||
private http: HttpRequestLibrary;
|
||||
private badge: Badge;
|
||||
private notifier: Notifier;
|
||||
private cryptoApi: CryptoApi;
|
||||
private processPreCoinConcurrent = 0;
|
||||
private processPreCoinThrottle: {[url: string]: number} = {};
|
||||
private timerGroup: TimerGroup;
|
||||
|
||||
/**
|
||||
* Set of identifiers for running operations.
|
||||
@ -613,7 +606,9 @@ export class Wallet {
|
||||
this.fillDefaults();
|
||||
this.resumePendingFromDb();
|
||||
|
||||
setInterval(() => this.updateExchanges(), 1000 * 60 * 15);
|
||||
this.timerGroup = new TimerGroup();
|
||||
|
||||
this.timerGroup.every(1000 * 60 * 15, () => this.updateExchanges());
|
||||
}
|
||||
|
||||
private async fillDefaults() {
|
||||
@ -1027,8 +1022,7 @@ export class Wallet {
|
||||
// random, exponential backoff truncated at 3 minutes
|
||||
const nextDelay = Math.min(2 * retryDelayMs + retryDelayMs * Math.random(), 3000 * 60);
|
||||
console.warn(`Failed to deplete reserve, trying again in ${retryDelayMs} ms`);
|
||||
setTimeout(() => this.processReserve(reserveRecord, nextDelay),
|
||||
retryDelayMs);
|
||||
this.timerGroup.after(retryDelayMs, () => this.processReserve(reserveRecord, nextDelay))
|
||||
} finally {
|
||||
this.stopOperation(opId);
|
||||
}
|
||||
@ -1039,8 +1033,7 @@ export class Wallet {
|
||||
retryDelayMs = 200): Promise<void> {
|
||||
if (this.processPreCoinConcurrent >= 4 || this.processPreCoinThrottle[preCoin.exchangeBaseUrl]) {
|
||||
console.log("delaying processPreCoin");
|
||||
setTimeout(() => this.processPreCoin(preCoin, Math.min(retryDelayMs * 2, 5 * 60 * 1000)),
|
||||
retryDelayMs);
|
||||
this.timerGroup.after(retryDelayMs, () => this.processPreCoin(preCoin, Math.min(retryDelayMs * 2, 5 * 60 * 1000)));
|
||||
return;
|
||||
}
|
||||
console.log("executing processPreCoin");
|
||||
@ -1098,12 +1091,11 @@ export class Wallet {
|
||||
"ms", e);
|
||||
// exponential backoff truncated at one minute
|
||||
const nextRetryDelayMs = Math.min(retryDelayMs * 2, 5 * 60 * 1000);
|
||||
setTimeout(() => this.processPreCoin(preCoin, nextRetryDelayMs),
|
||||
retryDelayMs);
|
||||
this.timerGroup.after(retryDelayMs, () => this.processPreCoin(preCoin, nextRetryDelayMs))
|
||||
|
||||
const currentThrottle = this.processPreCoinThrottle[preCoin.exchangeBaseUrl] || 0;
|
||||
this.processPreCoinThrottle[preCoin.exchangeBaseUrl] = currentThrottle + 1;
|
||||
setTimeout(() => {this.processPreCoinThrottle[preCoin.exchangeBaseUrl]--; }, retryDelayMs);
|
||||
this.timerGroup.after(retryDelayMs, () => {this.processPreCoinThrottle[preCoin.exchangeBaseUrl]--; });
|
||||
} finally {
|
||||
this.processPreCoinConcurrent--;
|
||||
}
|
||||
@ -2335,4 +2327,10 @@ export class Wallet {
|
||||
return await this.q().iter(Stores.reserves).filter((r) => r.hasPayback).toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop ongoing processing.
|
||||
*/
|
||||
stop() {
|
||||
this.timerGroup.stopCurrentAndFutureTimers();
|
||||
}
|
||||
}
|
||||
|
@ -526,7 +526,7 @@ function openExtensionPage(page: string) {
|
||||
|
||||
|
||||
function openTab(page: string) {
|
||||
return (evt) => {
|
||||
return (evt: React.SyntheticEvent<any>) => {
|
||||
evt.preventDefault();
|
||||
chrome.tabs.create({
|
||||
url: page,
|
||||
|
@ -246,9 +246,9 @@ function handleMessage(db: IDBDatabase,
|
||||
}
|
||||
}
|
||||
|
||||
async function dispatch(db: IDBDatabase, wallet: Wallet, req: any, sender: any, sendResponse: any): Promise<void> {
|
||||
async function dispatch(wallet: Wallet, req: any, sender: any, sendResponse: any): Promise<void> {
|
||||
try {
|
||||
const p = handleMessage(db, wallet, sender, req.type, req.detail);
|
||||
const p = handleMessage(wallet.db, wallet, sender, req.type, req.detail);
|
||||
const r = await p;
|
||||
try {
|
||||
sendResponse(r);
|
||||
@ -421,6 +421,38 @@ function clearRateLimitCache() {
|
||||
rateLimitCache = {};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Currently active wallet instance. Might be unloaded and
|
||||
* re-instantiated when the database is reset.
|
||||
*/
|
||||
let currentWallet: Wallet|undefined;
|
||||
|
||||
|
||||
async function reinitWallet() {
|
||||
if (currentWallet) {
|
||||
currentWallet.stop();
|
||||
currentWallet = undefined;
|
||||
}
|
||||
chrome.browserAction.setBadgeText({ text: "" });
|
||||
const badge = new ChromeBadge();
|
||||
let db: IDBDatabase;
|
||||
try {
|
||||
db = await openTalerDb();
|
||||
} catch (e) {
|
||||
console.error("could not open database", e);
|
||||
return;
|
||||
}
|
||||
const http = new BrowserHttpLib();
|
||||
const notifier = new ChromeNotifier();
|
||||
console.log("setting wallet");
|
||||
const wallet = new Wallet(db, http, badge, notifier);
|
||||
// Useful for debugging in the background page.
|
||||
(window as any).talerWallet = wallet;
|
||||
currentWallet = wallet;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Main function to run for the WebExtension backend.
|
||||
*
|
||||
@ -438,9 +470,6 @@ export async function wxMain() {
|
||||
logging.record("error", m + error, undefined, source || "(unknown)", lineno || 0, colno || 0);
|
||||
};
|
||||
|
||||
chrome.browserAction.setBadgeText({ text: "" });
|
||||
const badge = new ChromeBadge();
|
||||
|
||||
chrome.tabs.query({}, (tabs) => {
|
||||
for (const tab of tabs) {
|
||||
if (!tab.url || !tab.id) {
|
||||
@ -514,29 +543,28 @@ export async function wxMain() {
|
||||
|
||||
chrome.extension.getBackgroundPage().setInterval(clearRateLimitCache, 5000);
|
||||
|
||||
let db: IDBDatabase;
|
||||
try {
|
||||
db = await openTalerDb();
|
||||
} catch (e) {
|
||||
console.error("could not open database", e);
|
||||
return;
|
||||
}
|
||||
const http = new BrowserHttpLib();
|
||||
const notifier = new ChromeNotifier();
|
||||
console.log("setting wallet");
|
||||
const wallet = new Wallet(db, http, badge!, notifier);
|
||||
// Useful for debugging in the background page.
|
||||
(window as any).talerWallet = wallet;
|
||||
reinitWallet();
|
||||
|
||||
// Handlers for messages coming directly from the content
|
||||
// script on the page
|
||||
chrome.runtime.onMessage.addListener((req, sender, sendResponse) => {
|
||||
dispatch(db, wallet, req, sender, sendResponse);
|
||||
const wallet = currentWallet;
|
||||
if (!wallet) {
|
||||
console.warn("wallet not available while handling message");
|
||||
console.warn("dropped request message was", req);
|
||||
return;
|
||||
}
|
||||
dispatch(wallet, req, sender, sendResponse);
|
||||
return true;
|
||||
});
|
||||
|
||||
|
||||
// Handlers for catching HTTP requests
|
||||
chrome.webRequest.onHeadersReceived.addListener((details) => {
|
||||
const wallet = currentWallet;
|
||||
if (!wallet) {
|
||||
console.warn("wallet not available while handling header");
|
||||
}
|
||||
if (details.statusCode === 402) {
|
||||
console.log(`got 402 from ${details.url}`);
|
||||
return handleHttpPayment(details.responseHeaders || [],
|
||||
@ -559,9 +587,15 @@ function openTalerDb(): Promise<IDBDatabase> {
|
||||
return new Promise<IDBDatabase>((resolve, reject) => {
|
||||
const req = indexedDB.open(DB_NAME, DB_VERSION);
|
||||
req.onerror = (e) => {
|
||||
console.log("taler database error", e);
|
||||
reject(e);
|
||||
};
|
||||
req.onsuccess = (e) => {
|
||||
req.result.onversionchange = (evt: IDBVersionChangeEvent) => {
|
||||
console.log(`handling live db version change from ${evt.oldVersion} to ${evt.newVersion}`);
|
||||
req.result.close();
|
||||
reinitWallet();
|
||||
};
|
||||
resolve(req.result);
|
||||
};
|
||||
req.onupgradeneeded = (e) => {
|
||||
|
Loading…
Reference in New Issue
Block a user