private void WriteModuleContent(OciArtifactModuleReference reference, OciArtifactResult result) { /* * this should be kept in sync with the IsModuleRestoreRequired() implementation */ // write main.bicep this.fileResolver.Write(this.GetModuleFileUri(reference, ModuleFileType.ModuleMain), result.ModuleStream); // write manifest // it's important to write the original stream here rather than serialize the manifest object // this way we guarantee the manifest hash will match this.fileResolver.Write(this.GetModuleFileUri(reference, ModuleFileType.Manifest), result.ManifestStream); // write metadata var metadata = new ModuleMetadata(result.ManifestDigest); using var metadataStream = new MemoryStream(); OciSerialization.Serialize(metadataStream, metadata); metadataStream.Position = 0; this.fileResolver.Write(this.GetModuleFileUri(reference, ModuleFileType.Metadata), metadataStream); }
private async Task TryWriteModuleContentAsync(OciArtifactModuleReference reference, OciArtifactResult result) { // this has to be after downloading the manifest so we don't create directories for non-existent modules string modulePath = GetModuleDirectoryPath(reference); // creating the directory doesn't require locking CreateModuleDirectory(modulePath); /* * We have already downloaded the module content from the registry. * The following sections will attempt to synchronize the module write with other * instances of the language server running on the same machine. * * We are not trying to prevent tampering with the module cache by the user. */ Uri lockFileUri = GetModuleFileUri(reference, ModuleFileType.Lock); var sw = Stopwatch.StartNew(); while (sw.Elapsed < ModuleDirectoryContentionTimeout) { var @lock = this.fileResolver.TryAcquireFileLock(lockFileUri); using (@lock) { // the placement of "if" inside "using" guarantees that even an exception thrown by the condition results in the lock being released // (current condition can't throw, but this potentially avoids future regression) if (@lock is not null) { // we have acquired the lock if (!this.IsModuleRestoreRequired(reference)) { // the other instance has already written out the content to disk - we can discard the content we downloaded return; } // write the contents to disk this.WriteModuleContent(reference, result); return; } } // we have not acquired the lock - let's give the instance that has the lock some time to finish writing the content to the directory // (the operation involves only writing the already downloaded content to disk, so it "should" complete fairly quickly) await Task.Delay(ModuleDirectoryContentionRetryInterval); } // we have exceeded the timeout throw new OciModuleRegistryException($"Exceeded the timeout of \"{ModuleDirectoryContentionTimeout}\" to acquire the lock on file \"{lockFileUri}\"."); }