835c6c2001-06-17Martin Nilsson // This file is part of Roxen WebServer. // Copyright © 1996 - 2001, Roxen IS.
bfb3d62001-07-20Martin Stjernholm // $Id: cache.pike,v 1.73 2001/07/20 00:06:33 mast Exp $
aa5a892000-03-07Martin Nilsson  #pragma strict_types
a565891999-12-27Martin Nilsson 
b796b51998-11-18Per Hedbor #include <roxen.h>
b1fca01996-11-12Per Hedbor #include <config.h>
cfb98d2000-04-30Martin Nilsson // A cache entry is an array with four elements #define ENTRY_SIZE 4 // The elements are as follows: // A timestamp when the entry was last used
b1fca01996-11-12Per Hedbor #define TIMESTAMP 0
cfb98d2000-04-30Martin Nilsson // The actual data
b1fca01996-11-12Per Hedbor #define DATA 1
cfb98d2000-04-30Martin Nilsson // A timeout telling when the data is no longer valid.
33e3251998-05-07Johan Schön #define TIMEOUT 2
cfb98d2000-04-30Martin Nilsson // The size of the entry, in byts.
b1be432000-02-15Martin Nilsson #define SIZE 3
b1fca01996-11-12Per Hedbor 
14ccc61999-12-28Martin Nilsson #undef CACHE_WERR
a565891999-12-27Martin Nilsson #ifdef CACHE_DEBUG
9b44022001-03-11Martin Nilsson # define CACHE_WERR(X) report_debug("CACHE: "+X+"\n");
a565891999-12-27Martin Nilsson #else # define CACHE_WERR(X) #endif
f7a34a1998-06-24Johan Schön 
b9fbf72001-01-04Martin Nilsson #undef MORE_CACHE_WERR #ifdef MORE_CACHE_DEBUG
9b44022001-03-11Martin Nilsson # define MORE_CACHE_WERR(X) report_debug("CACHE: "+X+"\n");
b1be432000-02-15Martin Nilsson #else
b9fbf72001-01-04Martin Nilsson # define MORE_CACHE_WERR(X)
b1be432000-02-15Martin Nilsson #endif
cfb98d2000-04-30Martin Nilsson // The actual cache along with some statistics mappings.
1e857b2000-08-01Martin Nilsson static mapping(string:mapping(string:array)) cache; static mapping(string:int) hits=([]), all=([]);
b1fca01996-11-12Per Hedbor 
bfb3d62001-07-20Martin Stjernholm void flush_memory_cache (void|string in) { if (in) { m_delete (cache, in); m_delete (hits, in); m_delete (all, in); } else { cache=([]); hits=([]); all=([]); }
49f9242000-05-15Martin Nilsson }
fc7d5a2001-07-03Martin Nilsson constant svalsize = 4*4;
0281ea2000-03-07Martin Nilsson 
cfb98d2000-04-30Martin Nilsson // Expire a whole cache
f6d62d1997-03-26Per Hedbor void cache_expire(string in) {
49f9242000-05-15Martin Nilsson  CACHE_WERR(sprintf("cache_expire(\"%s\")", in));
f6d62d1997-03-26Per Hedbor  m_delete(cache, in); }
cfb98d2000-04-30Martin Nilsson // Lookup an entry in a cache
b1fca01996-11-12Per Hedbor mixed cache_lookup(string in, string what) {
a565891999-12-27Martin Nilsson  CACHE_WERR(sprintf("cache_lookup(\"%s\",\"%s\") -> ", in, what));
b1fca01996-11-12Per Hedbor  all[in]++;
9a002e2000-04-19Martin Nilsson  int t=time(1);
cfb98d2000-04-30Martin Nilsson  // Does the entry exist at all?
1b41df2000-04-18Martin Nilsson  if(array entry = (cache[in] && cache[in][what]) )
cfb98d2000-04-30Martin Nilsson  // Is it time outed?
1b41df2000-04-18Martin Nilsson  if (entry[TIMEOUT] && entry[TIMEOUT] < t) {
249ed12000-01-06Martin Stjernholm  m_delete (cache[in], what);
1b41df2000-04-18Martin Nilsson  CACHE_WERR("Timed out");
249ed12000-01-06Martin Stjernholm  } else {
cfb98d2000-04-30Martin Nilsson  // Update the timestamp and hits counter and return the value.
1b41df2000-04-18Martin Nilsson  cache[in][what][TIMESTAMP]=t;
249ed12000-01-06Martin Stjernholm  CACHE_WERR("Hit"); hits[in]++; return entry[DATA]; } else CACHE_WERR("Miss"); return ([])[0];
b1fca01996-11-12Per Hedbor }
471c292000-09-04Jonas Wallden // Return all indices used by a given cache or indices of available caches array(string) cache_indices(string|void in) { if (in) return (cache[in] && indices(cache[in])) || ({ }); else return indices(cache); }
cfb98d2000-04-30Martin Nilsson // Return some fancy cache statistics.
1e857b2000-08-01Martin Nilsson mapping(string:array(int)) status()
b1fca01996-11-12Per Hedbor {
a8228b2000-08-14Jonas Wallden  mapping(string:array(int)) ret = ([ ]); foreach(indices(cache), string name) { // We only show names up to the first ":" if present. This lets us // group entries together in the status table. string show_name = (name / ":")[0];
b7d8902000-08-14Jonas Wallden  array(int) entry = ({ sizeof(cache[name]), hits[name], all[name],
fc7d5a2001-07-03Martin Nilsson  sizeof(encode_value(cache[name])) });
a8228b2000-08-14Jonas Wallden  if (!zero_type(ret[show_name])) for (int idx = 0; idx < 3; idx++) ret[show_name][idx] += entry[idx]; else ret[show_name] = entry;
b1fca01996-11-12Per Hedbor  }
1e857b2000-08-01Martin Nilsson  return ret;
b1fca01996-11-12Per Hedbor }
cfb98d2000-04-30Martin Nilsson // Remove an entry from the cache. Removes the entire cache if no // entry key is given.
b1fca01996-11-12Per Hedbor void cache_remove(string in, string what) {
a565891999-12-27Martin Nilsson  CACHE_WERR(sprintf("cache_remove(\"%s\",\"%O\")", in, what));
b1fca01996-11-12Per Hedbor  if(!what) m_delete(cache, in); else
a565891999-12-27Martin Nilsson  if(cache[in])
b1fca01996-11-12Per Hedbor  m_delete(cache[in], what); }
cfb98d2000-04-30Martin Nilsson // Add an entry to a cache
f7a34a1998-06-24Johan Schön mixed cache_set(string in, string what, mixed to, int|void tm)
b1fca01996-11-12Per Hedbor {
b9fbf72001-01-04Martin Nilsson #if MORE_CACHE_DEBUG
7bcd142000-02-12Martin Nilsson  CACHE_WERR(sprintf("cache_set(\"%s\", \"%s\", %O)\n", in, what, to)); #else CACHE_WERR(sprintf("cache_set(\"%s\", \"%s\", %t)\n",
a565891999-12-27Martin Nilsson  in, what, to));
7bcd142000-02-12Martin Nilsson #endif
9a002e2000-04-19Martin Nilsson  int t=time(1);
b1fca01996-11-12Per Hedbor  if(!cache[in]) cache[in]=([ ]); cache[in][what] = allocate(ENTRY_SIZE); cache[in][what][DATA] = to;
1b41df2000-04-18Martin Nilsson  if(tm) cache[in][what][TIMEOUT] = t + tm; cache[in][what][TIMESTAMP] = t;
f7a34a1998-06-24Johan Schön  return to;
b1fca01996-11-12Per Hedbor }
cfb98d2000-04-30Martin Nilsson // Clean the cache.
b1fca01996-11-12Per Hedbor void cache_clean() {
460da41997-10-30Per Hedbor  remove_call_out(cache_clean);
ea9a4b2001-01-10Per Hedbor  int gc_time=[int](([function(string:mixed)]roxenp()->query)("mem_cache_gc"));
b1fca01996-11-12Per Hedbor  string a, b;
3492632000-02-08Martin Nilsson  array c;
9a002e2000-04-19Martin Nilsson  int t=time(1);
a565891999-12-27Martin Nilsson  CACHE_WERR("cache_clean()");
b1fca01996-11-12Per Hedbor  foreach(indices(cache), a) {
b9fbf72001-01-04Martin Nilsson  MORE_CACHE_WERR(" Class " + a);
b1fca01996-11-12Per Hedbor  foreach(indices(cache[a]), b) {
b9fbf72001-01-04Martin Nilsson  MORE_CACHE_WERR(" " + b + " ");
3492632000-02-08Martin Nilsson  c = cache[a][b];
ba73a21996-12-10Per Hedbor #ifdef DEBUG
3492632000-02-08Martin Nilsson  if(!intp(c[TIMESTAMP]))
b1be432000-02-15Martin Nilsson  error(" Illegal timestamp in cache ("+a+":"+b+")\n");
ba73a21996-12-10Per Hedbor #endif
7bcd142000-02-12Martin Nilsson  if(c[TIMEOUT] && c[TIMEOUT] < t) {
b9fbf72001-01-04Martin Nilsson  MORE_CACHE_WERR(" DELETED (explicit timeout)");
b1fca01996-11-12Per Hedbor  m_delete(cache[a], b); }
b1be432000-02-15Martin Nilsson  else { if(!c[SIZE]) {
fc7d5a2001-07-03Martin Nilsson  c[SIZE]=(sizeof(encode_value(b)) + sizeof(encode_value(c[DATA])) + 5*svalsize + 4)/100;
b1be432000-02-15Martin Nilsson  // (Entry size + cache overhead) / arbitrary factor
b9fbf72001-01-04Martin Nilsson  MORE_CACHE_WERR(" Cache entry size percieved as " + ([int]c[SIZE]*100) + " bytes\n");
b1be432000-02-15Martin Nilsson  }
bfac9d2000-02-17Martin Nilsson  if(c[TIMESTAMP]+1 < t && c[TIMESTAMP] + gc_time -
b1be432000-02-15Martin Nilsson  c[SIZE] < t) {
b9fbf72001-01-04Martin Nilsson  MORE_CACHE_WERR(" DELETED");
b1be432000-02-15Martin Nilsson  m_delete(cache[a], b); }
b9fbf72001-01-04Martin Nilsson #ifdef MORE_CACHE_DEBUG
b1be432000-02-15Martin Nilsson  else CACHE_WERR("Ok");
32aa832000-02-02Per Hedbor #endif
b1be432000-02-15Martin Nilsson  }
b1fca01996-11-12Per Hedbor  if(!sizeof(cache[a])) {
b9fbf72001-01-04Martin Nilsson  MORE_CACHE_WERR(" Class DELETED.");
b1fca01996-11-12Per Hedbor  m_delete(cache, a); } } }
4d3bfb2000-02-18Martin Nilsson  call_out(cache_clean, gc_time);
b1fca01996-11-12Per Hedbor }
b3a87b2001-01-21Martin Nilsson 
69c0412001-03-19Martin Nilsson // --- Non-garbing "cache" ----------- private mapping(string:mapping(string:mixed)) nongc_cache; //! Associates a @[value] to a @[key] in a cache identified with //! the @[cache_id]. This cache does not garb, hence it should be //! used for storing data where its size is well controled. void nongarbing_cache_set(string cache_id, string key, mixed value) {
38a2562001-04-19Jonas Wallden  if(nongc_cache[cache_id]) nongc_cache[cache_id][key] = value; else nongc_cache[cache_id] = ([ key:value ]);
69c0412001-03-19Martin Nilsson } //! Returns the value associated to the @[key] in the cache //! identified by @[cache_id] in the non-garbing cache. mixed nongarbing_cache_lookup(string cache_id, string key) { return nongc_cache[cache_id]?nongc_cache[cache_id][key]:([])[0]; } //! Remove a value from the non-garbing cache. void nongarbing_cache_remove(string cache_id, string key) { if(nongc_cache[cache_id]) m_delete(nongc_cache[cache_id], key); } //! Flush a cache in the non-garbing cache. void nongarbing_cache_flush(string cache_id) { m_delete(nongc_cache, cache_id); }
fc7d5a2001-07-03Martin Nilsson mapping(string:array(int)) ngc_status() { mapping(string:array(int)) res = ([]); foreach(indices(nongc_cache), string cache) res[cache] = ({ sizeof(nongc_cache[cache]), sizeof(encode_value(nongc_cache[cache])) }); return res; }
69c0412001-03-19Martin Nilsson 
b3a87b2001-01-21Martin Nilsson // --- Session cache ----------------- #ifndef SESSION_BUCKETS # define SESSION_BUCKETS 4 #endif #ifndef SESSION_SHIFT_TIME
d82ce42001-04-23Martin Nilsson # define SESSION_SHIFT_TIME 15*60
b3a87b2001-01-21Martin Nilsson #endif
9b44022001-03-11Martin Nilsson // The minimum time until which the session should be stored.
296af92001-02-04Martin Nilsson private mapping(string:int) session_persistence;
9b44022001-03-11Martin Nilsson // The sessions, divided into several buckets.
b3a87b2001-01-21Martin Nilsson private array(mapping(string:mixed)) session_buckets;
9b44022001-03-11Martin Nilsson // The database for storage of the sessions. private Sql.Sql db; // The biggest value in session_persistence private int max_persistence;
b3a87b2001-01-21Martin Nilsson 
9b44022001-03-11Martin Nilsson // The low level call for storing a session in the database private void store_session(string id, mixed data, int t) { data = encode_value(data); if(catch(db->query("INSERT INTO session_cache VALUES (%s," + t + ",%s)", id, data))) db->query("UPDATE session_cache SET data=%s, persistence=" + t + " WHERE id=%s", data, id); } // GC that, depending on the sessions session_persistence either // throw the session away or store it in a database.
b3a87b2001-01-21Martin Nilsson private void session_cache_handler() { remove_call_out(session_cache_handler);
296af92001-02-04Martin Nilsson  int t=time(1);
9b44022001-03-11Martin Nilsson  if(max_persistence>t) { clean: foreach(indices(session_buckets[-1]), string id) { if(session_persistence[id]<t) { m_delete(session_buckets[-1], id); m_delete(session_persistence, id); continue; } for(int i; i<SESSION_BUCKETS-2; i++) if(session_buckets[i][id]) { continue clean; } if(objectp(session_buckets[-1][id])) { m_delete(session_buckets[-1], id); m_delete(session_persistence, id); continue; } store_session(id, session_buckets[-1][id], session_persistence[id]);
296af92001-02-04Martin Nilsson  m_delete(session_buckets[-1], id); m_delete(session_persistence, id); } }
9b44022001-03-11Martin Nilsson 
296af92001-02-04Martin Nilsson  session_buckets = ({ ([]) }) + session_buckets[..SESSION_BUCKETS-2];
b3a87b2001-01-21Martin Nilsson  call_out(session_cache_handler, SESSION_SHIFT_TIME); }
9b44022001-03-11Martin Nilsson // Stores all sessions that should be persistent in the database. // This function is called upon exit. private void session_cache_destruct() { remove_call_out(session_cache_handler); int t=time(1); if(max_persistence>t) { report_notice("Synchronizing session cache"); foreach(session_buckets, mapping(string:mixed) session_bucket) foreach(indices(session_bucket), string id) if(session_persistence[id]>t) { store_session(id, session_bucket[id], session_persistence[id]); m_delete(session_persistence, id); } } report_notice("Session cache synchronized\n"); }
14151c2001-04-21Martin Nilsson //! Removes a session from the session cache and session database. void clear_session(string id) { m_delete(session_persistence, id); foreach(session_buckets, mapping bucket) m_delete(bucket, id); db->query("DELETE FROM session_cache WHERE id=%s", id); }
9b44022001-03-11Martin Nilsson //! Returns the data associated with the session @[id]. //! Returns a zero type upon failure.
b3a87b2001-01-21Martin Nilsson mixed get_session_data(string id) { mixed data; foreach(session_buckets, mapping bucket) if(data=bucket[id]) { session_buckets[0][id] = data; return data; }
9b44022001-03-11Martin Nilsson  data = db->query("SELECT data FROM session_cache WHERE id=%s", id); if(sizeof([array]data) && !catch(data=decode_value( ([array(mapping(string:string))]data)[0]->data ))) return data;
b3a87b2001-01-21Martin Nilsson  return ([])[0]; }
9b44022001-03-11Martin Nilsson //! Assiciates the session @[id] to the @[data]. If no @[id] is provided //! a unique id will be generated. The session id is returned from the //! function. The minimum guaranteed storage time may be set with the //! @[persistence] argument. Note that this is not a time out. //! If @[store] is set, the @[data] will be stored in a database directly, //! and not when the garbage collect tries to delete the data. This //! will ensure that the data is kept safe in case the server crashes //! before the next GC. string set_session_data(mixed data, void|string id, void|int persistence, void|int(0..1) store) {
b3a87b2001-01-21Martin Nilsson  if(!id) id = ([function(void:string)]roxenp()->create_unique_id)();
296af92001-02-04Martin Nilsson  session_persistence[id] = persistence;
b3a87b2001-01-21Martin Nilsson  session_buckets[0][id] = data;
9b44022001-03-11Martin Nilsson  max_persistence = max(max_persistence, persistence); if(store && persistence) store_session(id, data, persistence);
b3a87b2001-01-21Martin Nilsson  return id; }
9b44022001-03-11Martin Nilsson // Sets up the session database tables. private void setup_tables() { if(catch(db->query("select id from session_cache where id=''"))) { db->query("CREATE TABLE session_cache (" "id CHAR(32) NOT NULL PRIMARY KEY, " "persistence INT UNSIGNED NOT NULL DEFAULT 0, " "data BLOB NOT NULL)"); } } //! Initializes the session handler. void init_session_cache() {
3909932001-04-09Per Hedbor  db = (([function(string:function(string:object(Sql.Sql)))]master()->resolv) ("DBManager.get"))("local");
9b44022001-03-11Martin Nilsson  if( !db )
60d5622001-05-18Martin Nilsson  report_fatal("No 'local' database!\n"); else setup_tables();
9b44022001-03-11Martin Nilsson }
b1fca01996-11-12Per Hedbor void create() {
1847a21999-09-06Per Hedbor  add_constant( "cache", this_object() );
5b4a542001-02-08Martin Nilsson  cache = ([ ]);
bfac9d2000-02-17Martin Nilsson  call_out(cache_clean, 60);
5b4a542001-02-08Martin Nilsson 
69c0412001-03-19Martin Nilsson  nongc_cache = ([ ]);
5b4a542001-02-08Martin Nilsson  session_buckets = ({ ([]) }) * SESSION_BUCKETS; session_persistence = ([]);
b3a87b2001-01-21Martin Nilsson  call_out(session_cache_handler, SESSION_SHIFT_TIME);
5b4a542001-02-08Martin Nilsson 
b1be432000-02-15Martin Nilsson  CACHE_WERR("Now online.");
b1fca01996-11-12Per Hedbor }
9b44022001-03-11Martin Nilsson  void destroy() { session_cache_destruct(); return; }