Index: trunk/xcache.c
===================================================================
--- trunk/xcache.c	(revision 113)
+++ trunk/xcache.c	(revision 114)
@@ -39,4 +39,5 @@
 #include <assert.h>
 
+#define VAR_ENTRY_EXPIRED(pentry) ((pentry)->ttl && XG(request_time) > pentry->ctime + (pentry)->ttl)
 #define CHECK(x, e) do { if ((x) == NULL) { zend_error(E_ERROR, "XCache: " e); goto err; } } while (0)
 #define LOCK(x) xc_lock(x->lck)
@@ -65,4 +66,11 @@
 static xc_hash_t xc_var_hentry = {0};
 
+static zend_ulong xc_php_ttl    = 0;
+static zend_ulong xc_var_maxttl = 0;
+
+enum { xc_deletes_gc_interval = 120 };
+static zend_ulong xc_php_gc_interval = 0;
+static zend_ulong xc_var_gc_interval = 0;
+
 /* total size */
 static zend_ulong xc_php_size  = 0;
@@ -162,21 +170,27 @@
 }
 /* }}} */
+static void xc_entry_remove_real_dmz(xc_entry_t *xce, xc_entry_t **pp TSRMLS_DC) /* {{{ */
+{
+	*pp = xce->next;
+	xce->cache->entries_count --;
+	if (xce->refcount == 0) {
+		xc_entry_free_dmz(xce);
+	}
+	else {
+		xce->next = xce->cache->deletes;
+		xce->cache->deletes = xce;
+		xce->dtime = XG(request_time);
+		xce->cache->deletes_count ++;
+	}
+	return;
+}
+/* }}} */
 static void xc_entry_remove_dmz(xc_entry_t *xce TSRMLS_DC) /* {{{ */
 {
-	xc_entry_t **last = &(xce->cache->entries[xce->hvalue]);
+	xc_entry_t **pp = &(xce->cache->entries[xce->hvalue]);
 	xc_entry_t *p;
-	for (p = *last; p; last = &(p->next), p = p->next) {
+	for (p = *pp; p; pp = &(p->next), p = p->next) {
 		if (xc_entry_equal_dmz(xce, p)) {
-			*last = p->next;
-			xce->cache->entries_count ++;
-			if (p->refcount == 0) {
-				xc_entry_free_dmz(p);
-			}
-			else {
-				p->next = p->cache->deletes;
-				p->cache->deletes = p;
-				p->dtime = XG(request_time);
-				xce->cache->deletes_count ++;
-			}
+			xc_entry_remove_real_dmz(xce, pp TSRMLS_CC);
 			return;
 		}
@@ -219,4 +233,134 @@
 #endif
 
+/* helper function that loop through each entry */
+#define XC_ENTRY_APPLY_FUNC(name) int name(xc_entry_t *entry TSRMLS_DC)
+typedef XC_ENTRY_APPLY_FUNC((*cache_apply_dmz_func_t));
+static void xc_entry_apply_dmz(xc_cache_t *cache, cache_apply_dmz_func_t apply_func TSRMLS_DC) /* {{{ */
+{
+	xc_entry_t *p, **pp;
+	int i, c;
+
+	for (i = 0, c = cache->hentry->size; i < c; i ++) {
+		pp = &(cache->entries[i]);
+		for (p = *pp; p; p = p->next) {
+			if (apply_func(p TSRMLS_CC)) {
+				xc_entry_remove_real_dmz(p, pp TSRMLS_CC);
+			}
+			else {
+				pp = &(p->next);
+			}
+		}
+	}
+}
+/* }}} */
+
+#define XC_CACHE_APPLY_FUNC(name) void name(xc_cache_t *cache TSRMLS_DC)
+/* call graph:
+ * xc_php_gc_expires -> xc_gc_expires_var_one -> xc_entry_apply_dmz -> xc_gc_php_entry_expires_dmz
+ * xc_var_gc_expires -> xc_gc_expires_one -> xc_entry_apply_dmz -> xc_gc_var_entry_expires_dmz
+ */
+static XC_ENTRY_APPLY_FUNC(xc_gc_php_entry_expires_dmz) /* {{{ */
+{
+	fprintf(stderr, "ttl %d, %d %d\n", XG(request_time), entry->atime, xc_php_ttl);
+	if (XG(request_time) > entry->atime + xc_php_ttl) {
+		return 1;
+	}
+	return 0;
+}
+/* }}} */
+static XC_ENTRY_APPLY_FUNC(xc_gc_var_entry_expires_dmz) /* {{{ */
+{
+	if (VAR_ENTRY_EXPIRED(entry)) {
+		return 1;
+	}
+	return 0;
+}
+/* }}} */
+static void xc_gc_expires_one(xc_cache_t *cache, zend_ulong gc_interval, cache_apply_dmz_func_t apply_func TSRMLS_DC) /* {{{ */
+{
+	fprintf(stderr, "interval %d, %d %d\n", XG(request_time), cache->last_gc_expires, gc_interval);
+	if (XG(request_time) - cache->last_gc_expires > gc_interval) {
+		ENTER_LOCK(cache) {
+			if (XG(request_time) - cache->last_gc_expires > gc_interval) {
+				cache->last_gc_expires = XG(request_time);
+				xc_entry_apply_dmz(cache, apply_func TSRMLS_CC);
+			}
+		} LEAVE_LOCK(cache);
+	}
+}
+/* }}} */
+static void xc_php_gc_expires(TSRMLS_D) /* {{{ */
+{
+	int i, c;
+
+	if (!xc_php_ttl || !xc_php_gc_interval) {
+		return;
+	}
+
+	for (i = 0, c = xc_php_hcache.size; i < c; i ++) {
+		xc_gc_expires_one(xc_php_caches[i], xc_php_gc_interval, xc_gc_php_entry_expires_dmz TSRMLS_CC);
+	}
+}
+/* }}} */
+static void xc_var_gc_expires(TSRMLS_D) /* {{{ */
+{
+	int i, c;
+
+	if (!xc_var_gc_interval) {
+		return;
+	}
+
+	for (i = 0, c = xc_var_hcache.size; i < c; i ++) {
+		xc_gc_expires_one(xc_var_caches[i], xc_var_gc_interval, xc_gc_var_entry_expires_dmz TSRMLS_CC);
+	}
+}
+/* }}} */
+
+static XC_CACHE_APPLY_FUNC(xc_gc_delete_dmz) /* {{{ */
+{
+	xc_entry_t *p, **pp;
+
+	pp = &cache->deletes;
+	for (p = *pp; p; p = p->next) {
+		if (XG(request_time) - p->dtime > 3600) {
+			p->refcount = 0;
+			/* issue warning here */
+		}
+		if (p->refcount == 0) {
+			*pp = p->next;
+			cache->deletes_count --;
+			xc_entry_free_dmz(p);
+		}
+		else {
+			pp = &(p->next);
+		}
+	}
+}
+/* }}} */
+static XC_CACHE_APPLY_FUNC(xc_gc_deletes_one) /* {{{ */
+{
+	if (cache->deletes && XG(request_time) - cache->last_gc_deletes > xc_deletes_gc_interval) {
+		ENTER_LOCK(cache) {
+			if (cache->deletes && XG(request_time) - cache->last_gc_deletes > xc_deletes_gc_interval) {
+				xc_gc_delete_dmz(cache TSRMLS_CC);
+			}
+		} LEAVE_LOCK(cache);
+	}
+}
+/* }}} */
+static void xc_gc_deletes(TSRMLS_D) /* {{{ */
+{
+	int i, c;
+
+	for (i = 0, c = xc_php_hcache.size; i < c; i ++) {
+		xc_gc_deletes_one(xc_php_caches[i] TSRMLS_CC);
+	}
+
+	for (i = 0, c = xc_var_hcache.size; i < c; i ++) {
+		xc_gc_deletes_one(xc_var_caches[i] TSRMLS_CC);
+	}
+}
+/* }}} */
+
 /* helper functions for user functions */
 static void xc_fillinfo_dmz(xc_cache_t *cache, zval *return_value TSRMLS_DC) /* {{{ */
@@ -236,6 +380,6 @@
 	add_assoc_long_ex(return_value, ZEND_STRS("ooms"),      cache->ooms);
 
-	add_assoc_long_ex(return_value, ZEND_STRS("cached"), cache->entries_count);
-	add_assoc_long_ex(return_value, ZEND_STRS("deleted"), cache->deletes_count);
+	add_assoc_long_ex(return_value, ZEND_STRS("cached"),    cache->entries_count);
+	add_assoc_long_ex(return_value, ZEND_STRS("deleted"),   cache->deletes_count);
 
 	MAKE_STD_ZVAL(blocks);
@@ -402,42 +546,5 @@
 }
 /* }}} */
-static void xc_entry_gc_real(xc_cache_t **caches, int size TSRMLS_DC) /* {{{ */
-{
-	time_t t = XG(request_time);
-	int i;
-	xc_cache_t *cache;
-	typedef xc_entry_t *xc_delete_t;
-	xc_delete_t p, *last;
-
-	for (i = 0; i < size; i ++) {
-		cache = caches[i];
-		ENTER_LOCK(cache) {
-			if (cache->deletes) {
-				last = (xc_delete_t *) &cache->deletes;
-				for (p = *last; p; p = p->next) {
-					if (t - p->dtime > 3600) {
-						p->refcount = 0;
-						/* issue warning here */
-					}
-					if (p->refcount == 0) {
-						*last = p->next;
-						cache->deletes_count --;
-						xc_entry_free_dmz(p);
-					}
-					else {
-						last = &(p->next);
-					}
-				}
-			}
-		} LEAVE_LOCK(cache);
-	}
-}
-/* }}} */
-static void xc_entry_gc(TSRMLS_D) /* {{{ */
-{
-	xc_entry_gc_real(xc_php_caches, xc_php_hcache.size TSRMLS_CC);
-	xc_entry_gc_real(xc_var_caches, xc_var_hcache.size TSRMLS_CC);
-}
-/* }}} */
+
 static inline void xc_entry_unholds_real(xc_stack_t *holds, xc_cache_t **caches, int cachecount TSRMLS_DC) /* {{{ */
 {
@@ -781,5 +888,4 @@
 	}
 	/* }}} */
-	xc_entry_gc(TSRMLS_C);
 	ENTER_LOCK(cache) { /* {{{ store/add entry */
 		stored_xce = xc_entry_store_dmz(&xce TSRMLS_CC);
@@ -987,4 +1093,5 @@
 	xc_cache_t **caches = NULL, *cache;
 	xc_mem_t *mem;
+	time_t now = time(NULL);
 	int i;
 	xc_memsize_t memsize;
@@ -1017,4 +1124,6 @@
 		cache->mem     = mem;
 		cache->cacheid = i;
+		cache->last_gc_deletes = now;
+		cache->last_gc_expires = now;
 		caches[i] = cache;
 	}
@@ -1140,4 +1249,7 @@
 {
 	xc_entry_unholds(TSRMLS_C);
+	xc_php_gc_expires(TSRMLS_C);
+	xc_var_gc_expires(TSRMLS_C);
+	xc_gc_deletes(TSRMLS_C);
 #ifdef HAVE_XCACHE_COVERAGER
 	xc_coverager_request_shutdown(TSRMLS_C);
@@ -1345,5 +1457,5 @@
 					}
 				} LEAVE_LOCK(cache);
-				xc_entry_gc(TSRMLS_C);
+				xc_gc_deletes(TSRMLS_C);
 			}
 			break;
