/// <summary> /// Discover external module dependencies and enqueue them if necessary /// </summary> /// <remarks> /// The parsed file gets updated with the external module references as they are discovered. /// </remarks> private async Task <Possible <bool> > EnqueueSpecDependenciesIfAny(ModuleUnderConstruction owningModule, ISourceFile parsedFile) { var allSpecifiers = m_moduleReferenceResolver.GetExternalModuleReferences(parsedFile); foreach (var moduleName in allSpecifiers) { // Get the module definition from the resolver and enqueue it. Since we don't deal with versions yet at the // import level, there should be exactly one module definition with that name var maybeModuleDefinition = await m_workspaceProvider.FindUniqueModuleDefinitionWithName(moduleName); if (!maybeModuleDefinition.Succeeded) { return(maybeModuleDefinition.Failure); } var moduleDefinition = maybeModuleDefinition.Result; // Since the referenced module has been found, we update the parsed file with the reference. This // information is later used by the checker. if (!m_moduleReferenceResolver.TryUpdateExternalModuleReference(parsedFile, moduleDefinition, out var failure)) { return(failure); } // Update the owning module advertising that the found module was referenced owningModule.AddReferencedModule(moduleDefinition.Descriptor, moduleName.ReferencedFrom); EnqueueModuleForParsing(moduleDefinition); } return(true); }
private void CheckForCompletionAndSignalQueueIfDone(ModuleUnderConstruction owningModule) { // Now that all dependencies of the owning module have been scheduled, // we check if the module for which we just added a spec is complete if (owningModule.IsModuleFirstTimeComplete()) { var moduleId = owningModule.Definition.Descriptor; // We create a parsed module out of a module under construction. // Observe many threads can try to create the same completed module. But that's ok, the method is thread safe // and always return the same instance. // It is important to notice that we should add this module to m_modulesAlreadyParsed before removing it from // m_modulesToBeParsed. This is so no producer will think the module is not there at any point in time (which // would cause the producer to try to schedule that module again) var success = owningModule.TryCreateCompletedModule(m_moduleReferenceResolver, m_workspaceConfiguration, m_queueOptions.CancelOnFirstFailure, out ParsedModule module, out Failure[] failures); if (success) { m_modulesAlreadyParsed[moduleId] = module; // Now the module is completed and we can bind the changed file as well. var modifiedSourceFile = owningModule.GetSourceFileForInjectingQualifiers(); if (modifiedSourceFile != null) { ScheduleSourceFileBinding(new ParsedSpecWithOwningModule(parsedFile: modifiedSourceFile, owningModule: owningModule.Definition)); } } else { // This is a bogus module we put here when failures occurr. This guarantees that modules // are not scheduled more than once (detailed reasons above). When there are failures // we want to report them only once, so leave that to whoever successfully removes // the module from m_modulesToBeParsed to report them. // Observe that this (empty) module will be left in case of failures, but that's not // likely to be a problem m_modulesAlreadyParsed[moduleId] = new ParsedModule(owningModule.Definition, hasFailures: true); } // If removing fails, that means that somebody else successfully removed the module, so we do nothing // If removing succeeds, we are responsible for signaling if the queue is done if (m_modulesToBeParsed.TryRemove(moduleId, out ModuleUnderConstruction dummy)) { if (!success) { ReportFailureAndCancelParsingIfNeeded(failures); } // Now we decide if we are done with parsing the transitive closure. // This happens when the set of modules to be parsed is empty // and EnqueuingCompleted has been called. // Observe that it is important that we first add all dependencies to be parsed // before removing a completed module. This ensures // that if anybody sees an empty collection, we are done for sure if (m_modulesToBeParsed.IsEmpty && m_enqueingComplete) { cancellationTokenChain.Dispose(); m_parseQueue.Complete(); } } } }
/// <summary> /// Creates a workspace from all known modules in an incremental way /// </summary> /// <remarks> /// It reuses already parsed modules so they don't get recomputed again /// </remarks> public async Task <Workspace> CreateIncrementalWorkspaceForAllKnownModulesAsync( IEnumerable <ParsedModule> parsedModules, ModuleUnderConstruction moduleUnderConstruction, IEnumerable <Failure> failures, [CanBeNull] ParsedModule preludeModule) { var maybeModuleDefinitions = await GetModuleDefinitionsForAllResolversAsync(); if (!maybeModuleDefinitions.Succeeded) { return(Failure(maybeModuleDefinitions.Failure)); } var moduleDefinitions = maybeModuleDefinitions.Result; ModuleDefinition preludeDefinition; if (preludeModule != null) { // Need to add prelude to a list of parsed modules if awailable var parsedModuleList = parsedModules.ToList(); parsedModuleList.Add(preludeModule); parsedModules = parsedModuleList; preludeDefinition = preludeModule.Definition; } else { var possiblePreludeDefinition = await TryGetPreludeModuleDefinitionAsync(); if (!possiblePreludeDefinition.Succeeded) { return(Failure(possiblePreludeDefinition.Failure)); } preludeDefinition = possiblePreludeDefinition.Result; moduleDefinitions.Add(preludeDefinition); } if (!WorkspaceValidator.ValidateModuleDefinitions(moduleDefinitions, PathTable, out var validationFailures)) { return(Failure(validationFailures)); } var queue = ModuleParsingQueue.CreateIncrementalQueue( this, Configuration, m_moduleReferenceResolver, preludeDefinition, GetConfigurationModule(), parsedModules, failures); return(await queue.ProcessIncrementalAsync(moduleUnderConstruction)); }
/// <inheritdoc /> public async Task <Workspace> CreateIncrementalWorkspaceForAllKnownModulesAsync( IEnumerable <ParsedModule> parsedModules, ModuleUnderConstruction moduleUnderConstruction, IEnumerable <Failure> failures, ParsedModule preludeModule) { using (m_statistics.EndToEndParsing.Start()) { return(await m_decoratee.CreateIncrementalWorkspaceForAllKnownModulesAsync(parsedModules, moduleUnderConstruction, failures, preludeModule)); } }
/// <summary> /// Process a partially constructed module /// </summary> public Task <Workspace> ProcessIncrementalAsync(ModuleUnderConstruction moduleUnderConstruction) { Contract.Requires(moduleUnderConstruction != null); Contract.Requires(!moduleUnderConstruction.IsModuleComplete()); // Add the module to be parsed as is var definition = moduleUnderConstruction.Definition; Contract.Assert(!m_modulesToBeParsed.ContainsKey(definition.Descriptor)); m_modulesToBeParsed[definition.Descriptor] = moduleUnderConstruction; // Now schedule all the pending specs // It is extremely important to get specs in order to preserve determinism. var pendingSpecs = moduleUnderConstruction.GetPendingSpecPathsOrderedByPath(m_workspaceProvider.PathTable); foreach (var spec in pendingSpecs) { m_parseQueue.Post(new SpecWithOwningModule(spec.path, definition)); } return(EnqueuingFinishedAndWaitForCompletion()); }
/// <summary> /// Enqueues all the specs of <param name="moduleDefinition"/> for parsing. Actual enqueuing only happens if the module has not been /// completed yet, nor already scheduled. /// </summary> /// <remarks> /// This method can only be called before calling <see cref="EnqueuingCompleted"/> /// </remarks> private void EnqueueModuleForParsing(ModuleDefinition moduleDefinition) { if (m_modulesAlreadyParsed.ContainsKey(moduleDefinition.Descriptor) || m_modulesToBeParsed.ContainsKey(moduleDefinition.Descriptor)) { return; } // If the module has no specs, it is already parsed if (moduleDefinition.Specs.Count == 0) { m_modulesAlreadyParsed[moduleDefinition.Descriptor] = new ParsedModule(moduleDefinition); return; } // Create a module with no specs and add it to the modules-to-be-parsed collection var module = new ModuleUnderConstruction(moduleDefinition); if (!m_modulesToBeParsed.TryAdd(moduleDefinition.Descriptor, module)) { // If the module has already been added by someone else, we just declare the enqueue successful return; } // Now schedule all the specs foreach (var spec in moduleDefinition.Specs) { // Constructor of this class ensures that queue is unbounded. // In cases of bounded queue, posting to a queue can lead to a deadlock // because ActionBlock infrastructure removes item from the queue // only when call back that processes that element finishes its execution. // But in this case, callback can add item to the queue which will lead to a deadlock // when queue is full. m_parseQueue.Post(new SpecWithOwningModule(spec, moduleDefinition)); } }