Skip to content

Optionally send cancelled frames when context is canceled#890

Merged
cinchurge merged 6 commits intouber:devfrom
prashantv:prashant/cancel
Mar 20, 2023
Merged

Optionally send cancelled frames when context is canceled#890
cinchurge merged 6 commits intouber:devfrom
prashantv:prashant/cancel

Conversation

@prashantv
Copy link
Copy Markdown
Contributor

Replaces #817

Add a new option, SendCancelOnContextCanceled to send cancel frames when a context is
detected to be canceled on the request sender side.

Cancellations often happen while waiting to read a response, so we can rely on the
reader goroutine waiting on the message exchange to notice the cancellation and
send the cancel message to the remote side vs adding a separate goroutine.

Older servers and relays without this change will log Received unexpected frame
but otherwise be unaffected by the unknown frame. However, there's no point enabling
cancellations if the remote side doesn't support them first.

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 17, 2023

Codecov Report

Patch coverage: 76.71% and project coverage change: +0.02 🎉

Comparison is base (0c11cc2) 88.78% compared to head (05971e3) 88.81%.

❗ Current head 05971e3 differs from pull request most recent head 99a96e0. Consider uploading reports for the commit 99a96e0 to get more accurate results

Additional details and impacted files
@@            Coverage Diff             @@
##              dev     #890      +/-   ##
==========================================
+ Coverage   88.78%   88.81%   +0.02%     
==========================================
  Files          43       43              
  Lines        4440     4505      +65     
==========================================
+ Hits         3942     4001      +59     
- Misses        379      382       +3     
- Partials      119      122       +3     
Impacted Files Coverage Δ
outbound.go 84.97% <33.33%> (-2.17%) ⬇️
messages.go 91.58% <50.00%> (-6.27%) ⬇️
mex.go 72.88% <76.00%> (+0.36%) ⬆️
connection.go 88.39% <88.88%> (+3.40%) ⬆️
inbound.go 83.00% <100.00%> (+0.61%) ⬆️
relay.go 85.77% <100.00%> (+0.06%) ⬆️
relay_messages.go 100.00% <100.00%> (ø)

... and 1 file with indirect coverage changes

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

☔ View full report in Codecov by Sentry.
📢 Do you have feedback about the report comment? Let us know in this issue.

mex.go Outdated
}

func (mexset *messageExchangeSet) handleCancel(frame *Frame) {
if mexset.shutdown {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

a lock is acquired when it's set, so a read lock should be acquired here too?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Shouldn't need this actually, this method should look a lot like forwardPeerFrame above, which doesn't have this, so reverted it.

mex.go Outdated
// receive channel remains open, however, in case there are concurrent
// goroutines sending to it.
func (mex *messageExchange) shutdown() {
func (mex *messageExchange) shutdown() bool {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

the return value doesn't seem to be used anywhere

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch, this was from the old PR which I started off, no longer need this so reverted.

outbound.go Outdated
func (c *Connection) outboundCtxCancel() {
// outbound contexts are created by callers, can't cancel them.
// However, we shouldn't be trying to cancel them, so log.
c.log.Warn("unexpected cancel of outbound context")
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

worried that this might be overly verbose in practice - should it be a debug log instead?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Can change the level, though this should never happen. This is passed to the message exchange's ctxCancel func, and that's only used when a cancel frame is received. A cancel frame should only be received for inbound calls, which will cancel the real context. This is basically a no-op used for outbound message exchanges which aren't expected to receive a cancel.

None of the tests trigger this, and so this shouldn't happen unless a bad TChannel implementation sends an unexpected frame.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

...and so this shouldn't happen unless a bad TChannel implementation sends an unexpected frame.

yea, mostly worried that if this is somehow triggered by an edge case there won't be a way to silence it unless we roll back. upvote for changing it to debug level.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Sure, updated to debug.

outbound.go Outdated
if err := call.writeMethod([]byte(methodName)); err != nil {
return nil, err
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

intentional extra new line?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Nope, removed

Copy link
Copy Markdown
Contributor Author

@prashantv prashantv left a comment

Choose a reason for hiding this comment

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

Thanks for the quick review!

mex.go Outdated
// receive channel remains open, however, in case there are concurrent
// goroutines sending to it.
func (mex *messageExchange) shutdown() {
func (mex *messageExchange) shutdown() bool {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch, this was from the old PR which I started off, no longer need this so reverted.

outbound.go Outdated
if err := call.writeMethod([]byte(methodName)); err != nil {
return nil, err
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Nope, removed

outbound.go Outdated
func (c *Connection) outboundCtxCancel() {
// outbound contexts are created by callers, can't cancel them.
// However, we shouldn't be trying to cancel them, so log.
c.log.Warn("unexpected cancel of outbound context")
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Can change the level, though this should never happen. This is passed to the message exchange's ctxCancel func, and that's only used when a cancel frame is received. A cancel frame should only be received for inbound calls, which will cancel the real context. This is basically a no-op used for outbound message exchanges which aren't expected to receive a cancel.

None of the tests trigger this, and so this shouldn't happen unless a bad TChannel implementation sends an unexpected frame.

mex.go Outdated
}

func (mexset *messageExchangeSet) handleCancel(frame *Frame) {
if mexset.shutdown {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Shouldn't need this actually, this method should look a lot like forwardPeerFrame above, which doesn't have this, so reverted it.

}
}

func (mex *messageExchange) onCtxErr(err error) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nit: replace err with _ since we don't do anything with it. maybe worth commenting that the error is reported separately so we don't need to e.g. log it here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We actually should be using the error here to avoid unnecessary cancels from being sent when cancels are enabled + the context times out.

Added a err != context.Canceled check here

mex.go Outdated
}
}

func (mex *messageExchange) handleCancel(frame *Frame) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nit: replace frame with _

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

👍🏼

outbound.go Outdated
func (c *Connection) outboundCtxCancel() {
// outbound contexts are created by callers, can't cancel them.
// However, we shouldn't be trying to cancel them, so log.
c.log.Warn("unexpected cancel of outbound context")
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

...and so this shouldn't happen unless a bad TChannel implementation sends an unexpected frame.

yea, mostly worried that if this is somehow triggered by an edge case there won't be a way to silence it unless we roll back. upvote for changing it to debug level.

messages.go Outdated

type cancelMessage struct {
id uint32
ttl uint32 // unused.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nit: add something like but needed for writes or something like that

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

updated to: "unused by tchannel-go, but part of the protocol" since it's used in both the read/write paths.

Copy link
Copy Markdown

@lambdai lambdai left a comment

Choose a reason for hiding this comment

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

quick question on this new option


func (mex *messageExchange) onCtxErr(err error) {
// On canceled contexts, we may need to send a cancel message.
if err != context.Canceled {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: consider errors.Is instead for sanity

return
}

if onCancel := mex.mexset.onCancel; onCancel != nil {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: I think this condition is unnecessary - unless I'm misreading, we're unconditionally setting onCancel on the outbound exchange in connection.go, it's just the body of onCancel that is conditional

@cinchurge cinchurge merged commit a84c450 into uber:dev Mar 20, 2023
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.

4 participants