Some Eclipse Foundation services are deprecated, or will be soon. Please ensure you've read this important communication.

Bug 331383

Summary: Remote services with Java primitives fail
Product: [RT] ECF Reporter: Bryan Hunt <bhunt>
Component: ecf.remoteservicesAssignee: Markus Kuppe <bugs.eclipse.org>
Status: RESOLVED FIXED QA Contact:
Severity: major    
Priority: P3 CC: bob.ziuchkovski, slewis
Version: 3.4.0   
Target Milestone: ---   
Hardware: PC   
OS: Mac OS X - Carbon (unsup.)   
Whiteboard:
Attachments:
Description Flags
A (examplary) test case
none
mylyn/context/zip none

Description Bryan Hunt CLA 2010-11-29 18:23:55 EST
A remote service with functions that contain Java primitives as arguments cannot be called.  

For example:

public interface MyService
{
  void update(long value);
}

If you attempt to call update() remotely, you will get a NoSuchMethodException.  


The root cause of the problem is in org.eclipse.ecf.core.util.reflection.ClassUtil line 68:

clazzA.isAssignableFrom(clazzB) 

will return false if one class is a primitive and the other is the object form of the primitive.  See also:

http://stackoverflow.com/questions/1650614/isassignablefrom-with-reference-and-primitive-types


The workaround is to change all primitive arguments to their object form.  For example:

