Fix hanging IORedis connections; add extollo.wontstop debugging helper

This commit is contained in:
Garrett Mills 2022-03-29 02:30:48 -05:00
parent 8f08b94f74
commit cdecb7e628
5 changed files with 43 additions and 11 deletions

View File

@ -74,12 +74,14 @@
"@types/chai": "^4.2.22", "@types/chai": "^4.2.22",
"@types/mocha": "^9.0.0", "@types/mocha": "^9.0.0",
"@types/sinon": "^10.0.6", "@types/sinon": "^10.0.6",
"@types/wtfnode": "^0.7.0",
"@typescript-eslint/eslint-plugin": "^4.26.0", "@typescript-eslint/eslint-plugin": "^4.26.0",
"@typescript-eslint/parser": "^4.26.0", "@typescript-eslint/parser": "^4.26.0",
"chai": "^4.3.4", "chai": "^4.3.4",
"eslint": "^7.27.0", "eslint": "^7.27.0",
"mocha": "^9.1.3", "mocha": "^9.1.3",
"sinon": "^12.0.1" "sinon": "^12.0.1",
"wtfnode": "^0.9.1"
}, },
"extollo": { "extollo": {
"discover": true, "discover": true,

View File

@ -21,6 +21,7 @@ specifiers:
'@types/sinon': ^10.0.6 '@types/sinon': ^10.0.6
'@types/ssh2': ^0.5.46 '@types/ssh2': ^0.5.46
'@types/uuid': ^8.3.0 '@types/uuid': ^8.3.0
'@types/wtfnode': ^0.7.0
'@typescript-eslint/eslint-plugin': ^4.26.0 '@typescript-eslint/eslint-plugin': ^4.26.0
'@typescript-eslint/parser': ^4.26.0 '@typescript-eslint/parser': ^4.26.0
bcrypt: ^5.0.1 bcrypt: ^5.0.1
@ -50,6 +51,7 @@ specifiers:
typedoc-plugin-sourcefile-url: ^1.0.6 typedoc-plugin-sourcefile-url: ^1.0.6
typescript: ^4.2.3 typescript: ^4.2.3
uuid: ^8.3.2 uuid: ^8.3.2
wtfnode: ^0.9.1
zod: ^3.11.6 zod: ^3.11.6
dependencies: dependencies:
@ -99,12 +101,14 @@ devDependencies:
'@types/chai': 4.2.22 '@types/chai': 4.2.22
'@types/mocha': 9.0.0 '@types/mocha': 9.0.0
'@types/sinon': 10.0.6 '@types/sinon': 10.0.6
'@types/wtfnode': 0.7.0
'@typescript-eslint/eslint-plugin': 4.26.0_942c48837be95e76bb4156c78358cdbe '@typescript-eslint/eslint-plugin': 4.26.0_942c48837be95e76bb4156c78358cdbe
'@typescript-eslint/parser': 4.26.0_eslint@7.27.0+typescript@4.2.3 '@typescript-eslint/parser': 4.26.0_eslint@7.27.0+typescript@4.2.3
chai: 4.3.4 chai: 4.3.4
eslint: 7.27.0 eslint: 7.27.0
mocha: 9.1.3 mocha: 9.1.3
sinon: 12.0.1 sinon: 12.0.1
wtfnode: 0.9.1
packages: packages:
@ -447,6 +451,10 @@ packages:
resolution: {integrity: sha512-0LbEEx1zxrYB3pgpd1M5lEhLcXjKJnYghvhTRgaBeUivLHMDM1TzF3IJ6hXU2+8uA4Xz+5BA63mtZo5DjVT8iA==} resolution: {integrity: sha512-0LbEEx1zxrYB3pgpd1M5lEhLcXjKJnYghvhTRgaBeUivLHMDM1TzF3IJ6hXU2+8uA4Xz+5BA63mtZo5DjVT8iA==}
dev: false dev: false
/@types/wtfnode/0.7.0:
resolution: {integrity: sha512-kdBHgE9+M1Os7UqWZtiLhKye5reFl8cPBYyCsP2fatwZRz7F7GdIxIHZ20Kkc0hYBfbXE+lzPOTUU1I0qgjtHA==}
dev: true
/@typescript-eslint/eslint-plugin/4.26.0_942c48837be95e76bb4156c78358cdbe: /@typescript-eslint/eslint-plugin/4.26.0_942c48837be95e76bb4156c78358cdbe:
resolution: {integrity: sha512-yA7IWp+5Qqf+TLbd8b35ySFOFzUfL7i+4If50EqvjT6w35X8Lv0eBHb6rATeWmucks37w+zV+tWnOXI9JlG6Eg==} resolution: {integrity: sha512-yA7IWp+5Qqf+TLbd8b35ySFOFzUfL7i+4If50EqvjT6w35X8Lv0eBHb6rATeWmucks37w+zV+tWnOXI9JlG6Eg==}
engines: {node: ^10.12.0 || >=12.0.0} engines: {node: ^10.12.0 || >=12.0.0}
@ -3112,6 +3120,11 @@ packages:
/wrappy/1.0.2: /wrappy/1.0.2:
resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=} resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=}
/wtfnode/0.9.1:
resolution: {integrity: sha512-Ip6C2KeQPl/F3aP1EfOnPoQk14Udd9lffpoqWDNH3Xt78svxPbv53ngtmtfI0q2Te3oTq79XKTnRNXVIn/GsPA==}
hasBin: true
dev: true
/xtend/4.0.2: /xtend/4.0.2:
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
engines: {node: '>=0.4'} engines: {node: '>=0.4'}

