public void TestRemoveDesignFromDesignCache() { AddApplicationRouting(); var siteModel = DITAGFileAndSubGridRequestsWithIgniteFixture.NewEmptyModel(); var designUid = DITAGFileAndSubGridRequestsWithIgniteFixture.AddDesignToSiteModel(ref siteModel, TestHelper.CommonTestDataPath, TESTFILENAME, true); var localStorage = Path.Combine(FilePathHelper.GetTempFolderForProject(siteModel.ID), TESTFILENAME); var designFiles = DIContext.ObtainOptional <IDesignFiles>(); designFiles.NumDesignsInCache().Should().Be(0); designFiles.Lock(designUid, siteModel, SubGridTreeConsts.DefaultCellSize, out var loadResult); File.Exists(localStorage).Should().BeTrue(); designFiles.NumDesignsInCache().Should().Be(1); var design1 = loadResult.Should().Be(DesignLoadResult.Success); design1.Should().NotBeNull(); var message = new DesignChangedEvent() { SiteModelUid = siteModel.ID, DesignUid = designUid, DesignRemoved = true, FileType = ImportedFileType.DesignSurface }; var listener = new DesignChangedEventListener(TRexGrids.ImmutableGridName()); listener.Invoke(Guid.Empty, message).Should().BeTrue(); File.Exists(localStorage).Should().BeFalse(); designFiles.NumDesignsInCache().Should().Be(0); }
public IDesignChangedEventSenderResponse Invoke(IDesignChangedEvent arg) { var localNodeId = Guid.Empty; try { var siteModels = DIContext.Obtain <ISiteModels>(); if (siteModels == null) { _log.LogError("No ISiteModels instance available from DIContext to send design change message to"); return(new DesignChangedEventSenderResponse { Success = false, NodeUid = localNodeId }); } localNodeId = DIContext.ObtainRequired <ITRexGridFactory>().Grid(siteModels.PrimaryMutability).GetCluster().GetLocalNode().Id; var designFiles = DIContext.ObtainOptional <IDesignFiles>(); if (designFiles == null) { // No cache, leave early... return(new DesignChangedEventSenderResponse { Success = true, NodeUid = localNodeId }); } var siteModel = siteModels.GetSiteModel(arg.SiteModelUid); if (siteModel == null) { _log.LogWarning($"No site model found for ID {arg.SiteModelUid}"); return(new DesignChangedEventSenderResponse { Success = true, NodeUid = localNodeId }); } designFiles.DesignChangedEventHandler(arg.DesignUid, siteModel, arg.FileType); return(new DesignChangedEventSenderResponse { Success = true, NodeUid = localNodeId }); } catch (Exception e) { _log.LogError(e, "Exception occurred processing site model attributes changed event"); return(new DesignChangedEventSenderResponse { Success = false, NodeUid = localNodeId }); } finally { _log.LogInformation( $"Completed handling notification of design changed for Site:{arg.SiteModelUid}, Design:{arg.DesignUid}, DesignRemoved:{arg.DesignRemoved}, ImportedFileType:{arg.FileType}"); } }
public bool Invoke(Guid nodeId, IDesignChangedEvent message) { try { _log.LogInformation( $"Received notification of design changed for site:{message.SiteModelUid}, design:{message.DesignUid}, DesignType:{message.FileType}, DesignRemoved:{message.DesignRemoved}, ImportedFileType:{message.FileType}"); var designFiles = DIContext.ObtainOptional <IDesignFiles>(); if (designFiles == null) { // No cache, leave early... return(true); } var siteModel = DIContext.ObtainRequired <ISiteModels>().GetSiteModel(message.SiteModelUid); if (siteModel == null) { _log.LogWarning($"No site model found for ID {message.SiteModelUid}"); return(true); } designFiles.DesignChangedEventHandler(message.DesignUid, siteModel, message.FileType); } catch (Exception e) { _log.LogError(e, "Exception occurred processing design changed event"); return(true); // Stay subscribed } finally { _log.LogInformation( $"Completed handling notification of design changed for Site:{message.SiteModelUid}, Design:{message.DesignUid}, DesignRemoved:{message.DesignRemoved}, ImportedFileType:{message.FileType}"); } return(true); }
/// <summary> /// Handles the situation when TAG file processing or some other activity has modified the attributes of a site model /// requiring the site model to be reloaded /// </summary> public void SiteModelAttributesHaveChanged(ISiteModelAttributesChangedEvent message) { var messageAge = DateTime.UtcNow - message.TimeSentUtc; _log.LogInformation($"Entering attribute change notification processor for project {message.SiteModelID}, change event ID {message.ChangeEventUid}, event message age {messageAge}"); if (messageAge.TotalSeconds > 1.0) { _log.LogWarning($"Message age more than 1 second [{messageAge}]"); } // Site models have immutable characteristics in TRex. Multiple requests may reference the same site model // concurrently, with no interlocks enforcing access serialization. Any attempt to replace or modify an already loaded // site model may cause issue with concurrent request accessing that site model. // THe strategy here is to preserve continued access by concurrent requests to the site model retrieved // at the time the request was initiated by removing it from the SiteModels cache but not destroying it. // Once all request based references to the site model have completed the now orphaned site model will be cleaned // up by the garbage collector. Removal of the site model is interlocked with getting a site model reference // to ensure no concurrency issues within the underlying cache implementation // Note: The site model references some elements that may be preserved via the site model factory method that // accepts an origin site model. // These elements are: // 1. ExistenceMap // 2. Sub grid tree containing cached sub grid data // 3. Coordinate system // 4. Designs // 5. Surveyed Surfaces // 6. Machines // 7. Machines target values // 8. Machines design names // 9. Proofing runs // 10. Alignments // 11. Site model marked for deletion ISiteModel siteModel; // Construct a new site model that preserves elements not affected by the notification and replace the existing // site model reference with it. lock (_cachedModels) { _cachedModels.TryGetValue(message.SiteModelID, out siteModel); if (siteModel != null && message.SiteModelMarkedForDeletion) { // Remove the site model from the cache and exit. _cachedModels.Remove(message.SiteModelID); return; } // Note: The spatial data grid is highly conserved and never killed in a site model change notification. var originFlags = SiteModelOriginConstructionFlags.PreserveGrid | (!message.ExistenceMapModified ? SiteModelOriginConstructionFlags.PreserveExistenceMap : 0) | (!message.CsibModified ? SiteModelOriginConstructionFlags.PreserveCsib : 0) | (!message.DesignsModified ? SiteModelOriginConstructionFlags.PreserveDesigns : 0) | (!message.SurveyedSurfacesModified ? SiteModelOriginConstructionFlags.PreserveSurveyedSurfaces : 0) | (!message.MachinesModified ? SiteModelOriginConstructionFlags.PreserveMachines : 0) | (!message.MachineTargetValuesModified ? SiteModelOriginConstructionFlags.PreserveMachineTargetValues : 0) | (!message.MachineDesignsModified ? SiteModelOriginConstructionFlags.PreserveMachineDesigns | SiteModelOriginConstructionFlags.PreserveSiteModelDesigns : 0) | (!message.ProofingRunsModified ? SiteModelOriginConstructionFlags.PreserveProofingRuns : 0) | (!message.AlignmentsModified ? SiteModelOriginConstructionFlags.PreserveAlignments : 0) ; _log.LogInformation($"Processing attribute change notification for site model {message.SiteModelID}. Preserved elements are {originFlags}"); if (siteModel != null) { // First create a new site model to replace the site model with, requesting certain elements of the existing site model // to be preserved in the new site model instance. siteModel = DIContext.Obtain <ISiteModelFactory>().NewSiteModel(siteModel, originFlags); // Replace the site model reference in the cache with the new site model _cachedModels[message.SiteModelID] = siteModel; } } // If the notification contains an existence map change mask then all cached sub grid based elements that match the masked sub grids // need to be evicted from all cached contexts related to this site model. Note: This operation is not performed under a lock as the // removal operations on the cache are lock free if (message.ExistenceMapChangeMask != null) { // Create and deserialize the sub grid but mask from the message var mask = new SubGridTreeSubGridExistenceBitMask(); mask.FromBytes(message.ExistenceMapChangeMask); if (siteModel != null) { // Iterate over all leaf sub grids in the mask. For each get the matching node sub grid in siteModel.Grid, // and remove all sub grid references from that node sub grid matching the bits in the bit mask sub grid mask.ScanAllSubGrids(leaf => { // Obtain the matching node sub grid in Grid var node = siteModel.Grid.LocateClosestSubGridContaining (leaf.OriginX << SubGridTreeConsts.SubGridIndexBitsPerLevel, leaf.OriginY << SubGridTreeConsts.SubGridIndexBitsPerLevel, leaf.Level); // If there are sub grids present in Grid that match the sub grids identified by leaf // remove the elements identified in leaf from the node sub grid. if (node != null) { ((ISubGridTreeLeafBitmapSubGrid)leaf).ForEachSetBit((x, y) => node.SetSubGrid(x, y, null)); } return(true); // Keep processing leaf sub grids }); } // Advise the spatial memory general sub grid result cache of the change so it can invalidate cached derivatives DIContext.ObtainOptional <ITRexSpatialMemoryCache>()?.InvalidateDueToProductionDataIngest(message.SiteModelID, message.ChangeEventUid, mask); // Advise any registered site model change map notifier of the changes DIContext.ObtainOptional <ISiteModelChangeMapDeltaNotifier>()?.Notify(message.SiteModelID, DateTime.UtcNow, mask, SiteModelChangeMapOrigin.Ingest, SiteModelChangeMapOperation.AddSpatialChanges); } }