<div dir="ltr"><div dir="ltr"><div class="gmail_default" style="font-family:arial,helvetica,sans-serif"><br></div></div><br><div class="gmail_quote gmail_quote_container"><div dir="ltr" class="gmail_attr">On Mon, Dec 16, 2024 at 4:40 PM Ron Pressler <<a href="mailto:ron.pressler@oracle.com">ron.pressler@oracle.com</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">> I'm asking for the OpenJDK team to instead consider a different design for service loading in the Java platform itself, in the absence of an alternative solution which can use what exists today. Unfortunately, the constraints around the JPMS are such that I do not believe there is another design that will generally work for us.<br>
<br>
I understand what solution you’re proposing, but as with any feature design, the severity of the problem and its root causes need to be understood before settling on a specific solution.<br>
<br>
The issue you’re facing arises when making a particular design choice: a container that loads every module in a separate layer and with multiple parents. We need to ask ourselves, how common is this design, why do people pick it (yes, you explained why you picked it, but we’ll need to study alternatives), and are we expecting the number of such use cases to grow or shrink over time?<br></blockquote><div><br></div><div><div class="gmail_default" style="font-family:arial,helvetica,sans-serif">Of course, such container environments are rare. The `ModuleLayer.Controller` API itself exists at all because of what could easily be described as a very small/narrow use case. So I would not expect a large number of users of the API.</div><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">> I do believe that there are possible workarounds which belie the conceit that the encapsulation of the module system is as strong as you present it; the existence of `ModuleLayer.Controller` itself shows that it is nowhere near absolute. This level of encapsulation is much more superficial than, say, member access control, which is to say that it can be worked around in various ways without "breaking the rules" (for example, by having a class loader define generated helper classes in every package of a module, I can gain access to their original Lookup objects, breaking *any* form modular of encapsulation without breaking any of the rules of the platform specification). If you would recoil at such a thing, consider again the difference between "intent" and "specification".<br>
<br>
I would be interested to hear more about this, because our assumption is that the encapsulation — once integrity by default is done, i.e. there is no hidden use of Unsafe or JNI etc. — is, in fact, absolute (modulo bugs), to the point that finally the runtime will be able to actually trust Java’s invariants. Both the security of the platform and the correctness of the compilers depend on it, so if you think we’re wrong — this is important. You mention ModuleLayer.Controller, but I don’t see how it undermines anything (in particular notice how the method that enables a capability with potentially global impact — native access — is caller-sensitive). As an example, how would library use it to mutate a string (something that was trivial before strong encapsulation and the JNI restriction)?<br></blockquote><div><br></div><div><div class="gmail_default" style="font-family:arial,helvetica,sans-serif">You're confusing the idea of modular encapsulation with the idea of platform integrity. I'm talking specifically about the encapsulation of modules that are loaded by a custom ClassLoader. If the user defines their own version of a string class which purports to be immutable, and that class is defined within my ClassLoader, then yes, I can mutate it if I want to. There are many ways to do this.</div><div class="gmail_default" style="font-family:arial,helvetica,sans-serif"><br></div><div class="gmail_default" style="font-family:arial,helvetica,sans-serif">When I load a module in a class loader, I can basically do whatever I want with it, if I'm willing to do some tedious (and possibly performance-degrading) work. All I'm proposing is to make the work of doing *certain* things less tedious and performance degrading.</div><div class="gmail_default" style="font-family:arial,helvetica,sans-serif"><br></div><div class="gmail_default" style="font-family:arial,helvetica,sans-serif">For example, `Module` has an `addReads` method. I could, with a custom ClassLoader that loads some module, define an extra class within the module which lets me call that method from my ClassLoader. This work is tedious and has a performance impact. Thankfully, `ModuleLayer.Controller` lets me bypass having to do this, recognizing that I would already have the ability to bypass encapsulation in this way.</div><div class="gmail_default" style="font-family:arial,helvetica,sans-serif"><br></div><div class="gmail_default" style="font-family:arial,helvetica,sans-serif">However, to give a counter-example, `Module` has `addUses` as well. But if I want to call `addUses` on behalf of a module I've defined, there is no corresponding `ModuleLayer.Controller` method so I do have to define the extra class, and that's a bit silly. This is an example of an easy enhancement that would not affect the integrity of the platform, would not significantly increase maintenance burden (since the logic would be nearly identical to its sibling methods), and would be easy to achieve.</div><div class="gmail_default" style="font-family:arial,helvetica,sans-serif"><br></div><div class="gmail_default" style="font-family:arial,helvetica,sans-serif">Likewise, for service loading I can rewrite every class that is defined in the class loader to redirect `ServiceLoader.load()` calls through an API I specify, which in turn generates a synthetic module and layer which contains generated classes that can load services from whatever I choose, all without breaking a single encapsulation rule. While this is useful to illustrate a point, it would be much better if I could (for example) call `addProvides` at run time to link a module to its available service providers, or otherwise intercept the service loading process.</div><br></div><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
> I can tell you that several design choices of the JPMS had nothing to do with encapsulation principles and everything to do with the philosophy of those who created the constraints. There's nothing inherent in the system that makes it necessary to prevent circularity, *or* to eagerly load all modules in a layer, *or* to eagerly resolve services.<br>
<br>
True, but strong encapsulation is not the only benefit this feature provides and not every aspect of it serves strong encapsulation.<br></blockquote><div><br></div><div><div class="gmail_default" style="font-family:arial,helvetica,sans-serif">Of course.</div></div><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
> there are real implications to these choices that mean that many kinds of Java application containers cannot be satisfactorily migrated to use JPMS modules. I hope you recognize that I am trying to change that, but that doing so may require some form of compromise from the platform itself.<br>
<br>
I presume you’re trying to use the feature because you want to enjoy the benefits it offers now and in the future.</blockquote><div><br></div><div><div class="gmail_default" style="font-family:arial,helvetica,sans-serif">From our perspective, we started off with an open system, and the system has been made less and less open over time. Strictly speaking, the only "feature" here is fancy stack traces. Everything else arises out of adding restrictions. I'm not saying this to complain about modularity, but to remind you that we are presently only weakly incentivized to use modules at all, so dangling intangible "benefits" before me does not entice me much. We get more "features" by not using it. I'm essentially trying to be a team player here, but only out of a sense of cooperation.</div></div><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"> Obviously, we want all our features to be as easy to use to maximise the value users get from them, but we also need to balance the cost of additional complexity and the utility it adds to the ecosystem as a whole. So we can consider that, and you can consider if there really is no acceptable design for a container other than loading every module in its own layer and constructing a complex layer graph. Maybe it turns out that *both* of these things are true, i.e. it would be a good idea to change ServiceLoader and also your design.<br></blockquote><div><br></div><div><div class="gmail_default" style="font-family:arial,helvetica,sans-serif">Sure.</div><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">> Yes. Quarkus currently does not use JPMS modules for the user application at build time or in any deployment mode, both of which instead rely on an arrangement of specialized class loaders, with all classes ending up in flat unnamed modules. My current experiments revolve around trying to create more modular-oriented and encapsulated packaging options.<br>
<br>
So you want Quarkus to also load one module per layer?<br></blockquote><div><br></div><div><div class="gmail_default" style="font-family:arial,helvetica,sans-serif">I'm not sure yet. But I think so.</div><br></div><div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">To be more constructive, one idea that was floated in this discussion is to use a small number of layers. Perhaps one for the container and one for each user application.<br><br>You claimed that doing so would be inefficient (and that that is the reason, or one reason, why you opted for the layer-per-module design). Thing is that we do need to support many modules per layer efficiently, and since there’s currently much work focused on improving startup time, knowing if there’s a problem there would be very useful.<br><br>So if there’s a problem there, we’ll need to address it anyway, and if we do, then wouldn’t it also address yours? This way there is no addition of spec complexity, and many more usecases can benefit.<br></blockquote><div><br></div><div><div class="gmail_default" style="font-family:arial,helvetica,sans-serif">Inefficiency is just one reason that I believe that a small number of large, flat layers won't work for us, but we can focus on it. I don't really see any good way around it though. It's tied directly to the problem of having to eagerly resolve all modules in a graph, which as I said before, I believe is a very clear design error. I'll explain once again.</div><div class="gmail_default" style="font-family:arial,helvetica,sans-serif"><br></div><div class="gmail_default" style="font-family:arial,helvetica,sans-serif">Imagine a layer of 1000 modules, packaged in JAR files. To load a single class from the layer, the layer must be defined. To do this, the graph must be resolved, and to accomplish that, the descriptors of every module must be created. To get the descriptors, each of the 1000 JAR files has to be opened and the bytes of the descriptors must be read from each one. Because of eager resolution, the load time of a module layer will always scale in linear proportion to the potential number of modules in the layer, no matter how optimized each step in the process is made to be. The only possible optimization strategies involve paring down the root set - something which requires ahead-of-time analysis which again would not be necessary if modules were loaded and linked lazily, like classes are. Loading modules on demand would solve this performance issue fairly decisively; it would also not forbid ahead-of-time assembly of a module graph if that is what the user wants.</div></div></div><div class="gmail_default" style="font-family:arial,helvetica,sans-serif"><br></div><div class="gmail_default" style="font-family:arial,helvetica,sans-serif">However, based on the history of the JPMS, I don't see this ever changing (unfortunately) but you did ask.</div><div>--</div></div><div dir="ltr" class="gmail_signature"><div dir="ltr">- DML • he/him<br></div></div></div>