Request for review: 6597112: Referential integrity loophole during remote object export

Peter Jones pcj at roundroom.net
Tue Mar 29 06:01:10 UTC 2011


Neil,

Hi.  For background, I filed 6597112.  Thanks for looking into it.

I don't see any correctness flaws with your suggested fix.  It feels to me, however, unnecessarily elaborate for the problem being fixed.  It adds an additional (and less localized) use of the Target.pinImpl/unpinImpl mechanism, which is currently only used to "pin" remote objects that are known to have live remote references (or for the internal "permanent" case), for which ongoing strong reachability from a static root (i.e. ObjectTable) is necessary.  For this bug, it is only necessary to ensure strong reachability during the execution of the UnicastServerRef.exportObject method in the exporting thread.  The reachabilityFence method from the concurrency folks' Fences proposal would enable a robust one-line fix:

http://g.oswego.edu/dl/jsr166/dist/docs/java/util/concurrent/atomic/Fences.html#reachabilityFence(java.lang.Object)

But I haven't been following the status of that API making it into the JDK.  Barring that, other localized approaches should work-- the canonical if not optimal example is passing the reference to a native method.  It would be nice to have a recommended idiom from JVM experts.[*]

Another possibility, though, is to just not throw this ExportException (which is effectively an assertion failure) in ObjectTable.putTarget, but rather let the remote object's export appear to succeed even though it is known to have been garbage collected.  Given that the caller isn't ensuring strong reachability of the object, the effect would be indistinguishable from the remote object being collected immediately after the remote object export completes anyway.  [This likely indicates a bug in a real application, as opposed to a test case, which is why 6597112 didn't seem especially severe, unlike 6181943.]  With this approach, it would be important to guard against the case of ObjectTable.Reaper processing a WeakRef from the queue before ObjectTable.putTarget has executed for it.

Regarding this diff in particular:

> -     * If "permanent" is true, then the impl is pinned permanently
> -     * (the impl will not be collected via distributed and/or local
> -     * GC).  If "on" is false, than the impl is subject to
> -     * collection. Permanent objects do not keep a server from
> -     * exiting.
> +     * Upon return, the impl is pinned, thus not initially eligible
> +     * for collection via distributed and/or local GC).
> +     * If "permanent" is false, the impl subsequently may be made subject to
> +     * collection by calling {@link #unpinImpl()}.
> +     * Permanent or pinned objects do not keep a server from exiting.

I would leave the last sentence alone-- pinned objects can prevent JVM exit, the point there is really that so-called "permanent" objects don't.

Cheers,

-- Peter

[*] The fix for the similar bug 6181943, in StreamRemoteCall.executeCall, used a DGCAckHandler instance to hold references to objects to be kept strongly reachable for the duration of a remote call, and the invocation of "ackHandler" in the finally block ensured that the container itself remains reachable from the thread for the same duration.  For 6181943, there was a desire to hold only remote references, not the entirety of the call arguments, hence the use of DGCAckHandler which cooperates with the object output stream.  The 6597112 case is simpler: just need to hold the remote object being exported.


On Mar 24, 2011, at 4:54 PM, Neil Richards wrote:

> Hi all,
> Can I encourage further review / comment on my suggested fix (and
> testcase) for this problem [1] [2], please ?
> 
> For reference, I include the 'hg export' for the latest version of the
> change below,
> 
> Any feedback gratefully received,
> Thanks,
> Neil
> 
> PS: I see the bug details [2] are not currently externally visible, but
> don't know of a good reason for this. Could this be reviewed, please?
> 
> [1] http://mail.openjdk.java.net/pipermail/core-libs-dev/2011-March/006144.html
> [2] http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6597112
> 
> 
> -- 
> Unless stated above:
> IBM email: neil_richards at uk.ibm.com
> IBM United Kingdom Limited - Registered in England and Wales with number 741598.
> Registered office: PO Box 41, North Harbour, Portsmouth, Hampshire PO6 3AU
> 
> 
> # HG changeset patch
> # User Neil Richards <neil.richards at ngmr.net>, <neil_richards at uk.ibm.com>
> # Date 1298369045 0
> # Branch j6597112
> # Node ID 0c22e02a9016bd340e2182e96a10fe96cd75c8ac
> # Parent  6bbc7a4734952ae7604578f270e1566639fa8752
> 6597112: referential integrity loophole during remote object export
> Summary: Eliminate weak ref GC window during RMI export
> Contributed-by: <neil.richards at ngmr.net>
> 
> diff --git a/src/share/classes/sun/rmi/server/UnicastServerRef.java b/src/share/classes/sun/rmi/server/UnicastServerRef.java
> --- a/src/share/classes/sun/rmi/server/UnicastServerRef.java
> +++ b/src/share/classes/sun/rmi/server/UnicastServerRef.java
> @@ -204,7 +204,11 @@
> 
>         Target target =
>             new Target(impl, this, stub, ref.getObjID(), permanent);
> -        ref.exportObject(target);
> +        try {
> +            ref.exportObject(target);
> +        } finally {
> +            target.unpinImpl();
> +        }
>         hashToMethod_Map = hashToMethod_Maps.get(implClass);
>         return stub;
>     }
> diff --git a/src/share/classes/sun/rmi/transport/Target.java b/src/share/classes/sun/rmi/transport/Target.java
> --- a/src/share/classes/sun/rmi/transport/Target.java
> +++ b/src/share/classes/sun/rmi/transport/Target.java
> @@ -77,11 +77,11 @@
>      * Construct a Target for a remote object "impl" with
>      * a specific object id.
>      *
> -     * If "permanent" is true, then the impl is pinned permanently
> -     * (the impl will not be collected via distributed and/or local
> -     * GC).  If "on" is false, than the impl is subject to
> -     * collection. Permanent objects do not keep a server from
> -     * exiting.
> +     * Upon return, the impl is pinned, thus not initially eligible
> +     * for collection via distributed and/or local GC).
> +     * If "permanent" is false, the impl subsequently may be made subject to
> +     * collection by calling {@link #unpinImpl()}.
> +     * Permanent or pinned objects do not keep a server from exiting.
>      */
>     public Target(Remote impl, Dispatcher disp, Remote stub, ObjID id,
>                   boolean permanent)
> @@ -114,9 +114,6 @@
>         }
> 
>         this.permanent = permanent;
> -        if (permanent) {
> -            pinImpl();
> -        }
>     }
> 
>     /**
> @@ -212,7 +209,7 @@
>      * can be garbage collected locally. But only if there the refSet
>      * is empty.  All of the weak/strong handling is in WeakRef
>      */
> -    synchronized void unpinImpl() {
> +    public synchronized void unpinImpl() {
>         /* only unpin if:
>          * a) impl is not permanent, and
>          * b) impl is not already unpinned, and
> diff --git a/src/share/classes/sun/rmi/transport/WeakRef.java b/src/share/classes/sun/rmi/transport/WeakRef.java
> --- a/src/share/classes/sun/rmi/transport/WeakRef.java
> +++ b/src/share/classes/sun/rmi/transport/WeakRef.java
> @@ -33,7 +33,7 @@
>  *
>  * This class extends the functionality of java.lang.ref.WeakReference in
>  * several ways.  The methods pin() and unpin() can be used to set
> - * whether the contained reference is strong or weak (it is weak upon
> + * whether the contained reference is strong or weak (it is strong upon
>  * construction).  The hashCode() and equals() methods are overridden so
>  * that WeakRef objects hash and compare to each other according to the
>  * object identity of their referents.
> @@ -51,18 +51,24 @@
> 
>     /**
>      * Create a new WeakRef to the given object.
> +     * The given object is initially pinned, ie. referenced strongly.
> +     * It may be subsequently unpinned by calling {@link #unpin()}.
>      */
>     public WeakRef(Object obj) {
>         super(obj);
>         setHashValue(obj);      // cache object's "identity" hash code
> +        strongRef = obj;
>     }
> 
>     /**
>      * Create a new WeakRef to the given object, registered with a queue.
> +     * The given object is initially pinned, ie. referenced strongly.
> +     * It may be subsequently unpinned by calling {@link #unpin()}.
>      */
>     public WeakRef(Object obj, ReferenceQueue q) {
>         super(obj, q);
>         setHashValue(obj);      // cache object's "identity" hash code
> +        strongRef = obj;
>     }
> 
>     /**
> diff --git a/test/java/rmi/server/UnicastRemoteObject/gcDuringExport/GcDuringExport.java b/test/java/rmi/server/UnicastRemoteObject/gcDuringExport/GcDuringExport.java
> new file mode 100644
> --- /dev/null
> +++ b/test/java/rmi/server/UnicastRemoteObject/gcDuringExport/GcDuringExport.java
> @@ -0,0 +1,67 @@
> +/*
> + * Copyright (c) 2011 Oracle and/or its affiliates. All rights reserved.
> + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
> + * 
> + * This code is free software; you can redistribute it and/or modify it
> + * under the terms of the GNU General Public License version 2 only, as
> + * published by the Free Software Foundation.
> + * 
> + * This code is distributed in the hope that it will be useful, but WITHOUT
> + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
> + * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
> + * version 2 for more details (a copy is included in the LICENSE file that
> + * accompanied this code).
> + * 
> + * You should have received a copy of the GNU General Public License version
> + * 2 along with this work; if not, write to the Free Software Foundation,
> + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
> + * 
> + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
> + * or visit www.oracle.com if you need additional information or have any
> + * questions.
> + */
> +
> +/* 
> + * Portions Copyright (c) 2011 IBM Corporation 
> + */
> +
> +/*
> + * @test
> + * @bug 6597112
> + * @summary Objects should not be GC-able as they are being exported to RMI
> + * @author Neil Richards <neil.richards at ngmr.net>, <neil_richards at uk.ibm.com>
> + */
> +
> +import java.rmi.Remote;
> +import java.rmi.server.UnicastRemoteObject;
> +
> +public class GcDuringExport {
> +    private static final long MAX_EXPORT_ITERATIONS = 50000;
> +
> +    public static void main(String[] args) throws Exception {
> +        final Thread gcInducingThread = new Thread() {
> +            public void run() { while (true) {
> +                    System.gc();
> +                    try { Thread.sleep(1); } catch (InterruptedException e) { }
> +                }
> +            }
> +        };
> +
> +        gcInducingThread.setDaemon(true);
> +        gcInducingThread.start();
> +
> +        long i = 0;
> +        try {
> +            while (i < MAX_EXPORT_ITERATIONS) {
> +                i++;
> +                UnicastRemoteObject.exportObject(new Remote() { }, 0);
> +            }
> +        } catch (Throwable e) {
> +            throw new RuntimeException("Test FAILED on iteration " + i + ".", e);
> +        }
> +
> +        System.out.println("Test successfully exported " + i + " objects.");
> +    }
> +}
> +
> +
> 
> 




More information about the core-libs-dev mailing list