--------------------- PatchSet 1958 Date: 2005/10/17 23:30:37 Author: rousskov Branch: squid3-icap Tag: (none) Log: - Obtain ICAP options using ICAPOptXact. This is done when no options exists yet (e.g., at startup) and when old options expired. There are several hard-coded update delays when options are not available (e.g., the ICAP service refused a connection) or when options do not provide a TTL. This code needs more work so that still-fresh options are not immediately invalidated when the update starts. There is no way to update/sync options synchronously. One has to register a callback and be called when options are "ready" (i.e., the service is marked "up" or "down"). If multiple callers want ICAP options, they are all queued until options are ready. This queuing has not been tested enough. - Use ICAP::* types and constants from ICAPElements instead of ICAPServiceRep::* and globals. - methodStr() and vectPointStr() are now const. - Added status() method for use in debugging messages. Members: src/ICAPServiceRep.cc:1.1.2.7->1.1.2.8 src/ICAPServiceRep.h:1.1.2.6->1.1.2.7 Index: squid3/src/ICAPServiceRep.cc =================================================================== RCS file: /cvsroot/squid-sf//squid3/src/Attic/ICAPServiceRep.cc,v retrieving revision 1.1.2.7 retrieving revision 1.1.2.8 diff -u -r1.1.2.7 -r1.1.2.8 --- squid3/src/ICAPServiceRep.cc 6 Oct 2005 05:25:21 -0000 1.1.2.7 +++ squid3/src/ICAPServiceRep.cc 17 Oct 2005 23:30:37 -0000 1.1.2.8 @@ -1,34 +1,45 @@ +/* + * DEBUG: section 93 ICAP (RFC 3507) Client + */ + #include "squid.h" +#include "TextException.h" #include "ICAPServiceRep.h" +#include "ICAPOptions.h" +#include "ICAPOptXact.h" #include "ConfigParser.h" -ICAPServiceRep::ICAPServiceRep(): opts(NULL) +CBDATA_CLASS_INIT(ICAPServiceRep); + +ICAPServiceRep::ICAPServiceRep(): method(ICAP::methodNone), + point(ICAP::pointNone), port(-1), bypass(false), unreachable(false), + theOptions(NULL), theState(stateInit), notifying(false), self(NULL) { - debug(0,0)("ICAPServiceRep constructor called\n"); + debug(93,0)("ICAPServiceRep constructor called\n"); } ICAPServiceRep::~ICAPServiceRep() { - debug(0,0)("~ICAPServiceRep destructor called\n"); - debug(0,0)("~ICAPServiceRep deleting opts\n"); - delete opts; - debug(0,0)("~ICAPServiceRep done\n"); + debug(93,0)("~ICAPServiceRep destructor called\n"); + Must(!waiting()); + changeOptions(0); + debug(93,0)("~ICAPServiceRep done\n"); } const char * -ICAPServiceRep::methodStr() +ICAPServiceRep::methodStr() const { switch(method) { - case reqmod: + case ICAP::methodReqmod: return "REQMOD"; break; - case respmod: + case ICAP::methodRespmod: return "RESPMOD"; break; - case options: + case ICAP::methodOptions: return "OPTIONS"; break; @@ -39,29 +50,29 @@ return "NONE"; } -ICAPServiceRep::IcapMethod -ICAPServiceRep::parseMethod(const char *str) +ICAP::Method +ICAPServiceRep::parseMethod(const char *str) const { if (!strncasecmp(str, "REQMOD", 6)) - return reqmod; + return ICAP::methodReqmod; if (!strncasecmp(str, "RESPMOD", 7)) - return respmod; + return ICAP::methodRespmod; - return no_icap_method; + return ICAP::methodNone; } const char * -ICAPServiceRep::vectPointStr() +ICAPServiceRep::vectPointStr() const { switch(point) { - case precache: + case ICAP::pointPreCache: return "PRECACHE"; break; - case postcache: + case ICAP::pointPostCache: return "POSTCACHE"; break; @@ -72,8 +83,8 @@ return "NONE"; } -ICAPServiceRep::IcapVectPoint -ICAPServiceRep::parseVectPoint(const char *service) +ICAP::VectPoint +ICAPServiceRep::parseVectPoint(const char *service) const { const char *t = service; const char *q = strchr(t, '_'); @@ -82,17 +93,20 @@ t = q + 1; if (!strcasecmp(t, "precache")) - return precache; + return ICAP::pointPreCache; if (!strcasecmp(t, "postcache")) - return postcache; + return ICAP::pointPostCache; - return no_vect_point; + return ICAP::pointNone; } bool -ICAPServiceRep::parseConfigLine() +ICAPServiceRep::configure(Pointer &aSelf) { + assert(!self && aSelf != NULL); + self = aSelf; + char *service_type = NULL; ConfigParser::ParseString(&key); @@ -176,22 +190,228 @@ }; -bool ICAPServiceRep::wantsPreview(size_t &wantedSize) const +void ICAPServiceRep::invalidate() { - if (!opts || !opts->headers) - return false; + assert(self != NULL); + self = NULL; // may destroy us and, hence, invalidate cbdata(this) + // TODO: it would be nice to invalidate cbdata(this) when not destroyed +} - const int size = opts->headers->preview; +bool ICAPServiceRep::up() const +{ + return self != NULL && theState == stateUp; +} + +bool ICAPServiceRep::wantsPreview(size_t &wantedSize) const +{ + Must(up()); - if (size < 0) + if (theOptions->preview < 0) return false; - wantedSize = size; + wantedSize = theOptions->preview; return true; } bool ICAPServiceRep::allows204() const { + Must(up()); return true; // in the future, we may have ACLs to prevent 204s } + + +static +void ICAPServiceRep_noteTimeToUpdate(void *data) +{ + ICAPServiceRep *service = static_cast(data); + Must(service); + service->noteTimeToUpdate(); +} + +void ICAPServiceRep::noteTimeToUpdate() +{ + if (!self || waiting()) { + debugs(93,5, "ICAPService ignores options update " << status()); + return; + } + + debugs(93,5, "ICAPService performs a regular options update " << status()); + startGettingOptions(); +} + +static +void ICAPServiceRep_noteTimeToNotify(void *data) +{ + ICAPServiceRep *service = static_cast(data); + Must(service); + service->noteTimeToNotify(); +} + +void ICAPServiceRep::noteTimeToNotify() +{ + Must(!notifying); + notifying = true; + debugs(93,7, "ICAPService notifies " << theClients.size() << " clients " << + status()); + + // note: we must notify even if we are invalidated + + Pointer us = NULL; + + while (!theClients.empty()) { + Client i = theClients.pop_back(); + us = i.service; // prevent callbacks from destroying us while we loop + + if (cbdataReferenceValid(i.data)) + (*i.callback)(i.data, us); + + cbdataReferenceDone(i.data); + } + + notifying = false; +} + +void ICAPServiceRep::callWhenReady(Callback *cb, void *data) +{ + Must(cb); + Must(self != NULL); + + Client i; + i.service = self; + i.callback = cb; + i.data = cbdataReference(data); + theClients.push_back(i); + + if (waiting() || notifying) + return; // do nothing, we will be picked up in noteTimeToNotify() + + if (needNewOptions()) + startGettingOptions(); + else + scheduleNotification(); +} + +void ICAPServiceRep::scheduleNotification() +{ + debugs(93,7, "ICAPService will notify " << theClients.size() << " clients"); + eventAdd("ICAPServiceRep::noteTimeToNotify", &ICAPServiceRep_noteTimeToNotify, this, 0, 0, true); +} + +bool ICAPServiceRep::waiting() const +{ + return theState == stateWait; +} + +bool ICAPServiceRep::needNewOptions() const +{ + return !theOptions || !theOptions->fresh(); +} + +void ICAPServiceRep::changeOptions(ICAPOptions *newOptions) +{ + debugs(93,9, "ICAPService changes options from " << theOptions << " to " << + newOptions); + delete theOptions; + theOptions = newOptions; +} + +static +void ICAPServiceRep_noteNewOptions(ICAPOptXact *x, void *data) +{ + ICAPServiceRep *service = static_cast(data); + Must(service); + service->noteNewOptions(x); +} + +void ICAPServiceRep::noteNewOptions(ICAPOptXact *x) +{ + Must(x); + Must(waiting()); + + theState = stateDown; // default in case we fail to set new options + + changeOptions(x->options); + x->options = NULL; + delete x; + + if (theOptions && theOptions->valid()) + theState = stateUp; + + debugs(93,6, "ICAPService got new options and is now " << + (up() ? "up" : "down")); + + scheduleUpdate(); + + scheduleNotification(); +} + +void ICAPServiceRep::startGettingOptions() +{ + debugs(93,6, "ICAPService will get new options " << status()); + theState = stateWait; + + ICAPOptXact *x = new ICAPOptXact; + x->start(self, &ICAPServiceRep_noteNewOptions, this); + // TODO: timeout incase ICAPOptXact never calls us back? +} + +void ICAPServiceRep::scheduleUpdate() +{ + int delay = -1; + + if (theOptions && theOptions->valid()) { + const time_t expire = theOptions->expire(); + + if (expire > squid_curtime) + delay = expire - squid_curtime; + else + if (expire >= 0) + delay = 1; // delay for expired or 'expiring now' options + else + delay = 60*60; // default for options w/o known expiration time + } else { + delay = 5*60; // delay for a down service + } + + if (delay <= 0) { + debugs(93,0, "internal error: ICAPServiceRep failed to compute options update schedule"); + delay = 5*60; // delay for an internal error + } + + // with zero delay, the state changes to stateWait before + // notifications are sent out to clients + assert(delay > 0); + + debugs(93,7, "ICAPService will update options in " << delay << " sec"); + + eventAdd("ICAPServiceRep::noteTimeToUpdate", + &ICAPServiceRep_noteTimeToUpdate, this, delay, 0, true); + + // XXX: prompt updates of valid options should not disable concurrent ICAP + // xactions. 'Wait' state should not mark the service 'down'! This will + // also remove 'delay == 0' as a special case above. +} + +const char *ICAPServiceRep::status() const +{ + if (!self) + return "[invalidated]"; + + switch (theState) { + + case stateInit: + return "[init]"; + + case stateWait: + return "[wait]"; + + case stateUp: + return "[up]"; + + case stateDown: + return "[down]"; + } + + return "[unknown]"; +} Index: squid3/src/ICAPServiceRep.h =================================================================== RCS file: /cvsroot/squid-sf//squid3/src/Attic/ICAPServiceRep.h,v retrieving revision 1.1.2.6 retrieving revision 1.1.2.7 diff -u -r1.1.2.6 -r1.1.2.7 --- squid3/src/ICAPServiceRep.h 5 Oct 2005 22:24:28 -0000 1.1.2.6 +++ squid3/src/ICAPServiceRep.h 17 Oct 2005 23:30:37 -0000 1.1.2.7 @@ -1,6 +1,6 @@ /* - * $Id: ICAPServiceRep.h,v 1.1.2.6 2005/10/05 22:24:28 rousskov Exp $ + * $Id: ICAPServiceRep.h,v 1.1.2.7 2005/10/17 23:30:37 rousskov Exp $ * * * SQUID Web Proxy Cache http://www.squid-cache.org/ @@ -34,37 +34,53 @@ #ifndef SQUID_ICAPSERVICEREP_H #define SQUID_ICAPSERVICEREP_H -#include -#include "ICAPOptions.h" - -/* The ICAP service representative maintains information about a single - ICAP service that Squid communicates with. One ICAP server may host - many ICAP services */ +#include "ICAPElements.h" class ICAPOptions; +class ICAPOptXact; + +/* The ICAP service representative maintains information about a single ICAP + service that Squid communicates with. The representative initiates OPTIONS + requests to the service to keep cached options fresh. One ICAP server may + host many ICAP services */ + class ICAPServiceRep : public RefCountable { public: typedef RefCount Pointer; +public: ICAPServiceRep(); - ~ICAPServiceRep(); - bool parseConfigLine(); - const char *methodStr(); - const char *vectPointStr(); + virtual ~ICAPServiceRep(); + + bool configure(Pointer &aSelf); // needs self pointer for ICAPOptXact + void invalidate(); // call when the service is no longer needed or valid + + const char *methodStr() const; + const char *vectPointStr() const; + + bool up() const; + + /* Service is "up" iff there is a fresh cached OPTIONS response. To + get an OPTIONS response, ICAPServiceRep does an OPTIONS + transaction. Failed transaction results in a "down" service. The + Callback is called if/once the service is in a steady ("up" or + "down") state. */ + typedef void Callback(void *data, Pointer &service); + void callWhenReady(Callback *cb, void *data); + + + // the methods below can only be called on an up() service bool wantsPreview(size_t &wantedSize) const; bool allows204() const; - typedef enum { no_icap_method, reqmod, respmod, options } IcapMethod; - typedef enum { no_vect_point, precache, postcache } IcapVectPoint; - public: String key; - IcapMethod method; - IcapVectPoint point; + ICAP::Method method; + ICAP::VectPoint point; String uri; // service URI // URI components @@ -72,17 +88,51 @@ int port; String resource; - // non-options flags + // non-options flags; TODO: check that both are used. bool bypass; bool unreachable; - ICAPOptions *opts; +public: // treat these as private, they are for callbacks only + void noteTimeToUpdate(); + void noteTimeToNotify(); + void noteNewOptions(ICAPOptXact *x); + +private: + // stores Prepare() callback info + + struct Client + { + Pointer service; // one for each client to preserve service + Callback *callback; + void *data; + }; + + typedef Vector Clients; + Clients theClients; // all clients waiting for a call back + + ICAPOptions *theOptions; + + typedef enum { stateInit, stateWait, stateUp, stateDown } State; + State theState; + bool notifying; // may be true in any state except for the initial private: - IcapMethod parseMethod(const char *); - IcapVectPoint parseVectPoint(const char *); + ICAP::Method parseMethod(const char *) const; + ICAP::VectPoint parseVectPoint(const char *) const; + + bool waiting() const; + bool needNewOptions() const; + void scheduleNotification(); + void changeOptions(ICAPOptions *newOptions); + void startGettingOptions(); + void scheduleUpdate(); + const char *status() const; + + Pointer self; + CBDATA_CLASS2(ICAPServiceRep); }; + #endif /* SQUID_ICAPSERVICEREP_H */