Hi Michał,

Inline below.

On 16/02/2022 5:24 pm, Michał Kłeczek wrote:

On 16 Feb 2022, at 04:25, Peter Firmstone <peter.firmst...@zeus.net.au> wrote:

 From the CodebaseAccessor service.

The CodebaseAccessor proxy (local code) is passed as a parameter along with a 
MarshalledInstance of the proxy, by ProxySerializer to ProxyCodebaseSpi.

https://github.com/pfirmstone/JGDMS/blob/trunk/JGDMS/jgdms-platform/src/main/java/net/jini/export/CodebaseAccessor.java
 
<https://github.com/pfirmstone/JGDMS/blob/trunk/JGDMS/jgdms-platform/src/main/java/net/jini/export/CodebaseAccessor.java>
Ok, so you have introduced a level of indirection to retrieve String codebase 
annotations. Why not go one step further and instead of:

interface CodebaseAccessor {
   String getClassAnnotation() throws IOException;
}

have something along the lines of (conceptually):

interface Codebase extends RemoteMethodControl {
   ClassLoader getClassLoader() throws IOException;
}


I personally wouldn't take this step, because CodebaseAccessor is a Remote interface and ClassLoader is a class of high privilege, so it presents a security risk.  It is also used in IPv6 multicast lookup discovery, where I want to avoid sending any objects over the network.

If you want to send java bytecode, you could do that by converting the byte[] array to a String, then convert it back to byte[], that way you can leverage existing api.  The problem is that you are still constrained by the limitations I have chosen, that is, to use CodebaseAccessor requires the export of a remote object.  I think you wish to be able to send arbitrary graphs, so you will need to append these bytes in the stream, rather than use CodebaseAccessor.


The reasoning is:
In principle class annotation is a program in (unspecified) language that is 
executed by the client to create a ClassLoader instance.
You have to make sure all participants share this language and can execute it 
in the same way.
If the type of class annotation is String - it only means this is some kind of 
obscure interpreted scripting language.

The insight here is that we _already have_ the language that we _know_ all 
participants can execute: Java bytecode.
There is no reason not to use it.

Ok, so the client has to know in advance how to load the service code?
Yes, the client knows how to load the service code dynamically just prior to 
proxy unmarshalling, a default ProxyCodebaseSpi is provided for that purpose 
and it is used by default.
See above:
you can move this code from the client to the service and let it provide the 
implementation of class resolution algorithm.


I would advise against that, the remote service JVM knows little about the client JVM, both are likely to have completely different ClassLoader hierarchies.

What I try to do is have identical bundles loaded by the ClassLoaders at the ServerEndpoint and (client) Endpoint and let OSGi resolve the classes, so they both have an absolutely identical view of classes.  At the very least, dependency bundles resolved need to be version compatible for serialized form.   Same with other modular frameworks.



If you are using Maven, eg Rio Resolver, or OSGi, then a ProxyCodebaseSpi 
implementation specific to one of these should be used.  If you have a mixed 
environment, then you can use the codebase string to determine which to use.
See above: you can also abstract it away behind an interface.

Does it require to have it installed in advance? If so - how?
Only JGDMS platform and JERI.


How are service proxy classes loaded then?
ProxyCodebaseSpi::resolve does this by provisioning a ClassLoader then 
unmarshalling the proxy into it.

OSGi:

https://github.com/pfirmstone/JGDMS/blob/a774a9141e6571f1d7f9771f74b714850d447d3e/JGDMS/jgdms-osgi-proxy-bundle-provider/src/main/java/org/apache/river/osgi/ProxyBundleProvider.java#L131
 
<https://github.com/pfirmstone/JGDMS/blob/a774a9141e6571f1d7f9771f74b714850d447d3e/JGDMS/jgdms-osgi-proxy-bundle-provider/src/main/java/org/apache/river/osgi/ProxyBundleProvider.java#L131>
[...]

I am asking about something different - the smart proxy class depends on _two_ 
interfaces:

RemoteEventListener <—— proxy class ——> JavaSpace

RemoteEventListener is its service interface.
But it is not known in advance what interfaces the client already has:

1) Just RemoteEventListener
2) Both RemoteEventListener and JavaSpace
3) None

How is class resolution implemented in JGDMS so that it works properly in _all_ 
of the above cases?
This is a responsibility of the underlying platform used for class resolution 
or modularity.

