about summary refs log tree commit diff
path: root/third_party/nix/src/libstore/http-binary-cache-store.cc
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/nix/src/libstore/http-binary-cache-store.cc')
-rw-r--r--third_party/nix/src/libstore/http-binary-cache-store.cc171
1 files changed, 171 insertions, 0 deletions
diff --git a/third_party/nix/src/libstore/http-binary-cache-store.cc b/third_party/nix/src/libstore/http-binary-cache-store.cc
new file mode 100644
index 0000000000..c713ac43c4
--- /dev/null
+++ b/third_party/nix/src/libstore/http-binary-cache-store.cc
@@ -0,0 +1,171 @@
+#include <utility>
+
+#include <glog/logging.h>
+
+#include "libstore/binary-cache-store.hh"
+#include "libstore/download.hh"
+#include "libstore/globals.hh"
+#include "libstore/nar-info-disk-cache.hh"
+
+namespace nix {
+
+MakeError(UploadToHTTP, Error);
+
+class HttpBinaryCacheStore : public BinaryCacheStore {
+ private:
+  Path cacheUri;
+
+  struct State {
+    bool enabled = true;
+    std::chrono::steady_clock::time_point disabledUntil;
+  };
+
+  Sync<State> _state;
+
+ public:
+  HttpBinaryCacheStore(const Params& params, Path _cacheUri)
+      : BinaryCacheStore(params), cacheUri(std::move(_cacheUri)) {
+    if (cacheUri.back() == '/') {
+      cacheUri.pop_back();
+    }
+
+    diskCache = getNarInfoDiskCache();
+  }
+
+  std::string getUri() override { return cacheUri; }
+
+  void init() override {
+    // FIXME: do this lazily?
+    if (!diskCache->cacheExists(cacheUri, wantMassQuery_, priority)) {
+      try {
+        BinaryCacheStore::init();
+      } catch (UploadToHTTP&) {
+        throw Error("'%s' does not appear to be a binary cache", cacheUri);
+      }
+      diskCache->createCache(cacheUri, storeDir, wantMassQuery_, priority);
+    }
+  }
+
+ protected:
+  void maybeDisable() {
+    auto state(_state.lock());
+    if (state->enabled && settings.tryFallback) {
+      int t = 60;
+      LOG(WARNING) << "disabling binary cache '" << getUri() << "' for " << t
+                   << " seconds";
+      state->enabled = false;
+      state->disabledUntil =
+          std::chrono::steady_clock::now() + std::chrono::seconds(t);
+    }
+  }
+
+  void checkEnabled() {
+    auto state(_state.lock());
+    if (state->enabled) {
+      return;
+    }
+    if (std::chrono::steady_clock::now() > state->disabledUntil) {
+      state->enabled = true;
+      DLOG(INFO) << "re-enabling binary cache '" << getUri() << "'";
+      return;
+    }
+    throw SubstituterDisabled("substituter '%s' is disabled", getUri());
+  }
+
+  bool fileExists(const std::string& path) override {
+    checkEnabled();
+
+    try {
+      DownloadRequest request(cacheUri + "/" + path);
+      request.head = true;
+      getDownloader()->download(request);
+      return true;
+    } catch (DownloadError& e) {
+      /* S3 buckets return 403 if a file doesn't exist and the
+         bucket is unlistable, so treat 403 as 404. */
+      if (e.error == Downloader::NotFound || e.error == Downloader::Forbidden) {
+        return false;
+      }
+      maybeDisable();
+      throw;
+    }
+  }
+
+  void upsertFile(const std::string& path, const std::string& data,
+                  const std::string& mimeType) override {
+    auto req = DownloadRequest(cacheUri + "/" + path);
+    req.data = std::make_shared<std::string>(data);  // FIXME: inefficient
+    req.mimeType = mimeType;
+    try {
+      getDownloader()->download(req);
+    } catch (DownloadError& e) {
+      throw UploadToHTTP("while uploading to HTTP binary cache at '%s': %s",
+                         cacheUri, e.msg());
+    }
+  }
+
+  DownloadRequest makeRequest(const std::string& path) {
+    DownloadRequest request(cacheUri + "/" + path);
+    return request;
+  }
+
+  void getFile(const std::string& path, Sink& sink) override {
+    checkEnabled();
+    auto request(makeRequest(path));
+    try {
+      getDownloader()->download(std::move(request), sink);
+    } catch (DownloadError& e) {
+      if (e.error == Downloader::NotFound || e.error == Downloader::Forbidden) {
+        throw NoSuchBinaryCacheFile(
+            "file '%s' does not exist in binary cache '%s'", path, getUri());
+      }
+      maybeDisable();
+      throw;
+    }
+  }
+
+  void getFile(
+      const std::string& path,
+      Callback<std::shared_ptr<std::string>> callback) noexcept override {
+    checkEnabled();
+
+    auto request(makeRequest(path));
+
+    auto callbackPtr =
+        std::make_shared<decltype(callback)>(std::move(callback));
+
+    getDownloader()->enqueueDownload(
+        request,
+        Callback<DownloadResult>{
+            [callbackPtr, this](std::future<DownloadResult> result) {
+              try {
+                (*callbackPtr)(result.get().data);
+              } catch (DownloadError& e) {
+                if (e.error == Downloader::NotFound ||
+                    e.error == Downloader::Forbidden) {
+                  return (*callbackPtr)(std::shared_ptr<std::string>());
+                }
+                maybeDisable();
+                callbackPtr->rethrow();
+              } catch (...) {
+                callbackPtr->rethrow();
+              }
+            }});
+  }
+};
+
+static RegisterStoreImplementation regStore(
+    [](const std::string& uri,
+       const Store::Params& params) -> std::shared_ptr<Store> {
+      if (std::string(uri, 0, 7) != "http://" &&
+          std::string(uri, 0, 8) != "https://" &&
+          (getEnv("_NIX_FORCE_HTTP_BINARY_CACHE_STORE") != "1" ||
+           std::string(uri, 0, 7) != "file://")) {
+        return nullptr;
+      }
+      auto store = std::make_shared<HttpBinaryCacheStore>(params, uri);
+      store->init();
+      return store;
+    });
+
+}  // namespace nix