From 4fb9e4848364550d967b7eef768db5aaa2981305 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Renaudeau?= Date: Sat, 26 May 2018 14:01:16 +0200 Subject: [PATCH] add a retry mecanism on the open() --- src/helpers/deviceAccess.js | 9 ++++++--- src/helpers/promise.js | 27 +++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 src/helpers/promise.js diff --git a/src/helpers/deviceAccess.js b/src/helpers/deviceAccess.js index 68a74958..637faaa4 100644 --- a/src/helpers/deviceAccess.js +++ b/src/helpers/deviceAccess.js @@ -1,7 +1,8 @@ // @flow import createSemaphore from 'semaphore' import type Transport from '@ledgerhq/hw-transport' -import CommNodeHid from '@ledgerhq/hw-transport-node-hid' +import TransportNodeHid from '@ledgerhq/hw-transport-node-hid' +import { retry } from './promise' // all open to device must use openDevice so we can prevent race conditions // and guarantee we do one device access at a time. It also will handle the .close() @@ -18,17 +19,19 @@ export const withDevice: WithDevice = devicePath => { `deviceAccess is only expected to be used in process 'devices'. Any other usage may lead to race conditions. (Got: '${FORK_TYPE}')`, ) } + const sem = semaphorePerDevice[devicePath] || (semaphorePerDevice[devicePath] = createSemaphore(1)) + return job => takeSemaphorePromise(sem, async () => { - const t = await CommNodeHid.open(devicePath) + const t = await retry(() => TransportNodeHid.open(devicePath)) try { const res = await job(t) // $FlowFixMe return res } finally { - t.close() + await t.close() } }) } diff --git a/src/helpers/promise.js b/src/helpers/promise.js new file mode 100644 index 00000000..210c94ae --- /dev/null +++ b/src/helpers/promise.js @@ -0,0 +1,27 @@ +// @flow + +// small utilities for Promises + +export const delay = (ms: number): Promise => new Promise(f => setTimeout(f, ms)) + +const defaults = { + maxRetry: 4, + interval: 300, + intervalMultiplicator: 1.5, +} +export function retry(f: () => Promise, options?: $Shape): Promise { + const { maxRetry, interval, intervalMultiplicator } = { ...defaults, ...options } + + return rec(maxRetry, interval) + + function rec(remainingTry, interval) { + const result = f() + if (remainingTry <= 0) { + return result + } + // In case of failure, wait the interval, retry the action + return result.catch(() => + delay(interval).then(() => rec(remainingTry - 1, interval * intervalMultiplicator)), + ) + } +}