-
Notifications
You must be signed in to change notification settings - Fork 11k
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
Support JDK 21 Sequenced Collections #6903
Comments
One thing that Rhys mentioned that I hadn't considered before was the documentation aspect: For someone new to, say, Then there's the benefit of the methods themselves, like |
There is another aspect to this that, in my opinion, extends beyond documentation and instead would improve correctness. The
That practice is problematic in a subtle way: It's possible to pass in arguments with unspecified iteration order (e.g. As a concrete example, I'm currently hunting down instances of this in Bazel by patching I would be happy to help with this as well as the build system changes needed to make this possible. Where are we with this? |
Could you explain a little more what you have in mind? It sounds like maybe you want to be able to consistently define APIs in Bazel to require Unfortunately, I don't think we'll be in a position to make that particular change until Guava requires Java 21+. (And we're still waiting to require Java 11.) Even with a multi-release jar, the APIs across releases are supposed to match, and if they don't, bad things happen. (Sequenced-washing is definitely a problem, one that we also see with content that gets stringified or even copied to some other kind of |
Yes, that's exactly what I would like to be able to do.
I read the issue you linked, but I'm still not sure why we can't add support for this sooner, say like this:
We of course do need to be careful about Do you see a problem with this approach? |
Sorry for not providing a shorter version of that. My understanding is that tools do not reliably read the Java-21 section of the multi-release jar when building for Java 21. So you wouldn't necessarily be able to use Now, I'm actually not sure to what extent the JDK developers have gotten into the weeds of what it means for the API to "match": It seems as if it would be useful—and not harmful—to be able to do exactly what you describe. It's conceivable that we could get a clarification or change from the JDK developers, followed by changes to various tools to make those tools look at the release that they're targeting—presumably followed by changes by changes to various projects to configure those tools correctly in cases where they were getting away with doing things wrong before :) Sadly, I wouldn't hold my breath, but maybe it's a road that we should consider starting down, even if it wouldn't pay off for many years. (I do think there's a decent chance that new versions of Guava will someday require some version of Java that has built-in support for nullness. It's even conceivable that the next jump we made will be directly from Java 8 to that version, in which case we could pick up Java-21 changes at that point. But that's still far off and hypothetical.) |
That sounds like a hen-and-egg problem to me: As long as multi-release JARs aren't properly supported by all build tools, developers aren't incentivized to make use of them. But as long as they don't, there is little value in fixing the bugs in all these tools. :-) That said, wouldn't it be okay for Guava to add this kind of feature with the understanding that tools may need to fix bugs before they can make use of it? If a certain javac alternative doesn't see the All of this of course requires clarification from the JDK devs on whether they intend to support this use case. I could ask on the mailing lists. |
Yes, I think (no guarantees, but I think) that I'd be comfortable giving this a try if we had word from the JDK developers that it's an intended use case. It would be good to push the ecosystem forward to support the feature in that case. If you're up for asking, that would be great, and I'm happy to chime in in support if you CC cpovirk@google.com. |
I was reminded by raphw/asm-jdk-bridge#3 that That checking doesn't appear to mind a difference in supertypes:
Contrast that to what happens if I stick a direct method declaration into only
I don't mean to present that as conclusive, given that the JEP left things a bit up in the air, including noting that enforcement could be incomplete. But it's at least worth noting that this one specific potential obstacle does not exist, and that may be a blurry sign to us of how upstream has been thinking about "API compatibility"—or, alternatively, just of what they found easiest to implement or most likely to catch real-world problems :) |
Nice find! I looked through the history of that validation and found that its original version somewhat explicitly doesn't consider interfaces: https://github.com/openjdk/jdk/blob/def15478ebc213eeff8eb4da9178f8bac4c72604/jdk/src/jdk.jartool/share/classes/sun/tools/jar/FingerPrint.java#L240-L248 If I don't get a reply on the mailing list, I could try to add a test to the jar validation that codifies this behavior. |
I suspect that mr-jars aren't going to be an easy way out of this, and the only viable path may be pushing to #6614 and beyond. |
Consumers of an `Immutable{List,Map,Set}` cannot know whether it has been created from a source with unspecified iteration order, such as `HashMap`. Since `ImmutableMap` itself prescribes that it preserves insertion order, it's reasonable for consumers to expect a specified, reasonable, deterministic order. While the current implementation of `HashMap` in the OpenJDK still results in a deterministic order, this kind of "sequenced-washing" doesn't cause non-determinism, but it still runs the risk of having the order of elements change between JDK versions or vendors. The locations changed in this PR have been identified by instrumenting the factory methods of the `Immutable{List,Map,Set}` classes to track whether they 1) have been created from an unsequenced collection and 2) are iterated over. I then manually picked out those cases in which I couldn't rule out that a change in order would be observable to users. Related to google/guava#6903 (comment)
Consumers of an `Immutable{List,Map,Set}` cannot know whether it has been created from a source with unspecified iteration order, such as `HashMap`. Since `ImmutableMap` itself prescribes that it preserves insertion order, it's reasonable for consumers to expect a specified, reasonable, deterministic order. While the current implementation of `HashMap` in the OpenJDK still results in a deterministic order, this kind of "sequenced-washing" doesn't cause non-determinism, but it still runs the risk of having the order of elements change between JDK versions or vendors. The locations changed in this PR have been identified by instrumenting the factory methods of the `Immutable{List,Map,Set}` classes to track whether they 1) have been created from an unsequenced collection and 2) are iterated over. I then manually picked out those cases in which I couldn't rule out that a change in order would be observable to users. Related to google/guava#6903 (comment)
Consumers of an `Immutable{List,Map,Set}` cannot know whether it has been created from a source with unspecified iteration order, such as `HashMap`. Since `ImmutableMap` itself prescribes that it preserves insertion order, it's reasonable for consumers to expect a specified, reasonable, deterministic order. While the current implementation of `HashMap` in the OpenJDK still results in a deterministic order, this kind of "sequenced-washing" doesn't cause non-determinism, but it still runs the risk of having the order of elements change between JDK versions or vendors. The locations changed in this PR have been identified by instrumenting the factory methods of the `Immutable{List,Map,Set}` classes to track whether they 1) have been created from an unsequenced collection and 2) are iterated over. I then manually picked out those cases in which I couldn't rule out that a change in order would be observable to users. Related to google/guava#6903 (comment) Closes #25218. PiperOrigin-RevId: 724341188 Change-Id: I2f38a4c3dad80055cb8d80853e9f211836019e5b
Consumers of an `Immutable{List,Map,Set}` cannot know whether it has been created from a source with unspecified iteration order, such as `HashMap`. Since `ImmutableMap` itself prescribes that it preserves insertion order, it's reasonable for consumers to expect a specified, reasonable, deterministic order. While the current implementation of `HashMap` in the OpenJDK still results in a deterministic order, this kind of "sequenced-washing" doesn't cause non-determinism, but it still runs the risk of having the order of elements change between JDK versions or vendors. The locations changed in this PR have been identified by instrumenting the factory methods of the `Immutable{List,Map,Set}` classes to track whether they 1) have been created from an unsequenced collection and 2) are iterated over. I then manually picked out those cases in which I couldn't rule out that a change in order would be observable to users. Related to google/guava#6903 (comment) Closes bazelbuild#25218. PiperOrigin-RevId: 724341188 Change-Id: I2f38a4c3dad80055cb8d80853e9f211836019e5b
Consumers of an `Immutable{List,Map,Set}` cannot know whether it has been created from a source with unspecified iteration order, such as `HashMap`. Since `ImmutableMap` itself prescribes that it preserves insertion order, it's reasonable for consumers to expect a specified, reasonable, deterministic order. While the current implementation of `HashMap` in the OpenJDK still results in a deterministic order, this kind of "sequenced-washing" doesn't cause non-determinism, but it still runs the risk of having the order of elements change between JDK versions or vendors. The locations changed in this PR have been identified by instrumenting the factory methods of the `Immutable{List,Map,Set}` classes to track whether they 1) have been created from an unsequenced collection and 2) are iterated over. I then manually picked out those cases in which I couldn't rule out that a change in order would be observable to users. Related to google/guava#6903 (comment) Closes #25218. PiperOrigin-RevId: 724341188 Change-Id: I2f38a4c3dad80055cb8d80853e9f211836019e5b Commit b03cb63 Co-authored-by: Fabian Meumertzheim <fabian@meumertzhe.im>
Nearly all access should be performed through the various methods (e.g., `casListeners(...)`, `listeners()`). In fact, we could be principled and perform _all_ access through those methods (except of course for within the implementations of those methods themselves). I haven't done that here just out of fear that it will somehow affect performance or cause stack overflows. Accidental usage has been something that I've historically worried about in, e.g., `AbstractFuture.set(V value)`, whose parameter has had the same name as a field. That _particular_ example matters less at the moment because the field recently became `private` to a new superclass, `AbstractFutureState`, and so it's not accessible in the subclass `AbstractFuture`. But it's going to matter again: I'm likely to make the fields package-private as part of [work to migrate `guava-android` off `Unsafe`](#7742). Currently, the fields can be `private` because we call `MethodHandles.lookup()` from within `AbsractFutureState`. (Yes, it would initially seem that [we shouldn't have to](https://github.com/google/guava/blob/c7363f7fb40698bb5f99d198cc45884f38642f86/guava/src/com/google/common/util/concurrent/AbstractFutureState.java#L612-L632), but we do.) But that requires `AbstractFutureState` to refer to `MethodHandles.Lookup` in one of its method signatures\[*\], and that makes old versions of Android upset. To avoid that, I will make `value` package-private, at which point I won't need the `MethodHandles.Lookup` reference in `AbstractFutureState` itself. And when I tried making `value` package-private, _one test_ started to fail. It should have taken me less time to figure out, but I eventually discovered that the problem was that [the test refers to "`value`" inside an `AbstractFuture` subclass](https://github.com/google/guava/blob/c7363f7fb40698bb5f99d198cc45884f38642f86/guava-tests/test/com/google/common/util/concurrent/AbstractFutureTest.java#L78). Previously, this referred to the local variable `value` from the test method; with my change, it was instead referring to the `value` field. (I wouldn't have to have gone down the road of making the field non-`private` in the first place [if not for Java 8 compatibility](#6614 (comment)).... Still, as discussed above, this rename could protect against problems _within_ the file, too, and such problems could arise even if the field were to remain `private`.) \[*\] Or maybe I could declare the method as returning `Object` instead of `MethodHandles.Lookup`, and the caller could cast it back? But we found during [our `SequencedCollection` work](#6903) that Android (and I think maybe the JVM, but I can't find my record of this) can produce a `VerifyError` in some cases in which _implementation_ code refers to an unknown type, I think specifically when it needs to check whether a `return someThingOfTypeFoo` from that method is compatible with the return type `Bar` of the method. We _might_ be able to work around that by performing an explicit, "redundant" cast to `Object`, but I'm not even sure how to get javac to do that, and it feels very fragile, especially in the presence of optimization/minification tools. RELNOTES=n/a PiperOrigin-RevId: 741511657
Nearly all access should be performed through the various methods (e.g., `casListeners(...)`, `listeners()`). In fact, we could be principled and perform _all_ access through those methods (except of course for within the implementations of those methods themselves). I haven't done that here just out of fear that it will somehow affect performance or cause stack overflows. Accidental usage has been something that I've historically worried about in, e.g., `AbstractFuture.set(V value)`, whose parameter has had the same name as a field. That _particular_ example matters less at the moment because the field recently became `private` to a new superclass, `AbstractFutureState`, and so it's not accessible in the subclass `AbstractFuture`. But it's going to matter again: I'm likely to make the fields package-private as part of [work to migrate `guava-android` off `Unsafe`](#7742). Currently, the fields can be `private` because we call `MethodHandles.lookup()` from within `AbsractFutureState`. (Yes, it would initially seem that [we shouldn't have to](https://github.com/google/guava/blob/c7363f7fb40698bb5f99d198cc45884f38642f86/guava/src/com/google/common/util/concurrent/AbstractFutureState.java#L612-L632), but we do.) But that requires `AbstractFutureState` to refer to `MethodHandles.Lookup` in one of its method signatures\[*\], and that makes old versions of Android upset. To avoid that, I will make `value` package-private, at which point I won't need the `MethodHandles.Lookup` reference in `AbstractFutureState` itself. And when I tried making `value` package-private, _one test_ started to fail. It should have taken me less time to figure out, but I eventually discovered that the problem was that [the test refers to "`value`" inside an `AbstractFuture` subclass](https://github.com/google/guava/blob/c7363f7fb40698bb5f99d198cc45884f38642f86/guava-tests/test/com/google/common/util/concurrent/AbstractFutureTest.java#L78). Previously, this referred to the local variable `value` from the test method; with my change, it was instead referring to the `value` field. (I wouldn't have to have gone down the road of making the field non-`private` in the first place [if not for Java 8 compatibility](#6614 (comment)).... Still, as discussed above, this rename could protect against problems _within_ the file, too, and such problems could arise even if the field were to remain `private`.) \[*\] Or maybe I could declare the method as returning `Object` instead of `MethodHandles.Lookup`, and the caller could cast it back? But we found during [our `SequencedCollection` work](#6903) that Android (and I think maybe the JVM, but I can't find my record of this) can produce a `VerifyError` in some cases in which _implementation_ code refers to an unknown type, I think specifically when it needs to check whether a `return someThingOfTypeFoo` from that method is compatible with the return type `Bar` of the method. We _might_ be able to work around that by performing an explicit, "redundant" cast to `Object`, but I'm not even sure how to get javac to do that, and it feels very fragile, especially in the presence of optimization/minification tools. RELNOTES=n/a PiperOrigin-RevId: 741511657
Nearly all access should be performed through the various methods (e.g., `casListeners(...)`, `listeners()`). In fact, we could be principled and perform _all_ access through those methods (except of course for within the implementations of those methods themselves). I haven't done that here just out of fear that it will somehow affect performance or cause stack overflows. Accidental usage has been something that I've historically worried about in, e.g., `AbstractFuture.set(V value)`, whose parameter has had the same name as a field. That _particular_ example matters less at the moment because the field recently became `private` to a new superclass, `AbstractFutureState`, and so it's not accessible in the subclass `AbstractFuture`. But it's going to matter again: I'm likely to make the fields package-private as part of [work to migrate `guava-android` off `Unsafe`](#7742). Currently, the fields can be `private` because we call `MethodHandles.lookup()` from within `AbsractFutureState`. (Yes, it would initially seem that [we shouldn't have to](https://github.com/google/guava/blob/c7363f7fb40698bb5f99d198cc45884f38642f86/guava/src/com/google/common/util/concurrent/AbstractFutureState.java#L612-L632), but we do.) But that requires `AbstractFutureState` to refer to `MethodHandles.Lookup` in one of its method signatures\[*\], and that makes old versions of Android upset. To avoid that, I will make `value` package-private, at which point I won't need the `MethodHandles.Lookup` reference in `AbstractFutureState` itself. And when I tried making `value` package-private, _one test_ started to fail. It should have taken me less time to figure out, but I eventually discovered that the problem was that [the test refers to "`value`" inside an `AbstractFuture` subclass](https://github.com/google/guava/blob/c7363f7fb40698bb5f99d198cc45884f38642f86/guava-tests/test/com/google/common/util/concurrent/AbstractFutureTest.java#L78). Previously, this referred to the local variable `value` from the test method; with my change, it was instead referring to the `value` field. (I wouldn't have to have gone down the road of making the field non-`private` in the first place [if not for Java 8 compatibility](#6614 (comment)).... Still, as discussed above, this rename could protect against problems _within_ the file, too, and such problems could arise even if the field were to remain `private`.) \[*\] Or maybe I could declare the method as returning `Object` instead of `MethodHandles.Lookup`, and the caller could cast it back? But we found during [our `SequencedCollection` work](#6903) that Android (and I think maybe the JVM, but I can't find my record of this) can produce a `VerifyError` in some cases in which _implementation_ code refers to an unknown type, I think specifically when it needs to check whether a `return someThingOfTypeFoo` from that method is compatible with the return type `Bar` of the method. We _might_ be able to work around that by performing an explicit, "redundant" cast to `Object`, but I'm not even sure how to get javac to do that, and it feels very fragile, especially in the presence of optimization/minification tools. RELNOTES=n/a PiperOrigin-RevId: 741607075
1. What are you trying to do?
Please see JEP-431 for background on what a Sequenced Collection is.
The javadoc for ImmutableCollection mentions that most Guava Collections have a well-defined iteration order. Indeed, I often rely on this behaviour. This issue is to discuss support for the SequencedCollections interface, in order to better communicate that Guava Collections have this property.
2. What's the best code you can write to accomplish that without the new feature?
This example does not make much sense without the context of the added methods in SequencedCollection, however:
3. What would that same code look like if we added your feature?
Sequenced Collections add new convenience methods that arise from having a defined iteration order. Applying this to (for example) ImmutableSet, which preserves insertion order, would result in the following code being valid:
(Optional) What would the method signatures for your feature look like?
No response
Concrete Use Cases
I have not yet used Guava with JDK 21.
Please see related Guava discussion: https://groups.google.com/g/guava-discuss/c/IXWZCHn7yJs
Packages
com.google.common.collect
Checklist
I agree to follow the code of conduct.
I have read and understood the contribution guidelines.
I have read and understood Guava's philosophy, and I strongly believe that this proposal aligns with it.
I have visited the idea graveyard, and did not see anything similar to this idea.
The text was updated successfully, but these errors were encountered: