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 handling of AsNs streams #863

Merged

Conversation

rvolosatovs
Copy link
Contributor

@rvolosatovs rvolosatovs commented Jun 27, 2019

Summary

Fixes handling of As-Ns streams

Changes

  • Close AS streams when NS closed
  • Close AS streams in a nicer way

Notes for Reviewers

Initially was part of #858

@rvolosatovs rvolosatovs added the blocked This can't continue until another issue or pull request is done label Jun 27, 2019
@rvolosatovs rvolosatovs added this to the June 2019 milestone Jun 27, 2019
@rvolosatovs rvolosatovs requested a review from htdvisser as a code owner June 27, 2019 10:24
@rvolosatovs rvolosatovs self-assigned this Jun 27, 2019
@rvolosatovs
Copy link
Contributor Author

Blocked by #858

@rvolosatovs rvolosatovs force-pushed the fix/as-ns-close branch 3 times, most recently from ddfa012 to b8d8770 Compare June 27, 2019 10:51
@coveralls
Copy link

coveralls commented Jun 27, 2019

Coverage Status

Coverage decreased (-0.02%) to 73.086% when pulling 14938da4d01b5bd9b5fce8abab2dec9113535ef6 on rvolosatovs:fix/as-ns-close into 14be53a on TheThingsNetwork:master.

@rvolosatovs rvolosatovs force-pushed the fix/as-ns-close branch 4 times, most recently from c4b397e to dfeb42f Compare June 27, 2019 16:34
@rvolosatovs rvolosatovs added the c/network server This is related to the Network Server label Jun 27, 2019
@rvolosatovs rvolosatovs reopened this Jun 28, 2019
@rvolosatovs rvolosatovs changed the base branch from master to feature/ns-uplink June 28, 2019 09:06
@rvolosatovs rvolosatovs removed the blocked This can't continue until another issue or pull request is done label Jun 28, 2019
Copy link
Member

@johanstokking johanstokking left a comment

Choose a reason for hiding this comment

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

Can't we simply use builtin context cancellation? That supports waiting as well

@rvolosatovs
Copy link
Contributor Author

@johanstokking can you clarify what you mean exactly and how it would work?
Do you mean that LinkApplication would create a new context wrapped into context.WithCancel and Close() would call the context.CancelFunc returned?
How would LinkApplication determine if io.EOF should be returned or ctx.Error()?
In order for this approach to work, LinkApplication would need to maintain 2 completely separate contexts and Close() would need to be responsible for deleting the stream from the map. I don't see benefits of such approach, whereas the proposed solution is simpler and more efficient.

Copy link
Member

@johanstokking johanstokking left a comment

Choose a reason for hiding this comment

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

Maybe I don't get it but this implementation is not obvious to me.

How would LinkApplication determine if io.EOF should be returned or ctx.Error()?

If you want to differentiate between the client closing the context (context.Canceled) and another link replacing the current (io.EOF), use pkg/errorcontext.

In order for this approach to work, LinkApplication would need to maintain 2 completely separate contexts and Close() would need to be responsible for deleting the stream from the map. I don't see benefits of such approach, whereas the proposed solution is simpler and more efficient.

Why two separate contexts? It's the same thing right? You derive a cancelable context from the link context, and that context can be canceled (potentially with a custom error) by a new link.

// First read succeeds when either closeCh is closed by LinkApplication or LinkApplication initializes closing sequence.
// Second read succeeds when closeCh is closed by LinkApplication.
<-s.closeCh
<-s.closeCh
Copy link
Member

Choose a reason for hiding this comment

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

So Close() doesn't close but waits for a close

Copy link
Contributor Author

@rvolosatovs rvolosatovs Jun 28, 2019

Choose a reason for hiding this comment

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

It triggers the "closing sequence" in LinkApplication or returns immediately if it is already closed

return err
case <-ws.closeCh:
return errDuplicateSubscription
case ws.closeCh <- struct{}{}:
Copy link
Member

Choose a reason for hiding this comment

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

You may have ctx.Done() so you don't guarantee writing to closeCh

Copy link
Contributor Author

@rvolosatovs rvolosatovs Jun 28, 2019

Choose a reason for hiding this comment

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