If using OSGi, OSGi will resolve the required dependencies and download them if 
not already present on the client, OSGi will give preference if a compatible 
version of the bundle dependencies already loaded at the client.   If using 
preferred classes the preferred class list will determine the order of 
preference, whether classes are loaded from the proxy codebase or the client 
ClassLoader (the parent loader of the proxy ClassLoader) first.
I also tried this route and it is a dead end because

* it is not possible to statically (ie. as part of the software package like 
OSGi manifest) provide dependency resolution constraints to be able to exchange 
arbitrarily constructed object graphs *


This is a limitation and compromise I have accepted, JGDMS doesn't attempt to load arbitrarily constructed object graphs, instead it ensures that both endpoints of a Service have the same class resolution view, the same proxy bundle version is used at the Server and client.  At the server, the proxy bundle is depended upon by the service implementation, but  nothing at the client depends upon the proxy bundle loaded there, instead the proxy bundle depends on the api loaded by the client.  That is why JGDMS discourages marshaling of client subclasses that override service method parameter classes, because they cannot be exported as remote objects and are subject to codebase annotation loss and class resolution problems.  Sometimes, less is more, I've chosen this compromise in this instance to avoid complexity.  I saw little to be gained from the added complexity, it can be worked around by improving service api design and practices.  Instead the client can export method parameter interface types as remote objects, with independent ClassLoader visibility, that will resolve to common super interface types.

If there is a versioning problem at the client, where it uses an incompatible API with the client, ServiceDiscoveryManager will recognise the service is the incorrect type and discard it.

I recognise my own limitations, I'm not smart enough to solve the problems of de-serialization of arbitrary object graphs, so I have left it as a task for someone either smarter or more determined than myself and focused on what I believe is an acceptable and reliable compromise.  It is because of my own limitations, that I reduced complexity, so that I (as well as inexperienced users) didn't have to worry about codebase annotation loss or class resolution issues.

I'm not saying it can't be solved, or that you won't succeed, simply that after a lot of thought, I decided it would be easier to reduce complexity and accept some limitations as this seems like the best compromise, there are also other things which have a higher priority for my time.

Cheers,

Peter.


Take for example the RemoteEventSpacePublisher class from my previous email:

interface RemoteEventListener {
}

package net.jini.space;
interface JavaSpace {
}

Bundle manifest:
Import-Package: net.jini.space;version=“[1.0,1.7)”

//implements JavaSpace up to version 1.7
class JavaSpaceImplProxy implements JavaSpace, Serializable {
}

Bundle manifest:
Import-Package: net.jini.space;version=“[1.5,2.0)”

//Requires JavaSpace of at least version 1.5
class RemoteEventSpacePublisher implements RemoteEventListener, Serializable {
   private JavaSpace space;
}


During deserialisation of RemoteEventSpacePublisher the resolution of a bundle 
containing JavaSpace interface has to result in a _single_ bundle that provides 
net.jini.space package obeying _two_ constraints:
Import-Package: net.jini.space;version=“[1.5,2.0)” - from 
RemoteEventSpacePublisher
Import-Package: net.jini.space;version=“[1.0,1.7)” - from JavaSpaceImplProxy

The problem  is that this information is not known to the client OSGi runtime:

1. It first resolves RemoteEventSpacePublisher annotation to a bundle wired to 
some bundle providing net.jini.space;version=“[1.5,2.0)”
2. After that it _separately_ resolves JavaSpaceImplProxy annotation to a 
bundle wired to _possibly different_ bundle providing 
net.jini.space;version=“[1.0,1.7)”

The reason is that when serialising the object graph in your solution you are 
loosing information about effective runtime constraints used for class 
resolution.
Your ProxyBundleProvider interprets codebase annotation as a path to a specific 
bundle - but it does not contain any information about this bundle wiring in 
the service VM.

If using Rio Resolver, it will downloaded any required dependencies dynamically 
using Maven.
I am afraid it does not change anything.

https://github.com/pfirmstone/JGDMS/wiki/OSGi-and-JGDMS 
<https://github.com/pfirmstone/JGDMS/wiki/OSGi-and-JGDMS>

What I learned from the many discussions and arguments on River mailing lists, 
was that developers use different platforms to manage class resolution 
visibility or provide modularity, so I considered it important not to constrain 
developers and allow them to use their preferred platform, not to decide for 
them.
The point is that _none_ of the existing platforms can handle it properly (see 
above).

Once you solve the problem of exchanging arbitrarily constructed object graphs 
of dynamically downloaded and resolved classes it opens up a whole lot of 
possibilities because you can quite easily model solutions as POJOs.

Cheers,
Michal


Reply via email to