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 {
|
export function after(delayMs: number, callback: () => void): TimerHandle {
|
||||||
return new TimeoutHandle(setInterval(callback, delayMs));
|
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,
|
QueryRoot,
|
||||||
Store,
|
Store,
|
||||||
} from "./query";
|
} from "./query";
|
||||||
|
import {TimerGroup} from "./timer";
|
||||||
import {
|
import {
|
||||||
AmountJson,
|
AmountJson,
|
||||||
Amounts,
|
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) {
|
function isWithdrawableDenom(d: DenominationRecord) {
|
||||||
const nowSec = (new Date()).getTime() / 1000;
|
const nowSec = (new Date()).getTime() / 1000;
|
||||||
const stampWithdrawSec = getTalerStampSec(d.stampExpireWithdraw);
|
const stampWithdrawSec = getTalerStampSec(d.stampExpireWithdraw);
|
||||||
@ -583,13 +572,17 @@ interface CoinsForPaymentArgs {
|
|||||||
* The platform-independent wallet implementation.
|
* The platform-independent wallet implementation.
|
||||||
*/
|
*/
|
||||||
export class Wallet {
|
export class Wallet {
|
||||||
private db: IDBDatabase;
|
/**
|
||||||
|
* IndexedDB database used by the wallet.
|
||||||
|
*/
|
||||||
|
db: IDBDatabase;
|
||||||
private http: HttpRequestLibrary;
|
private http: HttpRequestLibrary;
|
||||||
private badge: Badge;
|
private badge: Badge;
|
||||||
private notifier: Notifier;
|
private notifier: Notifier;
|
||||||
private cryptoApi: CryptoApi;
|
private cryptoApi: CryptoApi;
|
||||||
private processPreCoinConcurrent = 0;
|
private processPreCoinConcurrent = 0;
|
||||||
private processPreCoinThrottle: {[url: string]: number} = {};
|
private processPreCoinThrottle: {[url: string]: number} = {};
|
||||||
|
private timerGroup: TimerGroup;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set of identifiers for running operations.
|
* Set of identifiers for running operations.
|
||||||
@ -613,7 +606,9 @@ export class Wallet {
|
|||||||
this.fillDefaults();
|
this.fillDefaults();
|
||||||
this.resumePendingFromDb();
|
this.resumePendingFromDb();
|
||||||
|
|
||||||
setInterval(() => this.updateExchanges(), 1000 * 60 * 15);
|
this.timerGroup = new TimerGroup();
|
||||||
|
|
||||||
|
this.timerGroup.every(1000 * 60 * 15, () => this.updateExchanges());
|
||||||
}
|
}
|
||||||
|
|
||||||
private async fillDefaults() {
|
private async fillDefaults() {
|
||||||
@ -1027,8 +1022,7 @@ export class Wallet {
|
|||||||
// random, exponential backoff truncated at 3 minutes
|
// random, exponential backoff truncated at 3 minutes
|
||||||
const nextDelay = Math.min(2 * retryDelayMs + retryDelayMs * Math.random(), 3000 * 60);
|
const nextDelay = Math.min(2 * retryDelayMs + retryDelayMs * Math.random(), 3000 * 60);
|
||||||
console.warn(`Failed to deplete reserve, trying again in ${retryDelayMs} ms`);
|
console.warn(`Failed to deplete reserve, trying again in ${retryDelayMs} ms`);
|
||||||
setTimeout(() => this.processReserve(reserveRecord, nextDelay),
|
this.timerGroup.after(retryDelayMs, () => this.processReserve(reserveRecord, nextDelay))
|
||||||
retryDelayMs);
|
|
||||||
} finally {
|
} finally {
|
||||||
this.stopOperation(opId);
|
this.stopOperation(opId);
|
||||||
}
|
}
|
||||||
@ -1039,8 +1033,7 @@ export class Wallet {
|
|||||||
retryDelayMs = 200): Promise<void> {
|
retryDelayMs = 200): Promise<void> {
|
||||||
if (this.processPreCoinConcurrent >= 4 || this.processPreCoinThrottle[preCoin.exchangeBaseUrl]) {
|
if (this.processPreCoinConcurrent >= 4 || this.processPreCoinThrottle[preCoin.exchangeBaseUrl]) {
|
||||||
console.log("delaying processPreCoin");
|
console.log("delaying processPreCoin");
|
||||||
setTimeout(() => this.processPreCoin(preCoin, Math.min(retryDelayMs * 2, 5 * 60 * 1000)),
|
this.timerGroup.after(retryDelayMs, () => this.processPreCoin(preCoin, Math.min(retryDelayMs * 2, 5 * 60 * 1000)));
|
||||||
retryDelayMs);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log("executing processPreCoin");
|
console.log("executing processPreCoin");
|
||||||
@ -1098,12 +1091,11 @@ export class Wallet {
|
|||||||
"ms", e);
|
"ms", e);
|
||||||
// exponential backoff truncated at one minute
|
// exponential backoff truncated at one minute
|
||||||
const nextRetryDelayMs = Math.min(retryDelayMs * 2, 5 * 60 * 1000);
|
const nextRetryDelayMs = Math.min(retryDelayMs * 2, 5 * 60 * 1000);
|
||||||
setTimeout(() => this.processPreCoin(preCoin, nextRetryDelayMs),
|
this.timerGroup.after(retryDelayMs, () => this.processPreCoin(preCoin, nextRetryDelayMs))
|
||||||
retryDelayMs);
|
|
||||||
|
|
||||||
const currentThrottle = this.processPreCoinThrottle[preCoin.exchangeBaseUrl] || 0;
|
const currentThrottle = this.processPreCoinThrottle[preCoin.exchangeBaseUrl] || 0;
|
||||||
this.processPreCoinThrottle[preCoin.exchangeBaseUrl] = currentThrottle + 1;
|
this.processPreCoinThrottle[preCoin.exchangeBaseUrl] = currentThrottle + 1;
|
||||||
setTimeout(() => {this.processPreCoinThrottle[preCoin.exchangeBaseUrl]--; }, retryDelayMs);
|
this.timerGroup.after(retryDelayMs, () => {this.processPreCoinThrottle[preCoin.exchangeBaseUrl]--; });
|
||||||
} finally {
|
} finally {
|
||||||
this.processPreCoinConcurrent--;
|
this.processPreCoinConcurrent--;
|
||||||
}
|
}
|
||||||
@ -2335,4 +2327,10 @@ export class Wallet {
|
|||||||
return await this.q().iter(Stores.reserves).filter((r) => r.hasPayback).toArray();
|
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) {
|
function openTab(page: string) {
|
||||||
return (evt) => {
|
return (evt: React.SyntheticEvent<any>) => {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
chrome.tabs.create({
|
chrome.tabs.create({
|
||||||
url: page,
|
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 {
|
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;
|
const r = await p;
|
||||||
try {
|
try {
|
||||||
sendResponse(r);
|
sendResponse(r);
|
||||||
@ -421,6 +421,38 @@ function clearRateLimitCache() {
|
|||||||
rateLimitCache = {};
|
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.
|
* 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);
|
logging.record("error", m + error, undefined, source || "(unknown)", lineno || 0, colno || 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
chrome.browserAction.setBadgeText({ text: "" });
|
|
||||||
const badge = new ChromeBadge();
|
|
||||||
|
|
||||||
chrome.tabs.query({}, (tabs) => {
|
chrome.tabs.query({}, (tabs) => {
|
||||||
for (const tab of tabs) {
|
for (const tab of tabs) {
|
||||||
if (!tab.url || !tab.id) {
|
if (!tab.url || !tab.id) {
|
||||||
@ -514,29 +543,28 @@ export async function wxMain() {
|
|||||||
|
|
||||||
chrome.extension.getBackgroundPage().setInterval(clearRateLimitCache, 5000);
|
chrome.extension.getBackgroundPage().setInterval(clearRateLimitCache, 5000);
|
||||||
|
|
||||||
let db: IDBDatabase;
|
reinitWallet();
|
||||||
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;
|
|
||||||
|
|
||||||
// Handlers for messages coming directly from the content
|
// Handlers for messages coming directly from the content
|
||||||
// script on the page
|
// script on the page
|
||||||
chrome.runtime.onMessage.addListener((req, sender, sendResponse) => {
|
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;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// Handlers for catching HTTP requests
|
// Handlers for catching HTTP requests
|
||||||
chrome.webRequest.onHeadersReceived.addListener((details) => {
|
chrome.webRequest.onHeadersReceived.addListener((details) => {
|
||||||
|
const wallet = currentWallet;
|
||||||
|
if (!wallet) {
|
||||||
|
console.warn("wallet not available while handling header");
|
||||||
|
}
|
||||||
if (details.statusCode === 402) {
|
if (details.statusCode === 402) {
|
||||||
console.log(`got 402 from ${details.url}`);
|
console.log(`got 402 from ${details.url}`);
|
||||||
return handleHttpPayment(details.responseHeaders || [],
|
return handleHttpPayment(details.responseHeaders || [],
|
||||||
@ -559,9 +587,15 @@ function openTalerDb(): Promise<IDBDatabase> {
|
|||||||
return new Promise<IDBDatabase>((resolve, reject) => {
|
return new Promise<IDBDatabase>((resolve, reject) => {
|
||||||
const req = indexedDB.open(DB_NAME, DB_VERSION);
|
const req = indexedDB.open(DB_NAME, DB_VERSION);
|
||||||
req.onerror = (e) => {
|
req.onerror = (e) => {
|
||||||
|
console.log("taler database error", e);
|
||||||
reject(e);
|
reject(e);
|
||||||
};
|
};
|
||||||
req.onsuccess = (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);
|
resolve(req.result);
|
||||||
};
|
};
|
||||||
req.onupgradeneeded = (e) => {
|
req.onupgradeneeded = (e) => {
|
||||||
|
Loading…
Reference in New Issue
Block a user