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
Conversation
*/ | ||
public abstract void attach(Context toAttach); | ||
public abstract Context attach(Context toAttach); |
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.
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.
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.
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); |
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.
Context.attach()
needs to use the returned Context.
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.
fixed
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 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.
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) 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. |
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.
Looks fine other than Kun's comments.
@adriancole wrote:
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.
Yep (although
If you have an incorrect pairing then the 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
While
We'll call it a bug internally; it seems to have no redeeming value and is easy to fix. |
Thank you for the detailed explanation, Eric. This helps me understand and
I appreciate your taking the time.
…On 1 Aug 2017 06:34, "Eric Anderson" ***@***.***> 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; we didn't at the time.
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()
<https://grpc.io/grpc-java/javadoc/io/grpc/Context.html#detach-io.grpc.Context->
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.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#3292 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AAD613a2iAV4WcOaP9xRShCud26GhtMoks5sTlaFgaJpZM4OnLG1>
.
|
private static final ThreadLocal<Context> localContext = new ThreadLocal<Context>() { | ||
@Override | ||
protected Context initialValue() { | ||
return Context.ROOT; |
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 would likely bite any implementation, because current and attach are working differently. I see two options:
-
We remove the
if (current == null)
check incurrent()
and require the storage to handle that case. We would still want acurrent == null
check, but it would throw an exception instead of choosing ROOT. -
Have
Context.attach()
check the return and useROOT
if it isnull
. This is functionally equivalent to what was being done before.
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.
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); |
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.
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().
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.
done
*/ | ||
@Test | ||
public void attachDetachNewThread() throws ExecutionException, InterruptedException { | ||
Future<?> future = scheduler.submit(Context.current().wrap(new Runnable() { |
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.
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)
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.
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) { |
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.
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; |
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.
is this the nullable you want? :)
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 I should be more careful about trusting my IDE. I will be removing this annotation as per Kun's comment about deps.
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. 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 |
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 make a test that via reflection changes Storage to something that returns null? then test that ROOT is substituted
@ejona86 any more comments on this one? |
(jenkins) restest this please |
retest this please |
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 whendetach
is called.