1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
#include "lix/libstore/http-binary-cache-store.hh"
#include "lix/libstore/binary-cache-store.hh"
#include "lix/libstore/globals.hh"
#include "lix/libstore/nar-info-disk-cache.hh"
#include "lix/libutil/async-io.hh"
#include "lix/libutil/box_ptr.hh"
#include "lix/libutil/result.hh"
#include "lix/libutil/serialise.hh"

namespace nix {

MakeError(UploadToHTTP, Error);

std::string HttpBinaryCacheStoreConfig::doc()
{
    return
#include "http-binary-cache-store.md"
        ;
}

HttpBinaryCacheStore::HttpBinaryCacheStore(
    const std::string & scheme, const Path & _cacheUri, HttpBinaryCacheStoreConfig config
)
    : Store(config)
    , BinaryCacheStore(config)
    , config_(std::move(config))
    , cacheUri(scheme + "://" + _cacheUri)
{
    if (cacheUri.back() == '/') {
        cacheUri.pop_back();
    }

    diskCache = getNarInfoDiskCache();
}

kj::Promise<Result<void>> HttpBinaryCacheStore::init()
try {
    // FIXME: do this lazily?
    if (auto cacheInfo = diskCache->upToDateCacheExists(cacheUri)) {
        config_.wantMassQuery.setDefault(cacheInfo->wantMassQuery);
        config_.priority.setDefault(cacheInfo->priority);
    } else {
        try {
            TRY_AWAIT(BinaryCacheStore::init());
        } catch (UploadToHTTP &) {
            throw Error("'%s' does not appear to be a binary cache", cacheUri);
        }
        diskCache->createCache(cacheUri, config_.storeDir, config_.wantMassQuery, config_.priority);
    }
    co_return result::success();
} catch (...) {
    co_return result::current_exception();
}

FileTransferOptions HttpBinaryCacheStore::makeOptions(Headers && headers)
{
    return {.headers = std::move(headers)};
}

void HttpBinaryCacheStore::maybeDisable()
{
    auto state(_state.lock());
    if (state->enabled && settings.tryFallback) {
        int t = 60;
        printError("disabling binary cache '%s' for %s seconds", getUri(), t);
        state->enabled = false;
        state->disabledUntil = std::chrono::steady_clock::now() + std::chrono::seconds(t);
    }
}

void HttpBinaryCacheStore::checkEnabled()
{
    auto state(_state.lock());
    if (state->enabled) {
        return;
    }
    if (std::chrono::steady_clock::now() > state->disabledUntil) {
        state->enabled = true;
        debug("re-enabling binary cache '%s'", getUri());
        return;
    }
    throw SubstituterDisabled("substituter '%s' is disabled", getUri());
}

kj::Promise<Result<bool>>
HttpBinaryCacheStore::fileExists(const std::string & path, const Activity * context)
try {
    checkEnabled();

    try {
        co_return TRY_AWAIT(getFileTransfer()->exists(makeURI(path), makeOptions(), context));
    } catch (FileTransferError & e) {
        maybeDisable();
        throw;
    }
} catch (...) {
    co_return result::current_exception();
}

kj::Promise<Result<void>> HttpBinaryCacheStore::upsertFile(
    const std::string & path,
    std::shared_ptr<std::basic_iostream<char>> istream,
    const std::string & mimeType,
        const Activity * context
)
try {
    auto data = StreamToSourceAdapter(istream).drain();
    try {
        TRY_AWAIT(getFileTransfer()->upload(
            makeURI(path), std::move(data), makeOptions({{"Content-Type", mimeType}}), context
        ));
    } catch (FileTransferError & e) {
        throw UploadToHTTP("while uploading to HTTP binary cache at '%s': %s", cacheUri, e.msg());
    }
    co_return result::success();
} catch (...) {
    co_return result::current_exception();
}

kj::Promise<Result<box_ptr<AsyncInputStream>>>
HttpBinaryCacheStore::getFile(const std::string & path, const Activity * context)
try {
    checkEnabled();
    try {
        co_return TRY_AWAIT(getFileTransfer()->download(makeURI(path), makeOptions(), context))
            .second;
    } catch (FileTransferError & e) {
        if (e.error == FileTransfer::NotFound || e.error == FileTransfer::Forbidden) {
            throw NoSuchBinaryCacheFile(
                "file '%s' does not exist in binary cache '%s'", path, getUri()
            );
        }
        maybeDisable();
        throw;
    }

} catch (...) {
    co_return result::current_exception();
}

void registerHttpBinaryCacheStore() {
    StoreImplementations::add<HttpBinaryCacheStore, HttpBinaryCacheStoreConfig>();
}

}