-
Notifications
You must be signed in to change notification settings - Fork 455
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 panic in temporal functions; check err before deferring. #1429
Conversation
09413d2
to
578aacd
Compare
.excludelint
Outdated
@@ -1,4 +1,5 @@ | |||
(vendor/) | |||
(generated/) | |||
(_mock.go) | |||
(_mock_test.go) |
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 btw, is to allow us to generate private mocks purely within testing scope; I don't want to export some mocks.
src/query/functions/temporal/base.go
Outdated
cache *blockCache | ||
processor Processor | ||
transformOpts transform.Options | ||
} | ||
|
||
// controller is a minimal interface of transform.Controller, used exclusively |
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.
Not a huge fan of having test specific stuff in our code. Anyway we can do this without putting this here?
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.
Agreed--let me explain what I'm trying to do; maybe there's a good way to do it without this :)
I want to test that blocks we create are closed (especially in case of an error). That means that I need a block that allows me to make that assertion; regular blocks don't (which is reasonable). Since we create blocks via controller.NewBlockBuilder
, controller
is my entry point into injecting a new block. Therefore, I mock out controller.
Alternatives considered:
- create a public transform.Controller interface, and use that everywhere instead. I didn't think it was worth refactoring every usage of controller for this, but we could.
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.
I'm definitely in favour of the alternative here; if I'm not mistaken the refactor should be pretty low-impact (mostly changing *transform.Controller
to transform.Controller
)
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.
So it's not too bad, but given that this is a bug fix for a panic, I'd rather get this in first and do the refactor after. Thoughts?
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 we're going more hotfix then I'd say jam it with no test; if it's not a hotfix wouldn't mind having the refactor in, but will be happy enough with pulling this into an isolated file then filing an explicit issue if that sounds good?
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.
Sure thing--filed #1430 to track. Will do that shortly!
578aacd
to
1af927d
Compare
Codecov Report
@@ Coverage Diff @@
## master #1429 +/- ##
========================================
+ Coverage 70.8% 70.8% +<.1%
========================================
Files 834 834
Lines 71487 71488 +1
========================================
+ Hits 50645 50673 +28
+ Misses 17534 17514 -20
+ Partials 3308 3301 -7
Continue to review full report at Codecov.
|
2c99d15
to
f5fa1c9
Compare
@@ -256,15 +267,16 @@ func (c *baseNode) processCompletedBlocks(queryCtx *models.QueryContext, process | |||
sp, _ := opentracingutil.StartSpanFromContext(queryCtx.Ctx, c.op.OpType()) | |||
defer sp.Finish() | |||
|
|||
blocks := make([]block.Block, len(processRequests)) | |||
for i, req := range processRequests { | |||
blocks := make([]block.Block, 0, len(processRequests)) |
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.
Why this change?
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.
Ah yeah this is the reason for the panic. Previously, I was allocating blocks with length = len(processRequests)
, which meant that the array was:
[]block.Block{nil, nil, ...}
In case of early return (on error), we'd loop through every block and try to close it, causing a segfault.
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.
Ah gotcha; would it be worth adding nil checks into closeBlocks
?
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.
Eh I don't think so; there's no valid use case for having a nil element in the slice. That probably means I should a check in the test though--good call!
BuiltBlock *closeSpyBlock | ||
} | ||
|
||
func (bbc *closeSpyBlockBuilder) Build() block.Block { |
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.
Maybe I've spent too long on the internet, but could we rename this to (b *closeSpyBlockBuilder)
?
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.
Haha yeah good call.
Closed bool | ||
} | ||
|
||
func (bwc *closeSpyBlock) Close() 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.
Same here, can we rename this to (b *closeSpyBlock)
type closeSpyBlock struct { | ||
block.Block | ||
|
||
Closed bool |
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 can probably be private
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.
So I don't know that style guides cover this, but my general rule is to make member's public when they're meant to be read outside of the struct, as this one is. That applies even if the class itself is private to the package.
Private struct fields imo are more for internal state where nothing outside of the struct should read the fields, which isn't true here.
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.
Don't have very strong thoughts on it myself; personally prefer to default to making everything as closed as possible, then open it when necessary
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.
For sure! It's worth noting that both options are actually equivalent in terms of visibility; in both cases, everything in the package can see the field, and nothing outside the package can. That's why I tend to treat it as a semantic marker.
Anyhow, just a point of discussion :)
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.
True enough; I guess there's a little weirdness in public accessors on private fields but that seems like a general go problem
src/query/functions/temporal/base.go
Outdated
cache *blockCache | ||
processor Processor | ||
transformOpts transform.Options | ||
} | ||
|
||
// controller is a minimal interface of transform.Controller, used exclusively |
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.
I'm definitely in favour of the alternative here; if I'm not mistaken the refactor should be pretty low-impact (mostly changing *transform.Controller
to transform.Controller
)
@@ -351,6 +353,158 @@ func setup(numBlocks int, duration time.Duration, nextBound int) *testContext { | |||
} | |||
} | |||
|
|||
// TestBaseWithDownstreamError checks that we handle errors from blocks correctly | |||
func TestBaseWithDownstreamError(t *testing.T) { |
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.
Don't know if you have the time for it, but this would be a good test to run on other functions as well; if you don't want to do it now maybe just put up an issue for it linking to this subject?
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.
but this would be a good test to run on other functions as well
Probably, but I'd rather fix the panic first :). I can put up a separate issue for it.
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.
Sounds good 👍
// an UnconsolidatedBlock which errors on SeriesIter() (unconsolidatedBlockWithSeriesIterErr) | ||
type blockWithDownstreamErr struct { | ||
block.Block | ||
Err 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.
Can probably be private
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.
See above response
|
||
type unconsolidatedBlockWithSeriesIterErr struct { | ||
block.UnconsolidatedBlock | ||
Err 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.
Can probably be private
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.
See above response
require.EqualError(t, err, testErr.Error()) | ||
} | ||
|
||
// Types for TestBaseWithDownstreamError |
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.
nit: I think may be a bit clearer to group a bunch of these test types together? Up to you
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.
Personally I like them closer to where they're used, since they're effectively part of the particular tests (I'd put them inside the tests if I could, but alas, no method definitions within functions :/ )
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.
Yeah totally fair; I guess the tradeoff is extra clarity for particular tests when the utils are defined next to them or extra clarity for what the tests themselves cover when they're grouped
Either way super minor nitpick haha
1bb49a5
to
a245566
Compare
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.
Approved with a final comment
f9d2abd
to
134a96b
Compare
134a96b
to
632b37d
Compare
Fix for #1428 .
I've added tests that:
The tests are a bit wonky due to some 2 level mocking I needed to do--let me know if you have questions or suggestions for improvements there!