001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: *
015: * See the License for the specific language governing permissions and
016: * limitations under the License.
017: */
018:
019: /**
020: * @author Mikhail A. Markov
021: * @version $Revision: 1.1.2.3 $
022: */package org.apache.harmony.rmi.server;
023:
024: import java.lang.ref.PhantomReference;
025: import java.lang.ref.ReferenceQueue;
026: import java.rmi.RemoteException;
027: import java.rmi.dgc.DGC;
028: import java.rmi.dgc.Lease;
029: import java.rmi.dgc.VMID;
030: import java.rmi.server.ObjID;
031: import java.rmi.server.RemoteRef;
032: import java.rmi.server.UID;
033: import java.security.AccessController;
034: import java.security.PrivilegedAction;
035: import java.util.Collections;
036: import java.util.Enumeration;
037: import java.util.HashSet;
038: import java.util.Hashtable;
039: import java.util.List;
040: import java.util.Set;
041: import java.util.Timer;
042: import java.util.TimerTask;
043: import java.util.Vector;
044:
045: import org.apache.harmony.rmi.common.CreateThreadAction;
046: import org.apache.harmony.rmi.common.GetLongPropAction;
047: import org.apache.harmony.rmi.common.InterruptThreadAction;
048: import org.apache.harmony.rmi.common.RMIProperties;
049: import org.apache.harmony.rmi.internal.nls.Messages;
050: import org.apache.harmony.rmi.remoteref.UnicastRef;
051: import org.apache.harmony.rmi.transport.Endpoint;
052:
053: /**
054: * DGC on Client's side - it's responsible for renewing/cancelling
055: * remote objects leases.
056: * It is made package protected for security reasons.
057: *
058: * @author Mikhail A. Markov
059: * @version $Revision: 1.1.2.3 $
060: */
061: class ClientDGC {
062:
063: /** ObjID for DGC. */
064: public static final ObjID DGC_ID = new ObjID(ObjID.DGC_ID);
065:
066: /*
067: * DGC ack timeout (in ms.) during which we hold strong refs to the objects.
068: * Default value is 300000 ms (5 minutes).
069: */
070: private static final long dgcAckTimeout = ((Long) AccessController
071: .doPrivileged(new GetLongPropAction(
072: RMIProperties.DGCACKTIMEOUT_PROP, 5 * 60 * 1000)))
073: .longValue();
074: /*
075: * Max length of time (in ms.) between DGC.clean() calls in case of failed
076: * calls. Default value is 180000 ms (3 minutes).
077: */
078: private static final long cleanInterval = ((Long) AccessController
079: .doPrivileged(new GetLongPropAction(
080: RMIProperties.DGCCLEANINTERVAL_PROP, 3 * 60 * 1000)))
081: .longValue();
082:
083: // VMID for this VM
084: private static final VMID vmid = new VMID();
085:
086: /*
087: * Sequentially increasing number for DGC calls.
088: * It should be accessed vid getSeqNumber() method only.
089: */
090: private static long seqNum = Long.MIN_VALUE;
091:
092: // Table where Endpoint's are keys and RenewInfo are values.
093: private static Hashtable epTable = new Hashtable();
094:
095: // List of strong refs to remoteObjects for referencing during DGC ack call.
096: private static Hashtable dgcAckTable = new Hashtable();
097:
098: // Thread renewing leases.
099: private static Thread lRenewer;
100:
101: // Thread detecting object which were garbage-collected.
102: private static Thread roDetector = (Thread) AccessController
103: .doPrivileged(new CreateThreadAction(
104: new RemovedObjectsDetector(),
105: "RemovedObjectsDetector", true)); //$NON-NLS-1$
106:
107: // Timer handling events waiting for DGC ack messages.
108: private static final Timer dgcAckTimer = (Timer) AccessController
109: .doPrivileged(new PrivilegedAction() {
110: public Object run() {
111: return new Timer(true);
112: }
113: });
114:
115: // DGC stub class
116: private static Class dgcStubClass;
117:
118: // Queue for handling already collected objects.
119: private static ReferenceQueue collectedQueue = new ReferenceQueue();
120:
121: static {
122:
123: // Initialize DGC implementation stub-class.
124: try {
125: dgcStubClass = Class
126: .forName("org.apache.harmony.rmi.server.DGCImpl_Stub"); //$NON-NLS-1$
127: } catch (Exception ex) {
128: // rmi.78=Unable to initialize ClientDGC.
129: throw new Error(Messages.getString("rmi.78"), ex); //$NON-NLS-1$
130: }
131:
132: // Start the thread detecting garbage-collected objects.
133: roDetector.start();
134: }
135:
136: /*
137: * Registers the given RemoteRef in the table for sending DGC.dirty() calls
138: * to the server, exported this object.
139: */
140: static void registerForRenew(RemoteRefBase ref) {
141: RenewInfo info = (RenewInfo) epTable.get(ref.ep);
142:
143: if (info == null) {
144: info = new RenewInfo(ref.ep);
145: info.renewTime = System.currentTimeMillis();
146: epTable.put(ref.ep, info);
147: info.addToDirtySet(ref);
148: } else if (info.addToDirtySet(ref)) {
149: info.renewTime = System.currentTimeMillis();
150: }
151:
152: if (lRenewer == null) {
153: (lRenewer = (Thread) AccessController
154: .doPrivileged(new CreateThreadAction(
155: new LeaseRenewer(), "LeaseRenewer", true))).start(); //$NON-NLS-1$
156: }
157: }
158:
159: /*
160: * Remove the given id for the specified Endpoint from the table and call
161: * DGC.clean() method.
162: */
163: static void unregisterForRenew(Endpoint ep, ObjID id) {
164: RenewInfo info = (RenewInfo) epTable.get(ep);
165:
166: if (info == null) {
167: return;
168: }
169:
170: // add ObjID to the list for DGC.clean() calls
171: info.addToCleanSet(id);
172: }
173:
174: /*
175: * Adds the given object to the list of strong references associated with
176: * the given UID.
177: */
178: static void registerForDGCAck(UID uid, Object obj) {
179: RemoveTask task = (RemoveTask) dgcAckTable.get(uid);
180:
181: if (task == null) {
182: task = new RemoveTask(uid);
183: dgcAckTable.put(uid, task);
184: dgcAckTimer.schedule(task, dgcAckTimeout);
185: }
186: task.addStrongRef(obj);
187: }
188:
189: /*
190: * Removes all strong refs associated with the given UID.
191: */
192: static void unregisterForDGCAck(UID uid) {
193: TimerTask task = (TimerTask) dgcAckTable.get(uid);
194:
195: if (task == null) {
196: return;
197: }
198: task.cancel();
199: dgcAckTable.remove(uid);
200: }
201:
202: /*
203: * Returns next sequence number
204: */
205: private static synchronized long getSeqNumber() {
206: return seqNum++;
207: }
208:
209: /*
210: * Auxiliary class for removing strong refs to the objects from the table.
211: */
212: private static class RemoveTask extends TimerTask {
213: // UID for removing from the table
214: private UID uid;
215:
216: // strong references to the objects
217: private List strongRefs = new Vector();
218:
219: /*
220: * Constructs RemoveTask for removing the given UID from the table.
221: *
222: * @param uid UID to be removed in the future by this task
223: */
224: RemoveTask(UID uid) {
225: this .uid = uid;
226: }
227:
228: /*
229: * Removes strong refs to the objects associated with the stored UID
230: * from the table;
231: */
232: public void run() {
233: dgcAckTable.remove(uid);
234: }
235:
236: /*
237: * Adds strong ref to the given object to the list.
238: *
239: * @param obj Object strong reference to which should be stored
240: */
241: void addStrongRef(Object obj) {
242: strongRefs.add(obj);
243: }
244: }
245:
246: /*
247: * Phantom reference holding Endpoint and ObjID information for cancelling
248: * leases after object is garbage-collected.
249: */
250: private static class PhantomRef extends PhantomReference {
251: private Endpoint ep;
252: private ObjID id;
253:
254: /*
255: * Constructs PhantomRef. Calls super() with the given queue and obj and
256: * stores ep and id.
257: */
258: PhantomRef(Object obj, ReferenceQueue queue, Endpoint ep,
259: ObjID id) {
260: super (obj, queue);
261: this .ep = ep;
262: this .id = id;
263: }
264: }
265:
266: /*
267: * Auxiliary class holding all info needed for leases renewing.
268: */
269: private static class RenewInfo {
270:
271: /*
272: * Remote Endpoint where leases should be renewed.
273: */
274: private Endpoint ep;
275:
276: /*
277: * Table where objIDs whose leases should be renewed are the keys
278: * and PhantomRefs are the values.
279: */
280: private Hashtable renewTable = new Hashtable();
281:
282: /*
283: * Table where objIDs whose leases should be cancelled are stored
284: */
285: private Set cleanSet = Collections
286: .synchronizedSet(new HashSet());
287:
288: // Object for tables synchronization.
289: private class TablesLock {
290: }
291:
292: private Object tablesLock = new TablesLock();
293:
294: // When to renew leases.
295: private long renewTime;
296:
297: // Initialized DGC stub.
298: private DGC dgcStub;
299:
300: // Thread calling DGC.clean method
301: private Thread cleanCaller = null;
302:
303: // Time when the first DGC.dirty call failed
304: private long failureStartTime = 0;
305:
306: // Number of failed DGC.dirty calls.
307: private int failedDirtyCallsNum = 0;
308:
309: // Base time for calculating renew time in case of failed dirty calls
310: private long failedRenewBaseDuration = 0;
311:
312: // Lease duration returned by the latest successful DGC.dirty call.
313: private long latestLeaseDuration = 0;
314:
315: /*
316: * Initializes DGC stub and stores the given Endpoint.
317: *
318: * @param ep RemoteEndpoint where leases should be renewed
319: */
320: RenewInfo(Endpoint ep) {
321: this .ep = ep;
322: try {
323: dgcStub = (DGC) dgcStubClass.getConstructor(
324: new Class[] { RemoteRef.class }).newInstance(
325: new Object[] { new UnicastRef(ep, DGC_ID) });
326: } catch (Exception ex) {
327: // rmi.79=Unable to initialized DGC stub.
328: throw new Error(Messages.getString("rmi.79"), ex); //$NON-NLS-1$
329: }
330: }
331:
332: /*
333: * Calls DGC.dirty() method.
334: */
335: void dgcDirty() {
336: ObjID[] ids;
337:
338: synchronized (tablesLock) {
339: ids = (ObjID[]) renewTable.keySet().toArray(
340: new ObjID[renewTable.size()]);
341: }
342:
343: try {
344: Lease lease = dgcStub.dirty(ids, getSeqNumber(),
345: new Lease(vmid, DGCImpl.maxDuration));
346: failedDirtyCallsNum = 0;
347: failureStartTime = 0;
348: latestLeaseDuration = lease.getValue();
349: renewTime = System.currentTimeMillis()
350: + latestLeaseDuration / 2;
351: } catch (RemoteException re) {
352: // dirty call failed
353: long curTime = System.currentTimeMillis();
354: ++failedDirtyCallsNum;
355:
356: if (failedDirtyCallsNum == 1) {
357: failureStartTime = curTime;
358: latestLeaseDuration = (latestLeaseDuration > 0) ? latestLeaseDuration
359: : DGCImpl.maxDuration;
360: failedRenewBaseDuration = latestLeaseDuration >> 5;
361: }
362: renewTime = curTime + failedRenewBaseDuration
363: * ((failedDirtyCallsNum - 1) << 1);
364:
365: if (renewTime > failureStartTime + latestLeaseDuration) {
366: renewTime = Long.MAX_VALUE;
367: }
368: }
369: }
370:
371: /*
372: * Adds ObjID of the given ref to the set of objIDs whose leases should
373: * be renewed, creates a PhantomReference for the object for sending
374: * clean request to the server's DGC when the object is
375: * garbage-collected.
376: *
377: * @param ref UnicastRef to be registered
378: *
379: * @return true if this RenewInfo already contained the given ref and
380: * false otherwise
381: */
382: boolean addToDirtySet(RemoteRefBase ref) {
383: synchronized (tablesLock) {
384: ObjID id = ref.getObjId();
385:
386: if (renewTable.containsKey(id)) {
387: return true;
388: }
389: renewTable.put(id, new PhantomRef(ref, collectedQueue,
390: ep, id));
391: return false;
392: }
393: }
394:
395: /*
396: * Adds the given ObjID to the list of objects for DGC.clean() method
397: * call and removes it from the dirty set.
398: *
399: * @param id ObjID of remote object
400: */
401: void addToCleanSet(ObjID id) {
402: synchronized (tablesLock) {
403: renewTable.remove(id);
404: cleanSet.add(id);
405:
406: if (cleanCaller == null) {
407: (cleanCaller = ((Thread) AccessController
408: .doPrivileged(new CreateThreadAction(
409: new CleanCaller(this ),
410: "CleanCaller for " + ep, true)))).start(); //$NON-NLS-1$
411: } else {
412: AccessController
413: .doPrivileged(new InterruptThreadAction(
414: cleanCaller));
415: }
416: }
417: }
418: }
419:
420: /*
421: * Auxiliary class renewing leases.
422: */
423: private static class LeaseRenewer implements Runnable {
424: /**
425: * Iterates over epTable and renews leases.
426: */
427: public void run() {
428: do {
429: long curTime = System.currentTimeMillis();
430: long awakeTime = curTime + DGCImpl.maxDuration / 2;
431:
432: try {
433: synchronized (epTable) {
434: for (Enumeration eps = epTable.keys(); eps
435: .hasMoreElements();) {
436: Endpoint ep = (Endpoint) eps.nextElement();
437: RenewInfo info = (RenewInfo) epTable
438: .get(ep);
439:
440: if (info.renewTime <= curTime) {
441: // we should renew lease for this ids
442: info.dgcDirty();
443: }
444:
445: if (info.renewTime < awakeTime) {
446: awakeTime = info.renewTime;
447: }
448: }
449: }
450:
451: if (awakeTime > curTime) {
452: Thread.sleep(awakeTime - curTime);
453: }
454: } catch (InterruptedException ie) {
455: }
456: } while (epTable.size() != 0);
457: lRenewer = null;
458: }
459: }
460:
461: /*
462: * Auxiliary thread detecting remote objects which where garbage-collected
463: * and spawning the thread calling DGC.clean() method.
464: */
465: private static class RemovedObjectsDetector implements Runnable {
466:
467: /**
468: * Thread sitting on blocking ReferenceQueue.remove() method and when
469: * it returns PhantomRef - removing the object from the dirty set and
470: * calling DGC.clean() method.
471: */
472: public void run() {
473: do {
474: try {
475: // this operation blocks the thread
476: PhantomRef ref = (PhantomRef) collectedQueue
477: .remove();
478:
479: unregisterForRenew(ref.ep, ref.id);
480: } catch (InterruptedException ie) {
481: }
482: } while (true);
483: }
484: }
485:
486: /*
487: * Auxiliary class calling DGC.clean() method on server side.
488: */
489: private static class CleanCaller implements Runnable {
490: // RenewInfo where this class was created
491: private RenewInfo info;
492:
493: // Number of failed DGC.clean calls.
494: private int failedCleanCallsNum = 0;
495:
496: /*
497: * Constructs CleanCaller holding the reference to the given RenewInfo.
498: */
499: CleanCaller(RenewInfo info) {
500: this .info = info;
501: }
502:
503: /**
504: * Call DGC.clean() method, in case of failed call retry it in a loop.
505: */
506: public void run() {
507: while (true) {
508: boolean success = true;
509: Set curCleanSet;
510:
511: synchronized (info.tablesLock) {
512: if (info.cleanSet.isEmpty()) {
513: break;
514: }
515: curCleanSet = new HashSet(info.cleanSet);
516: }
517:
518: try {
519: info.dgcStub.clean((ObjID[]) curCleanSet
520: .toArray(new ObjID[curCleanSet.size()]),
521: getSeqNumber(), vmid,
522: info.failedDirtyCallsNum == 0);
523:
524: // DGC.clean() call succeeded
525: synchronized (info.tablesLock) {
526: info.cleanSet.remove(curCleanSet);
527:
528: if (info.cleanSet.isEmpty()) {
529: break;
530: }
531: }
532: } catch (RemoteException re) {
533: // DGC.clean() call failed
534: success = false;
535: failedCleanCallsNum++;
536:
537: if (failedCleanCallsNum > 4) {
538: synchronized (info.tablesLock) {
539: if (!info.renewTable.isEmpty()) {
540: info.cleanSet.clear();
541: }
542: }
543: break;
544: }
545: }
546:
547: if (Thread.interrupted()) {
548: continue;
549: }
550:
551: /*
552: * If DGC.clean() call failed we should wait for a cleanInterval
553: * period of time.
554: */
555: if (!success) {
556: try {
557: Thread.sleep(cleanInterval);
558: } catch (InterruptedException ie) {
559: continue;
560: }
561: }
562: }
563:
564: synchronized (info.tablesLock) {
565: if (!info.renewTable.isEmpty()) {
566: epTable.remove(info.ep);
567: }
568: }
569: info.cleanCaller = null;
570: }
571: }
572: }
|