Skip to content

Commit c3b2ddd

Browse files
authored
Merge pull request #1 from github/should-retry
Delegate fatal socket close code handling
2 parents fb89d71 + 5979912 commit c3b2ddd

File tree

6 files changed

+101
-3
lines changed

6 files changed

+101
-3
lines changed

karma.config.cjs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,20 @@
1-
module.exports = function(config) {
1+
const ws = require('nodejs-websocket')
2+
3+
ws.createServer(function (conn) {
4+
conn.on('text', function (msg) {
5+
if (msg.startsWith('echo:')) {
6+
conn.sendText(msg.replace('echo:', ''))
7+
} else if (msg.startsWith('close:')) {
8+
const code = msg.replace('close:', '')
9+
conn.close(code, 'reason')
10+
}
11+
})
12+
conn.on('error', function (error) {
13+
if (error.code !== 'ECONNRESET') throw error
14+
})
15+
}).listen(7999)
16+
17+
module.exports = function (config) {
218
config.set({
319
frameworks: ['mocha', 'chai'],
420
files: [

package-lock.json

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"karma-mocha": "^2.0.1",
3636
"karma-mocha-reporter": "^2.2.5",
3737
"mocha": "^8.0.1",
38+
"nodejs-websocket": "^1.7.2",
3839
"rollup": "^2.21.0",
3940
"typescript": "^3.9.5"
4041
},

src/buffered-socket.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type {Socket, SocketDelegate, StableSocket} from './stable-socket'
2+
import {isFatal} from './stable-socket'
23

34
export class BufferedSocket implements Socket, SocketDelegate {
45
private buf: string[] = []
@@ -55,4 +56,8 @@ export class BufferedSocket implements Socket, SocketDelegate {
5556
socketDidReceiveMessage(socket: Socket, message: string): void {
5657
this.delegate.socketDidReceiveMessage(socket, message)
5758
}
59+
60+
socketShouldRetry(socket: Socket, code: number): boolean {
61+
return this.delegate.socketShouldRetry ? this.delegate.socketShouldRetry(socket, code) : !isFatal(code)
62+
}
5863
}

src/stable-socket.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export interface SocketDelegate {
1818
socketDidClose(socket: Socket, code?: number, reason?: string): void
1919
socketDidFinish(socket: Socket): void
2020
socketDidReceiveMessage(socket: Socket, message: string): void
21+
socketShouldRetry?(socket: Socket, code: number): boolean
2122
}
2223

2324
export class StableSocket implements Socket {
@@ -48,7 +49,10 @@ export class StableSocket implements Socket {
4849
this.socket.onclose = (event: CloseEvent) => {
4950
this.socket = null
5051
this.delegate.socketDidClose(this, event.code, event.reason)
51-
if (isFatal(event.code)) {
52+
const fatal = this.delegate.socketShouldRetry
53+
? !this.delegate.socketShouldRetry(this, event.code)
54+
: isFatal(event.code)
55+
if (fatal) {
5256
this.delegate.socketDidFinish(this)
5357
} else {
5458
setTimeout(() => this.open(), rand(100, 150))
@@ -88,7 +92,7 @@ function rand(min: number, max: number): number {
8892
return Math.random() * (max - min) + min
8993
}
9094

91-
function isFatal(code: number): boolean {
95+
export function isFatal(code: number): boolean {
9296
return code === POLICY_VIOLATION || code === INTERNAL_ERROR
9397
}
9498

test/test.js

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,70 @@
11
import {retry, timeout, wait} from '../dist/async-tasks.js'
2+
import {StableSocket} from '../dist/index.js'
3+
4+
class Delegate {
5+
constructor(fatal) {
6+
this.fatal = fatal
7+
this.states = []
8+
}
9+
socketDidOpen() {
10+
this.states.push('open')
11+
}
12+
socketDidClose() {
13+
this.states.push('closed')
14+
}
15+
socketDidFinish() {
16+
this.states.push('finished')
17+
}
18+
socketDidReceiveMessage(socket, message) {
19+
this.states.push(`msg:${message}`)
20+
}
21+
socketShouldRetry(socket, code) {
22+
return code !== this.fatal
23+
}
24+
}
25+
26+
describe('StableSocket', function () {
27+
it('invokes lifecycle delegate methods', async function () {
28+
const url = 'ws://localhost:7999'
29+
const delegate = new Delegate(0)
30+
const policy = {timeout: 100, attempts: 1, maxDelay: 100}
31+
const socket = new StableSocket(url, delegate, policy)
32+
await socket.open()
33+
assert(socket.isOpen())
34+
assert.deepEqual(['open'], delegate.states)
35+
socket.send('echo:hello')
36+
await wait(10)
37+
socket.close()
38+
assert.deepEqual(['open', 'msg:hello', 'closed', 'finished'], delegate.states)
39+
})
40+
41+
it('retries on non-fatal close code', async function () {
42+
const url = 'ws://localhost:7999'
43+
const delegate = new Delegate(0)
44+
const policy = {timeout: 100, attempts: 1, maxDelay: 100}
45+
const socket = new StableSocket(url, delegate, policy)
46+
await socket.open()
47+
assert(socket.isOpen())
48+
assert.deepEqual(['open'], delegate.states)
49+
socket.send('close:1000')
50+
await wait(200)
51+
assert.deepEqual(['open', 'closed', 'open'], delegate.states)
52+
socket.close()
53+
})
54+
55+
it('does not retry on fatal close code', async function () {
56+
const url = 'ws://localhost:7999'
57+
const delegate = new Delegate(4000)
58+
const policy = {timeout: 100, attempts: 1, maxDelay: 100}
59+
const socket = new StableSocket(url, delegate, policy)
60+
await socket.open()
61+
assert(socket.isOpen())
62+
assert.deepEqual(['open'], delegate.states)
63+
socket.send('close:4000')
64+
await wait(200)
65+
assert.deepEqual(['open', 'closed', 'finished'], delegate.states)
66+
})
67+
})
268

369
describe('async-tasks', function () {
470
describe('timeout', function () {

0 commit comments

Comments
 (0)