Skip to content

Commit 87e1704

Browse files
🧹 Make retry a stable feature (LayerZero-Labs#622)
1 parent 7db7fc5 commit 87e1704

File tree

4 files changed

+84
-170
lines changed

4 files changed

+84
-170
lines changed

‎.changeset/pretty-suits-enjoy.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@layerzerolabs/devtools": patch
3+
"@layerzerolabs/toolbox-hardhat": patch
4+
---
5+
6+
Remove LZ_ENABLE_EXPERIMENTAL_RETRY feature flag

‎EXPERIMENTAL.md

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@ By default, the RPC calls that check the current state of your contracts are exe
2424

2525
Parallel execution can improve the speed of this process significantly. However, since it requires a large number of RPC calls to be executed simultaneously, it can result in `HTTP 429 Too Many Requests` RPC errors when used with public RPCs.
2626

27-
In general, we recommend combining this feature with <a href="#automatic-retries">automatic retries</a>.
28-
2927
### To enable
3028

3129
`LZ_ENABLE_EXPERIMENTAL_PARALLEL_EXECUTION=1`
@@ -34,18 +32,6 @@ In general, we recommend combining this feature with <a href="#automatic-retries
3432

3533
`LZ_ENABLE_EXPERIMENTAL_PARALLEL_EXECUTION=`
3634

37-
## Automatic retries <a id="automatic-retries"></a>
38-
39-
By default, the RPC calls that check the current state of your contracts are executed without retrying any failed requests. This feature flag enables an exponential backoff retry functionality on the RPC reads.
40-
41-
### To enable
42-
43-
`LZ_ENABLE_EXPERIMENTAL_RETRY=1`
44-
45-
### To disable
46-
47-
`LZ_ENABLE_EXPERIMENTAL_RETRY=`
48-
4935
## Batched transaction sending <a id="batched-send"></a>
5036

5137
Some signers might support batched transaction sending (e.g. Gnosis Safe signer). If turned on, this feature flag will make use of the batched sending. If this feature flag is on and batched sending is not available, regular sending will be used instead.

‎packages/devtools/src/common/retry.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,7 @@ export const createDefaultRetryHandler = (loggerName: string = 'AsyncRetriable')
5555
}
5656