@@ -1417,5 +1529,4 @@
 }
 /* }}} */
-#define TIME_MAX (sizeof(time_t) == sizeof(long) ? LONG_MAX : INT_MAX)
 /* {{{ proto mixed xcache_get(string name)
    Get cached data by specified name */
@@ -1435,5 +1546,5 @@
 		stored_xce = xc_entry_find_dmz(&xce TSRMLS_CC);
 		if (stored_xce) {
-			if (XG(request_time) <= stored_xce->data.var->etime) {
+			if (XG(request_time) <= stored_xce->ctime + stored_xce->ttl) {
 				xc_processor_restore_zval(return_value, stored_xce->data.var->value TSRMLS_CC);
 				/* return */
@@ -1457,9 +1568,15 @@
 	zval *name;
 	zval *value;
-	long ttl = 0;
-
-	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zz|l", &name, &value, &ttl) == FAILURE) {
+
+	xce.ttl = XG(var_ttl);
+	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zz|l", &name, &value, &xce.ttl) == FAILURE) {
 		return;
 	}
+
+	/* max ttl */
+	if (xc_var_maxttl && (!xce.ttl || xce.ttl > xc_var_maxttl)) {
+		xce.ttl = xc_var_maxttl;
+	}
+
 	xce.data.var = &var;
 	xc_entry_init_key_var(&xce, name TSRMLS_CC);
@@ -1471,5 +1588,4 @@
 		}
 		var.value = value;
-		var.etime = ttl ? XG(request_time) + ttl : TIME_MAX;
 		RETVAL_BOOL(xc_entry_store_dmz(&xce TSRMLS_CC) != NULL ? 1 : 0);
 	} LEAVE_LOCK(xce.cache);
