-
Notifications
You must be signed in to change notification settings - Fork 62
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: error event propagation #233
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. Al through CI is failing, I don't think its related to this changes, but to the recent changes made with class-is
which I believe require releasing js-ipfs
and js-ipfs-api
.
@@ -5,6 +5,8 @@ yarn.lock | |||
**/*.log | |||
test/repo-tests* | |||
|
|||
.vscode | |||
.eslintrc |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any reason we need to ignore the . eslintrc
file if its not being used? Usually aegir takes care of all our configs, linting included.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
editors need to find a eslint config to provide linter messages and fix problems, because the config is hidden inside the aegir > eslint-config-aegir > .eslintrc
the editor cant find it.
because of this i need to manually add this file in every repo to have a normal ctrl-s workflow.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Editors will usually allow pointing their linting tools to custom locations, and with many contributors/editors being used I don't think it would be practical to add a custom entry for each to the .gitignore
file.
I don't know what our current guidelines in regards to .gitignore
are, maybe @diasdavid or @victorbjelkholm can provide more context.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This works for all editors because this is all based on how eslint works, nothing fancy here 😄
yes tests fail because of ipfs. with ipfs master linked locally everything is green 👍 . |
@travisperson wanna review here? Seems that we have to release this one with CI failing. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ya this LGTM
@@ -130,6 +130,7 @@ class FactoryInProc { | |||
} | |||
|
|||
const node = new Node(options) | |||
node.once('error', err => callback(err, node)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If a node emits an error after starting, won’t this result in spawn
’s callback getting called more than once? (First with a success, then, later, with an error.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't happen but ur right error should come first. Good catch, thank you
That didn’t actually fix the problem! Try this: const IPFSFactory = require('.')
const factory = IPFSFactory.create({type: 'proc', exec: require('ipfs')})
factory.spawn({disposable:true}, (error, daemon) => {
// This callback should only ever execute once, but instead, it’s about to happen twice.
console.log('Daemon spawned!')
if (error) {
return console.error('Spawn error:', error)
}
// Do an operation, just to make sure we're working
daemon.api.id((error, id) => {
if (error) {
console.error('Could not get ID!')
}
else {
console.log('Daemon ID:', id.id)
}
// Do something to make stopping fail (this is arbitrary, but let's say
// anything *could* happen to put the daemon in a bad state)
daemon.exec._repo.close(error => {
console.log('Repo prematurely closed')
// And finally, stop the daemon
console.log('Stopping daemon...')
daemon.stop(error => console.log('Daemon stopped:', error || 'no error'))
})
})
}) That should print:
But instead, you get:
Note the repeated |
Thats a great catch @Mr0grog and a pretty tricky scenario. I see two options:
try {
const node = new Node(options)
series([
(cb) => node.once('ready', cb),
(cb) => node.repo._isInitialized(err => {
// if err exists, repo failed to find config or the ipfs-repo package
// version is different than that of the existing repo.
node.initialized = !err
cb()
}),
(cb) => options.init
? node.init(cb)
: cb(),
(cb) => options.start
? node.start(options.args, cb)
: cb()
], (err) => callback(err, node))
} catch (err) {
return callback(err)
} Wrapping the initialization in a try/catch should take care of all possible error scenarios, but... See next case.
const node = new Node(options)
const errHandler = (err) => callback(err, node)
node.once('error', errHandler)
series([
(cb) => node.once('ready', cb),
(cb) => node.repo._isInitialized(err => {
// if err exists, repo failed to find config or the ipfs-repo package
// version is different than that of the existing repo.
node.initialized = !err
cb()
}),
(cb) => options.init
? node.init(cb)
: cb(),
(cb) => options.start
? node.start(options.args, cb)
: cb()
], (err) => {
node.removeListener('error', errHandler)
callback(err, node)
}) This would be the recommended approach, (case 1 can be seen as an anti-pattern of sorts), but it seems like there would still be cases (tho, most likely legit bugs in ipfs), where an error is thrown but initialization/startup is not aborted correctly, so spawn would be again called twice. There might be a better approach, so would love to hear suggestions. |
BTW, if not error handler is registered, EventEmitter will handle it as a special case and throw the error, see - https://nodejs.org/api/events.html#events_error_events. I think, this is pretty safe in our case, because we control the code paths and handling the error with a proper try/catch.... |
Yeah, I would agree that approach 2 is probably the right one. Some other thoughts that affect this code but are probably out of scope:
|
If this is happening, it should be considered a bug. But things being as they are, and errors handling being a bit of a mix (callbacks, eventemitter and possibly throws in some third party code), it might not be a bad idea, tho callbacks in particular should not affect this code path, since we're
Hmm... Not sure if I agree... Error events, specially in the EventEmitter sense are exceptions. If the exception was not handled by the library/function/method internally, it should terminate the process (albeit in a graceful manner), but I would not try to recover from such errors. So,
I think if cases where users want to recover from the error, it should not be thrown as an error in the first place, but handled internally by ipfs and re-thrown as a warning or logged somewhere, etc... |
Hmmm, I’m not sure I follow. factory.spawn({disposable: true, EXPERIMENTAL: {pubsub: 'hello!'}}, (error, node) => {
// this never runs because factory.spawn throws an exception synchronously
}) that throws (yes it’s dumb, but still, it’s a thing that could happen). I’m not sure it’s reasonable to expect that this library will be so perfectly locked to whatever version of js-ipfs you have installed that it can knowingly prevent every possible error case. You could expect that, in which case this would be a bug, but that seems like a pretty hard-to-support expectation in reality.
Totally! I’m not suggesting stopping that — I’m suggesting making the same facility available to a user of ipfsd as to a user of js-ipfs — that you can always listen for the error event (and if you don’t listen, it would still throw, as it always has). Maybe think of it this way: factory.spawn({disposable: true}, (error, node) => {
if (error) return console.error('uhoh:', error)
// Should this be something you can do? All I was suggesting was yes :)
// If you don’t subscribe, it would still thow — node.exec’s `error` is
// handled, but it’s only handled by forwarding to node’s `error`, which
// would still throw if a user doesn’t explicitly do the following line:
node.on('error', error => {
// do whatever you want here, same as you might with an IPFS node you
// created via `new IPFS()`
})
})
Also agree! 😄 I should have linked this related conversation where Victor suggested the same. But now I think we are getting way off topic from this issue. |
I'm actually agreeing 👍 sorry for not being clear - we should wrap
I was referring to this earlier point:
I don't think we should change the I think we have several actionable from this discussion (feel free to add more)
|
👍 to pretty much all of that!
I think this one is purely a js-ipfs matter and not anything to do with js-ipfsd-ctl, right? |
@Mr0grog awesome! Yes, 2nd and 3rd issues are js-ipfs specific 👍 |
When using node event emitter error events should always be handled and propagated, i'm doing a fix on ipfs cli related to this issue ipfs/js-ipfs#1180 and couldn't catch errors coming from ipfsd this PR fixes that.