View File

@ -8,13 +8,14 @@ import {
StandardLogger, StandardLogger,
universalPath, universalPath,
UniversalPath, UniversalPath,
FileLogger,
ifDebugging,
} from '../util' } from '../util'
import {Logging} from '../service/Logging' import {Logging} from '../service/Logging'
import {RunLevelErrorHandler} from './RunLevelErrorHandler' import {RunLevelErrorHandler} from './RunLevelErrorHandler'
import {Unit, UnitStatus} from './Unit' import {Unit, UnitStatus} from './Unit'
import * as dotenv from 'dotenv' import * as dotenv from 'dotenv'
import {CacheFactory} from '../support/cache/CacheFactory' import {CacheFactory} from '../support/cache/CacheFactory'
import {FileLogger} from '../util/logging/FileLogger'
/** /**
* Helper function that resolves and infers environment variable values. * Helper function that resolves and infers environment variable values.
@ -260,6 +261,13 @@ export class Application extends Container {
async run(): Promise<void> { async run(): Promise<void> {
try { try {
await this.up() await this.up()
ifDebugging('extollo.wontstop', () => {
setTimeout(() => {
import('wtfnode').then(wtf => wtf.dump())
}, 1000)
})
await this.down() await this.down()
} catch (e: unknown) { } catch (e: unknown) {
if ( e instanceof Error ) { if ( e instanceof Error ) {

View File

@ -121,7 +121,8 @@ export class RedisBus implements EventBus {
} }
down(): Awaitable<void> { down(): Awaitable<void> {
this.subscriberConnection?.disconnect() // The Redis service will clean up the connections when the framework exits,
this.publisherConnection?.disconnect() // so we don't need to do anything here.
return undefined
} }
} }

View File

@ -4,7 +4,7 @@ import * as IORedis from 'ioredis'
import {RedisOptions} from 'ioredis' import {RedisOptions} from 'ioredis'
import {Logging} from '../../service/Logging' import {Logging} from '../../service/Logging'
import {Unit} from '../../lifecycle/Unit' import {Unit} from '../../lifecycle/Unit'
import {AsyncPipe} from '../../util' import {AsyncPipe, Collection} from '../../util'
export {RedisOptions} from 'ioredis' export {RedisOptions} from 'ioredis'
@ -28,19 +28,25 @@ export class Redis extends Unit {
*/ */
private connection?: IORedis.Redis private connection?: IORedis.Redis
/**
* Collection of all Redis connections opened by this service.
* We keep track of these here so we can make sure -all- of them get closed
* when the framework tries to shut down.
* @private
*/
private spawnedConnections: Collection<IORedis.Redis> = new Collection()
async up(): Promise<void> { async up(): Promise<void> {
this.logging.info('Attempting initial connection to Redis...') this.logging.info('Attempting initial connection to Redis...')
this.logging.debug('Config:')
this.logging.debug(Config)
this.logging.debug(this.config) this.logging.debug(this.config)
await this.getConnection() await this.getConnection()
} }
async down(): Promise<void> { async down(): Promise<void> {
this.logging.info('Disconnecting Redis...') this.logging.info('Disconnecting Redis...')
if ( this.connection?.status === 'ready' ) { await this.spawnedConnections
await this.connection.disconnect() .where('status', '=', 'ready')
} .awaitMapCall('disconnect')
} }
/** /**
@ -59,7 +65,9 @@ export class Redis extends Unit {
*/ */
public async getNewConnection(): Promise<IORedis.Redis> { public async getNewConnection(): Promise<IORedis.Redis> {
const options = this.config.get('redis.connection') as RedisOptions const options = this.config.get('redis.connection') as RedisOptions
return new IORedis(options) const inst = new IORedis(options)
this.spawnedConnections.push(inst)
return inst
} }
/** /**