def new_obj_from_serialized( host_uuid,serial_obj_named_tuple,invalidation_listener): ''' @param {collections.namedtuple} serial_obj_named_tuple --- @see util._generate_serialization_named_tuple. Should have elements var_name, var_type,var_data, version_obj_data. @returns (a,b): a: a subtype of _ReferenceValue b: a version object (ie, a subtype of waldoReferenceBase._ReferenceVersion). ''' var_name = serial_obj_named_tuple.var_name serial_vobj = serial_obj_named_tuple.version_obj_data version_obj = RefVers.deserialize_version_obj_from_network_data( serial_vobj) var_data = serial_obj_named_tuple.var_data var_type = serial_obj_named_tuple.var_type if isinstance(var_data,util._SerializationHelperNamedTuple): nested_obj = new_obj_from_serialized( host_uuid,var_data,invalidation_listener) new_obj = get_constructed_obj( var_type,var_name,host_uuid,True,nested_obj, invalidation_listener) else: if (isinstance(var_data,dict) and (len(var_data) != 0) and (isinstance( next(iter(var_data.values())), # var_data.itervalues().next(), util._SerializationHelperNamedTuple))): new_obj = {} for key in var_data.keys(): new_obj[key] = get_constructed_obj( var_type,var_name,host_uuid,True,var_data[key], invalidation_listener) elif (isinstance(var_data,list) and (len(var_data) != 0) and (isinstance(var_data[0], util._SerializationHelperNamedTuple))): new_obj = [] for key in range(0,len(var_data)): new_obj.append( get_constructed_obj( var_type,var_name,host_uuid,True,var_data[key], invalidation_listener)) else: new_obj = get_constructed_obj( var_type,var_name,host_uuid,True,var_data,invalidation_listener) # do not need to copy in version object because creating a new # object, which means that no other events were able to perform # operations on new_obj anyways (because they could not see it). return new_obj
def deserialize_peered_object_into_variable( host_uuid,serial_obj_named_tuple,invalidation_listener,waldo_reference): ''' @param {uuid} host_uuid --- The uuid of the host currently on. @param {collections.namedtuple} serial_obj_named_tuple --- @see util._generate_serialization_named_tuple. Should have elements var_name, var_type,var_data, version_obj_data. @param {_InvalidationListener} invalidation_listener --- The event that we are serializing to. @param {_ReferenceValue} waldo_reference --- We write the value and version found in serial_obj_named_tuple into the dirtymapelement corresponding to invalidation_listener in waldo_reference. (Put another way, we try to append any changes that have happened to waldo_reference on the other endpoint to waldo_reference on this endpoint.) @returns {Nothing} --- Changes will be propagated through arguments. ''' # FIXME: probably do not need "var_name" var_name = serial_obj_named_tuple.var_name serial_vobj = serial_obj_named_tuple.version_obj_data version_obj = RefVers.deserialize_version_obj_from_network_data( serial_vobj) var_type = serial_obj_named_tuple.var_type #### DEBUG: Testing whether got a valid type if var_type not in ReferenceTypeConstructorDict: util.logger_assert( 'Error when in waldoNetworkSerializer.deserialize_peered_object' + '. Unknown Waldo type requested for deserialization.') #### END DEBUG # var_data can either be a python value (string,bool,number) or a # python list/map or a SerializationHelperNamedTuple. If it is a # list or map, it either has elements that are python values or it # has elements that are SerializationHelperNamedTuple-s. # # CASE 1: We have python values # Put var_data directly into the dirty_map_element associated # with invalidation_listener in waldo_reference. Similarly copy # over the version object. (We cannot do this for # SerializationHelperNamedTuples because these represent # pointers to additional InternalMaps/Lists/Structs. If we just # copied over the values of these, then they would refer to # different objects on the endpoint than they initially did. # And we wouldn't be able to detect conflicts.) # # CASE 2: We have lists/maps of python values # Do same as in Case 1. # # CASE 3: We have a single SerializationHelperNamedTuple # This means that we are currently deserializing a # WaldoVariable. (One of the classes defined in wVariables.py.) # There can be two cases for what happens. # a: The WaldoVariable has not been written to. # We want the changes to point to the same variable that we # have been using. Get the associated Waldo reference that # waldo_reference points to and write the contents of the # SerializationHelperNamedTuple into its dirty map element's # val and version object. This ensures that the changes that # we are making will be made to the same waldo object on one # endpoint as they were being made to the waldo object on the # other endpoint. # b: The WaldoVariable has been written to. # What this means is that, at some point in this event, we # re-defined the Waldo object that the peered variable was # pointing to. (Ie, we assigned the peered variable to # another map/list/user struct.) In this case, we should # just do what we did in Case 1/Case 2. We do NOT want our # new changes to point to the same old reference. We want # them to point to a new reference. # # CASE 4: We have a map/list of SerializationHelperNamedTuple-s # # Here's why we need to keep track of whether each element was # written to or read from. Assume that we have a map of maps: # Map(from: Text, to: Map(from: Text, to: Text)) m # # It is important to distinguish: # m['a'] = { 'other_a': 'other_a'} # from # m['a']['other_a'] = 'other_a' (assuming m['a'] is already defined) # because if we have another operation # m['a']['b'] = 'b' # for the first case, there's a conflict, for the second they # can execute in parallel # To determine whether written to or read from, use version_obj, # which will have type # waldoReferenceContainerBase._ReferenceContainerVersion. For # each key within it that was written, added, or deleted, treat # the index similarly to 3b above. For others, treat as 3a above. var_data = serial_obj_named_tuple.var_data if not isinstance(var_data,util._SerializationHelperNamedTuple): case1_or_case2 = True if isinstance(var_data,list): if ((len(var_data) != 0) and # it's fine to treat empty # lists as a list of python # values (instead of an empty # list of # SerHelperNamedTuple-s. # This is because we aren't # over-writing any pointed to # references by copying over # it. (isinstance(var_data[0],util._SerializationHelperNamedTuple))): case1_or_case2 = False if isinstance(var_data,dict): if ((len(var_data) != 0) and # see len comment in if # statement above. (isinstance( next(iter(var_data.values())), # var_data.itervalues().next(), util._SerializationHelperNamedTuple))): case1_or_case2 = False if case1_or_case2: # CASE 1/2 above ... overwrite val and version object of # the variable's associated dirty map element. waldo_reference.update_version_and_val( invalidation_listener,version_obj,var_data) return else: # CASE 3 above: we have a single SerializationHelperNamedTuple # means that we must have a _ReferenceValue if not version_obj.has_been_written_to: # case 3a above nested_reference = waldo_reference.get_val( invalidation_listener) deserialize_peered_object_into_variable( host_uuid,var_data,invalidation_listener,nested_reference) else: # case 3b above # FIXME: It is correct to just create a new object here. # However, we do not necessarily need to create a new # object if we had written to the data object much earlier # in the event and have since updated both sides. Could # incur a performance penalty doing this. new_obj = new_obj_from_serialized( host_uuid,var_data,invalidation_listener) waldo_reference.update_version_and_val( invalidation_listener,version_obj,new_obj) return # CASE 4 above: list/map of SerializationHelperNamedTuple-s # make it able to handle maps or lists. all_keys = range(0,len(var_data)) if isinstance(var_data, dict): all_keys = list(var_data.keys()) for key in all_keys: if ((key in version_obj.written_values_keys) or (key in version_obj.added_keys) or (key in version_obj.deleted_keys)): # handle same as 3b above # waldo_reference is an InternalMap/InternalDict and we # must notify it that this object has been # deleted/written/added new_obj = new_obj_from_serialized( host_uuid,var_data[key],invalidation_listener) waldo_reference.update_val_of_key_during_deserialize( invalidation_listener,key,new_obj) elif key in version_obj.read_values_keys: # only add others if have been read. do not willy-nilly # add references. # handle same as 3a above...except getting val for target # key nested_reference = waldo_reference.get_val_on_key( invalidation_listener,key) # recurse deserialize_peered_object_into_variable( host_uuid,var_data[key],invalidation_listener, nested_reference) # remove any elements that may have been deleted by the other side if isinstance(var_data,dict): # it's a map. look for keys that are not in all keys local_keys = waldo_reference.get_keys(invalidation_listener) for key in local_keys: if key not in all_keys: waldo_reference.del_key_called(invalidation_listener,key) if isinstance(var_data,list): if len(local_keys) > len(all_keys): # FIXME: if do something more intelligent about sliding # elements in the list down when they are not modified, # then may have to do something more intelligent than just # deleting off the end. num_times_to_delete = len(all_keys) - len(local_keys) for i in range(0,num_times_to_delete): # keep deleting the spot just beyond how long the list # should be. waldo_reference.del_key_called( invalidation_listener,len(all_keys)) # either way, reset the version obj of overall internal list/map waldo_reference.update_version_obj_during_deserialize( invalidation_listener,version_obj)