return nullptr;
}
+ // store hadWriter before checking ENTRY_REQUIRES_COLLAPSING to avoid racing
+ // the writer that clears that flag and then leaves
+ const auto hadWriter = map->peekAtWriter(index);
+ if (!hadWriter && EBIT_TEST(anchor->basics.flags, ENTRY_REQUIRES_COLLAPSING)) {
+ debugs(20, 3, "not joining abandoned entry " << index);
+ map->closeForReadingAndFreeIdle(index);
+ return nullptr;
+ }
+
StoreEntry *e = new StoreEntry();
e->createMemObject();
e->mem_obj->xitTable.index = index;
e->mem_obj->xitTable.io = Store::ioReading;
anchor->exportInto(*e);
- const bool collapsingRequired = EBIT_TEST(anchor->basics.flags, ENTRY_REQUIRES_COLLAPSING);
- e->setCollapsingRequirement(collapsingRequired);
+
+ if (EBIT_TEST(anchor->basics.flags, ENTRY_REQUIRES_COLLAPSING)) {
+ assert(hadWriter);
+ e->setCollapsingRequirement(true);
+ }
+
// keep read lock to receive updates from others
return e;
}
{
StoreIOBuffer tempresult;
removeStoreReference(&old_sc, &old_entry);
+
+ if (collapsedRevalidation)
+ http->storeEntry()->clearPublicKeyScope();
+
/* here the data to send is the data we just received */
tempBuffer.offset = 0;
old_reqsize = 0;
const auto &new_rep = http->storeEntry()->mem().freshestReply();
const auto status = new_rep.sline.status();
+ // XXX: Disregard stale incomplete (i.e. still being written) borrowed (i.e.
+ // not caused by our request) IMS responses. That new_rep may be very old!
+
// origin replied 304
if (status == Http::scNotModified) {
http->logType.update(LOG_TCP_REFRESH_UNMODIFIED);
http->logType.update(LOG_TCP_REFRESH_MODIFIED);
debugs(88, 3, "origin replied " << status <<
", replacing existing entry and forwarding to client");
-
- if (collapsedRevalidation)
- http->storeEntry()->clearPublicKeyScope();
-
sendClientUpstreamResponse();
}
}
bool keepInStoreTable = false; // keep only if somebody needs it there
- /* Notify the fs that we're not referencing this object any more */
+ // Notify the fs that we are not referencing this object any more. This
+ // should be done even if we overwrite keepInStoreTable afterwards.
if (e.hasDisk())
keepInStoreTable = swapDir->dereference(e) || keepInStoreTable;
keepInStoreTable = wantsLocalMemory || keepInStoreTable;
}
+ if (e.hittingRequiresCollapsing()) {
+ // If we were writing this now-locally-idle entry, then we did not
+ // finish and should now destroy an incomplete entry. Otherwise, do not
+ // leave this idle StoreEntry behind because handleIMSReply() lacks
+ // freshness checks when hitting a collapsed revalidation entry.
+ keepInStoreTable = false; // may overrule fs decisions made above
+ }
+
return keepInStoreTable;
}
return swapDir->hasReadableEntry(e);
}
+/// flags problematic entries before find() commits to finalizing/returning them
+void
+Store::Controller::checkFoundCandidate(const StoreEntry &entry) const
+{
+ checkTransients(entry);
+
+ // The "hittingRequiresCollapsing() has an active writer" checks below
+ // protect callers from getting stuck and/or from using a stale revalidation
+ // reply. However, these protections are not reliable because the writer may
+ // disappear at any time and/or without a trace. Collapsing adds risks...
+ if (entry.hittingRequiresCollapsing()) {
+ if (entry.hasTransients()) {
+ // Too late to check here because the writer may be gone by now, but
+ // Transients do check when they setCollapsingRequirement().
+ } else {
+ // a local writer must hold a lock on its writable entry
+ if (!(entry.locked() && entry.isAccepting()))
+ throw TextException("no local writer", Here());
+ }
+ }
+}
+
StoreEntry *
Store::Controller::find(const cache_key *key)
{
try {
if (!entry->key)
allowSharing(*entry, key);
- checkTransients(*entry);
+ checkFoundCandidate(*entry);
entry->touch();
referenceBusy(*entry);
return entry;
addReading(&entry, key);
if (entry.hasTransients()) {
+ // store hadWriter before computing `found`; \see Transients::get()
+ const auto hadWriter = transients->hasWriter(entry);
bool inSync = false;
const bool found = anchorToCache(entry, inSync);
if (found && !inSync)
throw TextException("transients entry missing ENTRY_REQUIRES_COLLAPSING", Here());
}
- if (!transients->hasWriter(entry)) {
+ if (!hadWriter) {
// prevent others from falling into the same trap
throw TextException("unattached transients entry missing writer", Here());
}