-
Notifications
You must be signed in to change notification settings - Fork 903
Replaced Context implement Closeable by AutoCloseable #1991
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
base: master
Are you sure you want to change the base?
Conversation
With mozilla#864 Context was extended to implemet Closeable. But this does not meet the interface contract. Javadoc of AutoCloseable.close(): Note that unlike the close method of Closeable, this close method is not required to be idempotent. In other words, calling this close method more than once may have some visible side effect, unlike Closeable.close which is required to have no effect if called more than once. However, implementers of this interface are strongly encouraged to make their close methods idempotent. In other words, when "Context implements Closeable" ```java Context cx = Context.enter(); cx.close(); cx.close(); // will fail ``` must not fail. If only Autocloseable is implemented, it is allowed (though not recommended)
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 seems objectively worse. If you are using Context via a construct like
try (Context cx = something()) {
//Do wrok
}Then probably something() should check that we aren't reentering an existing context., or it should return a proxy that can detect that it is only closed once.
Adhering to a spec in a way that will just cause people to shoot themselves in the foot isn't an improvement.
In other words I would say that you should not change the type of Context such that
Context cx = Context.enter();
cx.close();
cx.close(); // will fails allowed. You should work out how to fix things so that code does not fail.
I completely agree with you
Yes, that would be the right solution. But it would create new problems:
I think we should first vote on which solution we prefer... I hope I haven't opened Pandora's box here |
|
Well, changing from Closable to AutoClosable is technically a breaking change, so I fear the box is well and truly open. The issue we have is that we have two patterns (enter and exit, and get and close) which just don’t quite match. |
|
yes, enter and exit don't quite match, that's part of the problem. And we do have a problem with tests that don't close context, which is why the ":tests:test" task does a fork and takes forever to run. What if we change "close" so that is is idempotent, but add an assertion so that people don't abuse it in tests. Does that help any? IMO a lot of the "Kit.codeBug()" is in there because that code was literally written before Java had "assert!" |
I'm unsure, if that will work. Context cx1 = Context.enter();
Context cx2 = Context.enter();
cx2.someCode
cx2.close();
cx2.close();
cx1.someCode // will fail, because all contexts are closed
cx1.close(); // will adittionally throw a codebug-exceptionYou cannot make it idempotent, as long as One possible migration-path I see:
class CloseableContext implements Closeable {
private final Context theRealContext;
private boolean closed;
void close() {
if (!closed) {
closed = true;
theRealContext.exit();
}
}
}or should we refactor everything, so that |
|
How do we proceed from here? I realize that behavior of close is far from optimal.
@aardvark179 I basically agree with you here. But I wasn't the one who did it wrong (that's not meant as an excuse or a blame). I simply tried to make this PR at least as compliant as the Closeable/Autocloseable interface requires, even if the behaviour is error prone (but would be spec-conform until we have a better solution) If we have a coordinated plan on how to do it right, I am also happy to invest work here. |
|
Yes, this is my fault for assuming that most Rhino code entered the context once and should exit it once. I didn't understand the weirdness of Context in that it allows you to enter multiple times. What if we just changed close so that it WAS idempotent? I.e. this won't fail: Context cx = Context.enter(); It still lets people use try-with-resources and also doesn't break their code. I see more problems with code NOT exiting contexts than exiting it multiple times. |
|
I've just thought again about how we can solve the problem and I have to say, it's anything but simple. As long as Context.enter() returns the same instance when called nested, we won't be able to do this. Context cx1 = Context.enter();
Context cx2 = Context.enter();
cx2.close(); // should switch to cx1
cx2.close(); // should do nothing (but cx1 == cx2)
cx1.close(); // closes the outer contextSo we must either
what do you think |
|
I'm not sure I like either of those PoCs, but that is partly because I'm not convinced they are tackling the actual problem. Copying the context every time is also not a great option because you are likely changing the semantics of a lot of stuff since there is a bunch of state which isn't Changing I like the idea of delegates, and I think that is a thread worth pulling on. I'm also unsure why you say that removing So, let's take a step back and think about the problem(s) we really want to solve.
Also, we use contexts a lot, both passing them round explicitly and through getting the current context from our thread local, so anything we do has to be extremely fast and make as few semantic changes as possible. Maybe something to try would be this
I think those two changes might give us enough tools to start fixing this properly. The delegate returned by These would be API changes, but I don't think there is any good way to fix this without that, so this may be an exercise in finding the least breaking change and working out how to stage things so problems can be found. |
Yes, perhaps we should revisit and clarify what the exact problem is. (see below)
hmm. We need to return a different instance on Context.enter to implement Closeable correctly. So we need either to copy that context or return some wrapper with a delegate
Perhaps we also need to clearly define the intended semantics, rather than simply adopting an incorrect semantics dictated by the historical/faulty implementation. Let me make an example: We have a java app, that executes some javascript code. This code some java code and this java code calls some other javascript code. class App {
void execJs() {
try (Context cx = Context.enter()) {
cx.setLanguageVersion(Context.VERSION_ES6);
cx.evaluateString(..., "Util.someLegacyFunc()", ...);
cx.evaluateString(... more ES6 code ..) // code will be executed in language version 1.2
}
}
}
class Util {
static void someLegacyFunc() {
try (Context cx = Context.enter()) {
cx.setLanguageVersion(Context.VERSION_1_2);
cx.evaluateString(..., "some really old script code", ...);
}
}
}I think this example shows that the current implementation of nested contexts is error-prone, and that it might even be necessary to always return a new context. There are also methods like ContextFactory.enterContext(cx)/call, which just re-use an existing context.
I fear that anyone who has extended Context will need to modify their code. However, I’d like to avoid breaking the API for any methods that simply use the context. Therefore, an abstract class would likely be preferable to an interface, since otherwise dependent libraries would need to be recompiled (interfaces require invokeInterface instead of invokeVirtual).
I've used this as a shortcut, because there are many places in the code base, where package-private members are directly accessed. Would be easier, if we use getter/setter for this. I also think that people might need such a method to get their extended context, if a delegate-wrapper was returned
Normally, you should not close things that you do not own. However, some IDEs do suggest closing such returned values. Maybe, we can deprecate the close or remove completely and use a new API (e.g.
I would say, each
Nesting is one of the major problems I see in the whole discussion. We currently just count up a value. Maybe I can try an approach with optional nesting. (so reduce nesting as much as possible.
Isn't a double close exactly, what we want... I’ll be away for a few days, but I’ll give it some more thought. Thanks for your detailed feedback. Roland |
|
I couldn’t let this go, so in #2014 I tried out an approach to see whether we could completely avoid nesting. The idea is to execute everything using This would effectively allow us to remove the nestedCounter and we could add a I’m not yet sure what I would do with enter and exit. Possibly deprecate them and migrate everything to call, which would make Closeable obsolete |
|
I still haven't heard a solution to the problem that really makes sense, and I'm grappling with this too. What if we:
That way people using the very old nested Context behavior don't need to change, and newer code that relies on try-with-resources would have to change to: I feel like this is cleaner than relying on a "call" pattern, but doesn't break old Context behavior. And once we do THAT, maybe someone could try to understand ContextFactory and the way it uses inheritance to define optional features, which might have been Java state-of-the-art in 1996 but I find very confusing today! |
With #864 Context was extended to implemet Closeable. But this does not meet the interface contract.
Javadoc of AutoCloseable.close():
In other words, when "Context implements Closeable"
must not fail.
If only Autocloseable is implemented, it is allowed (though not recommended)