/* * Copyright (C) 1996-2023 The Squid Software Foundation and contributors * * Squid software is distributed under GPLv2+ license and includes * contributions from numerous individuals and organizations. * Please see the COPYING and CONTRIBUTORS files for details. */ /* DEBUG: section 93 ICAP (RFC 3507) Client */ #include "squid.h" #include "base/AsyncCall.h" #include "base/AsyncJob.h" #include "base/AsyncJobCalls.h" #include "base/PackableStream.h" #include "base/TextException.h" #include "cbdata.h" #include "mem/PoolingAllocator.h" #include "MemBuf.h" #include "mgr/Registration.h" #include "Store.h" #include #include InstanceIdDefinitions(AsyncJob, "job"); /// a set of all AsyncJob objects in existence static auto & AllJobs() { static const auto jobs = new std::unordered_set, std::equal_to, PoolingAllocator >(); return *jobs; } void AsyncJob::Start(const Pointer &job) { CallJobHere(93, 5, job, AsyncJob, start); job->started_ = true; // it is the attempt that counts } AsyncJob::AsyncJob(const char *aTypeName) : stopReason(nullptr), typeName(aTypeName), inCall(nullptr) { debugs(93,5, "AsyncJob constructed, this=" << this << " type=" << typeName << " [" << id << ']'); AllJobs().insert(this); } AsyncJob::~AsyncJob() { debugs(93,5, "AsyncJob destructed, this=" << this << " type=" << typeName << " [" << id << ']'); assert(!started_ || swanSang_); AllJobs().erase(this); } void AsyncJob::start() { } // XXX: temporary code to replace calls to "delete this" in jobs-in-transition. // Will be replaced with calls to mustStop() when transition is complete. void AsyncJob::deleteThis(const char *aReason) { Must(aReason); stopReason = aReason; if (inCall != nullptr) { // if we are in-call, then the call wrapper will delete us debugs(93, 4, typeName << " will NOT delete in-call job, reason: " << stopReason); return; } // there is no call wrapper waiting for our return, so we fake it debugs(93, 5, typeName << " will delete this, reason: " << stopReason); CbcPointer self(this); AsyncCall::Pointer fakeCall = asyncCall(93,4, "FAKE-deleteThis", JobMemFun(self, &AsyncJob::deleteThis, aReason)); inCall = fakeCall; callEnd(); // delete fakeCall; } void AsyncJob::mustStop(const char *aReason) { // XXX: temporary code to catch cases where mustStop is called outside // of an async call context. Will be removed when that becomes impossible. // Until then, this will cause memory leaks and possibly other problems. if (!inCall) { stopReason = aReason; debugs(93, 5, typeName << " will STALL, reason: " << stopReason); return; } Must(inCall != nullptr); // otherwise nobody will delete us if we are done() Must(aReason); if (!stopReason) { stopReason = aReason; debugs(93, 5, typeName << " will stop, reason: " << stopReason); } else { debugs(93, 5, typeName << " will stop, another reason: " << aReason); } } bool AsyncJob::done() const { // stopReason, set in mustStop(), overwrites all other conditions return stopReason != nullptr || doneAll(); } bool AsyncJob::doneAll() const { return true; // so that it is safe for kids to use } bool AsyncJob::canBeCalled(AsyncCall &call) const { if (inCall != nullptr) { // This may happen when we have bugs or some module is not calling // us asynchronously (comm used to do that). debugs(93, 5, inCall << " is in progress; " << call << " cannot reenter the job."); return call.cancel("reentrant job call"); } return true; } void AsyncJob::callStart(AsyncCall &call) { // we must be called asynchronously and hence, the caller must lock us Must(cbdataReferenceValid(toCbdata())); Must(!inCall); // see AsyncJob::canBeCalled inCall = &call; // XXX: ugly, but safe if callStart/callEnd,Ex are paired debugs(inCall->debugSection, inCall->debugLevel, typeName << " status in:" << status()); } void AsyncJob::callException(const std::exception &ex) { debugs(93, 2, ex.what()); // we must be called asynchronously and hence, the caller must lock us Must(cbdataReferenceValid(toCbdata())); mustStop("exception"); } void AsyncJob::callEnd() { if (done()) { debugs(93, 5, *inCall << " ends job" << status()); AsyncCall::Pointer inCallSaved = inCall; void *thisSaved = this; // TODO: Swallow swanSong() exceptions to reduce memory leaks. // Job callback invariant: swanSong() is (only) called for started jobs. // Here to detect violations in kids that forgot to call our swanSong(). assert(started_); swanSang_ = true; // it is the attempt that counts swanSong(); delete this; // this is the only place where a started job is deleted // careful: this object does not exist any more debugs(93, 6, *inCallSaved << " ended " << thisSaved); return; } debugs(inCall->debugSection, inCall->debugLevel, typeName << " status out:" << status()); inCall = nullptr; } // returns a temporary string depicting transaction status, for debugging const char *AsyncJob::status() const { static MemBuf buf; buf.reset(); buf.append(" [", 2); if (stopReason != nullptr) { buf.appendf("Stopped, reason:%s", stopReason); } buf.appendf(" %s%u]", id.prefix(), id.value); buf.terminate(); return buf.content(); } void AsyncJob::ReportAllJobs(StoreEntry *e) { PackableStream os(*e); // this loop uses YAML syntax, but AsyncJob::status() still needs to be adjusted to use YAML const char *indent = " "; for (const auto job: AllJobs()) { os << indent << job->id << ":\n"; os << indent << indent << "type: '" << job->typeName << "'\n"; os << indent << indent << "status:" << job->status() << '\n'; if (!job->started_) os << indent << indent << "started: false\n"; if (job->stopReason) os << indent << indent << "stopped: '" << job->stopReason << "'\n"; } } void AsyncJob::RegisterWithCacheManager() { Mgr::RegisterAction("jobs", "All AsyncJob objects", &AsyncJob::ReportAllJobs, 0, 1); }