public interface MyService
{
  void update(Long value);
}
Comment 1 Markus Kuppe CLA 2010-11-30 06:55:00 EST
(In reply to comment #0)
> The root cause of the problem is in
> org.eclipse.ecf.core.util.reflection.ClassUtil line 68:
> 
> clazzA.isAssignableFrom(clazzB)
> 
> will return false if one class is a primitive and the other is the object form
> of the primitive.  See also:

I'd argue that ClassUtil behaves correctly. The problem rather appears to be that primitives get converted into the Object form during transport. 
Question is, if we make sure providers reverse the conversion prior to calling ClassUtil or if ClassUtil should take autoboxing into account and return a match anyway (assuming there is no exact better match)?!
Comment 2 Markus Kuppe CLA 2010-11-30 06:56:06 EST
Created attachment 184109 [details]
A (examplary) test case
Comment 3 Markus Kuppe CLA 2010-11-30 06:56:09 EST
Created attachment 184110 [details]
mylyn/context/zip
Comment 4 Markus Kuppe CLA 2010-11-30 07:50:09 EST
Btw. org.apache.commons.lang.ClassUtils behaves exactly the same as the current implementation of ECF's org.eclipse.ecf.core.util.reflection.ClassUtil.
Comment 5 Scott Lewis CLA 2010-11-30 08:58:47 EST
(In reply to comment #1)
> (In reply to comment #0)
> > The root cause of the problem is in
> > org.eclipse.ecf.core.util.reflection.ClassUtil line 68:
> > 
> > clazzA.isAssignableFrom(clazzB)
> > 
> > will return false if one class is a primitive and the other is the object form
> > of the primitive.  See also:
> 
> I'd argue that ClassUtil behaves correctly. 

Well, I'd agree that ClassUtil behaves according to spec...but autoboxing was added to java after isAssignableFrom...so I'd also argue that isAssignableFrom isn't really spec'd properly given autoboxing.

>The problem rather appears to be
> that primitives get converted into the Object form during transport. 

Right...this is just autoboxing.  It happens well before transport...it's done by the proxy.

> Question is, if we make sure providers reverse the conversion prior to calling
> ClassUtil or if ClassUtil should take autoboxing into account and return a
> match anyway (assuming there is no exact better match)?!

Couldn't we use Class.isPrimitive() (see http://download.oracle.com/javase/1.4.2/docs/api/java/lang/Class.html#isPrimitive%28%29) to have ClassUtil return match in the case of (e.g.) long isAssignableFrom Long?

I'm not sure how we could require the providers to reverse the conversion.  If isPrimitive is not usable or doesn't do the right thing then it probably requires sending meta-data about the class (whether it's a primitive or not).
Comment 6 Markus Kuppe CLA 2010-11-30 11:28:20 EST
(In reply to comment #5)

> Couldn't we use Class.isPrimitive() (see
> http://download.oracle.com/javase/1.4.2/docs/api/java/lang/Class.html#isPrimitive%28%29)
> to have ClassUtil return match in the case of (e.g.) long isAssignableFrom
> Long?

That's certainly possible but it bloats the implementation and needs extra consideration to return the closest/exact match first. E.g.

class TestClass {
public void foo(final Boolean b) {}
public void foo(final boolean b) {}
}

> I'm not sure how we could require the providers to reverse the conversion.  If
> isPrimitive is not usable or doesn't do the right thing then it probably
> requires sending meta-data about the class (whether it's a primitive or not).

Btw. which provider shows this behavior? Does this happen for both r-OSGi as well as the generic provider?
Comment 7 Bryan Hunt CLA 2010-11-30 11:57:35 EST
(In reply to comment #6)
> Btw. which provider shows this behavior? Does this happen for both r-OSGi as
> well as the generic provider?

I'm using the generic provider.  I have not tested with R-OSGi.
Comment 8 Markus Kuppe CLA 2010-11-30 11:59:33 EST
(In reply to comment #7)
> I have not tested with R-OSGi.

Can you do so and test it with the generic provider?
Comment 9 Scott Lewis CLA 2010-11-30 12:13:47 EST
(In reply to comment #6)
<stuff deleted>
> 
> That's certainly possible but it bloats the implementation 

Do you mean just adds a little code to the method match matching?  I'm not sure if I would characterize that as bloat.

>and needs extra
> consideration to return the closest/exact match first. E.g.
> 
> class TestClass {
> public void foo(final Boolean b) {}
> public void foo(final boolean b) {}
> }


True...but again I'm not sure that this is so much of a problem...a couple of lines perhaps, but not a huge chunk of code.


> 
> > I'm not sure how we could require the providers to reverse the conversion.  If
> > isPrimitive is not usable or doesn't do the right thing then it probably
> > requires sending meta-data about the class (whether it's a primitive or not).
> 
> Btw. which provider shows this behavior? Does this happen for both r-OSGi as
> well as the generic provider?


I think it will probably be shown by both (since r-osgi uses proxy autoboxing and object serialization as well for arguments), but it would be worthwhile testing/verifying this.
Comment 10 Markus Kuppe CLA 2010-11-30 12:20:46 EST
(In reply to comment #9)
> (In reply to comment #6)
> <stuff deleted>
> > 
> > That's certainly possible but it bloats the implementation 
> 
> Do you mean just adds a little code to the method match matching?  I'm not sure
> if I would characterize that as bloat.
> 
> >and needs extra
> > consideration to return the closest/exact match first. E.g.
> > 
> > class TestClass {
> > public void foo(final Boolean b) {}
> > public void foo(final boolean b) {}
> > }
> 
> 
> True...but again I'm not sure that this is so much of a problem...a couple of
> lines perhaps, but not a huge chunk of code.

It's a few lines of code which have to be executed for each remote call. Given that, an extra bit on the network layer might a better option. Anyway, branch 331383 now contains a ClassUtil implementation that takes autoboxing into account.
Comment 11 Scott Lewis CLA 2010-11-30 12:32:55 EST
(In reply to comment #10)
> > True...but again I'm not sure that this is so much of a problem...a couple of
> > lines perhaps, but not a huge chunk of code.
> 
> It's a few lines of code which have to be executed for each remote call. 


Sure...but since we're talking about a remote call the I/O performance is going to swamp any performance penalty associated with such code.   Even the reflective Method lookup (getDeclaredMethods() is likely to swamp these calls I would guess.


>Given
> that, an extra bit on the network layer might a better option. Anyway, branch
> 331383 now contains a ClassUtil implementation that takes autoboxing into
> account.

Ok...I'll take a look at it when I can (not right away).  So isPrimitive() plus checks can/does work to deal with this issue?
Comment 12 Markus Kuppe CLA 2010-11-30 12:40:57 EST
(In reply to comment #11)

> Sure...but since we're talking about a remote call the I/O performance is going
> to swamp any performance penalty associated with such code.   Even the
> reflective Method lookup (getDeclaredMethods() is likely to swamp these calls I
> would guess.

Without some hard numbers to back up both of our statements are guesswork.

> Ok...I'll take a look at it when I can (not right away).  So isPrimitive() plus
> checks can/does work to deal with this issue?

http://git.eclipse.org/c/ecf/org.eclipse.ecf.git/commit/?h=331383&id=4cf138ce33a599f69cd84e9bd4afc70051683f37
Comment 13 Scott Lewis CLA 2010-11-30 12:45:13 EST
(In reply to comment #12)
> (In reply to comment #11)
> 
> > Sure...but since we're talking about a remote call the I/O performance is going
> > to swamp any performance penalty associated with such code.   Even the
> > reflective Method lookup (getDeclaredMethods() is likely to swamp these calls I
> > would guess.
> 
> Without some hard numbers to back up both of our statements are guesswork.

Agreed that we're guessing.  But it's a pretty good guess that when it comes to remote calls any network I/O will on balance be an order of magnitude more costly (in time) than anything that happens locally.  That's all I'm saying.

> 
> > Ok...I'll take a look at it when I can (not right away).  So isPrimitive() plus
> > checks can/does work to deal with this issue?
> 
> http://git.eclipse.org/c/ecf/org.eclipse.ecf.git/commit/?h=331383&id=4cf138ce33a599f69cd84e9bd4afc70051683f37

ok, ok :).
Comment 14 Markus Kuppe CLA 2010-11-30 12:48:26 EST
(In reply to comment #13)
> (In reply to comment #12)
> > (In reply to comment #11)
> > 
> > > Sure...but since we're talking about a remote call the I/O performance is going
> > > to swamp any performance penalty associated with such code.   Even the
> > > reflective Method lookup (getDeclaredMethods() is likely to swamp these calls I
> > > would guess.
> > 
> > Without some hard numbers to back up both of our statements are guesswork.
> 
> Agreed that we're guessing.  But it's a pretty good guess that when it comes to
> remote calls any network I/O will on balance be an order of magnitude more
> costly (in time) than anything that happens locally.  That's all I'm saying.

True, but in this specific case we are not talking about a complete new remote call. It would just be an extra bit per parameter type that has a primitive counterpart.
Comment 15 Scott Lewis CLA 2010-11-30 12:58:32 EST
(In reply to comment #14)
<stuff deleted>
> > remote calls any network I/O will on balance be an order of magnitude more
> > costly (in time) than anything that happens locally.  That's all I'm saying.
> 
> True, but in this specific case we are not talking about a complete new remote
> call. It would just be an extra bit per parameter type that has a primitive
> counterpart.

Yeah :)...I get that...but all I'm saying is that if we add a constant...and likely pretty small...amount to the host-side invocation...it's likely to not be noticeable in most remote services use cases.  And as this bug indicates, having support for autoboxing/primitive types in args for all providers that use ClassUtil will be a noticeable improvement for some remote services users.

I stared at the code on the branch.  It looks good to me.  I haven't had a chance to setup/run the tests yet, but if it passes all tests (including the new one you created) then I would be in favor of merging to master for 3.4.1 and 3.5.
Comment 16 Markus Kuppe CLA 2011-02-16 02:47:38 EST
Bryan, do you want this in ECF 3.5?
Comment 17 Markus Kuppe CLA 2011-02-16 02:49:31 EST
FWIW: http://git.eclipse.org/c/ecf/org.eclipse.ecf.git/log/?h=331383 is the branch that has been created to test a possible fix.
Comment 18 Bryan Hunt CLA 2011-02-16 09:13:14 EST
I have a workaround, so it's not critical, but it would be nice to get this fixed.
Comment 19 Markus Kuppe CLA 2011-02-16 09:15:29 EST
(In reply to comment #18)
> I have a workaround, so it's not critical, but it would be nice to get this
> fixed.

Have you tried the changes done in branch 331383?
Comment 20 Bryan Hunt CLA 2011-02-16 09:17:07 EST
I've not tried the fix.  I might be able to try it in 2 - 4 weeks.
Comment 21 Scott Lewis CLA 2011-08-09 00:43:28 EDT
I believe this has been fixed.
Comment 22 Bob Ziuchkovski CLA 2011-11-29 18:26:18 EST
I just ran into this issue on the ECF 3.5.3 release.  Per Scott's last comment, should this be fixed in 3.5.3, or is it fixed in a later commit?
Comment 23 Scott Lewis CLA 2011-11-29 20:54:54 EST
(In reply to comment #22)
> I just ran into this issue on the ECF 3.5.3 release.  Per Scott's last comment,
> should this be fixed in 3.5.3, or is it fixed in a later commit?

Hi Bob

Could you describe what you are (not) seeing for your use case?  (eg your interface sig...and what you are seeing at runtime?  Also...what jre version you are using?

Thanksinadvance
Comment 24 Markus Kuppe CLA 2011-11-30 13:12:59 EST
As a matter of fact, the fix hasn't been merge from git branch 331383 [0] to master. I'm still waiting to receive feedback first.

Markus

[0] http://git.eclipse.org/c/ecf/org.eclipse.ecf.git/log/?h=331383
Comment 25 Bob Ziuchkovski CLA 2011-11-30 16:11:07 EST
Thanks for the info.  I was testing the simple IPOJO DOSGI tutorial (http://felix.apache.org/site/apache-felix-ipojo-dosgi.html) and adapting it to ECF.

The exported service interface from this tutorial is as follows:

public interface AdderService {
    int add(int a,int b);
}

The primitive handling for the ECF 3.5.3 release was causing the remote call to fail.  Changing the interface to the following, however, works:

public interface AdderService {
    Int add(Int a,Int b);
}

Per Markus' comment, I checked-out and built the 331383 branch.  Using this branch, calls to the original primitive-based interface are successful.