refactored socket initialization to better catch and emit errors and remove offending sockets

will now emit 'error', 'warn', 'fatal'  when needed so instance can track and track easier and take action (like notification)
sockets will bubble these up now.
master
David Kebler 2019-08-29 13:41:32 -07:00
parent 3bf0110164
commit 1b3d5fceb7
4 changed files with 119 additions and 38 deletions

View File

@ -1,4 +1,4 @@
{ {
"ignoreRoot": [".git","examples/ws-fio-client"], "ignoreRoot": [".git","examples/ws-fio-client"],
"watch": ["node_modules/@uci/","node_modules/@uci-utils/","src/","index.js","examples/"] "watch": ["node_modules/@uci/","node_modules/@uci-utils/","src/","index.js","examples/","test/"]
} }

View File

@ -6,12 +6,9 @@
"scripts": { "scripts": {
"fiod": "UCI_ENV=dev ./node_modules/.bin/nodemon -r esm --preserve-symlinks examples/four-in-one", "fiod": "UCI_ENV=dev ./node_modules/.bin/nodemon -r esm --preserve-symlinks examples/four-in-one",
"fio": "nodemon -r esm examples/four-in-one", "fio": "nodemon -r esm examples/four-in-one",
"dy": "node -r esm examples/dynamic", "test": "UCI_ENV=dev nodemon -r esm --preserve-symlinks test/test",
"web": "UCI_DEV=true nodemon -r esm examples/web", "mtestw": "mocha -r esm test/*.test.mjs --watch --recurse ",
"mqtt": "nodemon -r esm examples/mqtt", "mtest": "mocha -r esm test/*.test.mjs"
"ha-mqtt": "nodemon -r esm examples/ha-mqtt",
"testw": "mocha -r esm test/*.test.mjs --watch --recurse ",
"test": "mocha -r esm test/*.test.mjs"
}, },
"author": "David Kebler", "author": "David Kebler",
"license": "MIT", "license": "MIT",
@ -42,6 +39,7 @@
"@uci/socket": "^0.2.19", "@uci/socket": "^0.2.19",
"@uci/websocket": "^0.3.8", "@uci/websocket": "^0.3.8",
"await-to-js": "^2.1.1", "await-to-js": "^2.1.1",
"p-reflect": "^2.1.0",
"p-settle": "^3.1.0" "p-settle": "^3.1.0"
} }
} }

View File

