From 0a720258ebe0806d0ee7953ab35df886f867a899 Mon Sep 17 00:00:00 2001 From: Alex Rousskov Date: Fri, 29 Nov 2013 12:47:54 -0700 Subject: [PATCH] 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. --- configure.ac | 4 +- src/EventLoop.cc | 7 ++ src/EventLoop.h | 4 + src/Makefile.am | 4 + src/adaptation/ecap/Host.cc | 53 ++++++++++- src/adaptation/ecap/Host.h | 2 +- src/adaptation/ecap/ServiceRep.cc | 140 ++++++++++++++++++++++-------- src/adaptation/ecap/XactionRep.cc | 26 ++++-- src/adaptation/ecap/XactionRep.h | 6 +- src/main.cc | 10 +-- src/tests/stub_EventLoop.cc | 13 +++ 11 files changed, 213 insertions(+), 56 deletions(-) create mode 100644 src/tests/stub_EventLoop.cc diff --git a/configure.ac b/configure.ac index b932e7068a..d88e661c0d 100644 --- a/configure.ac +++ b/configure.ac @@ -1040,10 +1040,10 @@ then 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 diff --git a/src/EventLoop.cc b/src/EventLoop.cc index b74d6ef90f..e10be5fcfd 100644 --- a/src/EventLoop.cc +++ b/src/EventLoop.cc @@ -37,6 +37,8 @@ #include "EventLoop.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 @@ EventLoop::run() { prepareToRun(); + assert(!Running); + Running = this; + while (!runOnce()); + + Running = NULL; } bool diff --git a/src/EventLoop.h b/src/EventLoop.h index fe26776065..3070051053 100644 --- a/src/EventLoop.h +++ b/src/EventLoop.h @@ -89,6 +89,10 @@ public: 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(); diff --git a/src/Makefile.am b/src/Makefile.am index 9d79ca7ebf..9595c5c7ac 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1392,6 +1392,7 @@ tests_testCacheManager_SOURCES = \ tests/stub_main_cc.cc \ tests/stub_ipc_Forwarder.cc \ tests/stub_store_stats.cc \ + tests/stub_EventLoop.cc \ time.cc \ BodyPipe.cc \ cache_manager.cc \ @@ -2469,6 +2470,7 @@ tests_test_http_range_SOURCES = \ tests/stub_main_cc.cc \ tests/stub_MemStore.cc \ tests/stub_store_stats.cc \ + tests/stub_EventLoop.cc \ time.cc \ tools.h \ tools.cc \ @@ -2588,6 +2590,7 @@ tests_testHttpRequest_SOURCES = \ tests/stub_ipc_Forwarder.cc \ tests/stub_libeui.cc \ tests/stub_store_stats.cc \ + tests/stub_EventLoop.cc \ time.cc \ BodyPipe.cc \ cache_manager.cc \ @@ -3569,6 +3572,7 @@ tests_testURL_SOURCES = \ tests/testURLScheme.h \ tests/testMain.cc \ tests/stub_time.cc \ + tests/stub_EventLoop.cc \ tools.h \ tools.cc \ tests/stub_tunnel.cc \ diff --git a/src/adaptation/ecap/Host.cc b/src/adaptation/ecap/Host.cc index 0883837052..add249c065 100644 --- a/src/adaptation/ecap/Host.cc +++ b/src/adaptation/ecap/Host.cc @@ -70,11 +70,55 @@ Adaptation::Ecap::Host::describe(std::ostream &os) const os << PACKAGE_NAME << " v" << PACKAGE_VERSION; } +/// Strips libecap version components not affecting compatibility decisions. +static SBuf +EssentialVersion(const SBuf &raw) +{ + // all libecap x.y.* releases are supposed to be compatible so we strip + // everything after the second period + const SBuf::size_type minorPos = raw.find('.'); + const SBuf::size_type microPos = minorPos == SBuf::npos ? + SBuf::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 char *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 SBuf vSupported(LIBECAP_VERSION); + debugs(93, 2, them << " with libecap v" << vTheir << "; us: v" << vSupported); + + if (EssentialVersion(SBuf(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 @@ Adaptation::Ecap::Host::newResponse() const 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); } diff --git a/src/adaptation/ecap/Host.h b/src/adaptation/ecap/Host.h index 31c0c179a9..9789fc1c2b 100644 --- a/src/adaptation/ecap/Host.h +++ b/src/adaptation/ecap/Host.h @@ -19,7 +19,7 @@ public: /* 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; diff --git a/src/adaptation/ecap/ServiceRep.cc b/src/adaptation/ecap/ServiceRep.cc index 260850591b..34e19ad6f4 100644 --- a/src/adaptation/ecap/ServiceRep.cc +++ b/src/adaptation/ecap/ServiceRep.cc @@ -3,19 +3,29 @@ */ #include "squid.h" #include "Debug.h" -#include -#include -#include -#include -#include +#include "EventLoop.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" -// configured eCAP service wrappers -static std::list TheServices; +#include +#include +#include +#include +#if HAVE_LIMITS +#include +#endif +#include + +/// 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 +49,17 @@ public: 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 +97,55 @@ Adaptation::Ecap::ConfigRep::visitEachOption(libecap::NamedValueVisitor &visitor 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 +193,11 @@ Adaptation::Ecap::ServiceRep::tryConfigureAndStart() 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 +243,16 @@ Adaptation::Ecap::ServiceRep::makeXactLauncher(HttpMsg *virgin, 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 +295,10 @@ bool Adaptation::Ecap::ServiceRep::detached() const 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 +306,18 @@ Adaptation::Ecap::FindAdapterService(const String& serviceUri) 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 +325,16 @@ Adaptation::Ecap::UnregisterAdapterService(const String& serviceUri) 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()); } } diff --git a/src/adaptation/ecap/XactionRep.cc b/src/adaptation/ecap/XactionRep.cc index 87aa179961..3938a76fb4 100644 --- a/src/adaptation/ecap/XactionRep.cc +++ b/src/adaptation/ecap/XactionRep.cc @@ -11,6 +11,7 @@ #include "adaptation/ecap/Config.h" #include "adaptation/ecap/XactionRep.h" #include "adaptation/Initiator.h" +#include "base/AsyncJobCalls.h" #include "base/TextException.h" #include "HttpReply.h" #include "HttpRequest.h" @@ -273,6 +274,25 @@ Adaptation::Ecap::XactionRep::swanSong() 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 @@ Adaptation::Ecap::XactionRep::adaptationAborted() mustStop("adaptationAborted"); } -bool -Adaptation::Ecap::XactionRep::callable() const -{ - return !done(); -} - void Adaptation::Ecap::XactionRep::noteMoreBodySpaceAvailable(RefCount bp) { diff --git a/src/adaptation/ecap/XactionRep.h b/src/adaptation/ecap/XactionRep.h index 3c100b0545..6723b1b7b0 100644 --- a/src/adaptation/ecap/XactionRep.h +++ b/src/adaptation/ecap/XactionRep.h @@ -44,6 +44,7 @@ public: 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 @@ public: 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 @@ protected: /// 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 diff --git a/src/main.cc b/src/main.cc index c5116e24fa..71c481dc42 100644 --- a/src/main.cc +++ b/src/main.cc @@ -221,17 +221,15 @@ class SignalEngine: public AsyncEngine { 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 @@ -1496,7 +1494,7 @@ SquidMain(int argc, char **argv) /* main loop */ EventLoop mainLoop; - SignalEngine signalEngine(mainLoop); + SignalEngine signalEngine; mainLoop.registerEngine(&signalEngine); diff --git a/src/tests/stub_EventLoop.cc b/src/tests/stub_EventLoop.cc new file mode 100644 index 0000000000..228f4db3ce --- /dev/null +++ b/src/tests/stub_EventLoop.cc @@ -0,0 +1,13 @@ +#include "squid.h" +#include "EventLoop.h" + +#define STUB_API "EventLoop.cc" +#include "tests/STUB.h" + +EventLoop *EventLoop::Running = NULL; + +EventLoop::EventLoop(): errcount(0), last_loop(false), timeService(NULL), + primaryEngine(NULL), loop_delay(0), error(false), runOnceResult(false) + STUB_NOP + +void EventLoop::registerEngine(AsyncEngine *engine) STUB -- 2.47.2