]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
Merging async-call branch changes to HEAD:
authorrousskov <>
Wed, 13 Feb 2008 06:40:02 +0000 (06:40 +0000)
committerrousskov <>
Wed, 13 Feb 2008 06:40:02 +0000 (06:40 +0000)
Async-call work replaces event-based asynchronous calls with
stand-alone implementation. The common async call API allows Squid
core do call, debug, and troubleshoot all callback handlers in a
uniform way.

An async "job" API is introduced to manage independent logical threads
or work such as protocol transaction handlers on client, server, and
ICAP sides. These jobs should communicate with each other using async
calls to minimize dependencies and avoid reentrant callback loops.

These changes will eventually improve overall code quality, debugging
quality, and Squid robustness.

The code is still more complex than I want it to be. However, these are
low-level interfaces that most developers will not have to deal with and
the complexity will be significantly reduced if we get rid of old style
function-pointer based callbacks, which is probably desirable for several
reasons. Other simplifications will be found in time as well.

TODO: src/ICAP/AsyncJob needs to be moved to src/. It is not moved yet to
preserve commit logs.

src/AsyncCall.cc
src/AsyncCall.h
src/ICAP/AsyncJob.cc
src/ICAP/AsyncJob.h

index 32ef8560a3575985473765cfabb1d368edf427e6..0e00569d9aeacaf734f9a4b4402e6c0d59a521a8 100644 (file)
@@ -1,47 +1,81 @@
+/*
+ * $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
+
index a731ae905b85c28d924e7102dc289ea2c24176c6..12f077bcd37f27d2445d586af56fe6c11927a323 100644 (file)
@@ -1,34 +1,5 @@
-
 /*
- * $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
@@ -36,6 +7,7 @@
 
 //#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 */
index e59badb2643a2ecc72d2fdebcba12b3b3125e141..eff9d0d9c59e04159d35220e65421c9ac6cc94e6 100644 (file)
@@ -4,19 +4,24 @@
 
 #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()
@@ -25,21 +30,46 @@ 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;
@@ -60,27 +90,35 @@ bool AsyncJob::doneAll() const
     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");
 }
@@ -88,26 +126,85 @@ void AsyncJob::callException(const TextException &e)
 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
+}
index 7fe84f8c3bc48552cb612f41c04764de2b10ddc5..e66948465ed922ed7836701aab0b98797eba9cfd 100644 (file)
@@ -1,39 +1,13 @@
-
-/*
- * $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"
 
 /*
@@ -65,51 +39,89 @@ public:
     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 */