Index: /trunk/admin/xcache.php
===================================================================
--- /trunk/admin/xcache.php	(revision 113)
+++ /trunk/admin/xcache.php	(revision 114)
@@ -165,13 +165,14 @@
 		$haveinode = false;
 		foreach ($cachelist['cache_list'] as $e) {
-			if (!isset($e['inode'])) {
-				break;
+			if (isset($e['inode'])) {
+				$haveinode = true;
 			}
-			$haveinode = true;
-			$i = &$inodes[$e['inode']];
-			if (isset($i) && $i == 1) {
-				trigger_error("duplicate inode $e[inode]");
+		}
+		if (!$haveinode) {
+			foreach ($cachelist['deleted_list'] as $e) {
+				if (isset($e['inode'])) {
+					$haveinode = true;
+				}
 			}
-			$i ++;
 		}
 	}
Index: /trunk/processor/processor.m4
===================================================================
--- /trunk/processor/processor.m4	(revision 113)
+++ /trunk/processor/processor.m4	(revision 114)
@@ -712,5 +712,4 @@
 dnl }}}
 DEF_STRUCT_P_FUNC(`xc_entry_data_var_t', , `dnl {{{
-	DISPATCH(time_t, etime)
 	IFSTORE(`
 		if (processor->reference) {
@@ -746,4 +745,5 @@
 	DISPATCH(time_t, atime)
 	DISPATCH(time_t, dtime)
+	DISPATCH(long, ttl)
 	DISPATCH(zend_ulong, hits)
 #ifdef IS_UNICODE
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) {
Index: /trunk/xcache.h
===================================================================
--- /trunk/xcache.h	(revision 113)
+++ /trunk/xcache.h	(revision 114)
@@ -157,4 +157,7 @@
 	int deletes_count;
 	xc_hash_t  *hentry; /* hash to entry */
+
+	time_t     last_gc_deletes;
+	time_t     last_gc_expires;
 } xc_cache_t;
 /* }}} */
@@ -218,5 +221,4 @@
 typedef struct {
 	zval   *value;
-	time_t etime;
 } xc_entry_data_var_t;
 /* }}} */
@@ -235,4 +237,5 @@
 	time_t     atime;           /* the atime of this entry */
 	time_t     dtime;           /* the deletion time of this entry */
+	long       ttl;             /* ttl of time entry, var only */
 
 #ifdef IS_UNICODE
Index: /trunk/xcache.ini
===================================================================
--- /trunk/xcache.ini	(revision 113)
+++ /trunk/xcache.ini	(revision 114)
@@ -26,9 +26,17 @@
 ; just a hash hints, you can always store count(items) > slots
 xcache.slots =                8K
+; ttl of the cache item, 0=forever
+xcache.ttl   =                 0
+; interval of gc scanning expired items, 0=no scan, other values is in seconds
+xcache.gc_interval =           0
 
 ; same as aboves but for variable cache
-xcache.var_size =              0
+xcache.var_size  =             0
 xcache.var_count =             1
 xcache.var_slots =            8K
+; default ttl
+xcache.var_ttl   =             0
+xcache.var_maxttl   =          0
+xcache.var_gc_interval =     300
 
 xcache.test =                Off
Index: /trunk/xcache_globals.h
===================================================================
--- /trunk/xcache_globals.h	(revision 113)
+++ /trunk/xcache_globals.h	(revision 114)
@@ -12,4 +12,5 @@
 	xc_stack_t *var_holds;
 	time_t request_time;
+	long   var_default_ttl;
 ZEND_END_MODULE_GLOBALS(xcache)
 