Yes, that's expected.
The write only succeeds when Close() is called at least once

@rvolosatovs
Copy link
Contributor Author

rvolosatovs commented Jun 28, 2019

Is there a problem with the proposed implementation?
It's really very simple - whenever Close() is called(possibly multiple times and/or concurrently) LinkApplication is signaled to close the stream gracefully - i.e. return io.EOF and delete the stream from the map.
If context is Done() before Close() is called, the ctx.Err() is returned and the stream is deleted from the map.

After the stream is closed(either via Close() or via ctx.Done()) all Close() calls will return immediately(since the channel is closed by LinkApplication).

NOTE, that Close() will only ever return after the stream is actually removed from the map

@johanstokking johanstokking modified the milestones: June 2019, July 2019 Jul 1, 2019
Copy link
Member

@johanstokking johanstokking left a comment

Choose a reason for hiding this comment

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

You don't have to explain me how it works because I can read Go code.

People have been writing custom trigger and time-based cancelation code for 7 years before context.Context became a standard library in 1.7.

Now, I very much prefer using that, because it solves exactly the problem at hand. In fact, it's much simpler because you wait for one context to close instead of switching on some weird channel.

For the record, this is an open source project, and I like to keep code accessible for the community to see what's going on and contribute.

But if you believe that rolling custom stuff for obvious things that are in standard libraries is better or simpler (I disagree on both counts), fine.

for _, cl := range ns.applicationServers {
logger.Debug("Close Application Server link")
if err := cl.Close(); err != nil {
logger.WithError(err).Warn("Failed to close AS link")
Copy link
Member

Choose a reason for hiding this comment

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

  1. Consistent component naming, Application Server
  2. else for Debug log below

@rvolosatovs
Copy link
Contributor Author

@johanstokking I simply do not understand how to implement following semantics using
context.Context:

  • Close() could be called multiple times(possibly concurrently) safely
  • Close() only returns once the stream is deleted from the map
  • Close() returns instantly if stream is already closed
  • LinkApplication is responsible for all the housekeeping, i.e. deleting/adding the strteam to the map

context.WithCancel can be used to cancel the context and trigger the "closing sequence" in LinkApplication, but there's no way to actually wait for LinkApplication in Close() to finish all the "housekeeping" if only one context is used.

Copy link
Contributor

@htdvisser htdvisser left a comment

Choose a reason for hiding this comment

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

Looks ok to me. Are you sure you want to merge all the added debug logs?

case <-ws.closeCh:
return errDuplicateSubscription
case ws.closeCh <- struct{}{}:
logger.Debug("Close() is called, close link")
Copy link
Contributor

Choose a reason for hiding this comment

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

Inconsistent with debug log above, which uses - close link

Copy link
Member

Choose a reason for hiding this comment

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

Even though it's Debug I would not print code

@htdvisser
Copy link
Contributor

htdvisser commented Jul 1, 2019

For the record, I think @rvolosatovs's points justify this "custom" cancelation mechanism. Especially the requirement Close() only returns once the stream is deleted from the map is one that Go doesn't solve. Cancelation is easy, but waiting for cleanup after cancelation is currently impossible with context.Context.

From https://dave.cheney.net/2017/08/20/context-isnt-for-cancellation:

[...] context.Context‘s most important facility, broadcasting a cancellation signal, is incomplete as there is no way to wait for the signal to be acknowledged.

@johanstokking
Copy link
Member

johanstokking commented Jul 1, 2019

context.WithCancel can be used to cancel the context and trigger the "closing sequence" in LinkApplication, but there's no way to actually wait for LinkApplication in Close() to finish all the "housekeeping" if only one context is used.

Do you need to wait for that though? Is it even desirable to wait for that? If so, why?

@rvolosatovs rvolosatovs force-pushed the fix/as-ns-close branch 2 times, most recently from 42dadf5 to 5e93bef Compare July 2, 2019 11:26
@rvolosatovs rvolosatovs merged this pull request into TheThingsNetwork:feature/ns-uplink Jul 2, 2019
@rvolosatovs rvolosatovs deleted the fix/as-ns-close branch July 2, 2019 15:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c/network server This is related to the Network Server
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants