| 78 | } |
| 79 | |
| 80 | Result<Ref<ValdiModuleArchive>> ResourceManager::getArchiveForModule(const StringBox& modulePath, |
| 81 | bool useMmap, |
| 82 | const Path& mmapCacheDir, |
| 83 | const Ref<Metrics>& metrics) { |
| 84 | auto bundleFilePath = resolveModuleArchiveFilePath(modulePath); |
| 85 | |
| 86 | auto bundleContent = _resourceLoader->loadModuleContent(bundleFilePath); |
| 87 | if (!bundleContent) { |
| 88 | // Fallback on .valdimodule for backward compatibility |
| 89 | auto bundleContentAlternative = _resourceLoader->loadModuleContent(modulePath.append(".valdimodule")); |
| 90 | if (!bundleContentAlternative) { |
| 91 | return bundleContent.error().rethrow(STRING_FORMAT("Unable to load module '{}'", modulePath)); |
| 92 | } else { |
| 93 | bundleContent = std::move(bundleContentAlternative); |
| 94 | } |
| 95 | } |
| 96 | |
| 97 | const auto& data = bundleContent.value(); |
| 98 | |
| 99 | // NOTE: do not acquire _mutex anywhere in this function. It is called from getBundle while the |
| 100 | // BundleInitializer holds the Bundle's mutex; the cleanup path (removeUnusedResources) takes |
| 101 | // _mutex and then a Bundle mutex, so taking _mutex here inverts that order and can deadlock. |
| 102 | // The mmap/metrics settings are snapshotted by getBundle under _mutex and passed in. |
| 103 | |
| 104 | bool usedMmap = false; |
| 105 | bool mmapPublishFailed = false; |
| 106 | MetricsStopWatch decompressStopWatch; |
| 107 | Result<ValdiModuleArchive> result = [&]() { |
| 108 | if (useMmap) { |
| 109 | // Flat filename keyed by SHA-256 of the module path. Avoids |
| 110 | // nested directories under the cache dir, sidesteps any |
| 111 | // path-traversal concern from manifest-supplied module paths, |
| 112 | // and is deterministic across app launches (std::hash is not — |
| 113 | // libc++ may seed it randomly, which would orphan every cache |
| 114 | // file on every restart and slowly fill the user's disk). |
| 115 | auto modulePathView = modulePath.toStringView(); |
| 116 | auto flatName = |
| 117 | BytesUtils::sha256String(reinterpret_cast<const Byte*>(modulePathView.data()), modulePathView.size()); |
| 118 | auto mmapPath = mmapCacheDir.appending(std::string_view(flatName)); |
| 119 | return ValdiModuleArchive::decompress(data.data(), data.size(), mmapPath, &usedMmap, &mmapPublishFailed); |
| 120 | } |
| 121 | return ValdiModuleArchive::decompress(data.data(), data.size()); |
| 122 | }(); |
| 123 | auto decompressDuration = decompressStopWatch.elapsed(); |
| 124 | |
| 125 | // A/B telemetry. Emit a single path counter and a latency timer per call. |
| 126 | // Failures don't get a path counter — they wouldn't tell us anything useful |
| 127 | // about realized mmap rate. |
| 128 | if (result && metrics != nullptr) { |
| 129 | if (useMmap) { |
| 130 | if (usedMmap) { |
| 131 | metrics->emitModuleArchiveMmapSuccess(modulePath); |
| 132 | // Emitted alongside Mmap_Success when the rename-into-cache step |
| 133 | // failed; the in-memory buffer is still valid but no file was |
| 134 | // published. Expected to be ~0 in production. |
| 135 | if (mmapPublishFailed) { |
| 136 | metrics->emitModuleArchiveMmapPublishFail(modulePath); |
| 137 | } |
nothing calls this directly
no test coverage detected