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

context: change storage API to return a restoreable context #3292

Merged
merged 9 commits into from Aug 11, 2017

Conversation

zpencer
Copy link
Contributor

@zpencer zpencer commented Jul 28, 2017

This API change allows storage implementations to put some state information into the return value, giving it the ability to act as a cookie. In environments where contexts can be replaced, the current original context can be stashed there and be restored when detach is called.

*/
public abstract void attach(Context toAttach);
public abstract Context attach(Context toAttach);
Copy link
Contributor

Choose a reason for hiding this comment

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

IIRC this is a binary-incompatible change (you may want to verify it). Although the storage API is experimental thus allows us to break, our practice has been that we will provide smooth transition as long as it doesn't take much effort.

If this is binary-incompatible, a trick I usually use is:

@Deprecated
public void attach(Context toAttach) {
  throw new UnsupportedOperationException("Deprecated. Do not call.");
}

public Context doAttach(Context toAttach) {
  attach(toAttach);
  return Context.current();
}

After one release, delete attach() and make doAttach() abstract.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Performed a simple test and verified that changing a void return type to a non avoid return type is not binary compatible.

*/
public abstract void attach(Context toAttach);
public abstract Context attach(Context toAttach);
Copy link
Contributor

Choose a reason for hiding this comment

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

Context.attach() needs to use the returned Context.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

Copy link
Contributor

@codefromthecrypt codefromthecrypt left a comment

Choose a reason for hiding this comment

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

I think a test that uses functionality as described would help explain and also make sure it isnt broken later. I understand that the way storage is initialized, testing might be awkward.

@codefromthecrypt
Copy link
Contributor

Curious what the driving use case is for this. For example, it is neat to stain a context, more interesting why you might need to?

A breaking change, but to understand this better, if the api was like this, it would make this more obvious?:

Object attach(Context toAttach)
void detatch(Object token, Context toRestore)

Ex the main point is you are passing back the token you got (otherwise things could be bad)

Aside, but the context storage api doesnt really say what happens when you have an incorrect pairing.

Copy link
Member

@ejona86 ejona86 left a comment

Choose a reason for hiding this comment

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

Looks fine other than Kun's comments.

@ejona86
Copy link
Member

ejona86 commented Jul 31, 2017

@adriancole wrote:

Curious what the driving use case is for this. For example, it is neat to stain a context, more interesting why you might need to?

We're using it to migrate a codebase from another context. The two ways we're using it: 1) to support replace() in the old API without losing the verification in detach() and 2) to save the old API's instance so we can return it later. (1) is rarely used and was basically legacy before we started, but is hard to migrate. It's questionable whether we need (2), but since we're doing the trick already it's one less risk.

A breaking change, but to understand this better, if the api was like this, it would make this more obvious?:

Object attach(Context toAttach)
void detach(Object token, Context toRestore)

Yep (although void detach(Object token)). Can't be done now though. That could have helped during earlier migration too. I'm not sure we would have done that, even if we knew then what we know now. But we would have considered it harder; we didn't really at the time (I think there may have been a 5 second consideration and a "meh").

Aside, but the context storage api doesnt really say what happens when you have an incorrect pairing.

If you have an incorrect pairing then the current() after the call isn't what is expected. detach() talks about the SEVERE log that happens when we detect such a breakage, but it is only after-the-fact; the log location isn't likely the source of the bug.

So the concern is more this case, that works without issue before the hack:

Context old = Context.current();
new.attach(); // throws away return :(
try {
  ...;
} finally {
  new.detach(old);
}

From an API perspective, it isn't clear if that is a bug, since attach() says:

The previously current context is returned.

While detach() says:

The provided replacement should be what was returned by the same attach() call.

We'll call it a bug internally; it seems to have no redeeming value and is easy to fix.

@codefromthecrypt
Copy link
Contributor

codefromthecrypt commented Aug 1, 2017 via email

private static final ThreadLocal<Context> localContext = new ThreadLocal<Context>() {
@Override
protected Context initialValue() {
return Context.ROOT;
Copy link
Member

Choose a reason for hiding this comment

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

This would likely bite any implementation, because current and attach are working differently. I see two options:

  1. We remove the if (current == null) check in current() and require the storage to handle that case. We would still want a current == null check, but it would throw an exception instead of choosing ROOT.

  2. Have Context.attach() check the return and use ROOT if it is null. This is functionally equivalent to what was being done before.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Let's go with option 2, since today implementations are allowed to return null and have Context handle it.

*/
public abstract void attach(Context toAttach);
public abstract Context doAttach(Context toAttach);
Copy link
Member

Choose a reason for hiding this comment

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

Adding a new method is not binary compatible. You can provide a default implementation that uses attach().

Note that because of the current definition of current(), you'd have to convert null to ROOT within doAttach().

Copy link
Contributor Author

@zpencer zpencer Aug 3, 2017

Choose a reason for hiding this comment

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

done

*/
@Test
public void attachDetachNewThread() throws ExecutionException, InterruptedException {
Future<?> future = scheduler.submit(Context.current().wrap(new Runnable() {
Copy link
Contributor

Choose a reason for hiding this comment

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

seems this could at least check what's current? (from another test)

Context blue = Context.current().withValue(COLOR, "blue");
--snip--
public void run() {
      assertTrue(blue.isCurrent());

Also, if you are testing that it is per-thread structures, maybe start one future with a countdown latch. change the current context to RED, start another future which checks red is current, finally countdown the blue future's latch (which should still be blue)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good suggestion on the current value, I will update the tests. As for per thread data structures, I was previously just referring to initializing the ThreadLocal to ROOT, which is not necessary anymore with ejona's suggestions.

- update tests
 - make Context handle NULL values
*/
public abstract void attach(Context toAttach);
public Context doAttach(Context toAttach) {
Copy link
Contributor

Choose a reason for hiding this comment

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

We should document that the return value can be null, and what Context.attach() will use in that case. I should have had the same comment on current() too.

@@ -16,6 +16,7 @@

package io.grpc;

import com.sun.istack.internal.Nullable;
Copy link
Contributor

Choose a reason for hiding this comment

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

is this the nullable you want? :)

Copy link
Contributor Author

@zpencer zpencer Aug 5, 2017

Choose a reason for hiding this comment

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

Ah I should be more careful about trusting my IDE. I will be removing this annotation as per Kun's comment about deps.

Copy link
Contributor

@zhangkun83 zhangkun83 left a comment

Choose a reason for hiding this comment

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

Ah. Do not use @Nullable. We must have zero dependency.

* Implements {@link io.grpc.Context#current}. Returns the context of the current scope.
* Implements {@link io.grpc.Context#current}.
*
* <p>Caution: {@link Context} interprets a return value of {@code null} to mean the same
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe make a test that via reflection changes Storage to something that returns null? then test that ROOT is substituted

@zpencer
Copy link
Contributor Author

zpencer commented Aug 9, 2017

@ejona86 any more comments on this one?

@zpencer
Copy link
Contributor Author

zpencer commented Aug 11, 2017

(jenkins) restest this please

@zpencer
Copy link
Contributor Author

zpencer commented Aug 11, 2017

retest this please

@zpencer zpencer merged commit e195c1a into grpc:master Aug 11, 2017
@lock lock bot locked as resolved and limited conversation to collaborators Jan 20, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants