Skip to content
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

Merged
merged 4 commits into from
Mar 6, 2019

Conversation

andrewmains12
Copy link
Contributor

Fix for #1428 .

I've added tests that:

  • reproduce the issue on master
  • make sure that we're still closing everything.

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!

@andrewmains12 andrewmains12 force-pushed the andrewmains12/fix_temporal_panic branch from 09413d2 to 578aacd Compare March 4, 2019 21:17
.excludelint Outdated
@@ -1,4 +1,5 @@
(vendor/)
(generated/)
(_mock.go)
(_mock_test.go)
Copy link
Contributor Author

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.

cache *blockCache
processor Processor
transformOpts transform.Options
}

// controller is a minimal interface of transform.Controller, used exclusively
Copy link
Collaborator

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?

Copy link
Contributor Author

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.

Copy link
Collaborator

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)

Copy link
Contributor Author

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?

Copy link
Collaborator

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?

Copy link
Contributor Author

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!

@andrewmains12 andrewmains12 force-pushed the andrewmains12/fix_temporal_panic branch from 578aacd to 1af927d Compare March 4, 2019 21:30
@codecov
Copy link

codecov bot commented Mar 4, 2019

Codecov Report

Merging #1429 into master will increase coverage by <.1%.
The diff coverage is 100%.

Impacted file tree graph

@@           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
Flag Coverage Δ
#aggregator 82.3% <ø> (ø) ⬆️
#cluster 85.8% <ø> (ø) ⬆️
#collector 63.7% <ø> (ø) ⬆️
#dbnode 80.8% <ø> (ø) ⬆️
#m3em 73.2% <ø> (ø) ⬆️
#m3ninx 74.2% <ø> (-0.1%) ⬇️
#m3nsch 51.1% <ø> (ø) ⬆️
#metrics 17.6% <ø> (ø) ⬆️
#msg 74.9% <ø> (ø) ⬆️
#query 65.7% <100%> (ø) ⬆️
#x 76% <ø> (ø) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update d78eee5...134a96b. Read the comment docs.

@andrewmains12 andrewmains12 force-pushed the andrewmains12/fix_temporal_panic branch 5 times, most recently from 2c99d15 to f5fa1c9 Compare March 5, 2019 15:48
@@ -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))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this change?

Copy link
Contributor Author

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.

Copy link
Collaborator

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?

Copy link
Contributor Author

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 {
Copy link
Collaborator

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)?

Copy link
Contributor Author

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 {
Copy link
Collaborator

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)

src/query/functions/temporal/base_test.go Outdated Show resolved Hide resolved
type closeSpyBlock struct {
block.Block

Closed bool
Copy link
Collaborator

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

Copy link
Contributor Author

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.

Copy link
Collaborator

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

Copy link
Contributor Author

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 :)

Copy link
Collaborator

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

cache *blockCache
processor Processor
transformOpts transform.Options
}

// controller is a minimal interface of transform.Controller, used exclusively
Copy link
Collaborator

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) {
Copy link
Collaborator

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?

Copy link
Contributor Author

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.

Copy link
Collaborator

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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can probably be private

Copy link
Contributor Author

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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can probably be private

Copy link
Contributor Author

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
Copy link
Collaborator

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

Copy link
Contributor Author

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 :/ )

Copy link
Collaborator

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

@andrewmains12 andrewmains12 force-pushed the andrewmains12/fix_temporal_panic branch 2 times, most recently from 1bb49a5 to a245566 Compare March 5, 2019 21:30
Copy link
Collaborator

@arnikola arnikola left a 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

@andrewmains12 andrewmains12 force-pushed the andrewmains12/fix_temporal_panic branch 2 times, most recently from f9d2abd to 134a96b Compare March 6, 2019 17:24
@andrewmains12 andrewmains12 force-pushed the andrewmains12/fix_temporal_panic branch from 134a96b to 632b37d Compare March 6, 2019 19:17
@andrewmains12 andrewmains12 merged commit 2454a92 into master Mar 6, 2019
@andrewmains12 andrewmains12 deleted the andrewmains12/fix_temporal_panic branch March 6, 2019 19:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants