/************************************************************************** * Copyright (c) 2001 by Punch Telematix. All rights reserved. * * * * Redistribution and use in source and binary forms, with or without * * modification, are permitted provided that the following conditions * * are met: * * 1. Redistributions of source code must retain the above copyright * * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * * notice, this list of conditions and the following disclaimer in the * * documentation and/or other materials provided with the distribution. * * 3. Neither the name of Punch Telematix nor the names of * * other contributors may be used to endorse or promote products * * derived from this software without specific prior written permission.* * * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED * * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * * IN NO EVENT SHALL PUNCH TELEMATIX OR OTHER CONTRIBUTORS BE LIABLE * * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN * * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * **************************************************************************/ package wonka.rmi; import java.io.BufferedOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.io.PushbackInputStream; import java.lang.ref.ReferenceQueue; import java.net.Socket; import java.rmi.Remote; import java.rmi.RemoteException; import java.rmi.dgc.Lease; import java.rmi.server.ObjID; import java.rmi.server.RMIClientSocketFactory; import java.rmi.server.RemoteObject; import java.rmi.server.RemoteRef; import java.rmi.server.UID; import java.util.Hashtable; import java.util.TimerTask; import java.util.Vector; import wonka.vm.SystemTimer; /** ** The DGCClient will manage all RemoteObjects recieved by RMI Servers. ** ** the DGCClient will add 1 TimerTask to the SystemTimer for each different RMI Servers it is connectected to. This ** will allow us to renew all Leases to one server in one call. An additional task is added to check the ReferenceQueue ** and to do the clean calls to the server if a RemoteObject is collected. ** ** TODO: make DGCClients use VMID as key. */ public class DGCClient { private static final ObjID DGCID = new ObjID(2); private static final ObjID[] ID_ARRAY = new ObjID[0]; static final ReferenceQueue QUEUE = new ReferenceQueue(); private static final Lease LEASE = new Lease(RMIConnection.TheVMID, Long.getLong("java.rmi.dgc.leaseValue", 10L * 60L * 1000L /* = 10 minutes */).longValue()); static Hashtable remotes = new Hashtable(11); private static Hashtable DGCClients = new Hashtable(7); private static long sequenceNumber; /** always synchronize the use of the cleanerTask on DGCClients */ private static DGCClientTask cleanerTask; static void registerRemote(Remote obj){ if(obj instanceof RemoteObject){ RemoteObject remote = (RemoteObject) obj; registerObject(remote.getRef()); } } /** all objects registered to DGCClient will be marked as dirty to the DGC */ /** DGCClient will only keep a WeakReference to this Object and will send a clean call to the server */ /** the DGCClient is also responseble for renewing the leases */ synchronized static void registerObject(RemoteRef obj){ Object o = remotes.get(obj); if(o == null){ if(obj instanceof UnicastRef){ UnicastRef ref = (UnicastRef) obj; String key = ref.address + ref.port; DGCClient client = (DGCClient) DGCClients.get(key); if(client == null){ client = new DGCClient(ref.address, ref.port, ref.csf); } DGCClientReference dgcRef = new DGCClientReference(obj, QUEUE, ref.id); remotes.put(dgcRef,client); synchronized(DGCClients){ DGCClients.put(key,client); if(cleanerTask == null){ cleanerTask = new DGCClientTask(); SystemTimer.scheduleSystemTask(cleanerTask, 750); } } new Registrator(dgcRef, obj, client, ref.id); } //TODO check what else can be recieved ... } /** else it is already registered, no extra steps needed */ } private Vector ids; private DGCClientTask currentTask; private int port; private String address; private RMIClientSocketFactory csf; //private VMID myVMID = RMIConnection.TheVMID; private long duration; private DGCClient(String addr, int p, RMIClientSocketFactory fact){ ids = new Vector(); address = addr; port = p; csf = fact; } synchronized void add(ObjID id, Object key)throws IOException { Lease lease = reportDirty(new ObjID[]{id}); if(lease != null){ long value = lease.getValue(); ids.add(id); //TODO check VMID. what todo if we get different VMID ... if(value < duration){ if(currentTask != null){ currentTask.cancel(); } if (value < 500){ /** we don't want to renew the Lease at a high speed rate it can cripple the system */ value = 500; } duration = value; currentTask = new DGCClientTask(this); if(RMIConnection.DEBUG < 7){System.out.println("Starting "+currentTask+" with "+value+" delay");} SystemTimer.scheduleOneTimeSystemTask(currentTask, value); } else if (currentTask == null){ currentTask = new DGCClientTask(this); value -= 500; if (value < 500){ /** we don't want to renew the Lease at a high speed rate it can cripple the system */ value = 500; } if(RMIConnection.DEBUG < 7){System.out.println("Starting "+currentTask+" with "+value+" delay");} SystemTimer.scheduleOneTimeSystemTask(currentTask, value); } } else { remotes.remove(key); if(ids.isEmpty()){ synchronized(DGCClients){ DGCClients.remove(address+port); if(DGCClients.isEmpty() && cleanerTask != null){ cleanerTask.cancel(); cleanerTask = null; } } } } } synchronized void remove(ObjID id, Object key) throws IOException { if(ids.remove(id)){ if(RMIConnection.DEBUG < 7){System.out.println("DGCClient.remove("+id+", ??) calling reportClean");} reportClean(new ObjID[]{id}, csf.createSocket(address,port), false); if(ids.isEmpty()){ if(RMIConnection.DEBUG < 7){System.out.println("DGCClient.remove("+id+", ??) stopping DGCClient");} if(currentTask != null){ currentTask.cancel(); currentTask = null; } synchronized(DGCClients){ DGCClients.remove(address+port); if(RMIConnection.DEBUG < 7){System.out.println("DGCClient.remove() cleanup DGCClients" +DGCClients);} if(DGCClients.isEmpty() && cleanerTask != null){ if(RMIConnection.DEBUG < 7){System.out.println("DGCClient.remove() cancelling cleanerTask");} cleanerTask.cancel(); cleanerTask = null; } } } else if(RMIConnection.DEBUG < 7){System.out.println("DGCClient.remove("+id+", ??) continuing DGCClient");} } } synchronized void renewLeases() throws IOException { ObjID[] o = (ObjID[])ids.toArray(ID_ARRAY); if(o == ID_ARRAY){ /** this means the vector is empty */ currentTask = null; return; } Lease lease = reportDirty(o); if(lease != null){ /** the lease should be renewed before it expires, so we take half a second to to get it renewed */ long duration = lease.getValue() - 500; if (duration < 500){ /** we don't want to renew the Lease at a high speed rate it can cripple the system */ duration = 500; } this.duration = duration; currentTask = new DGCClientTask(this); SystemTimer.scheduleOneTimeSystemTask(currentTask, duration); } else { /** we clean up */ synchronized(DGCClients){ DGCClients.remove(address+port); if(DGCClients.isEmpty() && cleanerTask != null){ cleanerTask.cancel(); cleanerTask = null; } } //TODO remove all references to 'this' from the remotes hashtable. } } private void reportClean(Object arg, Socket socket, boolean strong){ try { RMIConnection.handShake(socket); if(RMIConnection.DEBUG < 6) {System.out.println("handshake complete");} OutputStream out = socket.getOutputStream(); out.write(RMIConstants.CALL); ObjectOutputStream oos = new RMIObjectOutputStream(new BufferedOutputStream(socket.getOutputStream())); DGCID.write(oos); oos.writeInt(RMIConstants.OPERATION_CLEAN); oos.writeLong(RMIConstants.HASH_DIRTY); oos.writeObject(arg); /** no need to synchronize the use of the sequenceNumber. The same number can be used to contact different VM's */ oos.writeLong(sequenceNumber++); oos.writeObject(RMIConnection.TheVMID); oos.writeBoolean(strong); oos.flush(); if(RMIConnection.DEBUG < 6) {System.out.println("wrote request");} InputStream in = socket.getInputStream(); int rd = in.read(); if(RMIConnection.DEBUG < 8 && (rd & 0xff) != RMIConstants.RETURN_DATA){ System.out.println("Oops got :"+(rd & 0xff)); } if(RMIConnection.DEBUG < 6) {System.out.println("reading request");} ObjectInputStream ois = new RMIObjectInputStream(new PushbackInputStream(in)); rd = ois.read(); if(RMIConnection.DEBUG < 6) {System.out.println("read Header");} UID uid = UID.read(ois); if(RMIConnection.DEBUG < 6) {System.out.println("UID = "+uid);} //Object o = ois.readObject(); if(RMIConnection.DEBUG < 9 && rd != RMIConstants.RETURN_VALUE){ /** if o != null the clean called failed. We print out a debug message */ System.out.println("DGCClient.remove: strong clean operation failed for "+arg); /** TODO: do we need to retry ? The Lease will expire anyway */ } } catch(Exception _){ /** TODO: do we need to retry ? The Lease will expire anyway */ } } /** reportDirty will call a strong clean */ private Lease reportDirty(Object arg) throws IOException { Socket socket = null; try { socket = csf.createSocket(address, port); RMIConnection.handShake(socket); if(RMIConnection.DEBUG < 6) {System.out.println("handshake complete");} OutputStream out = socket.getOutputStream(); out.write(RMIConstants.CALL); ObjectOutputStream oos = new RMIObjectOutputStream(new BufferedOutputStream(socket.getOutputStream())); DGCID.write(oos); oos.writeInt(RMIConstants.OPERATION_DIRTY); oos.writeLong(RMIConstants.HASH_DIRTY); oos.writeObject(arg); /** no need to synchronize the use of the sequenceNumber. The same number can be used to contact different VM's */ oos.writeLong(sequenceNumber++); oos.writeObject(LEASE); oos.flush(); if(RMIConnection.DEBUG < 6) {System.out.println("wrote request");} InputStream in = socket.getInputStream(); int rd = in.read(); if(RMIConnection.DEBUG < 8 && (rd & 0xff) != RMIConstants.RETURN_DATA){ System.out.println("Oops got :"+(rd & 0xff)); } if(RMIConnection.DEBUG < 6) {System.out.println("reading request");} ObjectInputStream ois = new RMIObjectInputStream(new PushbackInputStream(in)); rd = ois.read(); if(RMIConnection.DEBUG < 6) {System.out.println("read Header");} UID uid = UID.read(ois); if(RMIConnection.DEBUG < 6) {System.out.println("UID = "+uid);} Object o = ois.readObject(); if(o instanceof Lease){ return (Lease)o; } } catch(RemoteException e){ /** if we get a RemoteException, the communication itself was not bad so we reuse the socket */ reportClean(arg,socket,true); } catch(IOException io){ if(socket != null){ socket.close(); /** if we get a IOException, the communication itself was bad so we create a new socket */ reportClean(arg,csf.createSocket(address, port), true); } } catch(Exception _){ } return null; } static class DGCClientTask extends TimerTask { private boolean started; private boolean cleanUp; private DGCClient client; DGCClientTask(DGCClient client){ this.client = client; } DGCClientTask(){ cleanUp = true; } public void run(){ if(cleanUp){ if(RMIConnection.DEBUG < 8){ System.out.println("DGCClient CleanupTask ...");} try { DGCClientReference ref = (DGCClientReference)QUEUE.poll(); if(RMIConnection.DEBUG < 7){ System.out.println("DGCClient CleanupTask poll() returns "+ref);} while(ref != null){ new Registrator((DGCClient)remotes.remove(ref), ref.id, ref); ref = (DGCClientReference)QUEUE.poll(); if(RMIConnection.DEBUG < 7){ System.out.println("DGCClient CleanupTask poll() returns "+ref);} } } catch(Throwable t){ if(RMIConnection.DEBUG < 9){ System.out.println("DGCClient CleanupTask got Exception "+t);} } } else if(!started){ if(RMIConnection.DEBUG < 7){ System.out.println("DGCClientTask "+this+": start new Thread");} started = true; new Thread(this,"DGCClientTask Thread "+this).start(); } else { try { if(RMIConnection.DEBUG < 8){ System.out.println("DGCClientTask "+this+": renewing Leases");} client.renewLeases(); } catch(Throwable t){} } } } static class Registrator implements Runnable { private Object key; /* needed to keep this object alive */ private Object remote; private DGCClient client; private ObjID id; private boolean remove; /** We keep a reference to the RemoteObject so the remoteObject cannot end up i/t ReferenceQueue before this thread stops */ Registrator(Object key, RemoteRef obj, DGCClient c, ObjID id){ remote = obj; client = c; this.key = key; this.id = id; new Thread(this, "DGCClient$Registrator for "+id).start(); } Registrator(DGCClient c, ObjID id, Object key){ remove = true; client = c; this.id = id; this.key = key; new Thread(this, "DGCClient$Registrator for "+id).start(); } public void run(){ if(RMIConnection.DEBUG < 7){ System.out.println("DGCClient Registrator for "+client+" remove = "+remove);} try { if(remove){ client.remove(id,key); } else { client.add(id,key); } } catch(IOException re){ if(RMIConnection.DEBUG < 7){ System.out.println("DGCClient Registrator for "+client+" failed "+re);} re.printStackTrace(); } } } }