-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
Sometimes execResult is undefined. #858
Conversation
@luin Could you please merge this PR? 🙏 |
@rimiti Thank you for the pull request. I'm wondering in which case the pipeline will return void? |
It occurred when I had a transaction with some del actions. I can’t share you a snipped code because it’s from my client software and the logic is too complicated to extract you an example. Otherwise the fix doesn’t break the CI. It cant be safely merged. Note: if you’re looking for a maintainer, I can help you. |
I just did some tests on my side, but the results seem to always be arrays no matter what the commands were in the transaction. Here's a sample: redis.multi().del('foo1').del('foo2').exec(console.log) The output is:
Could you create a minimum reproducible sample for this fix? |
ed25cdb
to
50b9ec5
Compare
I've re-run the tests with adding a console.log() when if (typeof execResult === 'undefined') {
console.log(pipeline);
return;
} Output: tests | ...
tests | [ Command {
tests | name: 'multi',
tests | transformed: true,
tests | isCustomCommand: false,
tests | replyEncoding: 'utf8',
tests | errorStack: undefined,
tests | args: [],
tests | callback: undefined,
tests | resolve: [Function],
tests | reject: [Function],
tests | promise: [Promise] },
tests | Command {
tests | name: 'srem',
tests | transformed: true,
tests | isCustomCommand: false,
tests | replyEncoding: 'utf8',
tests | errorStack: undefined,
tests | args: [Array],
tests | callback: undefined,
tests | resolve: [Function],
tests | reject: [Function],
tests | promise: [Promise] },
tests | Command {
tests | name: 'srem',
tests | transformed: true,
tests | isCustomCommand: false,
tests | replyEncoding: 'utf8',
tests | errorStack: undefined,
tests | args: [Array],
tests | callback: undefined,
tests | resolve: [Function],
tests | reject: [Function],
tests | promise: [Promise] },
tests | Command {
tests | name: 'del',
tests | transformed: true,
tests | isCustomCommand: false,
tests | replyEncoding: 'utf8',
tests | errorStack: undefined,
tests | args: [Array],
tests | callback: undefined,
tests | resolve: [Function],
tests | reject: [Function],
tests | promise: [Promise] },
tests | Command {
tests | name: 'del',
tests | transformed: true,
tests | isCustomCommand: false,
tests | replyEncoding: 'utf8',
tests | errorStack: undefined,
tests | args: [Array],
tests | callback: undefined,
tests | resolve: [Function],
tests | reject: [Function],
tests | promise: [Promise] },
tests | Command {
tests | name: 'srem',
tests | transformed: true,
tests | isCustomCommand: false,
tests | replyEncoding: 'utf8',
tests | errorStack: undefined,
tests | args: [Array],
tests | callback: undefined,
tests | resolve: [Function],
tests | reject: [Function],
tests | promise: [Promise] },
tests | Command {
tests | name: 'srem',
tests | transformed: true,
tests | isCustomCommand: false,
tests | replyEncoding: 'utf8',
tests | errorStack: undefined,
tests | args: [Array],
tests | callback: undefined,
tests | resolve: [Function],
tests | reject: [Function],
tests | promise: [Promise] },
tests | Command {
tests | name: 'exec',
tests | transformed: true,
tests | isCustomCommand: false,
tests | replyEncoding: null,
tests | errorStack: undefined,
tests | args: [],
tests | callback: undefined,
tests | resolve: [Function],
tests | reject: [Function],
tests | promise: [Promise] },
tests | Command {
tests | name: 'del',
tests | transformed: true,
tests | isCustomCommand: false,
tests | replyEncoding: 'utf8',
tests | errorStack: undefined,
tests | args: [Array],
tests | callback: undefined,
tests | resolve: [Function],
tests | reject: [Function],
tests | promise: [Promise] },
tests | Command {
tests | name: 'del',
tests | transformed: true,
tests | isCustomCommand: false,
tests | replyEncoding: 'utf8',
tests | errorStack: undefined,
tests | args: [Array],
tests | callback: undefined,
tests | resolve: [Function],
tests | reject: [Function],
tests | promise: [Promise] } ],
tests | _result:
tests | [ [ null, 'OK' ],
tests | [ null, 'QUEUED' ],
tests | [ null, 'QUEUED' ],
tests | [ null, 'QUEUED' ],
tests | [ null, 'QUEUED' ],
tests | [ null, 'QUEUED' ],
tests | [ null, 'QUEUED' ],
tests | [ null, [Array] ],
tests | undefined,
tests | undefined ],
tests | _transactions: 0,
tests | _shaToScript: {},
tests | resolve: [Function],
tests | reject: [Function],
tests | promise:
tests | Promise {
tests | [ [Array],
tests | [Array],
tests | [Array],
tests | [Array],
tests | [Array],
tests | [Array],
tests | [Array],
tests | [Array],
tests | undefined,
tests | undefined ] },
tests | exec: [Function],
tests | execBuffer: [Function],
tests | nodeifiedPromise: true,
tests | replyPending: 0 }".
tests |
tests | at BufferedConsole.log (node_modules/@jest/console/build/BufferedConsole.js:199:10)
tests | at node_modules/ioredis/built/transaction.js:47:25 I hope it can help you. |
Thanks for the logs. I can finally reproduce the issue. The key point is to send other commands after const pipeline = redis.multi().del('foo1')
pipeline.exec(console.log) // TypeError: Cannot read property '0' of undefined
pipeline.del('foo3') // pipeline shouldn't be used to send any commands when the `exec()` has been called on it. I think we should catch the exception (like this pr do) but re-throw it as a more meaningful error with a message like "Transaction resolved failed. Did you call any commands in a transaction after What do you think? |
I think there are two solutions:
I prefer the second solution because, it always works. |
This is the quickest solution for this issue.
This breaks BC since in the past commands invoked after exec() will not be sent to Redis. So even we chose this way, it have to be happened in the next major release. Another cons I can think of is allowing commands after exec() makes things obscure: Will these command be included in the transaction, or be excluded from the transaction? |
I think, if it's obscure, it's because this is not the right way to do that. After reflection, it'll be more clear to just throw an error instead of trying to bypass transaction logic. What disturbs me, if I run the same tests with redis package, it's works and without error. |
Just tried node_redis. The following code: const m = r.multi().del('foo1')
m.exec(console.log)
m.del('foo2') Both node_redis & ioredis will simply ignore I think in this case, simply throwing errors to let developers know that there are commands not being sent would be a fair solution. |
Oh, it's so a silent error... 😕 |
@luin Do you want I throw an error like that instead of the return? Before if (typeof execResult === 'undefined') {
return
} After if (typeof execResult === 'undefined') {
throw new Error('Pipeline cannot be used to send any commands when the `exec()` has been called on it.');
} What do you think? |
Looks good to me! |
It can be merged. 🎉 |
## [4.9.3](v4.9.2...v4.9.3) (2019-05-07) ### Bug Fixes * more meaningful errors when using pipeline after exec(). ([#858](#858)) ([0c3ef01](0c3ef01))
@luin Could you please publish on npm this new version? |
@rimiti The new version has been published automatically when merged. I just add the 4.9.3 to the latest tag. |
## [4.9.3](redis/ioredis@v4.9.2...v4.9.3) (2019-05-07) ### Bug Fixes * more meaningful errors when using pipeline after exec(). ([#858](redis/ioredis#858)) ([0c3ef01](redis/ioredis@0c3ef01))
Hello,
Sometimes, execResult return is undefined. In this case, we just have to return.
Note: Commit updated.
Regards,