2fe7462000-07-02Francesco Chemolli /* * A generic cache front-end * by Francesco Chemolli <kinkie@roxen.com> * */
259f1a2002-02-26Martin Nilsson //! This module serves as a front-end to different kinds of caching systems. //! It uses two helper objects to actually store data, and to determine //! expiration policies. Mechanisms to allow for distributed caching systems //! will be added in time, or at least that is the plan.
a580e12000-09-27Fredrik Hübinette (Hubbe) #pike __REAL_VERSION__
a20af62000-09-26Fredrik Hübinette (Hubbe) 
2fe7462000-07-02Francesco Chemolli #if constant(thread_create) #define do_possibly_threaded_call thread_create #else #define do_possibly_threaded_call call_function #endif #define DEFAULT_CLEANUP_CYCLE 300 private int cleanup_cycle=DEFAULT_CLEANUP_CYCLE;
9eaf1d2008-06-28Martin Nilsson protected object(Cache.Storage.Base) storage; protected object(Cache.Policy.Base) policy;
2fe7462000-07-02Francesco Chemolli 
8b8c2f2001-01-01Francesco Chemolli // TODO: check that Storage Managers return the appropriate zero_type
259f1a2002-02-26Martin Nilsson //! Looks in the cache for an element with the given key and, if available, //! returns it. Returns 0 if the element is not available
2fe7462000-07-02Francesco Chemolli mixed lookup(string key) {
8b8c2f2001-01-01Francesco Chemolli  if (!stringp(key)) key=(string)key; // paranoia.
2fe7462000-07-02Francesco Chemolli  object(Cache.Data) tmp=storage->get(key);
ad3d6d2002-02-14Martin Nilsson  return (tmp?tmp->data():UNDEFINED);
2fe7462000-07-02Francesco Chemolli } //structure: "key" -> (< ({function,args,0|timeout_call_out_id}) ...>)
8b8c2f2001-01-01Francesco Chemolli // this is used in case multiple async-lookups are attempted // against a single key, so that they can be collapsed into a // single backend-request.
2fe7462000-07-02Francesco Chemolli private mapping (string:multiset(array)) pending_requests=([]); private void got_results(string key, int|Cache.Data value) {
ad3d6d2002-02-14Martin Nilsson  mixed data=UNDEFINED;
2fe7462000-07-02Francesco Chemolli  if (pending_requests[key]) { if (value) { data=value->data(); } foreach(indices(pending_requests[key]),array cb) { cb[0](key,value,@cb[1]); if (cb[2]) remove_call_out(cb[2]); } m_delete(pending_requests,key); } //pending requests have timed out. Let's just ignore this result. } //hooray for aliasing. This implementation relies _heavily_ on it //for the "req" argument private void no_results(string key, array req, mixed call_out_id) { pending_requests[key][req]=0; //remove the pending request req[0](key,0,@req[1]); //invoke the callback with no data }
259f1a2002-02-26Martin Nilsson //! Asynchronously look the cache up. //! The callback will be given as arguments the key, the value, and then //! any user-supplied arguments. //! If the timeout (in seconds) expires before any data could be retrieved, //! the callback is called anyways, with 0 as value.
2fe7462000-07-02Francesco Chemolli void alookup(string key, function(string,mixed,mixed...:void) callback, int|float timeout, mixed ... args) {
8b8c2f2001-01-01Francesco Chemolli  if (!stringp(key)) key=(string)key; // paranoia
2fe7462000-07-02Francesco Chemolli  array req = ({callback,args,0}); if (!pending_requests[key]) { //FIXME: add double-indirection pending_requests[key]=(< req >); storage->aget(key,got_results); } else { pending_requests[key][req]=1; //no need to ask the storage manager, since a query is already pending } if (timeout) req[2]=call_out(no_results,timeout,key,req); //aliasing, gotta love it }
259f1a2002-02-26Martin Nilsson //! Sets some value in the cache. Notice that the actual set operation //! might even not happen at all if the set data doesn't make sense. For //! instance, storing an object or a program in an SQL-based backend //! will not be done, and no error will be given about the operation not being //! performed. //! //! Notice that while max_life will most likely be respected (objects will //! be garbage-collected at pre-determined intervals anyways), the //! preciousness . is to be seen as advisory only for the garbage collector //! If some data was stored with the same key, it gets returned. //! Also notice that max_life is @b{relative@} and in seconds. //! dependants are not fully implemented yet. They are implemented after //! a request by Martin Stjerrholm, and their purpose is to have some //! weak form of referential integrity. Simply speaking, they are a list //! of keys which (if present) will be deleted when the stored entry is
3524712015-05-26Martin Nilsson //! deleted (either forcibly or not). They must be handled by the storage
259f1a2002-02-26Martin Nilsson //! manager.
2fe7462000-07-02Francesco Chemolli void store(string key, mixed value, void|int max_life,
8b8c2f2001-01-01Francesco Chemolli  void|float preciousness, void|multiset(string) dependants ) { if (!stringp(key)) key=(string)key; // paranoia
ad3d6d2002-02-14Martin Nilsson  multiset(string) rd=UNDEFINED; // real-dependants, after string-check
8b8c2f2001-01-01Francesco Chemolli  if (dependants) { rd=(<>); foreach((array)dependants,mixed d) { rd[(string)d]=1; } }
2fe7462000-07-02Francesco Chemolli  storage->set(key,value, (max_life?time(1)+max_life:0),
8b8c2f2001-01-01Francesco Chemolli  preciousness,rd);
2fe7462000-07-02Francesco Chemolli }
259f1a2002-02-26Martin Nilsson //! Forcibly removes some key. //! If the 'hard' parameter is supplied and true, deleted objects will also
c071bc2017-11-05Henrik Grubbström (Grubba) //! have their @[lfun::_destruct] method called upon removal by some
259f1a2002-02-26Martin Nilsson //! backends (i.e. memory)
64ce612000-07-05Francesco Chemolli void delete(string key, void|int(0..1)hard) {
8b8c2f2001-01-01Francesco Chemolli  if (!stringp(key)) key=(string)key; // paranoia
64ce612000-07-05Francesco Chemolli  storage->delete(key,hard);
2fe7462000-07-02Francesco Chemolli }
8b8c2f2001-01-01Francesco Chemolli //notice. policy->expire is not guarranteed to be reentrant. // Generally speaking, it will not be, but it would be very stupid to // require so. The whole garbage collection architecture is heavily // oriented to either single-pass or mark-and-sweep algos, it would be // useless to require it be reentrant. int cleanup_lock=0; private void do_cleanup(function expiry_function, object storage) { // I know, generally speaking this would be racey. But this function will // be called at most every cleanup_cycle seconds, so no locking done here. if (cleanup_lock) return; cleanup_lock=1; expiry_function(storage); cleanup_lock=0; }
2fe7462000-07-02Francesco Chemolli 
0bea902006-01-25Henrik Grubbström (Grubba) #if constant(thread_create)
9eaf1d2008-06-28Martin Nilsson protected Thread.Thread cleanup_thread;
1d5b6e2006-01-23Martin Stjernholm 
c071bc2017-11-05Henrik Grubbström (Grubba) protected void _destruct()
1d5b6e2006-01-23Martin Stjernholm { if (Thread.Thread t = cleanup_thread) { cleanup_thread = 0; t->wait(); } }
0bea902006-01-25Henrik Grubbström (Grubba) #endif
1d5b6e2006-01-23Martin Stjernholm 
259f1a2002-02-26Martin Nilsson //!
2fe7462000-07-02Francesco Chemolli void start_cleanup_cycle() { if (master()->asyncp()) { //we're asynchronous. Let's use call_outs call_out(async_cleanup_cache,cleanup_cycle); return; } #if constant(thread_create)
1d5b6e2006-01-23Martin Stjernholm  cleanup_thread = thread_create(threaded_cleanup_cycle);
2fe7462000-07-02Francesco Chemolli #else call_out(async_cleanup_cache,cleanup_cycle); //let's hope we'll get async //sooner or later. #endif }
259f1a2002-02-26Martin Nilsson //!
2fe7462000-07-02Francesco Chemolli void async_cleanup_cache() {
8b8c2f2001-01-01Francesco Chemolli  mixed err=catch { do_possibly_threaded_call(do_cleanup,policy->expire,storage); };
2fe7462000-07-02Francesco Chemolli  call_out(async_cleanup_cache,cleanup_cycle);
8b8c2f2001-01-01Francesco Chemolli  if (err) throw (err);
2fe7462000-07-02Francesco Chemolli }
b4c1f22006-03-17Henrik Grubbström (Grubba) #if constant(thread_create)
259f1a2002-02-26Martin Nilsson //!
2fe7462000-07-02Francesco Chemolli void threaded_cleanup_cycle() { while (1) {
8b8c2f2001-01-01Francesco Chemolli  if (master()->asyncp()) { // might as well use call_out if we're async
2fe7462000-07-02Francesco Chemolli  call_out(async_cleanup_cache,0); return; }
1d5b6e2006-01-23Martin Stjernholm  for (int wait = 0; wait < cleanup_cycle; wait++) { sleep (1); if (!cleanup_thread) return; }
8b8c2f2001-01-01Francesco Chemolli  do_cleanup(policy->expire,storage);
2fe7462000-07-02Francesco Chemolli  } }
b4c1f22006-03-17Henrik Grubbström (Grubba) #endif
2fe7462000-07-02Francesco Chemolli 
259f1a2002-02-26Martin Nilsson //! Creates a new cache object. Required are a storage manager, and an //! expiration policy object.
2fe7462000-07-02Francesco Chemolli void create(Cache.Storage.Base storage_mgr, Cache.Policy.Base policy_mgr, void|int cleanup_cycle_delay) { if (!storage_mgr || !policy_mgr)
484a742002-03-09Martin Nilsson  error ( "I need a storage manager and a policy manager\n" );
2fe7462000-07-02Francesco Chemolli  storage=storage_mgr; policy=policy_mgr; if (cleanup_cycle_delay) cleanup_cycle=cleanup_cycle_delay; start_cleanup_cycle(); } /* * Miscellaneous thoughts. * * Some kind of settings-system will be needed, at least for the policy * manager. Maybe having a couple of pass-throught functions here might help. * * Data-objects should really be created by the Storage Manager, which can * then choose to use specialized forms (i.e. using some SQL tricks to * perform lazy work). * * I chose to go with call_outs for the cleanup cycle, and start a new thread * if possible when doing * cleanup. I have mixed feelings for this choice. On one side, it is quite * cheap and easily implemented. On the other side, it restricts us to * async mode, and creating a new thread can be not-so-cheap. * * It would be nice to have some statistics collection. But for some kind of * stats the storage manager has to be involved (if any kind of efficiency * is desired). However if we wish to do so, either we extend the storage * manager's API, or we're in trouble. */