5757
export const AsyncRetriable = ({
58-
// We'll feature flag this functionality for the time being
59-
enabled = !!process.env.LZ_ENABLE_EXPERIMENTAL_RETRY,
58+
enabled = true,
6059
maxDelay,
6160
numAttempts = 3,
6261
onRetry = createDefaultRetryHandler(),

‎packages/devtools/test/common/retry.test.ts

Lines changed: 77 additions & 154 deletions
Original file line numberDiff line numberDiff line change
@@ -2,185 +2,108 @@ import { AsyncRetriable } from '@/common/retry'
22

33
describe('common/retry', () => {
44
describe('AsyncRetriable', () => {
5-
describe('when LZ_ENABLE_EXPERIMENTAL_RETRY is off', () => {
6-
beforeAll(() => {
7-
// We'll enable the AsyncRetriable for these tests
8-
process.env.LZ_ENABLE_EXPERIMENTAL_RETRY = ''
9-
})
10-
11-
it('shoult not retry', async () => {
12-
const error = new Error('Told ya')
13-
const handleRetry = jest.fn()
14-
const mock = jest.fn().mockRejectedValue(error)
15-
16-
class WithAsyncRetriable {
17-
@AsyncRetriable({ onRetry: handleRetry })
18-
async iAlwaysFail(value: string) {
19-
return mock(value)
20-
}
5+
it('should retry a method call 3 times by default', async () => {
6+
const error = new Error('Told ya')
7+
const mock = jest.fn().mockRejectedValue(error)
8+
9+
class WithAsyncRetriable {
10+
@AsyncRetriable()
11+
async iAlwaysFail(value: string) {
12+
return mock(value)
2113
}
14+
}
2215

23-
await expect(new WithAsyncRetriable().iAlwaysFail('y')).rejects.toBe(error)
16+
await expect(new WithAsyncRetriable().iAlwaysFail('y')).rejects.toBe(error)
2417

25-
expect(mock).toHaveBeenCalledTimes(1)
26-
expect(handleRetry).not.toHaveBeenCalled()
27-
})
28-
29-
it('should retry if enabled is set to true', async () => {
30-
const error = new Error('Told ya')
31-
const mock = jest.fn().mockRejectedValue(error)
32-
33-
class WithAsyncRetriable {
34-
@AsyncRetriable({ enabled: true })
35-
async iAlwaysFail(value: string) {
36-
return mock(value)
37-
}
38-
}
39-
40-
await expect(new WithAsyncRetriable().iAlwaysFail('y')).rejects.toBe(error)
41-
42-
expect(mock).toHaveBeenCalledTimes(3)
43-
expect(mock).toHaveBeenNthCalledWith(1, 'y')
44-
expect(mock).toHaveBeenNthCalledWith(2, 'y')
45-
expect(mock).toHaveBeenNthCalledWith(3, 'y')
46-
})
47-
48-
it('should have access to non-prototype properties', async () => {
49-
const mock = jest.fn().mockResolvedValue(true)
18+
expect(mock).toHaveBeenCalledTimes(3)
19+
expect(mock).toHaveBeenNthCalledWith(1, 'y')
20+
expect(mock).toHaveBeenNthCalledWith(2, 'y')
21+
expect(mock).toHaveBeenNthCalledWith(3, 'y')
22+
})
5023

51-
class WithAsyncRetriable {
52-
constructor(protected readonly property = 7) {}
24+
it('should retry a method call N times if numAttempts is specified', async () => {
25+
const error = new Error('Told ya')
26+
const mock = jest.fn().mockRejectedValue(error)
5327

54-
@AsyncRetriable({ enabled: true })
55-
async iHaveAccessToProperties() {
56-
return mock(this.property)
57-
}
28+
class WithAsyncRetriable {
29+
@AsyncRetriable({ numAttempts: 2 })
30+
async iAlwaysFail(value: string) {
31+
return mock(value)
5832
}
33+
}
5934

60-
await expect(new WithAsyncRetriable().iHaveAccessToProperties()).resolves.toBe(true)
35+
await expect(new WithAsyncRetriable().iAlwaysFail('y')).rejects.toBe(error)
6136

62-
expect(mock).toHaveBeenCalledTimes(1)
63-
expect(mock).toHaveBeenNthCalledWith(1, 7)
64-
})
37+
expect(mock).toHaveBeenCalledTimes(2)
6538
})
6639

67-
describe('when LZ_ENABLE_EXPERIMENTAL_RETRY is on', () => {
68-
beforeAll(() => {
69-
// We'll enable the AsyncRetriable for these tests
70-
process.env.LZ_ENABLE_EXPERIMENTAL_RETRY = '1'
71-
})
72-
73-
afterAll(() => {
74-
process.env.LZ_ENABLE_EXPERIMENTAL_RETRY = ''
75-
})
76-
77-
it('should retry a method call 3 times by default', async () => {
78-
const error = new Error('Told ya')
79-
const mock = jest.fn().mockRejectedValue(error)
80-
81-
class WithAsyncRetriable {
82-
@AsyncRetriable()
83-
async iAlwaysFail(value: string) {
84-
return mock(value)
85-
}
40+
it('should stop retrying if the onRetry handler returns false', async () => {
41+
const error = new Error('Told ya')
42+
const mock = jest.fn().mockRejectedValue(error)
43+
const handleRetry = jest
44+
.fn()
45+
// We check that if we return undefined/void we'll keep trying
46+
.mockReturnValueOnce(undefined)
47+
// We check that if we return true we keep trying
48+
.mockReturnValueOnce(true)
49+
// After the third attempt we return false
50+
.mockReturnValueOnce(false)
51+
52+
class WithAsyncRetriable {
53+
@AsyncRetriable({ numAttempts: 10_000, onRetry: handleRetry })
54+
async iAlwaysFail(value: string) {
55+
return mock(value)
8656
}
57+
}
8758

88-
await expect(new WithAsyncRetriable().iAlwaysFail('y')).rejects.toBe(error)
89-
90-
expect(mock).toHaveBeenCalledTimes(3)
91-
expect(mock).toHaveBeenNthCalledWith(1, 'y')
92-
expect(mock).toHaveBeenNthCalledWith(2, 'y')
93-
expect(mock).toHaveBeenNthCalledWith(3, 'y')
94-
})
59+
await expect(new WithAsyncRetriable().iAlwaysFail('y')).rejects.toBe(error)
9560

96-
it('should retry a method call N times if numAttempts is specified', async () => {
97-
const error = new Error('Told ya')
98-
const mock = jest.fn().mockRejectedValue(error)
61+
expect(mock).toHaveBeenCalledTimes(3)
62+
expect(handleRetry).toHaveBeenCalledTimes(3)
63+
})
9964

100-
class WithAsyncRetriable {
101-
@AsyncRetriable({ numAttempts: 2 })
102-
async iAlwaysFail(value: string) {
103-
return mock(value)
104-
}
105-
}
65+
it('should call the onRetry callback if provided', async () => {
66+
const error = new Error('Told ya')
67+
const handleRetry = jest.fn()
68+
const mock = jest.fn().mockRejectedValue(error)
10669

107-
await expect(new WithAsyncRetriable().iAlwaysFail('y')).rejects.toBe(error)
108-
109-
expect(mock).toHaveBeenCalledTimes(2)
110-
})
111-
112-
it('should stop retrying if the onRetry handler returns false', async () => {
113-
const error = new Error('Told ya')
114-
const mock = jest.fn().mockRejectedValue(error)
115-
const handleRetry = jest
116-
.fn()
117-
// We check that if we return undefined/void we'll keep trying
118-
.mockReturnValueOnce(undefined)
119-
// We check that if we return true we keep trying
120-
.mockReturnValueOnce(true)
121-
// After the third attempt we return false
122-
.mockReturnValueOnce(false)
123-
124-
class WithAsyncRetriable {
125-
@AsyncRetriable({ numAttempts: 10_000, onRetry: handleRetry })
126-
async iAlwaysFail(value: string) {
127-
return mock(value)
128-
}
70+
class WithAsyncRetriable {
71+
@AsyncRetriable({ onRetry: handleRetry })
72+
async iAlwaysFail(value: string) {
73+
return mock(value)
12974
}
75+
}
13076

131-
await expect(new WithAsyncRetriable().iAlwaysFail('y')).rejects.toBe(error)
77+
const withAsyncRetriable = new WithAsyncRetriable()
13278

133-
expect(mock).toHaveBeenCalledTimes(3)
134-
expect(handleRetry).toHaveBeenCalledTimes(3)
135-
})
79+
await expect(withAsyncRetriable.iAlwaysFail('y')).rejects.toBe(error)
13680

137-
it('should call the onRetry callback if provided', async () => {
138-
const error = new Error('Told ya')
139-
const handleRetry = jest.fn()
140-
const mock = jest.fn().mockRejectedValue(error)
81+
expect(handleRetry).toHaveBeenCalledTimes(3)
82+
expect(handleRetry).toHaveBeenNthCalledWith(1, 1, 3, error, withAsyncRetriable, 'iAlwaysFail', ['y'])
83+
expect(handleRetry).toHaveBeenNthCalledWith(2, 2, 3, error, withAsyncRetriable, 'iAlwaysFail', ['y'])
84+
expect(handleRetry).toHaveBeenNthCalledWith(3, 3, 3, error, withAsyncRetriable, 'iAlwaysFail', ['y'])
85+
})
14186

142-
class WithAsyncRetriable {
143-
@AsyncRetriable({ onRetry: handleRetry })
144-
async iAlwaysFail(value: string) {
145-
return mock(value)
146-
}
147-
}
87+
it('should resolve if the method resolves within the specified number of attempts', async () => {
88+
const error = new Error('Told ya')
89+
const value = {}
90+
const handleRetry = jest.fn()
91+
const mock = jest.fn().mockRejectedValueOnce(error).mockRejectedValueOnce(error).mockResolvedValue(value)
14892

149-
const withAsyncRetriable = new WithAsyncRetriable()
150-
151-
await expect(withAsyncRetriable.iAlwaysFail('y')).rejects.toBe(error)
152-
153-
expect(handleRetry).toHaveBeenCalledTimes(3)
154-
expect(handleRetry).toHaveBeenNthCalledWith(1, 1, 3, error, withAsyncRetriable, 'iAlwaysFail', ['y'])
155-
expect(handleRetry).toHaveBeenNthCalledWith(2, 2, 3, error, withAsyncRetriable, 'iAlwaysFail', ['y'])
156-
expect(handleRetry).toHaveBeenNthCalledWith(3, 3, 3, error, withAsyncRetriable, 'iAlwaysFail', ['y'])
157-
})
158-
159-
it('should resolve if the method resolves within the specified number of attempts', async () => {
160-
const error = new Error('Told ya')
161-
const value = {}
162-
const handleRetry = jest.fn()
163-
const mock = jest
164-
.fn()
165-
.mockRejectedValueOnce(error)
166-
.mockRejectedValueOnce(error)
167-
.mockResolvedValue(value)
168-
169-
class WithAsyncRetriable {
170-
@AsyncRetriable({ onRetry: handleRetry })
171-
async iAlwaysFail(value: string) {
172-
return mock(value)
173-
}
93+
class WithAsyncRetriable {
94+
@AsyncRetriable({ onRetry: handleRetry })
95+
async iAlwaysFail(value: string) {
96+
return mock(value)
17497
}
98+
}
17599

176-
const withAsyncRetriable = new WithAsyncRetriable()
100+
const withAsyncRetriable = new WithAsyncRetriable()
177101

178-
await expect(withAsyncRetriable.iAlwaysFail('y')).resolves.toBe(value)
102+
await expect(withAsyncRetriable.iAlwaysFail('y')).resolves.toBe(value)
179103

180-
expect(handleRetry).toHaveBeenCalledTimes(2)
181-
expect(handleRetry).toHaveBeenNthCalledWith(1, 1, 3, error, withAsyncRetriable, 'iAlwaysFail', ['y'])
182-
expect(handleRetry).toHaveBeenNthCalledWith(2, 2, 3, error, withAsyncRetriable, 'iAlwaysFail', ['y'])
183-
})
104+
expect(handleRetry).toHaveBeenCalledTimes(2)
105+
expect(handleRetry).toHaveBeenNthCalledWith(1, 1, 3, error, withAsyncRetriable, 'iAlwaysFail', ['y'])
106+
expect(handleRetry).toHaveBeenNthCalledWith(2, 2, 3, error, withAsyncRetriable, 'iAlwaysFail', ['y'])
184107
})
185108
})
186109
})

0 commit comments

Comments
 (0)