1 // -*- mode: c++; c-basic-offset:4 -*- 2 3 // This file is part of libdap, A C++ implementation of the OPeNDAP Data 4 // Access Protocol. 5 6 // Copyright (c) 2008 OPeNDAP, Inc. 7 // Author: James Gallagher <jgallagher@opendap.org> 8 // 9 // This library is free software; you can redistribute it and/or 10 // modify it under the terms of the GNU Lesser General Public 11 // License as published by the Free Software Foundation; either 12 // version 2.1 of the License, or (at your option) any later version. 13 // 14 // This library is distributed in the hope that it will be useful, 15 // but WITHOUT ANY WARRANTY; without even the implied warranty of 16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 // Lesser General Public License for more details. 18 // 19 // You should have received a copy of the GNU Lesser General Public 20 // License along with this library; if not, write to the Free Software 21 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 22 // 23 // You can contact OPeNDAP, Inc. at PO Box 112, Saunderstown, RI. 02874-0112. 24 25 #ifndef _http_cache_table_h 26 #define _http_cache_table_h 27 28 //#define DODS_DEBUG 29 30 #include <pthread.h> 31 32 #ifdef WIN32 33 #include <io.h> // stat for win32? 09/05/02 jhrg 34 #endif 35 36 #include <cstring> 37 38 #include <string> 39 #include <vector> 40 #include <map> 41 42 #ifndef _http_cache_h 43 #include "HTTPCache.h" 44 #endif 45 46 #ifndef _error_h 47 #include "Error.h" 48 #endif 49 50 #ifndef _internalerr_h 51 #include "InternalErr.h" 52 #endif 53 54 #ifndef _util_h 55 #include "util.h" 56 #endif 57 58 #ifndef _debug_h 59 #include "debug.h" 60 #endif 61 62 //long_to_string(code)); 63 #define LOCK(m) do { \ 64 int code = pthread_mutex_lock((m)); \ 65 if (code != 0) \ 66 throw InternalErr(__FILE__, __LINE__, string("Mutex lock: ") + strerror(code)); \ 67 } while(0); 68 69 //+ long_to_string(code)); 70 #define UNLOCK(m) do { \ 71 int code = pthread_mutex_unlock((m)); \ 72 if (code != 0) \ 73 throw InternalErr(__FILE__, __LINE__, string("Mutex unlock: ") + strerror(code)); \ 74 } while(0); 75 76 #define TRYLOCK(m) pthread_mutex_trylock((m)) 77 #define INIT(m) pthread_mutex_init((m), 0) 78 #define DESTROY(m) pthread_mutex_destroy((m)) 79 80 //using namespace std; 81 82 namespace libdap { 83 84 int get_hash(const string &url); 85 86 /** The table of entries in the client-side cache. This class maintains a table 87 of CacheEntries, where one instance of CacheEntry is made for 88 each item in the cache. When an item is accessed it is either 89 locked for reading or writing. When locked for reading the entry is 90 recorded on a list of read-locked entries. The caller must explicitly 91 free the entry for it to be removed from this list (which is the only 92 way it can be opened for writing). An entry can be accessed by multiple 93 readers but only one writer. 94 95 @note The CacheEntry class used to contain a lock that was used to ensure 96 that the entry was locked during any changes to any of its fields. That 97 has been removed - its now the responsibility of the caller. This change 98 was made because it's likely the caller will need to lock all of the methods 99 that operate on a CacheEntry anyway, so the CacheEntry-specific lock was 100 redundant. */ 101 class HTTPCacheTable { 102 public: 103 /** A struct used to store information about responses in the 104 cache's volatile memory. 105 106 About entry locking: An entry is locked using both a mutex and a 107 counter. The counter keeps track of how many clients are accessing a 108 given entry while the mutex provides a guarantee that updates to the 109 counter are MT-safe. In addition, the HTTPCacheTable object maintains a 110 map which binds the FILE* returned to a client with a given entry. 111 This way the client can tell the HTTPCacheTable object that it is done 112 with <code>FILE *response</code> and the class can arrange to update 113 the lock counter and mutex. */ 114 struct CacheEntry { 115 private: 116 string url; // Location 117 int hash; 118 int hits; // Hit counts 119 string cachename; 120 121 string etag; 122 time_t lm; // Last modified 123 time_t expires; 124 time_t date; // From the response header. 125 time_t age; 126 time_t max_age; // From Cache-Control 127 128 unsigned long size; // Size of cached entity body 129 bool range; // Range is not currently supported. 10/02/02 jhrg 130 131 time_t freshness_lifetime; 132 time_t response_time; 133 time_t corrected_initial_age; 134 135 bool must_revalidate; 136 bool no_cache; // This field is not saved in the index. 137 138 int readers; 139 pthread_mutex_t d_response_lock; // set if being read 140 pthread_mutex_t d_response_write_lock; // set if being written 141 142 // Allow HTTPCacheTable methods access and the test class, too 143 friend class HTTPCacheTable; 144 friend class HTTPCacheTest; 145 146 // Allow access by the functors used in HTTPCacheTable 147 friend class DeleteCacheEntry; 148 friend class WriteOneCacheEntry; 149 friend class DeleteExpired; 150 friend class DeleteByHits; 151 friend class DeleteBySize; 152 153 public: get_cachenameCacheEntry154 string get_cachename() 155 { 156 return cachename; 157 } get_etagCacheEntry158 string get_etag() 159 { 160 return etag; 161 } get_lmCacheEntry162 time_t get_lm() 163 { 164 return lm; 165 } get_expiresCacheEntry166 time_t get_expires() 167 { 168 return expires; 169 } get_max_ageCacheEntry170 time_t get_max_age() 171 { 172 return max_age; 173 } set_sizeCacheEntry174 void set_size(unsigned long sz) 175 { 176 size = sz; 177 } get_freshness_lifetimeCacheEntry178 time_t get_freshness_lifetime() 179 { 180 return freshness_lifetime; 181 } get_response_timeCacheEntry182 time_t get_response_time() 183 { 184 return response_time; 185 } get_corrected_initial_ageCacheEntry186 time_t get_corrected_initial_age() 187 { 188 return corrected_initial_age; 189 } get_must_revalidateCacheEntry190 bool get_must_revalidate() 191 { 192 return must_revalidate; 193 } set_no_cacheCacheEntry194 void set_no_cache(bool state) 195 { 196 no_cache = state; 197 } is_no_cacheCacheEntry198 bool is_no_cache() 199 { 200 return no_cache; 201 } 202 lock_read_responseCacheEntry203 void lock_read_response() 204 { 205 DBG(cerr << "Try locking read response... (" << hex << &d_response_lock << dec << ") "); 206 int status = TRYLOCK(&d_response_lock); 207 if (status != 0 /*&& status == EBUSY*/) { 208 // If locked, wait for any writers 209 LOCK(&d_response_write_lock); 210 UNLOCK(&d_response_write_lock); 211 } 212 213 readers++; // Record number of readers 214 215 DBGN(cerr << "Done" << endl); 216 217 } 218 unlock_read_responseCacheEntry219 void unlock_read_response() 220 { 221 readers--; 222 if (readers == 0) { 223 DBG(cerr << "Unlocking read response... (" << hex << &d_response_lock << dec << ") "); 224 UNLOCK(&d_response_lock); DBGN(cerr << "Done" << endl); 225 } 226 } 227 lock_write_responseCacheEntry228 void lock_write_response() 229 { 230 DBG(cerr << "locking write response... (" << hex << &d_response_lock << dec << ") "); 231 LOCK(&d_response_lock); 232 LOCK(&d_response_write_lock); DBGN(cerr << "Done" << endl); 233 } 234 unlock_write_responseCacheEntry235 void unlock_write_response() 236 { 237 DBG(cerr << "Unlocking write response... (" << hex << &d_response_lock << dec << ") "); 238 UNLOCK(&d_response_write_lock); 239 UNLOCK(&d_response_lock); DBGN(cerr << "Done" << endl); 240 } 241 CacheEntryCacheEntry242 CacheEntry() : 243 url(""), hash(-1), hits(0), cachename(""), etag(""), lm(-1), expires(-1), date(-1), age(-1), max_age(-1), size( 244 0), range(false), freshness_lifetime(0), response_time(0), corrected_initial_age(0), must_revalidate( 245 false), no_cache(false), readers(0) 246 { 247 INIT(&d_response_lock); 248 INIT(&d_response_write_lock); 249 } CacheEntryCacheEntry250 CacheEntry(const string &u) : 251 url(u), hash(-1), hits(0), cachename(""), etag(""), lm(-1), expires(-1), date(-1), age(-1), max_age(-1), size( 252 0), range(false), freshness_lifetime(0), response_time(0), corrected_initial_age(0), must_revalidate( 253 false), no_cache(false), readers(0) 254 { 255 INIT(&d_response_lock); 256 INIT(&d_response_write_lock); 257 hash = get_hash(url); 258 } 259 }; 260 261 // Typedefs for CacheTable. A CacheTable is a vector of vectors of 262 // CacheEntries. The outer vector is accessed using the hash value. 263 // Entries with matching hashes occupy successive positions in the inner 264 // vector (that's how hash collisions are resolved). Search the inner 265 // vector for a specific match. 266 typedef vector<CacheEntry *> CacheEntries; 267 typedef CacheEntries::iterator CacheEntriesIter; 268 269 typedef CacheEntries **CacheTable; // Array of pointers to CacheEntries 270 271 friend class HTTPCacheTest; 272 273 private: 274 CacheTable d_cache_table; 275 276 string d_cache_root; 277 unsigned int d_block_size; // File block size. 278 unsigned long d_current_size; 279 280 string d_cache_index; 281 int d_new_entries; 282 283 map<FILE *, HTTPCacheTable::CacheEntry *> d_locked_entries; 284 285 // Make these private to prevent use 286 HTTPCacheTable(const HTTPCacheTable &); 287 HTTPCacheTable &operator=(const HTTPCacheTable &); 288 HTTPCacheTable(); 289 get_cache_table()290 CacheTable &get_cache_table() 291 { 292 return d_cache_table; 293 } 294 295 CacheEntry *get_locked_entry_from_cache_table(int hash, const string &url); /*const*/ 296 297 public: 298 HTTPCacheTable(const string &cache_root, int block_size); 299 ~HTTPCacheTable(); 300 301 //@{ @name Accessors/Mutators get_current_size()302 unsigned long get_current_size() const 303 { 304 return d_current_size; 305 } set_current_size(unsigned long sz)306 void set_current_size(unsigned long sz) 307 { 308 d_current_size = sz; 309 } 310 get_block_size()311 unsigned int get_block_size() const 312 { 313 return d_block_size; 314 } set_block_size(unsigned int sz)315 void set_block_size(unsigned int sz) 316 { 317 d_block_size = sz; 318 } 319 get_new_entries()320 int get_new_entries() const 321 { 322 return d_new_entries; 323 } increment_new_entries()324 void increment_new_entries() 325 { 326 ++d_new_entries; 327 } 328 get_cache_root()329 string get_cache_root() 330 { 331 return d_cache_root; 332 } set_cache_root(const string & cr)333 void set_cache_root(const string &cr) 334 { 335 d_cache_root = cr; 336 } 337 //@} 338 339 void delete_expired_entries(time_t time = 0); 340 void delete_by_hits(int hits); 341 void delete_by_size(unsigned int size); 342 void delete_all_entries(); 343 344 bool cache_index_delete(); 345 bool cache_index_read(); 346 CacheEntry *cache_index_parse_line(const char *line); 347 void cache_index_write(); 348 349 string create_hash_directory(int hash); 350 void create_location(CacheEntry *entry); 351 352 void add_entry_to_cache_table(CacheEntry *entry); 353 void remove_cache_entry(HTTPCacheTable::CacheEntry *entry); 354 355 void remove_entry_from_cache_table(const string &url); 356 CacheEntry *get_locked_entry_from_cache_table(const string &url); 357 CacheEntry *get_write_locked_entry_from_cache_table(const string &url); 358 359 void calculate_time(HTTPCacheTable::CacheEntry *entry, int default_expiration, time_t request_time); 360 void parse_headers(HTTPCacheTable::CacheEntry *entry, unsigned long max_entry_size, const vector<string> &headers); 361 362 // These should move back to HTTPCache 363 void bind_entry_to_data(CacheEntry *entry, FILE *body); 364 void uncouple_entry_from_data(FILE *body); 365 bool is_locked_read_responses(); 366 }; 367 368 } // namespace libdap 369 #endif 370