package water; import water.api.schemas3.*; import water.nbhm.NonBlockingHashMap; import water.util.Log; import java.util.Arrays; import static water.Weaver.classForName; /** Internal H2O class used to build and maintain the cloud-wide type mapping. * Only public to expose a few constants to subpackages. No exposed user * calls. */ public class TypeMap { static public final short NULL, PRIM_B, ICED, H2OCC, C1NCHUNK, FRAME, VECGROUP, ESPCGROUP; // This list contains all classes that are needed at cloud initialization time. static final String BOOTSTRAP_CLASSES[] = { " BAD", "[B", // 1 - water.Iced.class.getName(), // 2 - Base serialization class water.H2O.H2OCountedCompleter.class.getName(), // 3 - Base serialization class water.HeartBeat.class.getName(), // Used to Paxos up a cloud & leader water.H2ONode.class.getName(), // Needed to write H2ONode target/sources water.FetchClazz.class.getName(), // used to fetch IDs from leader water.FetchId.class.getName(), // used to fetch IDs from leader water.DTask.class.getName(), // Needed for those first Tasks water.UDPClientEvent.ClientEvent.class.getName(), // Needed for client event broadcast water.fvec.Chunk.class.getName(), // parent of Chunk water.fvec.C1NChunk.class.getName(),// used as constant in parser water.fvec.Frame.class.getName(), // used in TypeaheadKeys & Exec2 water.fvec.Vec.VectorGroup.class.getName(), // Used in TestUtil water.fvec.Vec.ESPC.class.getName(), // Used in TestUtil // Status pages looked at without locking the cloud water.api.Schema.class.getName(), RequestSchemaV3.class.getName(), SchemaV3.Meta.class.getName(), SchemaV3.class.getName(), CloudV3.class.getName(), CloudV3.NodeV3.class.getName(), AboutV3.class.getName(), AboutEntryV3.class.getName(), water.UDPRebooted.ShutdownTsk.class.getName(), // Mistyped hack URLs H2OErrorV3.class.getName(), // Ask for ModelBuilders list RouteV3.class.getName(), ModelBuildersV3.class.getName(), // So Flow can ask about possible Model Builders without locking water.util.IcedSortedHashMap.class.getName(), // Seems wildly not-needed hex.schemas.ModelBuilderSchema.IcedHashMapStringModelBuilderSchema.class.getName(), // Checking for Flow clips NodePersistentStorageV3.class.getName(), NodePersistentStorageV3.NodePersistentStorageEntryV3.class.getName(), // Beginning to hunt for files water.util.IcedHashMap.class.getName(), water.util.IcedHashMapBase.class.getName(), water.util.IcedHashMapGeneric.class.getName(), water.util.IcedHashMapGeneric.IcedHashMapStringString.class.getName(), water.util.IcedHashMapGeneric.IcedHashMapStringObject.class.getName(), TypeaheadV3.class.getName(), // Allow typeahead without locking }; // Class name -> ID mapping static private final NonBlockingHashMap<String, Integer> MAP = new NonBlockingHashMap<>(); // ID -> Class name mapping static String[] CLAZZES; // ID -> pre-allocated Golden Instance of Icer static private Icer[] GOLD; // Unique IDs static private int IDS; // JUnit helper flag static public volatile boolean _check_no_locking; // ONLY TOUCH IN AAA_PreCloudLock! static { CLAZZES = BOOTSTRAP_CLASSES; GOLD = new Icer[BOOTSTRAP_CLASSES.length]; int id=0; // The initial set of Type IDs to boot with for( String s : CLAZZES ) MAP.put(s,id++); IDS = id; // Some statically known names, to make life easier during e.g. bootup & parse NULL = (short) -1; PRIM_B = (short)onIce("[B"); ICED = (short)onIce("water.Iced"); assert ICED ==2; // Matches Iced customer serializer H2OCC = (short)onIce("water.H2O$H2OCountedCompleter"); assert H2OCC==3; // Matches customer serializer C1NCHUNK = (short)onIce("water.fvec.C1NChunk"); // Used in water.fvec.FileVec FRAME = (short)onIce("water.fvec.Frame"); // Used in water.Value VECGROUP = (short)onIce("water.fvec.Vec$VectorGroup"); // Used in TestUtil ESPCGROUP = (short)onIce("water.fvec.Vec$ESPC"); // Used in TestUtil } // The major complexity of this code is that the are FOUR major data forms // which get converted to one another. At various times the code is // presented with one of the forms, and asked for another form, sometimes // forcing first to the other form. // // (1) Type ID - 2 byte shortcut for an Iced type // (2) String clazz name - the class name for an Iced type // (3) Iced POJO - an instance of Iced, the distributable workhorse object // (4) Icer POJO - an instance of Icer, the serializing delegate for Iced // // Some sample code paths: // <clinit>: convert string -> ID (then set static globals) // new code: fetch remote string->ID mapping // new code: leader sets string->ID mapping // printing: id -> string // deserial: id -> string -> Icer -> Iced (slow path) // deserial: id -> Icer -> Iced (fath path) // lookup : id -> string (on leader) // // During first Icing, get a globally unique class ID for a className static int onIce(Iced ice) { return onIce(ice.getClass().getName()); } static int onIce(Freezable ice) { return onIce(ice.getClass().getName()); } public static int onIce(String className) { Integer I = MAP.get(className); if( I != null ) return I; // Need to install a new cloud-wide type ID for className. assert H2O.CLOUD.size() > 0 : "No cloud when getting type id for "+className; // Am I leader, or not? Lock the cloud to find out Paxos.lockCloud(className); // Leader: pick an ID. Not-the-Leader: fetch ID from leader. int id = H2O.CLOUD.leader() == H2O.SELF ? -1 : FetchId.fetchId(className); return install(className,id); } // Quick check to see if cached private static Icer goForGold( int id ) { Icer gold[] = GOLD; // Read once, in case resizing // Racily read the GOLD array return id < gold.length ? gold[id] : null; } // Reverse: convert an ID to a className possibly fetching it from leader. public static String className(int id) { if( id == PRIM_B ) return "[B"; String clazs[] = CLAZZES; // Read once, in case resizing if( id < clazs.length ) { // Might be installed as a className mapping no Icer (yet) String s = clazs[id]; // Racily read the CLAZZES array if( s != null ) return s; // Has the className already } assert H2O.CLOUD.leader() != H2O.SELF : "Leader has no mapping for id "+id; // Leaders always have the latest mapping already String s = FetchClazz.fetchClazz(id); // Fetch class name string from leader Paxos.lockCloud(s); // If the leader is already selected, then the cloud is already locked but maybe we dont know; lock now install( s, id ); // Install name<->id mapping return s; } // Install the type mapping under lock, and grow all the arrays as needed. // The grow-step is not obviously race-safe: readers of all the arrays will // get either the old or new arrays. However readers are all reader with // smaller type ids, and these will work fine in either old or new arrays. synchronized static private int install( String className, int id ) { assert !_check_no_locking : "Locking cloud to assign typeid to "+className; if( id == -1 ) { // Leader requesting a new ID assert H2O.CLOUD.leader() == H2O.SELF; // Only leaders get to pick new IDs Integer i = MAP.get(className); if( i != null ) return i; // Check again under lock for already having an ID id = IDS++; // Leader gets an ID under lock } MAP.put(className,id); // No race on insert, since under lock // Expand lists to handle new ID, as needed if( id >= CLAZZES.length ) CLAZZES = Arrays.copyOf(CLAZZES,Math.max(CLAZZES.length<<1,id+1)); if( id >= GOLD .length ) GOLD = Arrays.copyOf(GOLD ,Math.max(CLAZZES.length<<1,id+1)); CLAZZES[id] = className; return id; } // Figure out the mapping from a type ID to a Class. Happens many places, // including during deserialization when a Node will be presented with a // fresh new ID with no idea what it stands for. Does NOT resize the GOLD // array, since the id->className mapping has already happened. public static Icer getIcer( Freezable ice ) { return getIcer(onIce(ice),ice.getClass()); } static Icer getIcer( int id, Iced ice ) { return getIcer(id,ice.getClass()); } static Icer getIcer( int id, Freezable ice ) { return getIcer(id,ice.getClass()); } static Icer getIcer( int id, Class ice_clz ) { Icer f = goForGold(id); if( f != null ) return f; // Lock on the Iced class during auto-gen - so we only gen the Icer for // a particular Iced class once. //noinspection SynchronizationOnLocalVariableOrMethodParameter synchronized( ice_clz ) { f = goForGold(id); // Recheck under lock if( f != null ) return f; // Hard work: make a new delegate class try { f = Weaver.genDelegate(id,ice_clz); } catch( Exception e ) { Log.err("Weaver generally only throws if classfiles are not found, e.g. IDE setups running test code from a remote node that is not in the classpath on this node."); throw Log.throwErr(e); } // Now install under the TypeMap class lock, so the GOLD array is not // resized out from under the installation. synchronized( TypeMap.class ) { return GOLD[id]=f; } } } static void drop(String ice_clz) { Integer I = MAP.get(ice_clz); if( I==null ) return; // no icer, no problem synchronized( TypeMap.class ) { // install null GOLD[I] = null; } } static Iced newInstance(int id) { return (Iced) newFreezable(id); } /** Create a new freezable object based on its unique ID. * * @param id freezable unique id (provided by TypeMap) * @return new instance of Freezable object */ public static Freezable newFreezable(int id) { Freezable iced = theFreezable(id); assert iced != null : "No instance of id "+id+", class="+CLAZZES[id]; return iced.clone(); } /** Create a new freezable object based on its className. * * @param className class name * @return new instance of Freezable object */ public static Freezable newFreezable(String className) { return (Freezable)theFreezable(onIce(className)).clone(); } /** The single golden instance of an Iced, used for cloning and instanceof * tests, do-not-modify since it's The Golden Instance and shared. */ public static Freezable theFreezable(int id) { try { Icer f = goForGold(id); return (f==null ? getIcer(id, classForName(className(id))) : f).theFreezable(); } catch( ClassNotFoundException e ) { throw Log.throwErr(e); } } public static Freezable getTheFreezableOrThrow(int id) throws ClassNotFoundException { Icer f = goForGold(id); return (f==null ? getIcer(id, classForName(className(id))) : f).theFreezable(); } }