Support libecap v1.0, allowing asynchronous adapters and eCAP version checks. After these changes, Squid can support eCAP adapters built with libecap v1.0, but stops supporting adapters built with earlier libecap versions (due to API changes). The new libecap version allows Squid to better check the version of the eCAP adapter being loaded as well as the version of the eCAP library being used. This should help with migration to libecap v1.0. Expose [running] main event loop as a global so that modules can add engines. === modified file 'configure.ac' --- configure.ac 2013-07-09 11:15:51 +0000 +++ configure.ac 2013-09-12 23:08:18 +0000 @@ -1035,10 +1035,10 @@ if test -n "$PKG_CONFIG"; then dnl eCAP support requires libecap. - dnl This Squid supports libecap v0.2.x. + dnl This Squid supports libecap v1.0.x. dnl Use EXT_ prefix to distinguish external libecap (that we check for dnl here) from our own convenience ecap library in Makefiles. - PKG_CHECK_MODULES([EXT_LIBECAP],[libecap >= 0.2.0 libecap < 0.3]) + PKG_CHECK_MODULES([EXT_LIBECAP],[libecap >= 1.0 libecap < 1.1]) else AC_MSG_NOTICE([eCAP support requires pkg-config to verify the correct library version. Trouble may follow.]) fi === modified file 'src/EventLoop.cc' --- src/EventLoop.cc 2013-02-17 02:09:16 +0000 +++ src/EventLoop.cc 2013-09-16 00:07:51 +0000 @@ -37,6 +37,8 @@ #include "base/AsyncCallQueue.h" #include "SquidTime.h" +EventLoop *EventLoop::Running = NULL; + EventLoop::EventLoop() : errcount(0), last_loop(false), timeService(NULL), primaryEngine(NULL), loop_delay(EVENT_LOOP_TIMEOUT), @@ -96,7 +98,12 @@ { prepareToRun(); + assert(!Running); + Running = this; + while (!runOnce()); + + Running = NULL; } bool === modified file 'src/EventLoop.h' --- src/EventLoop.h 2013-05-04 11:50:26 +0000 +++ src/EventLoop.h 2013-09-16 00:07:51 +0000 @@ -89,6 +89,10 @@ int errcount; + /// the [main program] loop running now; may be nil + /// for simplicity, we assume there are no concurrent loops + static EventLoop *Running; + private: /** setup state variables prior to running */ void prepareToRun(); === modified file 'src/adaptation/ecap/Host.cc' --- src/adaptation/ecap/Host.cc 2012-08-28 13:00:30 +0000 +++ src/adaptation/ecap/Host.cc 2013-09-16 18:04:34 +0000 @@ -70,11 +70,55 @@ os << PACKAGE_NAME << " v" << PACKAGE_VERSION; } +/// Strips libecap version components not affecting compatibility decisions. +static std::string +EssentialVersion(const std::string &raw) +{ + // all libecap x.y.* releases are supposed to be compatible so we strip + // everything after the second period + const std::string::size_type minorPos = raw.find('.'); + const std::string::size_type microPos = minorPos == std::string::npos ? + std::string::npos : raw.find('.', minorPos+1); + return raw.substr(0, microPos); // becomes raw if microPos is npos +} + +/// If "their" libecap version is not compatible with what Squid has been built +/// with, then complain and return false. +static bool +SupportedVersion(const char *vTheir, const std::string &them) +{ + if (!vTheir || !*vTheir) { + debugs(93, DBG_CRITICAL, "ERROR: Cannot use " << them << + " with libecap prior to v1.0."); + return false; + } + + // we support what we are built with + const std::string vSupported(LIBECAP_VERSION); + debugs(93, 2, them << " with libecap v" << vTheir << "; us: v" << vSupported); + + if (EssentialVersion(vTheir) == EssentialVersion(vSupported)) + return true; // their version is supported + + debugs(93, DBG_CRITICAL, "ERROR: Cannot use " << them << + " with libecap v" << vTheir << + ": incompatible with supported libecap v" << vSupported); + return false; +} + void -Adaptation::Ecap::Host::noteService(const libecap::weak_ptr &weak) +Adaptation::Ecap::Host::noteVersionedService(const char *vGiven, const libecap::weak_ptr &weak) { - Must(!weak.expired()); - RegisterAdapterService(weak.lock()); + /* + * Check that libecap used to build the service is compatible with ours. + * This has to be done using vGiven string and not Service object itself + * because dereferencing a Service pointer coming from an unsupported + * version is unsafe. + */ + if (SupportedVersion(vGiven, "eCAP service built")) { + Must(!weak.expired()); + RegisterAdapterService(weak.lock()); + } } static int @@ -126,7 +170,8 @@ void Adaptation::Ecap::Host::Register() { - if (!TheHost) { + if (!TheHost && SupportedVersion(libecap::VersionString(), + "Squid executable dynamically linked")) { TheHost.reset(new Adaptation::Ecap::Host); libecap::RegisterHost(TheHost); } === modified file 'src/adaptation/ecap/Host.h' --- src/adaptation/ecap/Host.h 2012-10-04 11:10:17 +0000 +++ src/adaptation/ecap/Host.h 2013-09-16 18:04:34 +0000 @@ -19,7 +19,7 @@ /* libecap::host::Host API */ virtual std::string uri() const; // unique across all vendors virtual void describe(std::ostream &os) const; // free-format info - virtual void noteService(const libecap::weak_ptr &s); + virtual void noteVersionedService(const char *libEcapVersion, const libecap::weak_ptr &s); virtual std::ostream *openDebug(libecap::LogVerbosity lv); virtual void closeDebug(std::ostream *debug); typedef libecap::shared_ptr MessagePtr; === modified file 'src/adaptation/ecap/ServiceRep.cc' --- src/adaptation/ecap/ServiceRep.cc 2012-08-28 13:00:30 +0000 +++ src/adaptation/ecap/ServiceRep.cc 2013-09-16 18:04:34 +0000 @@ -2,20 +2,29 @@ * DEBUG: section 93 eCAP Interface */ #include "squid.h" +#include "adaptation/ecap/Config.h" +#include "adaptation/ecap/Host.h" +#include "adaptation/ecap/ServiceRep.h" +#include "adaptation/ecap/XactionRep.h" +#include "AsyncEngine.h" +#include "base/TextException.h" #include "Debug.h" -#include +#include "EventLoop.h" #include #include #include #include -#include "adaptation/ecap/Config.h" -#include "adaptation/ecap/Host.h" -#include "adaptation/ecap/ServiceRep.h" -#include "adaptation/ecap/XactionRep.h" -#include "base/TextException.h" +#if HAVE_LIMITS +#include +#endif +#include -// configured eCAP service wrappers -static std::list TheServices; +/// libecap::adapter::services indexed by their URI +typedef std::map AdapterServices; +/// all loaded services +static AdapterServices TheServices; +/// configured services producing async transactions +static AdapterServices AsyncServices; namespace Adaptation { @@ -39,6 +48,17 @@ const Master &master; ///< the configuration being wrapped }; +/// manages async eCAP transactions +class Engine: public AsyncEngine +{ +public: + /* AsyncEngine API */ + virtual int checkEvents(int timeout); + +private: + void kickAsyncServices(timeval &timeout); +}; + } // namespace Ecap } // namespace Adaptation @@ -76,6 +96,55 @@ visitor.visit(Name(i->first), Area::FromTempString(i->second)); } +/* Adaptation::Ecap::Engine */ + +int +Adaptation::Ecap::Engine::checkEvents(int) +{ + // Start with the default I/O loop timeout, convert from milliseconds. + static const struct timeval maxTimeout { + EVENT_LOOP_TIMEOUT/1000, // seconds + (EVENT_LOOP_TIMEOUT % 1000)*1000 + }; // microseconds + struct timeval timeout = maxTimeout; + + kickAsyncServices(timeout); + if (timeout.tv_sec == maxTimeout.tv_sec && timeout.tv_usec == maxTimeout.tv_usec) + return EVENT_IDLE; + + debugs(93, 7, "timeout: " << timeout.tv_sec << "s+" << timeout.tv_usec << "us"); + + // convert back to milliseconds, avoiding int overflows + if (timeout.tv_sec >= std::numeric_limits::max()/1000 - 1000) + return std::numeric_limits::max(); + else + return timeout.tv_sec*1000 + timeout.tv_usec/1000; +} + +/// resumes async transactions (if any) and returns true if they set a timeout +void +Adaptation::Ecap::Engine::kickAsyncServices(timeval &timeout) +{ + if (AsyncServices.empty()) + return; + + debugs(93, 3, "async services: " << AsyncServices.size()); + + // Activate waiting async transactions, if any. + typedef AdapterServices::iterator ASI; + for (ASI s = AsyncServices.begin(); s != AsyncServices.end(); ++s) { + assert(s->second); + s->second->resume(); // may call Ecap::Xaction::resume() + } + + // Give services a chance to decrease the default timeout. + for (ASI s = AsyncServices.begin(); s != AsyncServices.end(); ++s) { + s->second->suspend(timeout); + } +} + +/* Adaptation::Ecap::ServiceRep */ + Adaptation::Ecap::ServiceRep::ServiceRep(const ServiceConfigPointer &cfg): /*AsyncJob("Adaptation::Ecap::ServiceRep"),*/ Adaptation::Service(cfg), isDetached(false) @@ -123,6 +192,11 @@ debugs(93,DBG_IMPORTANT, "Starting eCAP service: " << theService->uri()); theService->start(); + + if (theService->makesAsyncXactions()) { + AsyncServices[theService->uri()] = theService; + debugs(93, 5, "asyncs: " << AsyncServices.size()); + } } /// handles failures while configuring or starting an eCAP service; @@ -168,6 +242,16 @@ HttpRequest *cause) { Must(up()); + + // register now because (a) we need EventLoop::Running and (b) we do not + // want to add more main loop overheads unless an async service is used. + static AsyncEngine *TheEngine = NULL; + if (AsyncServices.size() && !TheEngine && EventLoop::Running) { + TheEngine = new Engine; + EventLoop::Running->registerEngine(TheEngine); + debugs(93, 3, "asyncs: " << AsyncServices.size() << ' ' << TheEngine); + } + XactionRep *rep = new XactionRep(virgin, cause, Pointer(this)); XactionRep::AdapterXaction x(theService->makeXaction(rep)); rep->master(x); @@ -210,11 +294,10 @@ Adaptation::Ecap::ServiceRep::AdapterService Adaptation::Ecap::FindAdapterService(const String& serviceUri) { - typedef std::list::const_iterator ASCI; - for (ASCI s = TheServices.begin(); s != TheServices.end(); ++s) { - Must(*s); - if (serviceUri == (*s)->uri().c_str()) - return *s; + AdapterServices::const_iterator pos = TheServices.find(serviceUri.termedBuf()); + if (pos != TheServices.end()) { + Must(pos->second); + return pos->second; } return ServiceRep::AdapterService(); } @@ -222,30 +305,18 @@ void Adaptation::Ecap::RegisterAdapterService(const Adaptation::Ecap::ServiceRep::AdapterService& adapterService) { - typedef std::list::iterator ASI; - for (ASI s = TheServices.begin(); s != TheServices.end(); ++s) { - Must(*s); - if (adapterService->uri() == (*s)->uri()) { - *s = adapterService; - debugs(93, 3, "updated eCAP module service: " << - adapterService->uri()); - return; - } - } - TheServices.push_back(adapterService); - debugs(93, 3, "registered eCAP module service: " << adapterService->uri()); + TheServices[adapterService->uri()] = adapterService; // may update old one + debugs(93, 3, "stored eCAP module service: " << adapterService->uri()); + // We do not update AsyncServices here in case they are not configured. } void Adaptation::Ecap::UnregisterAdapterService(const String& serviceUri) { - typedef std::list::iterator ASI; - for (ASI s = TheServices.begin(); s != TheServices.end(); ++s) { - if (serviceUri == (*s)->uri().c_str()) { - TheServices.erase(s); - debugs(93, 3, "unregistered eCAP module service: " << serviceUri); - return; - } + if (TheServices.erase(serviceUri.termedBuf())) { + debugs(93, 3, "unregistered eCAP module service: " << serviceUri); + AsyncServices.erase(serviceUri.termedBuf()); // no-op for non-async + return; } debugs(93, 3, "failed to unregister eCAP module service: " << serviceUri); } @@ -253,16 +324,16 @@ void Adaptation::Ecap::CheckUnusedAdapterServices(const Adaptation::Services& cfgs) { - typedef std::list::const_iterator ASCI; + typedef AdapterServices::const_iterator ASCI; for (ASCI loaded = TheServices.begin(); loaded != TheServices.end(); ++loaded) { bool found = false; for (Services::const_iterator cfged = cfgs.begin(); cfged != cfgs.end() && !found; ++cfged) { - found = (*cfged)->cfg().uri == (*loaded)->uri().c_str(); + found = (*cfged)->cfg().uri == loaded->second->uri().c_str(); } if (!found) debugs(93, DBG_IMPORTANT, "Warning: loaded eCAP service has no matching " << - "ecap_service config option: " << (*loaded)->uri()); + "ecap_service config option: " << loaded->second->uri()); } } === modified file 'src/adaptation/ecap/XactionRep.cc' --- src/adaptation/ecap/XactionRep.cc 2013-06-03 14:05:16 +0000 +++ src/adaptation/ecap/XactionRep.cc 2013-09-23 16:22:57 +0000 @@ -2,19 +2,20 @@ * DEBUG: section 93 eCAP Interface */ #include "squid.h" +#include "adaptation/Answer.h" +#include "adaptation/ecap/XactionRep.h" +#include "adaptation/ecap/Config.h" +#include "adaptation/Initiator.h" +#include "base/AsyncJobCalls.h" +#include "base/TextException.h" +#include "HttpRequest.h" +#include "HttpReply.h" +#include "SquidTime.h" #include #include #include #include #include -#include "HttpRequest.h" -#include "HttpReply.h" -#include "SquidTime.h" -#include "adaptation/Answer.h" -#include "adaptation/ecap/XactionRep.h" -#include "adaptation/ecap/Config.h" -#include "adaptation/Initiator.h" -#include "base/TextException.h" CBDATA_NAMESPACED_CLASS_INIT(Adaptation::Ecap::XactionRep, XactionRep); @@ -273,6 +274,25 @@ Adaptation::Initiate::swanSong(); } +void +Adaptation::Ecap::XactionRep::resume() +{ + // go async to gain exception protection and done()-based job destruction + typedef NullaryMemFunT Dialer; + AsyncCall::Pointer call = asyncCall(93, 5, "Adaptation::Ecap::XactionRep::doResume", + Dialer(this, &Adaptation::Ecap::XactionRep::doResume)); + ScheduleCallHere(call); +} + +/// the guts of libecap::host::Xaction::resume() API implementation +/// which just goes async in Adaptation::Ecap::XactionRep::resume(). +void +Adaptation::Ecap::XactionRep::doResume() +{ + Must(theMaster); + theMaster->resume(); +} + libecap::Message & Adaptation::Ecap::XactionRep::virgin() { @@ -595,12 +615,6 @@ mustStop("adaptationAborted"); } -bool -Adaptation::Ecap::XactionRep::callable() const -{ - return !done(); -} - void Adaptation::Ecap::XactionRep::noteMoreBodySpaceAvailable(RefCount bp) { === modified file 'src/adaptation/ecap/XactionRep.h' --- src/adaptation/ecap/XactionRep.h 2012-10-04 11:10:17 +0000 +++ src/adaptation/ecap/XactionRep.h 2013-09-23 16:22:57 +0000 @@ -44,6 +44,7 @@ virtual void blockVirgin(); virtual void adaptationDelayed(const libecap::Delay &); virtual void adaptationAborted(); + virtual void resume(); virtual void vbDiscard(); virtual void vbMake(); virtual void vbStopMaking(); @@ -53,9 +54,6 @@ virtual void noteAbContentDone(bool atEnd); virtual void noteAbContentAvailable(); - // libecap::Callable API, via libecap::host::Xaction - virtual bool callable() const; - // BodyProducer API virtual void noteMoreBodySpaceAvailable(RefCount bp); virtual void noteBodyConsumerAborted(RefCount bp); @@ -97,6 +95,8 @@ /// Return the adaptation meta headers and their values void visitEachMetaHeader(libecap::NamedValueVisitor &visitor) const; + void doResume(); + private: AdapterXaction theMaster; // the actual adaptation xaction we represent Adaptation::ServicePointer theService; ///< xaction's adaptation service === modified file 'src/main.cc' --- src/main.cc 2013-06-07 04:35:25 +0000 +++ src/main.cc 2013-09-16 00:12:40 +0000 @@ -225,17 +225,15 @@ { public: - SignalEngine(EventLoop &evtLoop) : loop(evtLoop) {} virtual int checkEvents(int timeout); private: - static void StopEventLoop(void * data) { - static_cast(data)->loop.stop(); + static void StopEventLoop(void *) { + if (EventLoop::Running) + EventLoop::Running->stop(); } void doShutdown(time_t wait); - - EventLoop &loop; }; int @@ -1506,7 +1504,7 @@ /* main loop */ EventLoop mainLoop; - SignalEngine signalEngine(mainLoop); + SignalEngine signalEngine; mainLoop.registerEngine(&signalEngine);