1/*
2 * Copyright (c) 1999, 2012, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24/* @test
25 * @bug 4268258
26 * @summary When a DGC dirty call fails, RMI's client-side DGC implementation
27 * should attempt to retry the same dirty call a few times, at least until the
28 * known lease for that endpoint has expired, instead of just giving up
29 * renewing that lease at all after the first failure.
30 * @author Peter Jones (inspired by Adrian Colley's test case in 4268258)
31 *
32 * @build RetryDirtyCalls RetryDirtyCalls_Stub
33 * @run main/othervm RetryDirtyCalls
34 */
35
36import java.io.*;
37import java.net.*;
38import java.rmi.*;
39import java.rmi.server.*;
40
41interface Self extends Remote {
42    Self getSelf() throws RemoteException;
43}
44
45public class RetryDirtyCalls implements Self, Unreferenced {
46
47    /** how long we wait before declaring that this test has passed */
48    private final static long TIMEOUT = 20000;
49
50    /** true if this object's unreferenced method has been called */
51    private boolean unreferenced = false;
52
53    /**
54     * Return this object.  The need for this method is explained below.
55     */
56    public Self getSelf() {
57        return this;
58    }
59
60    public void unreferenced() {
61        synchronized (this) {
62            unreferenced = true;
63            notifyAll();
64        }
65    }
66
67    public static void main(String[] args) {
68
69        System.err.println("\nRegression test for bug 4268258\n");
70
71        /*
72         * Set properties to tweak DGC behavior so that this test will execute
73         * quickly: set the granted lease duration to 10 seconds, the interval
74         * that leases are checked to 3 seconds.
75         */
76        System.setProperty("java.rmi.dgc.leaseValue", "10000");
77        System.setProperty("sun.rmi.dgc.checkInterval", "3000");
78
79        /*
80         * Make idle connections time out almost instantly (0.1 seconds) so
81         * that the DGC implementation will have to make a new connection for
82         * each dirty call, thus going through the socket factory, where we
83         * can easily cause the operation to fail.
84         */
85        System.setProperty("sun.rmi.transport.connectionTimeout", "100");
86
87        RetryDirtyCalls impl = new RetryDirtyCalls();
88
89        try {
90            TestSF sf = new TestSF();
91            RMISocketFactory.setSocketFactory(sf);
92
93            /*
94             * The stub returned by UnicastRemoteObject.exportObject() does
95             * not participate in DGC, but it does allow us to invoke a method
96             * on the remote object through RMI.  Therefore, we invoke the
97             * getSelf() method through RMI, which returns an equivalent stub
98             * that does participate in DGC.
99             */
100            Self stub = (Self) UnicastRemoteObject.exportObject(impl);
101            Self dgcStub = stub.getSelf();
102            stub = null;                // in case 4114579 has been fixed
103
104            /*
105             * Set the socket factory to cause 3 connections attempts in a row
106             * to fail before allowing a connection to succeed, expecting the
107             * client-side DGC implementation to make at least four attempts.
108             */
109            final int FLAKE_FACTOR = 3;
110            sf.setFlakeFactor(FLAKE_FACTOR);
111
112            long deadline = System.currentTimeMillis() + TIMEOUT;
113            boolean unreferenced;
114
115            synchronized (impl) {
116                while (!(unreferenced = impl.unreferenced)) {
117                    long timeToWait = deadline - System.currentTimeMillis();
118                    if (timeToWait > 0) {
119                        impl.wait(timeToWait);
120                    } else {
121                        break;
122                    }
123                }
124            }
125
126            if (unreferenced) {
127                throw new RuntimeException("remote object unreferenced");
128            }
129
130            int createCount = sf.getCreateCount();
131            if (createCount == 0) {
132                throw new RuntimeException("test socket factory never used");
133            } else if (createCount < (FLAKE_FACTOR + 3)) {
134                /*
135                 * The unreferenced method was not invoked for some reason,
136                 * but the dirty calls were clearly not retried well enough.
137                 */
138                throw new RuntimeException(
139                    "test failed because dirty calls not retried enough, " +
140                    "but remote object not unreferenced");
141            }
142
143            System.err.println(
144                "TEST PASSED: remote object not unreferenced");
145
146        } catch (Exception e) {
147            e.printStackTrace();
148            throw new RuntimeException("TEST FAILED: " + e.toString());
149        } finally {
150            /*
151             * When all is said and done, try to unexport the remote object
152             * so that the VM has a chance to exit.
153             */
154            try {
155                UnicastRemoteObject.unexportObject(impl, true);
156            } catch (Exception e) {
157            }
158        }
159    }
160}
161
162class TestSF extends RMISocketFactory {
163
164    private int flakeFactor = 0;
165
166    private int flakeState = 0;
167
168    private int createCount = 0;
169
170    public synchronized void setFlakeFactor(int newFlakeFactor) {
171        flakeFactor = newFlakeFactor;
172    }
173
174    public synchronized int getCreateCount() {
175        return createCount;
176    }
177
178    public synchronized Socket createSocket(String host, int port)
179        throws IOException
180    {
181        createCount++;
182
183        if (++flakeState > flakeFactor) {
184            flakeState = 0;
185        }
186
187        if (flakeState == 0) {
188            return new Socket(host, port);
189        } else {
190            throw new IOException("random network failure");
191        }
192    }
193
194    public ServerSocket createServerSocket(int port) throws IOException {
195        return new ServerSocket(port);
196    }
197}
198