Fix detection of concurrent ACLChecklist checks, avoiding !accessList asserts. Concurrent checks are not supported, but it is possible for the same ACLChecklist to be used for a sequence of checks, alternating fastCheck(void) and fastCheck(list) calls. We needed a different/dedicated mechanism to detect check concurrency (added ACLChecklist::occupied_), and we needed to preserve (and then restore) pre-set accessList during fastCheck(list) checks. === modified file 'src/acl/Checklist.cc' --- src/acl/Checklist.cc 2013-06-01 10:01:13 +0000 +++ src/acl/Checklist.cc 2013-06-07 16:47:47 +0000 @@ -43,40 +43,45 @@ calcImplicitAnswer(); cbdataReferenceDone(accessList); checkCallback(currentAnswer()); } void ACLChecklist::markFinished(const allow_t &finalAnswer, const char *reason) { assert (!finished() && !asyncInProgress()); finished_ = true; allow_ = finalAnswer; debugs(28, 3, HERE << this << " answer " << allow_ << " for " << reason); } /// Called first (and once) by all checks to initialize their state void ACLChecklist::preCheck(const char *what) { debugs(28, 3, HERE << this << " checking " << what); + + // concurrent checks using the same Checklist are not supported + assert(!occupied_); + occupied_ = true; + AclMatchedName = NULL; finished_ = false; } bool ACLChecklist::matchChild(const Acl::InnerNode *current, Acl::Nodes::const_iterator pos, const ACL *child) { assert(current && child); // Remember the current tree location to prevent "async loop" cases where // the same child node wants to go async more than once. matchLoc_ = Breadcrumb(current, pos); // if there are any breadcrumbs left, then follow them on the way down bool result = false; if (matchPath.empty()) { result = child->matches(this); } else { const Breadcrumb top(matchPath.top()); assert(child == top.parent); @@ -131,48 +136,52 @@ // yes, we must pause until the async callback calls resumeNonBlockingCheck asyncStage_ = asyncRunning; return true; } // ACLFilledChecklist overwrites this to unclock something before we // "delete this" void ACLChecklist::checkCallback(allow_t answer) { ACLCB *callback_; void *cbdata_; debugs(28, 3, "ACLChecklist::checkCallback: " << this << " answer=" << answer); callback_ = callback; callback = NULL; if (cbdataReferenceValidDone(callback_data, &cbdata_)) callback_(answer, cbdata_); + // not really meaningful just before delete, but here for completeness sake + occupied_ = false; + delete this; } ACLChecklist::ACLChecklist() : accessList (NULL), callback (NULL), callback_data (NULL), asyncCaller_(false), + occupied_(false), finished_(false), allow_(ACCESS_DENIED), asyncStage_(asyncNone), state_(NullState::Instance()) { } ACLChecklist::~ACLChecklist() { assert (!asyncInProgress()); cbdataReferenceDone(accessList); debugs(28, 4, "ACLChecklist::~ACLChecklist: destroyed " << this); } ACLChecklist::NullState * ACLChecklist::NullState::Instance() { return &_instance; @@ -270,87 +279,91 @@ if (matchPath.empty()) { result = accessList->matches(this); } else { const Breadcrumb top(matchPath.top()); matchPath.pop(); result = top.parent->resumeMatchingAt(this, top.position); } if (result) // the entire tree matched markFinished(accessList->winningAction(), "match"); } allow_t const & ACLChecklist::fastCheck(const Acl::Tree * list) { PROF_start(aclCheckFast); preCheck("fast ACLs"); asyncCaller_ = false; - // This call is not compatible with a pre-set accessList because we cannot - // tell whether this Checklist is used by some other concurent call, which - // is not supported. - assert(!accessList); + // Concurrent checks are not supported, but sequential checks are, and they + // may use a mixture of fastCheck(void) and fastCheck(list) calls. + const Acl::Tree * const savedList = accessList; + accessList = cbdataReference(list); // assume DENY/ALLOW on mis/matches due to action-free accessList // matchAndFinish() takes care of the ALLOW case if (accessList && cbdataReferenceValid(accessList)) matchAndFinish(); // calls markFinished() on success if (!finished()) markFinished(ACCESS_DENIED, "ACLs failed to match"); cbdataReferenceDone(accessList); + accessList = savedList; + occupied_ = false; PROF_stop(aclCheckFast); return currentAnswer(); } /* Warning: do not cbdata lock this here - it * may be static or on the stack */ allow_t const & ACLChecklist::fastCheck() { PROF_start(aclCheckFast); preCheck("fast rules"); asyncCaller_ = false; debugs(28, 5, "aclCheckFast: list: " << accessList); const Acl::Tree *acl = cbdataReference(accessList); if (acl != NULL && cbdataReferenceValid(acl)) { matchAndFinish(); // calls markFinished() on success // if finished (on a match or in exceptional cases), stop if (finished()) { cbdataReferenceDone(acl); + occupied_ = false; PROF_stop(aclCheckFast); return currentAnswer(); } // fall through for mismatch handling } // There were no rules to match or no rules matched calcImplicitAnswer(); cbdataReferenceDone(acl); + occupied_ = false; PROF_stop(aclCheckFast); return currentAnswer(); } /// When no rules matched, the answer is the inversion of the last rule /// action (or ACCESS_DUNNO if the reversal is not possible). void ACLChecklist::calcImplicitAnswer() { const allow_t lastAction = (accessList && cbdataReferenceValid(accessList)) ? accessList->lastAction() : allow_t(ACCESS_DUNNO); allow_t implicitRuleAnswer = ACCESS_DUNNO; if (lastAction == ACCESS_DENIED) // reverse last seen "deny" implicitRuleAnswer = ACCESS_ALLOWED; else if (lastAction == ACCESS_ALLOWED) // reverse last seen "allow" implicitRuleAnswer = ACCESS_DENIED; // else we saw no rules and will respond with ACCESS_DUNNO debugs(28, 3, HERE << this << " NO match found, last action " << === modified file 'src/acl/Checklist.h' --- src/acl/Checklist.h 2013-05-28 14:28:15 +0000 +++ src/acl/Checklist.h 2013-06-07 16:47:47 +0000 @@ -207,36 +207,37 @@ Breadcrumb(): parent(NULL) {} Breadcrumb(const Acl::InnerNode *aParent, Acl::Nodes::const_iterator aPos): parent(aParent), position(aPos) {} bool operator ==(const Breadcrumb &b) const { return parent == b.parent && (!parent || position == b.position); } bool operator !=(const Breadcrumb &b) const { return !this->operator ==(b); } void clear() { parent = NULL; } const Acl::InnerNode *parent; ///< intermediate node in the ACL tree Acl::Nodes::const_iterator position; ///< child position inside parent }; /// possible outcomes when trying to match a single ACL node in a list typedef enum { nmrMatch, nmrMismatch, nmrFinished, nmrNeedsAsync } NodeMatchingResult; /// prepare for checking ACLs; called once per check void preCheck(const char *what); bool prepNonBlocking(); void completeNonBlocking(); void calcImplicitAnswer(); bool asyncCaller_; ///< whether the caller supports async/slow ACLs + bool occupied_; ///< whether a check (fast or non-blocking) is in progress bool finished_; allow_t allow_; enum AsyncStage { asyncNone, asyncStarting, asyncRunning, asyncFailed }; AsyncStage asyncStage_; AsyncState *state_; Breadcrumb matchLoc_; ///< location of the node running matches() now Breadcrumb asyncLoc_; ///< currentNode_ that called goAsync() bool callerGone(); /// suspended (due to an async lookup) matches() in the ACL tree std::stack matchPath; }; #endif /* SQUID_ACLCHECKLIST_H */