@@ -1493,5 +1609,5 @@
 		stored_xce = xc_entry_find_dmz(&xce TSRMLS_CC);
 		if (stored_xce) {
-			if (XG(request_time) <= stored_xce->data.var->etime) {
+			if (VAR_ENTRY_EXPIRED(stored_xce)) {
 				RETVAL_TRUE;
 				/* return */
@@ -1539,11 +1655,17 @@
 	zval *name;
 	long count = 1;
-	long ttl = 0;
 	long value = 0;
 	zval oldzval;
 
-	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z|ll", &name, &count, &ttl) == FAILURE) {
+	xce.ttl = XG(var_ttl);
+	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z|ll", &name, &count, &xce.ttl) == FAILURE) {
 		return;
 	}
+
+	/* max ttl */
+	if (xc_var_maxttl && (!xce.ttl || xce.ttl > xc_var_maxttl)) {
+		xce.ttl = xc_var_maxttl;
+	}
+
 	xce.data.var = &var;
 	xc_entry_init_key_var(&xce, name TSRMLS_CC);
@@ -1555,7 +1677,6 @@
 			fprintf(stderr, "incdec: gotxce %s\n", xce.name.str.val);
 #endif
-			stored_var = stored_xce->data.var;
 			/* timeout */
-			if (XG(request_time) > stored_var->etime) {
+			if (VAR_ENTRY_EXPIRED(stored_xce)) {
 #ifdef DEBUG
 				fprintf(stderr, "incdec: expired\n");
@@ -1566,5 +1687,8 @@
 			else {
 				/* do it in place */
+				stored_var = stored_xce->data.var;
 				if (Z_TYPE_P(stored_var->value) == IS_LONG) {
+					stored_xce->ctime = XG(request_time);
+					stored_xce->ttl   = xce.ttl;
 #ifdef DEBUG
 					fprintf(stderr, "incdec: islong\n");
@@ -1574,5 +1698,5 @@
 					RETVAL_LONG(value);
 					Z_LVAL_P(stored_var->value) = value;
-					break;
+					break; /* leave lock */
 				}
 				else {
@@ -1596,5 +1720,5 @@
 		RETVAL_LONG(value);
 		var.value = return_value;
-		var.etime = ttl ? XG(request_time) + ttl : TIME_MAX;
+
 		if (stored_xce) {
 			xce.atime = stored_xce->atime;
@@ -1913,6 +2037,6 @@
 }
 
-#ifdef ZEND_ENGINE_2
-#define OnUpdateInt OnUpdateLong
+#ifndef ZEND_ENGINE_2
+#define OnUpdateLong OnUpdateInt
 #endif
 
@@ -1932,4 +2056,5 @@
 	STD_PHP_INI_BOOLEAN("xcache.optimizer",              "0", PHP_INI_ALL,    OnUpdateBool,        optimizer,         zend_xcache_globals, xcache_globals)
 #endif
+	STD_PHP_INI_BOOLEAN("xcache.var_ttl",                "0", PHP_INI_ALL,    OnUpdateLong,        var_ttl,   zend_xcache_globals, xcache_globals)
 #ifdef HAVE_XCACHE_COVERAGER
 	PHP_INI_ENTRY1     ("xcache.coveragedump_directory", "/tmp/pcov/", PHP_INI_SYSTEM, xc_OnUpdateString,   &xc_coveragedump_dir)
@@ -1992,11 +2117,15 @@
 	php_info_print_table_start();
 	php_info_print_table_header(2, "Directive ", "Value");
-	xc_config_long_disp("xcache.size",       "0");
-	xc_config_hash_disp("xcache.count",      "1");
-	xc_config_hash_disp("xcache.slots",     "8K");
-
-	xc_config_long_disp("xcache.var_size",   "0");
-	xc_config_hash_disp("xcache.var_count",  "1");
-	xc_config_hash_disp("xcache.var_slots", "8K");
+	xc_config_long_disp("xcache.size",        "0");
+	xc_config_hash_disp("xcache.count",       "1");
+	xc_config_hash_disp("xcache.slots",      "8K");
+	xc_config_hash_disp("xcache.ttl",         "0");
+	xc_config_hash_disp("xcache.gc_interval", "0");
+
+	xc_config_long_disp("xcache.var_size",           "0");
+	xc_config_hash_disp("xcache.var_count",          "1");
+	xc_config_hash_disp("xcache.var_slots",         "8K");
+	xc_config_hash_disp("xcache.var_maxttl",         "0");
+	xc_config_hash_disp("xcache.var_gc_interval",  "300");
 	php_info_print_table_end();
 
@@ -2097,11 +2226,15 @@
 	}
 
-	xc_config_long(&xc_php_size,   "xcache.size",       "0");
-	xc_config_hash(&xc_php_hcache, "xcache.count",      "1");
-	xc_config_hash(&xc_php_hentry, "xcache.slots",     "8K");
-
-	xc_config_long(&xc_var_size,   "xcache.var_size",   "0");
-	xc_config_hash(&xc_var_hcache, "xcache.var_count",  "1");
-	xc_config_hash(&xc_var_hentry, "xcache.var_slots", "8K");
+	xc_config_long(&xc_php_size,       "xcache.size",        "0");
+	xc_config_hash(&xc_php_hcache,     "xcache.count",       "1");
+	xc_config_hash(&xc_php_hentry,     "xcache.slots",      "8K");
+	xc_config_long(&xc_php_ttl,        "xcache.ttl",         "0");
+	xc_config_long(&xc_php_gc_interval, "xcache.gc_interval", "0");
+
+	xc_config_long(&xc_var_size,       "xcache.var_size",          "0");
+	xc_config_hash(&xc_var_hcache,     "xcache.var_count",         "1");
+	xc_config_hash(&xc_var_hentry,     "xcache.var_slots",        "8K");
+	xc_config_long(&xc_var_maxttl,     "xcache.var_maxttl",        "0");
+	xc_config_long(&xc_var_gc_interval, "xcache.var_gc_interval", "120");
 
 	if (xc_php_size <= 0) {
