Skip to content

Commit

Permalink
feat: possible correction in 'timeout'
Browse files Browse the repository at this point in the history
  • Loading branch information
Bugs5382 committed Feb 13, 2024
1 parent 81864e6 commit 9d96944
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 14 deletions.
23 changes: 18 additions & 5 deletions __tests__/hl7.end2end.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,21 @@ describe('node hl7 end to end - client', () => {
describe('server/client failure checks', () => {
test('...host does not exist, error out', async () => {

const client = new Client({ host: '0.0.0.0' })
const outbound = client.createConnection({ port: 1234, maxConnectionAttempts: 1 }, async () => {})
const client = new Client({ host: '0.0.0.0', connectionTimeout: 1000 })
const outbound = client.createConnection({ port: 1234 }, async () => {})

await expectEvent(outbound, 'client.timeout')

const error = await expectEvent(outbound, 'client.error')
expect(error.code).toBe('ECONNREFUSED')
})

test('...tls host does not exist, error out', async () => {

const client = new Client({ host: '0.0.0.0', connectionTimeout: 1000, tls: { rejectUnauthorized: false } })
const outbound = client.createConnection({ port: 1234 }, async () => {})

await expectEvent(outbound, 'client.timeout')

const error = await expectEvent(outbound, 'client.error')
expect(error.code).toBe('ECONNREFUSED')
Expand Down Expand Up @@ -219,14 +232,14 @@ describe('node hl7 end to end - client', () => {

await outbound.sendMessage(message)

dfd.promise
//dfd.promise

await outbound.close()
await inbound.close()
//await inbound.close()

client.closeAll()

})
}, 70000)

})

Expand Down
38 changes: 29 additions & 9 deletions src/client/connection.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import EventEmitter from 'node:events'
import net, { Socket } from 'node:net'
import { clearTimeout } from 'node:timers'
import net, {Socket} from 'node:net'
import {clearTimeout} from 'node:timers'
import tls from 'node:tls'
import Batch from '../builder/batch.js'
import FileBatch from '../builder/fileBatch.js'
import Message from '../builder/message.js'
import { PROTOCOL_MLLP_FOOTER, PROTOCOL_MLLP_HEADER } from '../utils/constants.js'
import { ReadyState } from '../utils/enum.js'
import { HL7FatalError } from '../utils/exception.js'
import { ClientListenerOptions, normalizeClientListenerOptions, OutboundHandler } from '../utils/normalizedClient.js'
import { createDeferred, Deferred, expBackoff } from '../utils/utils.js'
import { Client } from './client.js'
import { InboundResponse } from './module/inboundResponse.js'
import {PROTOCOL_MLLP_FOOTER, PROTOCOL_MLLP_HEADER} from '../utils/constants.js'
import {ReadyState} from '../utils/enum.js'
import {HL7FatalError} from '../utils/exception.js'
import {ClientListenerOptions, normalizeClientListenerOptions, OutboundHandler} from '../utils/normalizedClient.js'
import {createDeferred, Deferred, expBackoff} from '../utils/utils.js'
import {Client} from './client.js'
import {InboundResponse} from './module/inboundResponse.js'

/* eslint-disable */
export interface Connection extends EventEmitter {
Expand All @@ -29,6 +29,8 @@ export interface Connection extends EventEmitter {
on(name: 'client.error', cb: (err: any) => void): this;
/** The total sent for this connection. */
on(name: 'client.sent', cb: (number: number) => void): this;
/** The connection has timeout. Review "client.error" event for the reason. */
on(name: 'client.timeout', cb: () => void): this;
}
/* eslint-enable */

Expand All @@ -45,8 +47,12 @@ export class Connection extends EventEmitter implements Connection {
/** @internal */
private _retryCount: number
/** @internal */
private _retryTimeoutCount: number
/** @internal */
_retryTimer: NodeJS.Timeout | undefined
/** @internal */
_connectionTimer?: NodeJS.Timeout | undefined
/** @internal */
private _socket: Socket | undefined
/** @internal */
protected _readyState: ReadyState
Expand Down Expand Up @@ -90,7 +96,9 @@ export class Connection extends EventEmitter implements Connection {

this._pendingSetup = true
this._retryCount = 0
this._retryTimeoutCount = 0
this._retryTimer = undefined
this._connectionTimer = undefined
this._onConnect = createDeferred(true)

if (this._opt.autoConnect) {
Expand Down Expand Up @@ -307,6 +315,18 @@ export class Connection extends EventEmitter implements Connection {

let connectionError: Error | boolean | undefined

if (this._main._opt.connectionTimeout > 0 && (this._readyState === ReadyState.CONNECTED || this._readyState === ReadyState.CONNECTING)) {
if (this._retryTimeoutCount < this._main._opt.maxTimeout) {
this._connectionTimer = setTimeout(() => {
++this._retryTimeoutCount
this.emit('client.timeout')
socket.destroy()
}, this._main._opt.connectionTimeout)
} else if (this._retryTimeoutCount >= this._main._opt.maxTimeout) {
void this.close()
}
}

socket.on('error', err => {
connectionError = (connectionError != null) ? connectionError : err
})
Expand Down
23 changes: 23 additions & 0 deletions src/utils/normalizedClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ export type OutboundHandler = (res: InboundResponse) => Promise<void> | void

const DEFAULT_CLIENT_OPTS = {
encoding: 'utf-8',
connectionTimeout: 10000,
maxAttempts: 10,
maxConnectionAttempts: 10,
maxTimeout: 10,
retryHigh: 30000,
retryLow: 1000
}
Expand All @@ -26,6 +30,14 @@ const DEFAULT_LISTEN_CLIENT_OPTS = {
}

export interface ClientOptions {
/**
* How long a connection attempt checked before ending the socket and attempting again.
* Min. is 1000 (1 second) and Max. is 60000 (60 seconds.)
* Note: Less than 10 seconds could cause some serious issues.
* Use with caution.
* @default 10000
*/
connectionTimeout?: number
/** Host - You can do a FQDN or the IPv(4|6) address. */
host?: string
/** IPv4 - If this is set to true, only IPv4 address will be used and also validated upon installation from the hostname property.
Expand All @@ -46,6 +58,11 @@ export interface ClientOptions {
* @default 30
*/
maxConnectionAttempts?: number
/** The number of times a connection timeout occurs until it stops attempting and just stops.
* @since 2.1.0
* @default 10
*/
maxTimeout?: number
/** Max delay, in milliseconds, for exponential-backoff when reconnecting
* @default 30_000 */
retryHigh?: number
Expand Down Expand Up @@ -86,6 +103,7 @@ export interface ClientListenerOptions extends ClientOptions {

type ValidatedClientKeys =
| 'host'
| 'connectionTimeout'

type ValidatedClientListenerKeys =
| 'autoConnect'
Expand All @@ -94,7 +112,9 @@ type ValidatedClientListenerKeys =
| 'maxConnectionAttempts'

interface ValidatedClientOptions extends Pick<Required<ClientOptions>, ValidatedClientKeys> {
connectionTimeout: number
host: string
maxTimeout: number
retryHigh: number
retryLow: number
socket?: TcpSocketConnectOpts
Expand Down Expand Up @@ -140,6 +160,9 @@ export function normalizeClientOptions (raw?: ClientOptions): ValidatedClientOpt
props.tls = {}
}

assertNumber(props, 'connectionTimeout', 1000, 60000)
assertNumber(props, 'maxTimeout', 1, 50)

return props
}

Expand Down

0 comments on commit 9d96944

Please sign in to comment.