@ -17,7 +17,8 @@ let log = {} // declare module wide log to be set during construction
// Community dependencies // Community dependencies
import to from 'await-to-js' import to from 'await-to-js'
import EventEmitter from 'events' import EventEmitter from 'events'
import pSettle from 'p-settle' // import pSettle from 'p-settle'
// import pReflect from 'p-reflect'
// Internal dependencies // Internal dependencies
import { processor, defaultCmds, namespaces } from './processing' import { processor, defaultCmds, namespaces } from './processing'
@ -112,26 +113,42 @@ class Base extends EventEmitter {
*/ */
async init() { async init() {
let sockets = []
let initSockets = [] let results = {}
for (let name of Object.keys(this._socket)) { let errors = {}
initSockets.push(this._initSocket(name))
sockets.push(name) const pReflect = async socket => {
try {
const value = await socket.init()
results[socket.name] = value
} catch (error) {
this.emit('error',{msg:'socket init error',error:error})// emit an error here, remove socket
let res = await this.removeSocket(socket.name)
errors[socket.name]={error:error, remove:res}
}
} }
return pSettle(initSockets).then(res => {
log.debug({ sockets: res, method:'init', line:122, msg:'response from intializing sockets via instance options'}) // console.log('in init', this._socket)
let err = [] // for (let name in this._socket) {
res.forEach((p, index) => { // let socket = this._initSocket(name)
if (p.isRejected) { // console.log(socket)
err.push({ name: sockets[index], err: p.reason }) // let [err,res] = await to(socket.init())
} // if (err) errors[name] = err
}) // results[name] = res
this._started = true // }
return err // this._started = true
// TODO if a websocket server was working then push status // return {results:results, errors:errors}
// TODO if no mqtt broker then attempt to start one
}) let sockets = []
} // init for (let name of Object.keys(this._socket)) {
sockets.push(this._initSocket(name))
}
await Promise.all(sockets.map(pReflect))
if(Object.keys(errors).length===0) errors=false
this._started = true
return {results:results, errors:errors}
}
/** /**
* addSocket - Add a socket at runtime as opposed to via the sockets option at creation * addSocket - Add a socket at runtime as opposed to via the sockets option at creation
@ -170,6 +187,20 @@ class Base extends EventEmitter {
this._socket[name].type = type this._socket[name].type = type
this._socket[name].transport = transport this._socket[name].transport = transport
this._socket[name]._packetProcess = this._packetProcess.bind(this, name) this._socket[name]._packetProcess = this._packetProcess.bind(this, name)
let bubble = (msg) => {
console.log(msg,name,this._socket[name].name)
this._socket[name].on(msg, ev => {
ev.socketName=name
this.emit(msg, ev)
})
}
const msgs = ['error','warn','fatal']
msgs.map(bubble) // bubble up any emitted errors
// do this as .then promise then addSocket doesn't need to be async before init
if (this._started) return await this._initSocket(name) if (this._started) return await this._initSocket(name)
else return `socket ${name} added` else return `socket ${name} added`
} }
@ -182,14 +213,16 @@ class Base extends EventEmitter {
*/ */
async removeSocket(name) { async removeSocket(name) {
// NOTE: uci consumers have .end renamed as .close to match socket method for convenience // NOTE: uci consumers have .end renamed as .close to match socket method for convenience
let closeError
let [err] = await to(this._socket[name].close()) let [err] = await to(this._socket[name].close())
let errmsg = {socket:this._socket[name].name, error:err, msg:'socket/consumer closed with errors but still removed'} if (err.code !== 'ERR_SERVER_NOT_RUNNING') {
if (err) log.warn(errmsg) closeError = {socket:this._socket[name].name, error:err, msg:'socket/consumer closed with errors, but removed'}
}
this.emit('warn', {msg:`socket ${name} has been removed`, socket:this._socket[name].opts})
delete this._socket[name] delete this._socket[name]
return err ? errmsg : 'success' return closeError ? closeError : 'success'
} }
getSocket(name) { getSocket(name) {
if (name) return this._socket[name] if (name) return this._socket[name]
else return this._socket else return this._socket
@ -383,8 +416,6 @@ class Base extends EventEmitter {
* *
*/ */
_packetHook(hook,func,opts) { _packetHook(hook,func,opts) {
log.debug({msg:'hooking a socket(s)', method:'_packetHook', line:334, hook:hook, function:func, options:opts}) log.debug({msg:'hooking a socket(s)', method:'_packetHook', line:334, hook:hook, function:func, options:opts})
let {name,type,trans,all} = opts let {name,type,trans,all} = opts
@ -421,7 +452,7 @@ class Base extends EventEmitter {
return res return res
} }
async _initSocket(name) { _initSocket(name) {
let socket = this._socket[name] let socket = this._socket[name]
let init = {} let init = {}
if (this._socket[name].type === 's' && this._socket[name].transport !== 'm') { if (this._socket[name].type === 's' && this._socket[name].transport !== 'm') {
@ -429,10 +460,19 @@ class Base extends EventEmitter {
} else { } else {
init = socket.connect init = socket.connect
} }
log.debug(`initializing socket ${name}, ${socket.type}, ${socket.transport}`) log.info({msg:`initializing socket ${name}, ${socket.type}, ${socket.transport}`})
if (this._started)
return `socket ${name} added and initialzed, ${await init()}` if (this._started) {
else return init() return init().then(function(res) {
return `socket ${name} added and initialzed, ${res}`
})
.catch(function(err) {
this.emit('error', {msg:'failed initialization', error:err, socket:socket, code:'SOCKET_INIT'})
return {msg:`socket ${name} failed initialization`, error:err}
}.bind(this)
)
}
else return {name:name, init:init}
} }
// all sockets are emitters. Adds a listener to all sockets of a type with given event. // all sockets are emitters. Adds a listener to all sockets of a type with given event.

43
test/test.js Normal file
View File

@ -0,0 +1,43 @@
import Base from '../src/base'
let wstest = new Base({})
;
(async () => {
wstest.on('error', err => {
console.log('ATTENTION! =========base instance error emitted ========')
console.log(err)
})
wstest.on('warn', warn => {
console.log('ATTENTION! =========base instance warning emitted ========')
console.log(warn)
})
// await wstest.addSocket('web0','s','w',{port:9000})
// await wstest.addSocket('m','s','m',{host:'nas.kebler.net'})
await wstest.addSocket('t1','s','t',{port:8001})
await wstest.addSocket('t2','s','t',{port:8001})
await wstest.addSocket('t3','s','t',{port:8003})
// await wstest.addSocket('mxxx','s','m',{host:'nas.kebler.net'})
// await wstest.addSocket('m2','s','m',{host:'nas.kebler.net'})
// await wstest.addSocket('web1','s','w',{port:9001})
let res = await wstest.init()
// await wstest.addSocket('web2','s','w',{port:9002})
if (res.errors) {
console.log('initialize errors reported')
console.log(res.errors)
}
console.log('sockets initialize responses\n',res.results)
wstest.push({cmd:'test', data:'test'})
// await wstest.addSocket('web2','s','w',{port:9002})
wstest.removeAllListeners('error')
})().catch(err => {
console.error('FATAL: UNABLE TO START SYSTEM!\n',err)
process.kill(process.pid, 'SIGTERM')
})