+/*
+ * $Id: AsyncCall.cc,v 1.3 2008/02/12 23:40:02 rousskov Exp $
+ */
+
#include "squid.h"
#include "AsyncCall.h"
+#include "AsyncCallQueue.h"
+#include "cbdata.h"
+
+unsigned int AsyncCall::TheLastId = 0;
+
+
+/* AsyncCall */
-void scheduleAsyncCall(int debugSection, int debugLevel,
- const char *fileName, int fileLine, void *objectPtr, const char *callName,
- EVH *wrapper, bool cbdataProtected)
+AsyncCall::AsyncCall(int aDebugSection, int aDebugLevel,
+ const char *aName): name(aName), debugSection(aDebugSection),
+ debugLevel(aDebugLevel), id(++TheLastId), theNext(0), isCanceled(NULL)
{
- debugs(debugSection, debugLevel, fileName << "(" << fileLine <<
- ") will call " << callName << '(' << objectPtr << ')');
- eventAdd(callName, wrapper, objectPtr, 0.0, 0, cbdataProtected);
+ debugs(debugSection, debugLevel, "The AsyncCall " << name << " constructed, this=" << this <<
+ " [call" << id << ']');
}
-bool enterAsyncCallWrapper(int debugSection, int debugLevel,
- void *objectPtr, const char *className, const char *methodName)
+AsyncCall::~AsyncCall()
{
- assert(objectPtr);
- debugs(debugSection, debugLevel, "entering " << className << "::" <<
- methodName << '(' << objectPtr << ')');
- return true;
+ assert(!theNext); // AsyncCallQueue must clean
}
-void exitAsyncCallWrapper(int debugSection, int debugLevel,
- void *objectPtr, const char *className, const char *methodName)
+void
+AsyncCall::make()
{
- debugs(debugSection, debugLevel, "exiting " << className << "::" <<
- methodName << '(' << objectPtr << ')');
-}
+ debugs(debugSection, debugLevel, HERE << "make call " << name <<
+ " [call"<< id << ']');
+ if (canFire()) {
+ fire();
+ return;
+ }
+ if (!isCanceled) // we did not cancel() when returning false from canFire()
+ isCanceled = "unknown reason";
-#if USAGE_SKETCH
+ debugs(debugSection, debugLevel, HERE << "will not call " << name <<
+ " [call"<< id << ']' << " because of " << isCanceled);
+}
-class TestClass {
- public:
- virtual ~TestClass();
+bool
+AsyncCall::cancel(const char *reason)
+{
+ if (isCanceled)
+ debugs(debugSection, debugLevel, HERE << "will not call " << name <<
+ " [call"<< id << ']' << " also because " << reason);
+ isCanceled = reason;
+ return false;
+}
- virtual void testMethod(); // does not have to be virtual
- AsyncCallWrapper(0,0, TestClass, testMethod) // define a wrapper
+bool
+AsyncCall::canFire()
+{
+ return !isCanceled;
+}
- private:
- CBDATA_CLASS2(TestClass);
-};
+// TODO: make this method const by providing a const getDialer()
+void
+AsyncCall::print(std::ostream &os)
+{
+ os << name;
+ if (const CallDialer *dialer = getDialer())
+ dialer->print(os);
+ else
+ os << "(?" << this << "?)";
+}
-void testCase(TestClass *c) {
- AsyncCall(0,0, &c, TestClass::testMethod); // make an async call to c
+bool
+ScheduleCall(const char *fileName, int fileLine, AsyncCall::Pointer &call)
+{
+ debugs(call->debugSection, call->debugLevel, fileName << "(" << fileLine <<
+ ") will call " << *call << " [call"<< call->id << ']' );
+ AsyncCallQueue::Instance().schedule(call);
+ return true;
}
-#endif
+
-
/*
- * $Id: AsyncCall.h,v 1.2 2007/05/08 16:15:50 rousskov Exp $
- *
- *
- * SQUID Web Proxy Cache http://www.squid-cache.org/
- * ----------------------------------------------------------
- *
- * Squid is the result of efforts by numerous individuals from
- * the Internet community; see the CONTRIBUTORS file for full
- * details. Many organizations have provided support for Squid's
- * development; see the SPONSORS file for full details. Squid is
- * Copyrighted (C) 2001 by the Regents of the University of
- * California; see the COPYRIGHT file for full details. Squid
- * incorporates software developed and/or copyrighted by other
- * sources; see the CREDITS file for full details.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
- *
+ * $Id: AsyncCall.h,v 1.3 2008/02/12 23:40:02 rousskov Exp $
*/
#ifndef SQUID_ASYNCCALL_H
//#include "cbdata.h"
#include "event.h"
+//#include "TextException.h"
// A call is asynchronous if the caller proceeds after the call is made,
// and the callee receives the call during the next main loop iteration.
// method calls, but they give you a uniform interface and handy call
// debugging.
-// See AsyncCall.cc for a usage sketch.
-
-
-// Make an asynchronous object->callName() call.
-#define AsyncCall(debugSection, debugLevel, objectPtr, callName) \
- scheduleAsyncCall((debugSection), (debugLevel), __FILE__, __LINE__, \
- (objectPtr), #callName, \
- &(callName ## Wrapper))
-
-// Declare and define a wrapper for an asynchronous call handler method
-#define AsyncCallWrapper(debugSection, debugLevel, ClassName, callName) \
-static \
-void callName ## Wrapper(void *data) { \
- ClassName *objectPtr = static_cast<ClassName*>(data); \
- if (enterAsyncCallWrapper((debugSection), (debugLevel), data, #ClassName, #callName)) { \
- objectPtr->callName(); \
- exitAsyncCallWrapper((debugSection), (debugLevel), data, #ClassName, #callName); \
- } \
+class CallDialer;
+class AsyncCallQueue;
+
+// TODO: add unique call IDs
+// TODO: CBDATA_CLASS2 kids
+class AsyncCall: public RefCountable
+{
+public:
+ typedef RefCount <AsyncCall> Pointer;
+ friend class AsyncCallQueue;
+
+ AsyncCall(int aDebugSection, int aDebugLevel, const char *aName);
+ virtual ~AsyncCall();
+
+ void make(); // fire if we can; handles general call debugging
+
+ // can be called from canFire() for debugging; always returns false
+ bool cancel(const char *reason);
+
+ bool canceled() { return isCanceled != NULL; }
+
+ virtual CallDialer *getDialer() = 0;
+
+ void print(std::ostream &os);
+
+ void setNext(AsyncCall::Pointer aNext) {
+ theNext = aNext;
+ }
+
+ AsyncCall::Pointer &Next() {
+ return theNext;
+ }
+
+public:
+ const char *const name;
+ const int debugSection;
+ const int debugLevel;
+ const unsigned int id;
+
+protected:
+ virtual bool canFire();
+
+ virtual void fire() = 0;
+
+ AsyncCall::Pointer theNext; // used exclusively by AsyncCallQueue
+
+private:
+ const char *isCanceled; // set to the cancelation reason by cancel()
+ static unsigned int TheLastId;
+};
+
+inline
+std::ostream &operator <<(std::ostream &os, AsyncCall &call)
+{
+ call.print(os);
+ return os;
}
+// Interface for all async call dialers
+class CallDialer
+{
+public:
+ CallDialer() {}
+ virtual ~CallDialer() {}
+
+ // TODO: Add these for clarity when CommCbFunPtrCallT is gone
+ //virtual bool canDial(AsyncCall &call) = 0;
+ //virtual void dial(AsyncCall &call) = 0;
+
+ virtual void print(std::ostream &os) const = 0;
+};
+
+// This template implements an AsyncCall using a specified Dialer class
+template <class Dialer>
+class AsyncCallT: public AsyncCall
+{
+public:
+ AsyncCallT(int aDebugSection, int aDebugLevel, const char *aName,
+ const Dialer &aDialer): AsyncCall(aDebugSection, aDebugLevel, aName),
+ dialer(aDialer) {}
+
+ CallDialer *getDialer() { return &dialer; }
+
+protected:
+ virtual bool canFire() { return AsyncCall::canFire() &&
+ dialer.canDial(*this); }
+ virtual void fire() { dialer.dial(*this); }
+
+ Dialer dialer;
+};
+
+template <class Dialer>
+inline
+AsyncCall *
+asyncCall(int aDebugSection, int aDebugLevel, const char *aName,
+ const Dialer &aDialer)
+{
+ return new AsyncCallT<Dialer>(aDebugSection, aDebugLevel, aName, aDialer);
+}
-// Hint: to customize debugging of asynchronous messages in a class, provide
-// class method called scheduleAsyncCall, enterAsyncCallWrapper, and/or
-// exitAsyncCallWrapper. Class method will take priority over these globals.
-
-extern void scheduleAsyncCall(int debugSection, int debugLevel,
- const char *fileName, int fileLine, void *objectPtr, const char *callName,
- EVH *wrapper, bool cbdataProtected = true);
-
-extern bool enterAsyncCallWrapper(int debugSection, int debugLevel,
- void *objectPtr, const char *className, const char *methodName);
-
-extern void exitAsyncCallWrapper(int debugSection, int debugLevel,
- void *objectPtr, const char *className, const char *methodName);
+/* Call scheduling helpers. Use ScheduleCallHere if you can. */
+extern bool ScheduleCall(const char *fileName, int fileLine, AsyncCall::Pointer &call);
+#define ScheduleCallHere(call) ScheduleCall(__FILE__, __LINE__, (call))
#endif /* SQUID_ASYNCCALL_H */
#include "squid.h"
#include "cbdata.h"
+#include "MemBuf.h"
#include "TextException.h"
#include "AsyncJob.h"
+#include "AsyncCall.h"
+unsigned int AsyncJob::TheLastId = 0;
+
AsyncJob *AsyncJob::AsyncStart(AsyncJob *job) {
assert(job);
- job = cbdataReference(job); // unlocked when done() in callEnd()
- AsyncCall(93,5, job, AsyncJob::noteStart);
+ CallJobHere(93, 5, job, AsyncJob::noteStart);
return job;
}
-AsyncJob::AsyncJob(const char *aTypeName): typeName(aTypeName), inCall(NULL)
+AsyncJob::AsyncJob(const char *aTypeName): typeName(aTypeName), inCall(NULL), id(++TheLastId)
{
+ debugs(93,3, "AsyncJob of type " << typeName << " constructed, this=" << this <<
+ " [async" << id << ']');
}
AsyncJob::~AsyncJob()
void AsyncJob::noteStart()
{
- AsyncCallEnter(noteStart);
-
start();
-
- AsyncCallExit();
}
void AsyncJob::start()
{
- Must(cbdataReferenceValid(this)); // locked in AsyncStart
+}
+
+// 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 != NULL) {
+ // 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);
+ AsyncCall::Pointer fakeCall = asyncCall(93,4, "FAKE-deleteThis",
+ MemFun(this, &AsyncJob::deleteThis, aReason));
+ inCall = fakeCall;
+ callEnd();
+// delete fakeCall;
}
void AsyncJob::mustStop(const char *aReason)
{
- Must(inCall); // otherwise nobody will delete us if we are done()
+ // 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 != NULL); // otherwise nobody will delete us if we are done()
Must(aReason);
if (!stopReason) {
stopReason = aReason;
return true; // so that it is safe for kids to use
}
-bool AsyncJob::callStart(const char *method)
+bool AsyncJob::canBeCalled(AsyncCall &call) const
{
- debugs(93, 5, typeName << "::" << method << " called" << status());
-
- if (inCall) {
- // this may happen when we have bugs or when arguably buggy
- // comm interface calls us while we are closing the connection
- debugs(93, 5, HERE << typeName << "::" << inCall <<
- " is in progress; " << typeName << "::" << method <<
- " cancels reentry.");
- return false;
+ if (inCall != NULL) {
+ // This may happen when we have bugs or some module is not calling
+ // us asynchronously (comm used to do that).
+ debugs(93, 5, HERE << inCall << " is in progress; " <<
+ call << " canot reenter the job.");
+ return call.cancel("reentrant job call");
}
- inCall = method;
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 TextException &e)
{
- debugs(93, 3, typeName << "::" << inCall << " caught an exception: " <<
- e.message << ' ' << status());
+ // 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, typeName << "::" << inCall << " ends job " <<
- status());
+ debugs(93, 5, *inCall << " ends job" << status());
- const char *inCallSaved = inCall;
- const char *typeNameSaved = typeName;
+ AsyncCall::Pointer inCallSaved = inCall;
void *thisSaved = this;
swanSong();
- void *cbdata = this;
delete this; // this is the only place where the object is deleted
- cbdataReferenceDone(cbdata); // locked by AsyncStart
// careful: this object does not exist any more
- debugs(93, 6, HERE << typeNameSaved << "::" << inCallSaved <<
- " ended " << thisSaved);
+ debugs(93, 6, HERE << *inCallSaved << " ended " << thisSaved);
return;
}
- debugs(93, 6, typeName << "::" << inCall << " ended" << status());
+ debugs(inCall->debugSection, inCall->debugLevel,
+ typeName << " status out:" << status());
inCall = NULL;
}
+// returns a temporary string depicting transaction status, for debugging
+const char *AsyncJob::status() const
+{
+ static MemBuf buf;
+ buf.reset();
+
+ buf.append(" [", 2);
+ if (stopReason != NULL){
+ buf.Printf("Stopped, reason:");
+ buf.Printf(stopReason);
+ }
+ buf.Printf(" job%d]", id);
+ buf.terminate();
+
+ return buf.content();
+}
+
+
+/* JobDialer */
+
+JobDialer::JobDialer(AsyncJob *aJob): job(aJob), lock(NULL)
+{
+ lock = cbdataReference(job->toCbdata());
+}
+
+JobDialer::JobDialer(const JobDialer &d): CallDialer(d),
+ job(d.job), lock(d.lock)
+{
+ cbdataReference(lock);
+}
+
+JobDialer::~JobDialer(){
+ cbdataReferenceDone(lock);
+}
+
+
+bool
+JobDialer::canDial(AsyncCall &call)
+{
+ if (!cbdataReferenceValid(lock))
+ return call.cancel("job is gone");
+
+ return job->canBeCalled(call);
+}
+
+void
+JobDialer::dial(AsyncCall &call)
+{
+ assert(cbdataReferenceValid(lock)); // canDial() checks for this
+
+ job->callStart(call);
+
+ try {
+ doDial();
+ }
+ catch (const TextException &e) {
+ debugs(call.debugSection, 3,
+ HERE << call.name << " threw exception: " << e.message);
+ job->callException(e);
+ }
+
+ job->callEnd(); // may delete job
+}
-
-/*
- * $Id: AsyncJob.h,v 1.2 2007/06/19 21:00:11 rousskov Exp $
- *
- *
- * SQUID Web Proxy Cache http://www.squid-cache.org/
- * ----------------------------------------------------------
- *
- * Squid is the result of efforts by numerous individuals from
- * the Internet community; see the CONTRIBUTORS file for full
- * details. Many organizations have provided support for Squid's
- * development; see the SPONSORS file for full details. Squid is
- * Copyrighted (C) 2001 by the Regents of the University of
- * California; see the COPYRIGHT file for full details. Squid
- * incorporates software developed and/or copyrighted by other
- * sources; see the CREDITS file for full details.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
- *
- */
+ /*
+ * $Id: AsyncJob.h,v 1.3 2008/02/12 23:40:02 rousskov Exp $
+ */
#ifndef SQUID_ASYNC_JOB_H
#define SQUID_ASYNC_JOB_H
+// TODO: move src/ICAP/AsyncJob.* to src/
+
+
#include "AsyncCall.h"
/*
AsyncJob(const char *aTypeName);
virtual ~AsyncJob();
+ virtual void *toCbdata() = 0;
void noteStart(); // calls virtual start
- AsyncCallWrapper(93,3, AsyncJob, noteStart);
protected:
+ // XXX: temporary method to replace "delete this" in jobs-in-transition.
+ // Will be replaced with calls to mustStop() when transition is complete.
+ void deleteThis(const char *aReason);
+
void mustStop(const char *aReason); // force done() for a reason
bool done() const; // the job is destroyed in callEnd() when done()
- virtual void start() = 0;
- virtual bool doneAll() const = 0; // return true when done
- virtual void swanSong() = 0; // perform internal cleanup
- virtual const char *status() const = 0; // for debugging
+ virtual void start();
+ virtual bool doneAll() const; // return true when done
+ virtual void swanSong() {}; // perform internal cleanup
+ virtual const char *status() const; // for debugging
+public:
// asynchronous call maintenance
- bool callStart(const char *methodName);
+ bool canBeCalled(AsyncCall &call) const;
+ void callStart(AsyncCall &call);
virtual void callException(const TextException &e);
virtual void callEnd();
+protected:
const char *stopReason; // reason for forcing done() to be true
const char *typeName; // kid (leaf) class name, for debugging
- const char *inCall; // name of the asynchronous call being executed, if any
+ AsyncCall::Pointer inCall; // the asynchronous call being handled, if any
+ const unsigned int id;
+
+private:
+ static unsigned int TheLastId;
+};
+
+
+/*
+ * This is a base class for all job call dialers. It does all the job
+ * dialing logic (debugging, handling exceptions, etc.) except for calling
+ * the job method. The latter is not possible without templates and we
+ * want to keep this class simple and template-free. Thus, we add a dial()
+ * virtual method that the JobCallT template below will implement for us,
+ * calling the job.
+ */
+class JobDialer: public CallDialer
+{
+public:
+ JobDialer(AsyncJob *aJob);
+ JobDialer(const JobDialer &d);
+ virtual ~JobDialer();
+
+ virtual bool canDial(AsyncCall &call);
+ void dial(AsyncCall &call);
+
+ AsyncJob *job;
+ void *lock; // job's cbdata
+
+protected:
+ virtual void doDial() = 0; // actually calls the job method
+
+private:
+ // not implemented and should not be needed
+ JobDialer &operator =(const JobDialer &);
};
+#include "AsyncJobCalls.h"
+
+template <class Dialer>
+bool
+CallJob(int debugSection, int debugLevel, const char *fileName, int fileLine,
+ const char *callName, const Dialer &dialer)
+{
+ AsyncCall::Pointer call = asyncCall(debugSection, debugLevel, callName, dialer);
+ return ScheduleCall(fileName, fileLine, call);
+}
+
+
+#define CallJobHere(debugSection, debugLevel, job, method) \
+ CallJob((debugSection), (debugLevel), __FILE__, __LINE__, #method, \
+ MemFun((job), &method))
-// call guards for all "asynchronous" note*() methods
-// TODO: Move to core.
-
-// asynchronous call entry:
-// - open the try clause;
-// - call callStart().
-#define AsyncCallEnter(method) \
- try { \
- if (!callStart(#method)) \
- return;
-
-// asynchronous call exit:
-// - close the try clause;
-// - catch exceptions;
-// - let callEnd() handle transaction termination conditions
-#define AsyncCallExit() \
- } \
- catch (const TextException &e) { \
- callException(e); \
- } \
- callEnd();
+#define CallJobHere1(debugSection, debugLevel, job, method, arg1) \
+ CallJob((debugSection), (debugLevel), __FILE__, __LINE__, #method, \
+ MemFun((job), &method, (arg1)))
#endif /* SQUID_ASYNC_JOB_H */