--------------------- PatchSet 6237 Date: 2007/12/05 04:40:09 Author: rousskov Branch: async-calls Tag: (none) Log: Revised and polished AsyncCall APIs. Finalized two kinds of async calls: Old style calls using function pointers and new calls to member functions (methods). The latter are further restricted to children of AsyncJob, to simplify support. AsyncCall class hierarchy does not know the difference between async call kinds. Instead, AsyncCall kids are parametrized with "dialer" objects. The dialer implements one of the two call kinds. Dialers also debug call parameters because only they know about call parameters. Exception handling and job maintenance is impossible for old style function pointer calls. Thus, their dialers simply call the function they are configured to call. Job dialers support callStart/handleException/callEnd logic now. AsyncCall now supports canFire/fire logic which allows specific calls to be canceled if their preconditions are not met. For example, old style function-pointer calls are not fired if the cbdata-protected data pointer becomes invalid. TODO: Review file names and especially locations. I did not want to move Job-related code from ICAP/ in hope to reduce the number of future conflicts when merging with other v3.1 branches. The files should probably be moved before merging this branch to HEAD. Members: src/AsyncCall.cc:1.3.22.8->1.3.22.9 src/AsyncCall.h:1.3.22.15->1.3.22.16 src/AsyncJobCalls.h:1.1->1.1.2.1 src/Makefile.am:1.131.4.1->1.131.4.2 Index: squid3/src/AsyncCall.cc =================================================================== RCS file: /cvsroot/squid-sf//squid3/src/AsyncCall.cc,v retrieving revision 1.3.22.8 retrieving revision 1.3.22.9 diff -u -r1.3.22.8 -r1.3.22.9 --- squid3/src/AsyncCall.cc 29 Nov 2007 20:39:01 -0000 1.3.22.8 +++ squid3/src/AsyncCall.cc 5 Dec 2007 04:40:09 -0000 1.3.22.9 @@ -1,152 +1,82 @@ +/* + * $Id: AsyncCall.cc,v 1.3.22.9 2007/12/05 04:40:09 rousskov Exp $ + */ + #include "squid.h" #include "AsyncCall.h" #include "cbdata.h" -void scheduleAsyncCall(int debugSection, int debugLevel, - const char *fileName, int fileLine, void *objectPtr, const char *callName, - EVH *wrapper, bool cbdataProtected) -{ - debugs(debugSection, debugLevel, fileName << "(" << fileLine << - ") will call " << callName << '(' << objectPtr << ')'); - eventAdd(callName, wrapper, objectPtr, 0.0, 0, cbdataProtected); +// This static method will be moved to the global async call loop +void AsyncCall::FireWrapper(void *data) { + AsyncCall *call = static_cast(data); + debugs(call->debugSection, call->debugLevel, "entering " << *call); + call->make(); + debugs(call->debugSection, call->debugLevel, "leaving " << *call); + delete call; } -bool enterAsyncCallWrapper(int debugSection, int debugLevel, - void *objectPtr, const char *className, const char *methodName) -{ - assert(objectPtr); - debugs(debugSection, debugLevel, "entering " << className << "::" << - methodName << '(' << objectPtr << ')'); - return true; -} -void exitAsyncCallWrapper(int debugSection, int debugLevel, - void *objectPtr, const char *className, const char *methodName) -{ - debugs(debugSection, debugLevel, "exiting " << className << "::" << - methodName << '(' << objectPtr << ')'); -} +/* AsyncCall */ -JobCall::JobCall(int debugSection, int debugLevel, const char *callName, AsyncJob *aJob): - AsyncCall(debugSection, debugLevel, callName) +AsyncCall::AsyncCall(int aDebugSection, int aDebugLevel, + const char *aName): name(aName), debugSection(aDebugSection), + debugLevel(aDebugLevel), isCanceled(NULL) { - theJob = aJob; - theCbdata = aJob->toCbdata(); - cbdataReference(theCbdata); } -JobCall::~JobCall(){ - cbdataReferenceDone(theCbdata); +AsyncCall::~AsyncCall() +{ } - -bool JobCall::fire() -{ - AsyncJob *theJob = job(); - - if(!cbdataReferenceValid(theCbdata)) - return false; - - if (!theJob->callStart(name)) { - debugs(debugSection, debugLevel, HERE << "job call "<< name <<" NOT fire " << this); - return false; +void +AsyncCall::make() +{ + if (canFire()) { + fire(); + return; } - debugs(debugSection, debugLevel, HERE << "job call " << name << " fire " << this); - callJob(); - debugs(debugSection, debugLevel, HERE << name << " fired"); - return true; -} + if (!isCanceled) // we did not cancel() when returning false from canFire() + isCanceled = "unknown reason"; -void JobCall::handleException(const TextException &e) -{ - debugs(debugSection, debugLevel, HERE << "job call exception"); - AsyncJob *theJob = job(); - theJob->callException(e); + debugs(debugSection, debugLevel, HERE << "will not call " << name << + " because of " << isCanceled); } -void JobCall::end() -{ - debugs(debugSection, debugLevel, HERE << "job call end"); - - AsyncJob *theJob = job(); - if(!cbdataReferenceValid(theCbdata)) - return; - theJob->callEnd(); -} - - -#if USAGE_SKETCH - -class TestClass { - public: - virtual ~TestClass(); - - virtual void testMethod(); // does not have to be virtual - AsyncCallWrapper(0,0, TestClass, testMethod) // define a wrapper - - private: - CBDATA_CLASS2(TestClass); -}; - -void testCase(TestClass *c) { - AsyncCall(0,0, &c, TestClass::testMethod); // make an async call to c -} - -// This is a class that mimics some job class. -// Note that it is not inherited from AsyncJob yet. It will be once JobCall -// methods are implemented and start casting AsyncCall::theObject -// to AsyncJob to call well-known job API methods such as swanSong. -struct Tester: virtual public AsyncJob { - Tester():AsyncJob("Tester"){} - virtual ~Tester() {} - - virtual bool doneAll() const{return true;}; // return true when done - - virtual void method0() { debugs(0,0, HERE << "METHOD0()"); } - virtual void method1(int arg1) { debugs(0,0, HERE << "METHOD1(" << arg1 << ")"); } - CBDATA_CLASS2(Tester); -}; -CBDATA_CLASS_INIT(Tester); - -// Set to false to test immediate call firing from test cases. No calls -// will be fired without submitting an asynchronous event in real code. -extern const bool scheduleAsyncCalls; -const bool scheduleAsyncCalls = true; - -// test a created call (sychronously or asynchronously) -void do_test_call(AsyncCall *call) +bool +AsyncCall::cancel(const char *reason) { - if (!scheduleAsyncCalls) { - call->fire(); - debugs(0,0, HERE << "sync-fired"); - delete call; - } else { - ScheduleAsyncCall(0, 0, call, AsyncCall::fire); // TODO:be more specific - debugs(0,0, HERE << "scheduled"); - } + if (isCanceled) + debugs(debugSection, debugLevel, HERE << "will not call " << name << + " also because " << reason); + isCanceled = reason; + return false; } -// do one test with a argument-less member function. -void do_test_arg0() +bool +AsyncCall::canFire() { - Tester *tester = new Tester; // leaking - CallJobHere(0,0,tester,Tester::method0); - return; + return !isCanceled; } -// do one test with a member function having one int argument. -void do_test_arg1() +// TODO: make this method const by providing a const getDialer() +void +AsyncCall::print(std::ostream &os) { - Tester *tester = new Tester; // leaking - CallJobHere1(0,0,tester,Tester::method1,5); - return; + os << name; + if (const CallDialer *dialer = getDialer()) + dialer->print(os); + else + os << "(?" << this << "?)"; } -void do_testAsyncCalls() +bool +ScheduleCall(const char *fileName, int fileLine, AsyncCall *call) { - do_test_arg0(); - do_test_arg1(); + debugs(call->debugSection, call->debugLevel, fileName << "(" << fileLine << + ") will call " << *call); + eventAdd(call->name, &(AsyncCall::FireWrapper), call, 0.0, 0, false); + return true; } -#endif + Index: squid3/src/AsyncCall.h =================================================================== RCS file: /cvsroot/squid-sf//squid3/src/AsyncCall.h,v retrieving revision 1.3.22.15 retrieving revision 1.3.22.16 diff -u -r1.3.22.15 -r1.3.22.16 --- squid3/src/AsyncCall.h 29 Nov 2007 20:39:01 -0000 1.3.22.15 +++ squid3/src/AsyncCall.h 5 Dec 2007 04:40:09 -0000 1.3.22.16 @@ -1,34 +1,6 @@ /* - * $Id: AsyncCall.h,v 1.3.22.15 2007/11/29 20:39:01 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.22.16 2007/12/05 04:40:09 rousskov Exp $ */ #ifndef SQUID_ASYNCCALL_H @@ -36,7 +8,6 @@ #include "cbdata.h" #include "event.h" -#include "ICAP/AsyncJob.h" #include "ICAP/TextException.h" // A call is asynchronous if the caller proceeds after the call is made, @@ -55,219 +26,92 @@ // method calls, but they give you a uniform interface and handy call // debugging. -// See AsyncCall.cc for a usage sketch. +class CallDialer; +// TODO: add unique call IDs +// TODO: CBDATA_CLASS2 kids +class AsyncCall +{ +public: -// 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); - + static void FireWrapper(void *data); -// Our goal here is to encapsulate details of the call in an object. -// Details may include the object pointer, the handler method pointer, and -// parameters. To simplify, we require all handlers to return void and not -// be constant. + AsyncCall(int aDebugSection, int aDebugLevel, const char *aName); + virtual ~AsyncCall(); + + void make(); // fire if we can; handles general call debugging -// *MemFunT is a member function wrapper. We need one for every supported -// member function arity (i.e., number of handler arguments). The first -// template parameter is the class type of the handler. + // can be called from canFire() for debugging; always returns false + bool cancel(const char *reason); -// Arity names are from http://en.wikipedia.org/wiki/Arity + virtual CallDialer *getDialer() = 0; + void print(std::ostream &os); -template -class NullaryMemFunT -{ public: - typedef void (C::*MemFunPtr)(); - explicit NullaryMemFunT(C *anObject, MemFunPtr aP): - theObject(anObject), theP(aP) {} - - void operator ()() { (theObject->*theP)(); } - C *object() const {return theObject;} + const char *const name; + const int debugSection; + const int debugLevel; -private: - C *theObject; - MemFunPtr theP; -}; +protected: + virtual bool canFire(); -template -class UnaryMemFunT -{ -public: - typedef void (C::*MemFunPtr)(Argument1); - explicit UnaryMemFunT(C *anObject, MemFunPtr aP, const Argument1 &anArg1): - theObject(anObject), theP(aP), theArg1(anArg1) {} - - void operator ()() { (theObject->*theP)(theArg1); } - C *object() const {return theObject;} - Argument1 arg1() const {return theArg1;} + virtual void fire() = 0; private: - C *theObject; - MemFunPtr theP; - Argument1 theArg1; + const char *isCanceled; // set to the cancelation reason by cancel() }; -// Now we add global templated functions that create the member function -// wrappers above. These are for convenience: it is often easier to -// call a templated function than to create a templated object. - -template -NullaryMemFunT MemFun(C *object, - typename NullaryMemFunT::MemFunPtr p) +inline +std::ostream &operator <<(std::ostream &os, AsyncCall &call) { - return NullaryMemFunT(object, p); + call.print(os); + return os; } -template -UnaryMemFunT MemFun(C *object, - typename UnaryMemFunT::MemFunPtr p, Argument1 arg1) -{ - return UnaryMemFunT(object, p, arg1); -} - -// TODO: move up and prevent assignment and copying -class AsyncCall +// Interfaces for all async call dialers +class CallDialer { public: + CallDialer(): call(NULL) {} + virtual ~CallDialer() {} - static void fireWrapper(void *data) { -// debugs(0,0, HERE << "data=" << data); - AsyncCall *call = static_cast(data); - if (enterAsyncCallWrapper(0, 0, data, "AsyncCall", "fire")) { -// debugs(0,0, HERE << "call=" << call); - try{ - call->fire(); - } - catch (const TextException &e) { - call->handleException(e); - } - call->end(); - exitAsyncCallWrapper(0, 0, data, "AsyncCall", "fire"); - } - delete call; - } - - AsyncCall(int aDebugSection, int aDebugLevel, const char *aName): - name(aName), debugSection(aDebugSection), debugLevel(aDebugLevel) {} - virtual ~AsyncCall() {} - - virtual bool fire() = 0; - virtual void handleException(const TextException &e) = 0; - virtual void end() = 0; + virtual void print(std::ostream &os) const = 0; public: - const char *const name; - const int debugSection; - const int debugLevel; + AsyncCall *call; }; -// Same as AsyncCall, but does not assume the object is cbdata-protected. -// Note that the object here is the call object, not the handler object. -// Calls to handlers are done by AsyncCall::fire and do not use the -// old scheduleAsyncCall(). Once the main loop knows about async calls, -// we will not wrap call scheduling in events. -#define ScheduleAsyncCall(debugSection, debugLevel, objectPtr, callName) \ -{ \ - debugs(0,0, HERE << "objectPtr=" << (objectPtr)); \ - scheduleAsyncCall((debugSection), (debugLevel), __FILE__, __LINE__, \ - (objectPtr), #callName, \ - &(callName ## Wrapper), false); \ -} - -// This is a base class for all job calls. It does (well, will do) -// all the job calling logic (debugging, handling exceptions, etc.) except -// for calling the job itself. The latter is not possible without templates -// and we want to keep this class simple and template-free. Thus, we -// add a callJob() virtual method that the JobCallT template below will -// implement for us, calling the job. -//class AsyncJob; -class JobCall: public AsyncCall +// This template implements an AsyncCall using a specified Dialer class +template +class AsyncCallT: public AsyncCall { public: - JobCall(int debugSection, int debugLevel, const char *callName, AsyncJob *aJob); - ~JobCall(); - virtual bool fire(); - virtual void handleException(const TextException &e); - virtual void end(); + AsyncCallT(int aDebugSection, int aDebugLevel, const char *aName, + const Dialer &aDialer): AsyncCall(aDebugSection, aDebugLevel, aName), + dialer(aDialer) { dialer.call = this; } -protected: - virtual void callJob() = 0; - AsyncJob *job() {return theJob;} - AsyncJob *theJob; - void *theCbdata; -}; - -// This template combines member function pointer calling ability of MemFunT -// templates above with the job handling abilities of JobCall. + CallDialer *getDialer() { return &dialer; } -template -class JobCallT: public JobCall -{ -public: - JobCallT(const Dialer &aDialer,int debugSection, int debugLevel, const char *callName): - JobCall(debugSection, debugLevel, callName, aDialer.object()), theDialer(aDialer) {} protected: - virtual void callJob() { debugs(debugSection, debugLevel, HERE << "dialing "<< name); theDialer(); } -//private: - Dialer theDialer; + virtual bool canFire() { return AsyncCall::canFire() && dialer.canDial(); } + virtual void fire() { dialer.dial(); } + + Dialer dialer; }; -// Add a global templated function that create a job call using the -// template above. This is for convenience: it is often easier to call -// a templated function than to create a templated object. -// We need one function for each *MemFunT template. template -JobCall *jobCall(const Dialer &dialer,int debugSection, int debugLevel, const char *callName) +inline +AsyncCall * +asyncCall(int aDebugSection, int aDebugLevel, const char *aName, + const Dialer &aDialer) { - return new JobCallT(dialer, debugSection, debugLevel, callName); -} - -template bool CallJob(int debugSection, int debugLevel, const char *fileName, int fileLine, const char *callName, const Dialer &dialer){ - debugs(debugSection, debugLevel, HERE << "creating call for " << dialer.object()); - AsyncCall *call = jobCall(dialer, debugSection, debugLevel, callName); - debugs(debugSection, debugLevel, HERE << "created"); - - debugs(debugSection, debugLevel, HERE << "objectPtr=" << call); - scheduleAsyncCall(debugSection, debugLevel, fileName, fileLine, - call, callName, - &(AsyncCall::fireWrapper), false); - - debugs(debugSection, debugLevel, HERE << "scheduled"); - return true; + return new AsyncCallT(aDebugSection, aDebugLevel, aName, aDialer); } +/* Call scheduling helpers. Use ScheduleCallHere if you can. */ +extern bool ScheduleCall(const char *fileName, int fileLine, AsyncCall *call); +#define ScheduleCallHere(call) ScheduleCall(__FILE__, __LINE__, (call)) -#define CallJobHere(debugSection, debugLevel, objectPtr, callName) \ - CallJob(debugSection, debugLevel, __FILE__, __LINE__, #callName, MemFun(objectPtr,&callName)) - -#define CallJobHere1(debugSection, debugLevel, objectPtr, callName, arg1) \ - CallJob(debugSection, debugLevel, __FILE__, __LINE__, #callName, MemFun(objectPtr,&callName, arg1)) - -inline bool -ScheduleCall(int debugSection, int debugLevel, const char *fileName, int fileLine, AsyncCall *call){ - debugs(debugSection, debugLevel, HERE << "call=" << call); - scheduleAsyncCall(debugSection, debugLevel, fileName, fileLine, - call, call->name, - &(AsyncCall::fireWrapper), false); - - debugs(debugSection, debugLevel, HERE << "scheduled"); - return true; -} - -#define ScheduleCallHere(call) \ - ScheduleCall((call)->debugSection, (call)->debugLevel, __FILE__, __LINE__, \ - (call)) #endif /* SQUID_ASYNCCALL_H */ --- /dev/null Thu Dec 6 01:20:41 2007 +++ squid3/src/AsyncJobCalls.h Thu Dec 6 01:20:41 2007 @@ -0,0 +1,87 @@ + +/* + * $Id: AsyncJobCalls.h,v 1.1.2.1 2007/12/05 04:40:09 rousskov Exp $ + */ + +#ifndef SQUID_ASYNCJOBCALLS_H +#define SQUID_ASYNCJOBCALLS_H + +#include "ICAP/AsyncJob.h" + +/* + * *MemFunT are member function (i.e., class method) wrappers. They store + * details of a method call in an object so that the call can be delayed + * and executed asynchronously. Details may include the object pointer, + * the handler method pointer, and parameters. To simplify, we require + * all handlers to return void and not be constant. + */ + +/* + * We need one wrapper for every supported member function arity (i.e., + * number of handler arguments). The first template parameter is the class + * type of the handler. That class must be an AsyncJob child. + */ + +// Arity names are from http://en.wikipedia.org/wiki/Arity + +template +class NullaryMemFunT: public JobDialer +{ +public: + typedef void (C::*Method)(); + explicit NullaryMemFunT(C *anObject, Method aMethod): + JobDialer(anObject), object(anObject), method(aMethod) {} + + virtual void print(std::ostream &os) const { os << "()"; } + +public: + C *object; + Method method; + +protected: + virtual void doDial() { (object->*method)(); } +}; + +template +class UnaryMemFunT: public JobDialer +{ +public: + typedef void (C::*Method)(Argument1); + explicit UnaryMemFunT(C *anObject, Method aMethod, const Argument1 &anArg1): + JobDialer(anObject), + object(anObject), method(aMethod), arg1(anArg1) {} + + virtual void print(std::ostream &os) const { os << '(' << arg1 << ')'; } + +public: + C *object; + Method method; + Argument1 arg1; + +protected: + virtual void doDial() { (object->*method)(arg1); } +}; + +// ... add more as needed + + +// Now we add global templated functions that create the member function +// wrappers above. These are for convenience: it is often easier to +// call a templated function than to create a templated object. + +template +NullaryMemFunT +MemFun(C *object, typename NullaryMemFunT::Method method) +{ + return NullaryMemFunT(object, method); +} + +template +UnaryMemFunT +MemFun(C *object, typename UnaryMemFunT::Method method, + Argument1 arg1) +{ + return UnaryMemFunT(object, method, arg1); +} + +#endif /* SQUID_ASYNCJOBCALLS_H */ Index: squid3/src/Makefile.am =================================================================== RCS file: /cvsroot/squid-sf//squid3/src/Makefile.am,v retrieving revision 1.131.4.1 retrieving revision 1.131.4.2 diff -u -r1.131.4.1 -r1.131.4.2 --- squid3/src/Makefile.am 29 Nov 2007 21:00:27 -0000 1.131.4.1 +++ squid3/src/Makefile.am 5 Dec 2007 04:40:09 -0000 1.131.4.2 @@ -1,7 +1,7 @@ # # Makefile for the Squid Object Cache server # -# $Id: Makefile.am,v 1.131.4.1 2007/11/29 21:00:27 rousskov Exp $ +# $Id: Makefile.am,v 1.131.4.2 2007/12/05 04:40:09 rousskov Exp $ # # Uncomment and customize the following to suit your needs: # @@ -415,6 +415,7 @@ asn.cc \ AsyncCall.cc \ AsyncCall.h \ + AsyncJobCalls.h \ AsyncEngine.cc \ AsyncEngine.h \ authenticate.cc \