Line data Source code
1 : #include "source/extensions/http/cache/file_system_http_cache/lookup_context.h" 2 : 3 : #include "source/extensions/http/cache/file_system_http_cache/cache_file_header.pb.h" 4 : #include "source/extensions/http/cache/file_system_http_cache/cache_file_header_proto_util.h" 5 : #include "source/extensions/http/cache/file_system_http_cache/file_system_http_cache.h" 6 : 7 : #include "cache_file_fixed_block.h" 8 : 9 : namespace Envoy { 10 : namespace Extensions { 11 : namespace HttpFilters { 12 : namespace Cache { 13 : namespace FileSystemHttpCache { 14 : 15 0 : std::string FileLookupContext::filepath() { 16 0 : return absl::StrCat(cache_.cachePath(), cache_.generateFilename(key_)); 17 0 : } 18 : 19 0 : bool FileLookupContext::workInProgress() const { return cache_.workInProgress(key()); } 20 : 21 0 : void FileLookupContext::getHeaders(LookupHeadersCallback&& cb) { 22 : // TODO(ravenblack): Consider adding a memory cache check here for uncacheable keys, to save 23 : // on the repeated filesystem hit. Capture some performance metrics to see if it's worth it. 24 : // If migrating to "shared stream" implementation, this question answers itself. 25 0 : absl::MutexLock lock(&mu_); 26 0 : getHeadersWithLock(std::move(cb)); 27 0 : } 28 : 29 0 : void FileLookupContext::getHeadersWithLock(LookupHeadersCallback cb) { 30 0 : mu_.AssertHeld(); 31 0 : cancel_action_in_flight_ = cache_.asyncFileManager()->openExistingFile( 32 0 : filepath(), Common::AsyncFiles::AsyncFileManager::Mode::ReadOnly, 33 0 : [this, cb](absl::StatusOr<AsyncFileHandle> open_result) { 34 0 : absl::MutexLock lock(&mu_); 35 0 : cancel_action_in_flight_ = nullptr; 36 0 : if (!open_result.ok()) { 37 0 : cache_.stats().cache_miss_.inc(); 38 0 : cb(LookupResult{}); 39 0 : return; 40 0 : } 41 0 : ASSERT(!file_handle_); 42 0 : file_handle_ = std::move(open_result.value()); 43 0 : auto queued = file_handle_->read( 44 0 : 0, CacheFileFixedBlock::size(), 45 0 : [this, cb](absl::StatusOr<Buffer::InstancePtr> read_result) { 46 0 : absl::MutexLock lock(&mu_); 47 0 : cancel_action_in_flight_ = nullptr; 48 0 : if (!read_result.ok() || 49 0 : read_result.value()->length() != CacheFileFixedBlock::size()) { 50 0 : invalidateCacheEntry(); 51 0 : cache_.stats().cache_miss_.inc(); 52 0 : cb(LookupResult{}); 53 0 : return; 54 0 : } 55 0 : header_block_.populateFromStringView(read_result.value()->toString()); 56 0 : if (!header_block_.isValid()) { 57 0 : invalidateCacheEntry(); 58 0 : cache_.stats().cache_miss_.inc(); 59 0 : cb(LookupResult{}); 60 0 : return; 61 0 : } 62 0 : auto queued = file_handle_->read( 63 0 : header_block_.offsetToHeaders(), header_block_.headerSize(), 64 0 : [this, cb](absl::StatusOr<Buffer::InstancePtr> read_result) { 65 0 : absl::MutexLock lock(&mu_); 66 0 : cancel_action_in_flight_ = nullptr; 67 0 : if (!read_result.ok() || 68 0 : read_result.value()->length() != header_block_.headerSize()) { 69 0 : invalidateCacheEntry(); 70 0 : cache_.stats().cache_miss_.inc(); 71 0 : cb(LookupResult{}); 72 0 : return; 73 0 : } 74 0 : auto header_proto = makeCacheFileHeaderProto(*read_result.value()); 75 0 : if (header_proto.headers_size() == 1 && 76 0 : header_proto.headers().at(0).key() == "vary") { 77 0 : auto maybe_vary_key = cache_.makeVaryKey( 78 0 : key_, lookup().varyAllowList(), 79 0 : absl::StrSplit(header_proto.headers().at(0).value(), ','), 80 0 : lookup().requestHeaders()); 81 0 : if (!maybe_vary_key.has_value()) { 82 0 : cache_.stats().cache_miss_.inc(); 83 0 : cb(LookupResult{}); 84 0 : return; 85 0 : } 86 0 : key_ = maybe_vary_key.value(); 87 0 : auto fh = std::move(file_handle_); 88 0 : file_handle_ = nullptr; 89 : // It should be possible to cancel close, to make this safe. 90 : // (it should still close the file, but cancel the callback.) 91 0 : auto queued = fh->close([this, cb](absl::Status) { 92 0 : absl::MutexLock lock(&mu_); 93 : // Restart getHeaders with the new key. 94 0 : return getHeadersWithLock(cb); 95 0 : }); 96 0 : ASSERT(queued.ok(), queued.ToString()); 97 0 : return; 98 0 : } 99 0 : cache_.stats().cache_hit_.inc(); 100 0 : cb(lookup().makeLookupResult( 101 0 : headersFromHeaderProto(header_proto), metadataFromHeaderProto(header_proto), 102 0 : header_block_.bodySize(), header_block_.trailerSize() > 0)); 103 0 : }); 104 0 : ASSERT(queued.ok(), queued.status().ToString()); 105 0 : cancel_action_in_flight_ = queued.value(); 106 0 : }); 107 0 : ASSERT(queued.ok(), queued.status().ToString()); 108 0 : cancel_action_in_flight_ = queued.value(); 109 0 : }); 110 0 : } 111 : 112 0 : void FileLookupContext::invalidateCacheEntry() { 113 0 : cache_.asyncFileManager()->stat( 114 0 : filepath(), [file = filepath(), 115 0 : cache = cache_.shared_from_this()](absl::StatusOr<struct stat> stat_result) { 116 0 : size_t file_size = 0; 117 0 : if (stat_result.ok()) { 118 0 : file_size = stat_result.value().st_size; 119 0 : } 120 0 : cache->asyncFileManager()->unlink(file, [cache, file_size](absl::Status unlink_result) { 121 0 : if (unlink_result.ok()) { 122 0 : cache->trackFileRemoved(file_size); 123 0 : } 124 0 : }); 125 0 : }); 126 0 : } 127 : 128 0 : void FileLookupContext::getBody(const AdjustedByteRange& range, LookupBodyCallback&& cb) { 129 0 : absl::MutexLock lock(&mu_); 130 0 : ASSERT(!cancel_action_in_flight_); 131 0 : auto queued = file_handle_->read( 132 0 : header_block_.offsetToBody() + range.begin(), range.length(), 133 0 : [this, cb, range](absl::StatusOr<Buffer::InstancePtr> read_result) { 134 0 : absl::MutexLock lock(&mu_); 135 0 : cancel_action_in_flight_ = nullptr; 136 0 : if (!read_result.ok() || read_result.value()->length() != range.length()) { 137 0 : invalidateCacheEntry(); 138 : // Calling callback with nullptr fails the request. 139 0 : cb(nullptr); 140 0 : return; 141 0 : } 142 0 : cb(std::move(read_result.value())); 143 0 : }); 144 0 : ASSERT(queued.ok(), queued.status().ToString()); 145 0 : cancel_action_in_flight_ = queued.value(); 146 0 : } 147 : 148 0 : void FileLookupContext::getTrailers(LookupTrailersCallback&& cb) { 149 0 : ASSERT(cb); 150 0 : absl::MutexLock lock(&mu_); 151 0 : ASSERT(!cancel_action_in_flight_); 152 0 : auto queued = file_handle_->read(header_block_.offsetToTrailers(), header_block_.trailerSize(), 153 0 : [this, cb](absl::StatusOr<Buffer::InstancePtr> read_result) { 154 0 : absl::MutexLock lock(&mu_); 155 0 : cancel_action_in_flight_ = nullptr; 156 0 : if (!read_result.ok() || read_result.value()->length() != 157 0 : header_block_.trailerSize()) { 158 0 : invalidateCacheEntry(); 159 : // There is no failure response for getTrailers, so we just 160 : // say there were no trailers in the event of this failure. 161 0 : cb(Http::ResponseTrailerMapImpl::create()); 162 0 : return; 163 0 : } 164 0 : CacheFileTrailer trailer; 165 0 : trailer.ParseFromString(read_result.value()->toString()); 166 0 : cb(trailersFromTrailerProto(trailer)); 167 0 : }); 168 0 : ASSERT(queued.ok(), queued.status().ToString()); 169 0 : cancel_action_in_flight_ = queued.value(); 170 0 : } 171 : 172 0 : void FileLookupContext::onDestroy() { 173 0 : CancelFunction cancel; 174 0 : { 175 0 : absl::MutexLock lock(&mu_); 176 0 : cancel = std::move(cancel_action_in_flight_); 177 0 : cancel_action_in_flight_ = nullptr; 178 0 : } 179 0 : while (cancel) { 180 : // We mustn't hold the lock while calling cancel, as it can potentially wait for 181 : // a callback to complete, and the callback might take the lock. 182 0 : cancel(); 183 0 : { 184 : // It's possible that while calling cancel, another action was started - if 185 : // that happened, we must cancel that one too! 186 0 : absl::MutexLock lock(&mu_); 187 0 : cancel = std::move(cancel_action_in_flight_); 188 0 : cancel_action_in_flight_ = nullptr; 189 0 : } 190 0 : } 191 0 : { 192 0 : absl::MutexLock lock(&mu_); 193 0 : if (file_handle_) { 194 0 : auto status = file_handle_->close([](absl::Status) {}); 195 0 : ASSERT(status.ok(), status.ToString()); 196 0 : file_handle_ = nullptr; 197 0 : } 198 0 : } 199 0 : } 200 : 201 : } // namespace FileSystemHttpCache 202 : } // namespace Cache 203 : } // namespace HttpFilters 204 : } // namespace Extensions 205 : } // namespace Envoy