From: Geoff Simmons <geoff@uplex.de>
Date: Tue, 1 Mar 2011 17:34:41 +0100
Subject: [PATCH] backend conditional requests first release

---
 bin/varnishd/cache.h              |   14 ++
 bin/varnishd/cache_center.c       |   57 +++++-
 bin/varnishd/cache_expire.c       |    5 +-
 bin/varnishd/cache_fetch.c        |    7 +-
 bin/varnishd/cache_hash.c         |   38 ++++-
 bin/varnishd/cache_http.c         |  124 +++++++++++
 bin/varnishd/cache_vrt.c          |    6 +
 bin/varnishd/cache_vrt_var.c      |  190 ++++++++++++++---
 bin/varnishd/heritage.h           |    3 +
 bin/varnishd/mgt_param.c          |   10 +
 bin/varnishd/stevedore.c          |   45 ++++
 bin/varnishd/stevedore.h          |    6 +
 bin/varnishd/storage_file.c       |    1 +
 bin/varnishd/storage_malloc.c     |    1 +
 bin/varnishd/storage_persistent.c |    1 +
 bin/varnishd/storage_synth.c      |    1 +
 bin/varnishd/storage_umem.c       |    1 +
 bin/varnishtest/tests/i00000.vtc  |  405 +++++++++++++++++++++++++++++++++++++
 bin/varnishtest/tests/i00001.vtc  |   69 +++++++
 bin/varnishtest/tests/i00002.vtc  |  113 ++++++++++
 bin/varnishtest/tests/i00003.vtc  |   90 ++++++++
 bin/varnishtest/tests/i00004.vtc  |  153 ++++++++++++++
 bin/varnishtest/tests/i00005.vtc  |  124 +++++++++++
 bin/varnishtest/tests/i00006.vtc  |   88 ++++++++
 bin/varnishtest/tests/i00007.vtc  |  126 ++++++++++++
 include/vrt.h                     |    2 +-
 include/vsc_fields.h              |    3 +
 lib/libvcl/generate.py            |   72 +++++++
 28 files changed, 1709 insertions(+), 46 deletions(-)
 create mode 100644 bin/varnishtest/tests/i00000.vtc
 create mode 100644 bin/varnishtest/tests/i00001.vtc
 create mode 100644 bin/varnishtest/tests/i00002.vtc
 create mode 100644 bin/varnishtest/tests/i00003.vtc
 create mode 100644 bin/varnishtest/tests/i00004.vtc
 create mode 100644 bin/varnishtest/tests/i00005.vtc
 create mode 100644 bin/varnishtest/tests/i00006.vtc
 create mode 100644 bin/varnishtest/tests/i00007.vtc

diff --git a/bin/varnishd/cache.h b/bin/varnishd/cache.h
index 9ca8034..0b6d240 100644
--- a/bin/varnishd/cache.h
+++ b/bin/varnishd/cache.h
@@ -274,6 +274,7 @@ struct worker {
 	double			entered;
 	double			ttl;
 	double			grace;
+        double                  conditional_timeout;
 
 	/* This is only here so VRT can find it */
 	const char		*storage_hint;
@@ -455,6 +456,7 @@ struct object {
 	double			age;
 	double			entered;
 	double			grace;
+        double                  conditional_timeout;
 
 	double			last_modified;
 	double			last_lru;
@@ -468,6 +470,11 @@ struct object {
 	double			last_use;
 
 	int			hits;
+
+        /* Flag this object as a backend conditional candidate
+         * XXX: Is just non-NULL stale_obj enough?
+         */
+        unsigned                freshen;
 };
 
 /* -------------------------------------------------------------------*/
@@ -537,6 +544,7 @@ struct sess {
 	struct objcore		*objcore;
 	struct VCL_conf		*vcl;
 
+	struct object		*stale_obj;
 	/* The busy objhead we sleep on */
 	struct objhead		*hash_objhead;
 
@@ -675,6 +683,12 @@ void http_SetResp(struct http *to, const char *proto, int status,
 void http_FilterFields(struct worker *w, int fd, struct http *to,
     const struct http *fm, unsigned how);
 void http_FilterHeader(const struct sess *sp, unsigned how);
+
+/* Check if a refresh should be done */
+void http_CheckRefresh(struct sess *sp);
+/* Check if we got 304 response */
+int http_Check304(struct sess *sp);
+
 void http_PutProtocol(struct worker *w, int fd, const struct http *to,
     const char *protocol);
 void http_PutStatus(struct http *to, int status);
diff --git a/bin/varnishd/cache_center.c b/bin/varnishd/cache_center.c
index f6065f0..80180cf 100644
--- a/bin/varnishd/cache_center.c
+++ b/bin/varnishd/cache_center.c
@@ -462,10 +462,10 @@ DOT errfetch [label="ERROR",shape=plaintext]
 static int
 cnt_fetch(struct sess *sp)
 {
-	int i;
+	int i, refresh_status = 0;
 	struct http *hp, *hp2;
 	char *b;
-	unsigned l, nhttp;
+	unsigned l, nhttp, stale_nhttp;
 	int varyl = 0, pass;
 	struct vsb *vary = NULL;
 
@@ -528,6 +528,7 @@ cnt_fetch(struct sess *sp)
 	sp->wrk->entered = TIM_real();
 	sp->wrk->age = 0;
 	sp->wrk->ttl = RFC2616_Ttl(sp);
+        sp->wrk->conditional_timeout = params->default_conditional_timeout;
 
 	/*
 	 * Initial cacheability determination per [RFC2616, 13.4]
@@ -635,6 +636,10 @@ cnt_fetch(struct sess *sp)
 
 	l = http_EstimateWS(sp->wrk->beresp,
 	    pass ? HTTPH_R_PASS : HTTPH_A_INS, &nhttp);
+        if (sp->stale_obj) {
+            l += http_EstimateWS(sp->stale_obj->http, 0, &stale_nhttp);
+            nhttp += stale_nhttp;
+        }
 
 	/* Create Vary instructions */
 	if (sp->objcore != NULL) {
@@ -661,6 +666,9 @@ cnt_fetch(struct sess *sp)
 	    sp->wrk->ttl, nhttp);
 	/* XXX: -> 513 */
 	CHECK_OBJ_NOTNULL(sp->obj, OBJECT_MAGIC);
+        if (sp->stale_obj)
+            sp->obj->freshen = sp->stale_obj->freshen;
+        sp->obj->conditional_timeout = sp->wrk->conditional_timeout;
 
 	sp->wrk->storage_hint = NULL;
 
@@ -689,11 +697,31 @@ cnt_fetch(struct sess *sp)
 
 	hp2->logtag = HTTP_Obj;
 	http_CopyResp(hp2, hp);
-	http_FilterFields(sp->wrk, sp->fd, hp2, hp,
-	    pass ? HTTPH_R_PASS : HTTPH_A_INS);
-	http_CopyHome(sp->wrk, sp->fd, hp2);
-
-	if (http_GetHdr(hp, H_Last_Modified, &b))
+        
+        http_FilterFields(sp->wrk, sp->fd, hp2, hp,
+            	pass ? HTTPH_R_PASS : HTTPH_A_INS);
+
+        /* If we found a candidate for conditional backend request, attempt it
+         * now. If backend responds with 304, http_Check304() merges stale_obj
+         * into sp->obj; for 200, we just use that response. In either case,
+         * the stale_obj is no longer needed in the cache, so discard it.
+         * Any other response is eligible to deliver as is to the client.
+         */
+        if (sp->stale_obj) {
+            refresh_status = http_Check304(sp);
+            if (refresh_status == 200 || refresh_status == 304) {
+                assert(sp->obj->http->status == 200);
+                sp->stale_obj->grace = 0;
+                sp->stale_obj->ttl = sp->t_req - 1.0;
+                HSH_Deref(sp->wrk, NULL, &sp->stale_obj);
+                if (sp->stale_obj != NULL)
+                    EXP_Rearm(sp->stale_obj);
+            }
+        }
+        http_CopyHome(sp->wrk, sp->fd, hp2);
+
+	if (http_GetHdr(hp, H_Last_Modified, &b)
+            || http_GetHdr(sp->obj->http, H_Last_Modified, &b))
 		sp->obj->last_modified = TIM_parse(b);
 	else
 		sp->obj->last_modified = sp->wrk->entered;
@@ -735,7 +763,10 @@ cnt_fetch(struct sess *sp)
 		WRONG("Illegal action in vcl_fetch{}");
 	}
 
-	if (sp->obj->objcore != NULL) {
+        if (sp->obj->freshen)
+            sp->obj->freshen = 0;
+
+        if (sp->obj->objcore != NULL) {
 		EXP_Insert(sp->obj);
 		AN(sp->obj->objcore);
 		AN(sp->obj->objcore->ban);
@@ -892,6 +923,7 @@ cnt_lookup(struct sess *sp)
 
 	/* If we inserted a new object it's a miss */
 	if (oc->flags & OC_F_BUSY) {
+                /* XXX: Do we count this stat if there's a stale hit? */
 		sp->wrk->stats.cache_miss++;
 
 		sp->objcore = oc;
@@ -907,6 +939,8 @@ cnt_lookup(struct sess *sp)
 		sp->wrk->stats.cache_hitpass++;
 		WSP(sp, SLT_HitPass, "%u", sp->obj->xid);
 		(void)HSH_Deref(sp->wrk, NULL, &sp->obj);
+                if (sp->stale_obj != NULL)
+                    (void)HSH_Deref(sp->wrk, NULL, &sp->stale_obj);
 		sp->objcore = NULL;
 		sp->step = STP_PASS;
 		return (0);
@@ -968,6 +1002,13 @@ cnt_miss(struct sess *sp)
 	sp->wrk->connect_timeout = 0;
 	sp->wrk->first_byte_timeout = 0;
 	sp->wrk->between_bytes_timeout = 0;
+
+        /* If a candidate for a conditional backend request was found,
+         * add If-Modified-Since and/or If-None-Match to the bereq.
+         */
+        if (sp->stale_obj)
+                http_CheckRefresh(sp);
+
 	VCL_miss_method(sp);
 	switch(sp->handling) {
 	case VCL_RET_ERROR:
diff --git a/bin/varnishd/cache_expire.c b/bin/varnishd/cache_expire.c
index 5d2e588..b45e28e 100644
--- a/bin/varnishd/cache_expire.c
+++ b/bin/varnishd/cache_expire.c
@@ -65,14 +65,15 @@ static int
 update_object_when(const struct object *o)
 {
 	struct objcore *oc;
-	double when;
+	double when, grace;
 
 	CHECK_OBJ_NOTNULL(o, OBJECT_MAGIC);
 	oc = o->objcore;
 	CHECK_OBJ_NOTNULL(oc, OBJCORE_MAGIC);
 	Lck_AssertHeld(&exp_mtx);
 
-	when = o->ttl + HSH_Grace(o->grace);
+        grace = HSH_Grace(o->grace);
+	when = o->ttl + (grace > o->conditional_timeout ? grace : o->conditional_timeout);
 	assert(!isnan(when));
 	if (when == oc->timer_when)
 		return (0);
diff --git a/bin/varnishd/cache_fetch.c b/bin/varnishd/cache_fetch.c
index 911d576..2ce6ab6 100644
--- a/bin/varnishd/cache_fetch.c
+++ b/bin/varnishd/cache_fetch.c
@@ -505,8 +505,13 @@ FetchBody(struct sess *sp, const struct http *hp)
 	 * XXX: Missing:  RFC2616 sec. 4.4 in re 1xx, 204 & 304 responses
 	 */
 
+        /* If we've freshened from another object and got a "Not Modified"
+         * response, then we have already duped the other object's body.
+         */
+        if (!(sp->obj->freshen && hp->status == 304)) 
+        	AZ(VTAILQ_FIRST(&sp->obj->store));
+
 	AZ(sp->wrk->vgz_rx);
-	AZ(VTAILQ_FIRST(&sp->obj->store));
 	switch (sp->wrk->body_status) {
 	case BS_NONE:
 		cls = 0;
diff --git a/bin/varnishd/cache_hash.c b/bin/varnishd/cache_hash.c
index b3363cc..f78ee8e 100644
--- a/bin/varnishd/cache_hash.c
+++ b/bin/varnishd/cache_hash.c
@@ -312,13 +312,16 @@ HSH_Lookup(struct sess *sp, struct objhead **poh)
 	struct objcore *oc;
 	struct objcore *busy_oc, *grace_oc;
 	struct object *o;
-	double grace_ttl;
+	struct object *stale_o;       /* for freshness check */
+	double grace_ttl, stale_ttl;
+        char *p;
 
 	CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);
 	CHECK_OBJ_NOTNULL(sp->wrk, WORKER_MAGIC);
 	CHECK_OBJ_NOTNULL(sp->http, HTTP_MAGIC);
 	CHECK_OBJ_NOTNULL(sp->director, DIRECTOR_MAGIC);
 	AN(hash);
+        AZ(sp->stale_obj);
 	w = sp->wrk;
 
 	HSH_Prealloc(sp);
@@ -346,7 +349,9 @@ HSH_Lookup(struct sess *sp, struct objhead **poh)
 	assert(oh->refcnt > 0);
 	busy_oc = NULL;
 	grace_oc = NULL;
+	stale_o = NULL;       /* for freshness check */
 	grace_ttl = NAN;
+        stale_ttl = NAN;
 	VTAILQ_FOREACH(oc, &oh->objcs, list) {
 		/* Must be at least our own ref + the objcore we examine */
 		assert(oh->refcnt > 1);
@@ -364,6 +369,9 @@ HSH_Lookup(struct sess *sp, struct objhead **poh)
 
 		if (o->ttl == 0)
 			continue;
+                /* Don't pick if undergoing refresh. XXX: ? */
+                if (o->freshen)
+                        continue;
 		if (BAN_CheckObject(o, sp))
 			continue;
 		if (o->vary != NULL && !VRY_Match(sp, o->vary))
@@ -373,6 +381,22 @@ HSH_Lookup(struct sess *sp, struct objhead **poh)
 		if (o->ttl >= sp->t_req)
 			break;
 
+		/* At this point we know o->ttl < sp->t_req, o is not banned,
+                 * and o is not a Vary match.
+                 * The object may be used for a conditional backend request if
+                 * - conditional_timeout has not elapsed, and
+                 * - it has a Last-Modified and/or an ETag header.
+                 * If there are several, use the least expired one.
+                 */
+               if (o->ttl + o->conditional_timeout >= sp->t_req
+                   && (http_GetHdr(o->http, H_Last_Modified, &p)
+                       || http_GetHdr(o->http, H_ETag, &p)))
+                   if (stale_o == NULL || stale_ttl < o->ttl)
+                   {
+                       stale_o = o;
+                       stale_ttl = o->ttl;
+                   }
+
 		/*
 		 * Remember any matching objects inside their grace period
 		 * and if there are several, use the least expired one.
@@ -451,6 +475,18 @@ HSH_Lookup(struct sess *sp, struct objhead **poh)
 		return (NULL);
 	}
 
+        /* If we saved stale_o, it is a candidate for the conditional backend
+         * request. */
+        AZ(oc && !sp->hash_always_miss);
+        AZ(busy_oc);
+        if (stale_o != NULL) {
+                AZ(stale_o->objcore->flags & OC_F_BUSY);
+                stale_o->freshen = 1;
+                Lck_AssertHeld(&oh->mtx);
+                stale_o->objcore->refcnt++;
+                sp->stale_obj = stale_o;
+        }
+
 	/* Insert (precreated) objcore in objecthead */
 	oc = w->nobjcore;
 	w->nobjcore = NULL;
diff --git a/bin/varnishd/cache_http.c b/bin/varnishd/cache_http.c
index 4ebf878..3617f63 100644
--- a/bin/varnishd/cache_http.c
+++ b/bin/varnishd/cache_http.c
@@ -43,6 +43,8 @@ SVNID("$Id$")
 
 #include "vct.h"
 #include "cache.h"
+#include "hash_slinger.h"
+#include "stevedore.h"
 
 #ifndef HAVE_STRLCPY
 #include <compat/strlcpy.h>
@@ -71,6 +73,9 @@ static const enum vsl_tag logmtx[][HTTP_HDR_FIRST + 1] = {
 };
 /*lint -restore */
 
+void http_FilterMissingFields(struct worker *w, int fd, struct http *to,
+    const struct http *fm);
+
 static enum vsl_tag
 http2shmlog(const struct http *hp, int t)
 {
@@ -855,6 +860,36 @@ http_FilterFields(struct worker *w, int fd, struct http *to,
 	}
 }
 
+/*---------------------------------------------------------------------
+ * Same as http_FilterFields but keep any existing hdrs in fm.
+ * Furthermore, before copy, check if fm already has that hdr, and if so
+ * do not copy.  Used for 304 refresh processing.
+ */
+
+/* XXX: uplex/GS: Also, don't filter according to the "how" bitmap in
+ *      http_headers.h. We only use this to copy from one cached object to
+ *      another, so if a header made into the first object, we want it.
+ */
+
+void
+http_FilterMissingFields(struct worker *w, int fd, struct http *to,
+    const struct http *fm)
+{
+	unsigned u;
+	unsigned hdrlen;
+
+	CHECK_OBJ_NOTNULL(fm, HTTP_MAGIC);
+	CHECK_OBJ_NOTNULL(to, HTTP_MAGIC);
+	for (u = HTTP_HDR_FIRST; u < fm->nhd; u++) {
+		if (fm->hd[u].b == NULL)
+			continue;
+                hdrlen = strchr(fm->hd[u].b, ':') - fm->hd[u].b;
+                if (http_findhdr(to, hdrlen, fm->hd[u].b))
+                    continue;
+                http_copyheader(w, fd, to, fm, u);
+	}
+}
+
 /*--------------------------------------------------------------------*/
 
 void
@@ -876,6 +911,95 @@ http_FilterHeader(const struct sess *sp, unsigned how)
 	http_PrintfHeader(sp->wrk, sp->fd, hp, "X-Varnish: %u", sp->xid);
 }
 
+/*-------------------------------------------------------------------
+ * This function checks for sp->freshen_obj.  If present, HSH_Lookup()
+ * found an expired object that qualifies for a refresh check,
+ * so add the appropriate headers.
+ */
+
+void
+http_CheckRefresh(struct sess *sp)
+{
+	struct object *freshen_obj;
+	struct http *obj_hp, *bereq_hp;
+	char *p;
+
+	freshen_obj = sp->stale_obj;
+	CHECK_OBJ_NOTNULL(freshen_obj, OBJECT_MAGIC);
+	bereq_hp = sp->wrk->bereq;
+	CHECK_OBJ_NOTNULL(bereq_hp, HTTP_MAGIC);
+	obj_hp = freshen_obj->http;
+	CHECK_OBJ_NOTNULL(obj_hp, HTTP_MAGIC);
+
+	if(http_GetHdr(obj_hp, H_ETag, &p))
+		http_PrintfHeader(sp->wrk, sp->fd, bereq_hp, "If-None-Match: %s", p);
+
+	if(http_GetHdr(obj_hp, H_Last_Modified, &p))
+		http_PrintfHeader(sp->wrk, sp->fd, bereq_hp, "If-Modified-Since: %s",p);
+}
+
+/*-------------------------------------------------------------------
+ * Called after fetch and sp->freshen_obj present.  Check
+ * response and handle as needed.
+ */
+
+int
+http_Check304(struct sess *sp)
+{
+	struct object *o, *o_stale;
+
+	CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);
+	o_stale = sp->stale_obj;
+	CHECK_OBJ_NOTNULL(o_stale, OBJECT_MAGIC);
+	o = sp->obj;
+	CHECK_OBJ_NOTNULL(o, OBJECT_MAGIC);
+
+	switch (sp->wrk->beresp->status) {
+
+	case 200:
+		sp->wrk->stats.cache_cond_modified++;
+		break;
+
+	case 304:
+
+		sp->wrk->stats.cache_cond_not_modified++;
+
+                /* XXX: Unnecessary, since we're about to discard stale_obj.
+                 *      Should we copy o_stale->hits into o->hits?
+                 */
+		if (o_stale->hits < INT_MAX)
+			o_stale->hits++;
+
+                /* Copy headers we need from the stale object into the 304 response */
+                http_FilterMissingFields(sp->wrk, sp->fd, sp->obj->http,
+                                         sp->stale_obj->http);
+
+                /* Dup the stale object's storage in to the new object
+                 * and reset Content-Length from the size of the storage.
+                 */
+                STV_dup(sp, o_stale, o);
+                http_Unset(o->http, H_Content_Length);
+                http_PrintfHeader(sp->wrk, sp->fd, o->http, "Content-Length: %u", o->len);
+
+                /*
+                 * XXX replace response message with something else (XXX: ?)
+                 */
+                sp->obj->response = 200;
+                http_SetResp(o->http, "HTTP/1.1", 200, "OK Not Modified");
+                http_SetH(o->http, HTTP_HDR_REQ, "GET");
+                http_copyh(o->http, sp->wrk->bereq, HTTP_HDR_URL);
+
+                break;
+
+	default:
+		/* All other responses are passed through unchanged */
+		break;
+	}
+
+        AZ(o_stale->objcore->flags & OC_F_BUSY);
+        return(sp->wrk->beresp->status);
+}
+
 /*--------------------------------------------------------------------
  * This function copies any header fields which reference foreign
  * storage into our own WS.
diff --git a/bin/varnishd/cache_vrt.c b/bin/varnishd/cache_vrt.c
index 944f583..5cef5e0 100644
--- a/bin/varnishd/cache_vrt.c
+++ b/bin/varnishd/cache_vrt.c
@@ -113,6 +113,10 @@ vrt_selecthttp(const struct sess *sp, enum gethdr_e where)
 		CHECK_OBJ_NOTNULL(sp->obj, OBJECT_MAGIC);
 		hp = sp->obj->http;
 		break;
+        case HDR_STALE_OBJ:
+		CHECK_OBJ_NOTNULL(sp->stale_obj, OBJECT_MAGIC);
+		hp = sp->stale_obj->http;
+		break;
 	default:
 		INCOMPL();
 	}
@@ -127,6 +131,8 @@ VRT_GetHdr(const struct sess *sp, enum gethdr_e where, const char *n)
 	struct http *hp;
 
 	CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);
+        if (where == HDR_STALE_OBJ && sp->stale_obj == NULL)
+            return NULL;
 	hp = vrt_selecthttp(sp, where);
 	if (!http_GetHdr(hp, n, &p))
 		return (NULL);
diff --git a/bin/varnishd/cache_vrt_var.c b/bin/varnishd/cache_vrt_var.c
index 9fa56ba..ab07f3e 100644
--- a/bin/varnishd/cache_vrt_var.c
+++ b/bin/varnishd/cache_vrt_var.c
@@ -44,6 +44,9 @@ SVNID("$Id$")
 #include "cache_backend.h"
 #include "hash_slinger.h"
 
+#define ILLEGAL_R(sess, obj, field)                                         \
+WSP(sess, SLT_VCL_error, "%s does not exist (reading field %s)", obj, field)
+
 static char vrt_hostname[255] = "";
 
 /*--------------------------------------------------------------------*/
@@ -65,7 +68,16 @@ vrt_do_string(struct worker *w, int fd, const struct http *hp, int fld,
 	va_end(ap);
 }
 
-#define VRT_DO_HDR(obj, hdr, http, fld)				\
+/* XXX: uplex/GS: Here and in the following, macros were re-used to execute
+ *      the same code for stale_obj, but now checking for stale_obj == NULL,
+ *      and returning an "undefined" value if so. But this changes the semantics
+ *      for the other kinds of objects -- previously there had been
+ *      CHECK_OBJ_NOTNULL, so that there was an assert failure if they are NULL.
+ *      The assert failure is more appropriate, but now they return the
+ *      undefined value instead.
+ */
+
+#define VRT_DO_HDR(obj, hdr, cont, http, fld)			\
 void								\
 VRT_l_##obj##_##hdr(const struct sess *sp, const char *p, ...)	\
 {								\
@@ -73,7 +85,7 @@ VRT_l_##obj##_##hdr(const struct sess *sp, const char *p, ...)	\
 								\
 	va_start(ap, p);					\
 	vrt_do_string(sp->wrk, sp->fd,				\
-	    http, fld, #obj "." #hdr, p, ap);			\
+	    cont->http, fld, #obj "." #hdr, p, ap);		\
 	va_end(ap);						\
 }								\
 								\
@@ -81,32 +93,38 @@ const char *							\
 VRT_r_##obj##_##hdr(const struct sess *sp)			\
 {								\
 	CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);			\
-	CHECK_OBJ_NOTNULL(http, HTTP_MAGIC);			\
-	return (http->hd[fld].b);				\
-}
-
-VRT_DO_HDR(req,   request,	sp->http,		HTTP_HDR_REQ)
-VRT_DO_HDR(req,   url,		sp->http,		HTTP_HDR_URL)
-VRT_DO_HDR(req,   proto,	sp->http,		HTTP_HDR_PROTO)
-VRT_DO_HDR(bereq, request,	sp->wrk->bereq,		HTTP_HDR_REQ)
-VRT_DO_HDR(bereq, url,		sp->wrk->bereq,		HTTP_HDR_URL)
-VRT_DO_HDR(bereq, proto,	sp->wrk->bereq,		HTTP_HDR_PROTO)
-VRT_DO_HDR(obj,   proto,	sp->obj->http,		HTTP_HDR_PROTO)
-VRT_DO_HDR(obj,   response,	sp->obj->http,		HTTP_HDR_RESPONSE)
-VRT_DO_HDR(resp,  proto,	sp->wrk->resp,		HTTP_HDR_PROTO)
-VRT_DO_HDR(resp,  response,	sp->wrk->resp,		HTTP_HDR_RESPONSE)
-VRT_DO_HDR(beresp,  proto,	sp->wrk->beresp,	HTTP_HDR_PROTO)
-VRT_DO_HDR(beresp,  response,	sp->wrk->beresp,	HTTP_HDR_RESPONSE)
+        if (cont != NULL) {                                     \
+            CHECK_OBJ(cont->http, HTTP_MAGIC);      		\
+            return (cont->http->hd[fld].b);	                \
+        }                                                       \
+        ILLEGAL_R(sp, #obj, #hdr);                            \
+        return(NULL);                                           \
+}                                                               \
+
+VRT_DO_HDR(req,         request,    sp,             http,	HTTP_HDR_REQ)
+VRT_DO_HDR(req,         url,        sp,             http,	HTTP_HDR_URL)
+VRT_DO_HDR(req,         proto,      sp,             http,	HTTP_HDR_PROTO)
+VRT_DO_HDR(bereq,       request,    sp->wrk,        bereq,      HTTP_HDR_REQ)
+VRT_DO_HDR(bereq,       url,        sp->wrk,        bereq,	HTTP_HDR_URL)
+VRT_DO_HDR(bereq,       proto,      sp->wrk,        bereq,	HTTP_HDR_PROTO)
+VRT_DO_HDR(obj,         proto,      sp->obj,        http,	HTTP_HDR_PROTO)
+VRT_DO_HDR(obj,         response,   sp->obj,        http,	HTTP_HDR_RESPONSE)
+VRT_DO_HDR(stale_obj,   proto,      sp->stale_obj,  http,	HTTP_HDR_PROTO)
+VRT_DO_HDR(stale_obj,   response,   sp->stale_obj,  http,	HTTP_HDR_RESPONSE)
+VRT_DO_HDR(resp,        proto,      sp->wrk,        resp,	HTTP_HDR_PROTO)
+VRT_DO_HDR(resp,        response,   sp->wrk,        resp,	HTTP_HDR_RESPONSE)
+VRT_DO_HDR(beresp,      proto,      sp->wrk,        beresp,	HTTP_HDR_PROTO)
+VRT_DO_HDR(beresp,      response,   sp->wrk,        beresp,	HTTP_HDR_RESPONSE)
 
 /*--------------------------------------------------------------------*/
 
-#define VRT_DO_STATUS(obj, http)				\
+#define VRT_DO_STATUS(obj, cont, http)				\
 void								\
 VRT_l_##obj##_status(const struct sess *sp, int num)		\
 {								\
 								\
 	assert(num >= 100 && num <= 999);			\
-	http->status = num;					\
+	cont->http->status = num;				\
 }								\
 								\
 int								\
@@ -114,12 +132,17 @@ VRT_r_##obj##_status(const struct sess *sp)			\
 {								\
 								\
 	CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);			\
-	return(http->status);					\
+        if (cont == NULL) {                                     \
+            ILLEGAL_R(sp, #obj, "status");                     \
+            return (503);                                       \
+        }                                                       \
+	return(cont->http->status);				\
 }
 
-VRT_DO_STATUS(obj, sp->obj->http)
-VRT_DO_STATUS(beresp, sp->wrk->beresp)
-VRT_DO_STATUS(resp, sp->wrk->resp)
+VRT_DO_STATUS(obj,          sp->obj,        http)
+VRT_DO_STATUS(stale_obj,    sp->stale_obj,  http)
+VRT_DO_STATUS(beresp,       sp->wrk,        beresp)
+VRT_DO_STATUS(resp,         sp->wrk,        resp)
 
 /*--------------------------------------------------------------------*/
 
@@ -180,7 +203,7 @@ VRT_l_beresp_saintmode(const struct sess *sp, double a)
 
 /*--------------------------------------------------------------------*/
 
-#define VBERESP(dir, type, onm, field)					\
+#define VBERESP(dir, type, onm, field)             			\
 void									\
 VRT_l_##dir##_##onm(const struct sess *sp, type a)			\
 {									\
@@ -198,6 +221,7 @@ VRT_r_##dir##_##onm(const struct sess *sp)				\
 VBERESP(beresp, unsigned, do_esi, do_esi)
 VBERESP(beresp, unsigned, do_gzip, do_gzip)
 VBERESP(beresp, unsigned, do_gunzip, do_gunzip)
+VBERESP(beresp, double, conditional_timeout, conditional_timeout)
 
 /*--------------------------------------------------------------------*/
 
@@ -249,6 +273,7 @@ VRT_l_beresp_ttl(const struct sess *sp, double a)
 	if (a <= 0) {
 		sp->wrk->ttl = sp->t_req - 1;
 		sp->wrk->grace = 0.;
+                sp->wrk->conditional_timeout = 0.;
 	} else
 		sp->wrk->ttl = sp->t_req + a;
 }
@@ -353,21 +378,81 @@ VRT_l_obj_ttl(const struct sess *sp, double a)
 	if (a <= 0) {
 		sp->obj->ttl = sp->t_req - 1;
 		sp->obj->grace = 0;
+                sp->obj->conditional_timeout = 0;
 	} else
 		sp->obj->ttl = sp->t_req + a;
 	EXP_Rearm(sp->obj);
 }
 
 double
+vrt_r_obj_ttl(const struct sess *sp, struct object *obj)
+{
+    	if (obj->objcore == NULL)
+		return (0.0);
+	return (obj->ttl - sp->t_req);
+}
+
+double
 VRT_r_obj_ttl(const struct sess *sp)
 {
 	CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);
 	CHECK_OBJ_NOTNULL(sp->obj, OBJECT_MAGIC);	/* XXX */
-	if (sp->obj->objcore == NULL)
+	return vrt_r_obj_ttl(sp, sp->obj);
+}
+
+double
+VRT_r_stale_obj_ttl(const struct sess *sp)
+{
+	CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);
+        if (sp->stale_obj == NULL) {
+            ILLEGAL_R(sp, "stale_obj", "ttl");
+            return (0.0);
+        }
+	CHECK_OBJ(sp->stale_obj, OBJECT_MAGIC);	/* XXX */
+	return vrt_r_obj_ttl(sp, sp->stale_obj);
+}
+
+double
+vrt_r_obj_conditional_timeout(struct object *obj)
+{
+    	if (obj->objcore == NULL)
 		return (0.0);
-	return (sp->obj->ttl - sp->t_req);
+	return (obj->conditional_timeout);
 }
 
+
+void
+VRT_l_obj_conditional_timeout(const struct sess *sp, double a)
+{
+        CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);
+        CHECK_OBJ_NOTNULL(sp->obj, OBJECT_MAGIC);
+        if (sp->obj->objcore == NULL)
+            return;
+        sp->obj->conditional_timeout = a;
+        EXP_Rearm(sp->obj);
+}
+
+double
+VRT_r_obj_conditional_timeout(const struct sess *sp)
+{
+        CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);
+        CHECK_OBJ_NOTNULL(sp->obj, OBJECT_MAGIC);
+        return vrt_r_obj_conditional_timeout(sp->obj);
+}
+
+
+double
+VRT_r_stale_obj_conditional_timeout(const struct sess *sp)
+{
+        CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);
+        if (sp->stale_obj == NULL) {
+            ILLEGAL_R(sp, "stale_obj", "conditional_timeout");
+            return (0.0);
+        }
+        CHECK_OBJ(sp->stale_obj, OBJECT_MAGIC);
+        return vrt_r_obj_conditional_timeout(sp->stale_obj);
+};
+
 /*--------------------------------------------------------------------*/
 
 void
@@ -436,13 +521,13 @@ VRT_r_req_restarts(const struct sess *sp)
 
 /*--------------------------------------------------------------------*/
 
-#define VRT_DO_GRACE(which, fld, extra)				\
+#define VRT_DO_GRACE(which, cont, fld, extra)				\
 void __match_proto__()						\
 VRT_l_##which##_grace(struct sess *sp, double a)		\
 {								\
 								\
 	CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);			\
-	fld = a >= 0.0 ? a : NAN;				\
+	cont->fld = a >= 0.0 ? a : NAN;				\
 	extra;							\
 }								\
 								\
@@ -451,12 +536,17 @@ VRT_r_##which##_grace(struct sess *sp)				\
 {								\
 								\
 	CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);			\
-	return(HSH_Grace(fld));					\
+        if (cont == NULL) {                                     \
+            ILLEGAL_R(sp, #which, "grace");                     \
+            return (0.0);                                       \
+        }                                                       \
+	return(HSH_Grace(cont->fld));				\
 }
 
-VRT_DO_GRACE(req, sp->grace, )
-VRT_DO_GRACE(obj, sp->obj->grace, EXP_Rearm(sp->obj))
-VRT_DO_GRACE(beresp, sp->wrk->grace, )
+VRT_DO_GRACE(req,       sp,             grace, )
+VRT_DO_GRACE(obj,       sp->obj,        grace, EXP_Rearm(sp->obj))
+VRT_DO_GRACE(stale_obj, sp->stale_obj,  grace, )
+VRT_DO_GRACE(beresp,    sp->wrk,        grace, )
 
 /*--------------------------------------------------------------------
  * req.xid
@@ -557,6 +647,8 @@ VRT_r_server_port(struct sess *sp)
 
 /*--------------------------------------------------------------------*/
 
+/* XXX: uplex/GS: a nice macro would eliminate the repetition here ... */
+
 int
 VRT_r_obj_hits(const struct sess *sp)
 {
@@ -566,6 +658,19 @@ VRT_r_obj_hits(const struct sess *sp)
 	return (sp->obj->hits);
 }
 
+int
+VRT_r_stale_obj_hits(const struct sess *sp)
+{
+
+	CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);
+        if (sp->stale_obj == NULL) {
+            ILLEGAL_R(sp, "stale_obj", "hits");
+            return (0);
+        }
+	CHECK_OBJ(sp->stale_obj, OBJECT_MAGIC);	/* XXX */
+	return (sp->stale_obj->hits);
+}
+
 double
 VRT_r_obj_lastuse(const struct sess *sp)
 {
@@ -575,6 +680,19 @@ VRT_r_obj_lastuse(const struct sess *sp)
 	return (TIM_real() - sp->obj->last_use);
 }
 
+double
+VRT_r_stale_obj_lastuse(const struct sess *sp)
+{
+
+	CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);
+        if (sp->stale_obj == NULL) {
+            ILLEGAL_R(sp, "stale_obj", "lastuse");
+            return (0);
+        }
+	CHECK_OBJ(sp->stale_obj, OBJECT_MAGIC);	/* XXX */
+	return (TIM_real() - sp->stale_obj->last_use);
+}
+
 unsigned
 VRT_r_req_backend_healthy(const struct sess *sp)
 {
@@ -583,3 +701,9 @@ VRT_r_req_backend_healthy(const struct sess *sp)
 	return (VDI_Healthy(sp->director, sp));
 }
 
+unsigned
+VRT_r_stale_obj(const struct sess *sp)
+{
+        CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);
+        return (sp->stale_obj != NULL);
+}
diff --git a/bin/varnishd/heritage.h b/bin/varnishd/heritage.h
index c027838..1d025ea 100644
--- a/bin/varnishd/heritage.h
+++ b/bin/varnishd/heritage.h
@@ -206,6 +206,9 @@ struct params {
 	double			critbit_cooloff;
 
 	double			shortlived;
+
+        /* default timeout for conditional backend requests after TTL = 0 */
+        unsigned                default_conditional_timeout;
 };
 
 /*
diff --git a/bin/varnishd/mgt_param.c b/bin/varnishd/mgt_param.c
index 201aaab..a0be1e0 100644
--- a/bin/varnishd/mgt_param.c
+++ b/bin/varnishd/mgt_param.c
@@ -917,6 +917,16 @@ static const struct parspec input_parspec[] = {
 		"Unreferenced VCL objects result in error.\n",
 		0,
 		"on", "bool" },
+	{ "default_conditional_timeout", tweak_timeout,
+                &master.default_conditional_timeout, 0, UINT_MAX,
+		"Default timeout for conditional backend requests.  For this "
+                "long after an object expires, Varnish will attempt a request "
+		"to the backend with If-Modified-Since if it has a Last-Modified  "
+		"header, and/or If-None-Match if it has an ETag header.\n"
+                "If Varnish recevies a 304 Not Modified response, the object's "
+                "contents will be retained in the cache.",
+		DELAYED_EFFECT,
+		"10", "seconds" },
 	{ NULL, NULL, NULL }
 };
 
diff --git a/bin/varnishd/stevedore.c b/bin/varnishd/stevedore.c
index 912fd68..6c605ba 100644
--- a/bin/varnishd/stevedore.c
+++ b/bin/varnishd/stevedore.c
@@ -364,6 +364,30 @@ STV_trim(struct storage *st, size_t size)
 		st->stevedore->trim(st, size);
 }
 
+/*
+ * Duplicate the object storage (HTML body) from src into target, using a
+ * stevedore-specific dup method for src's stevedore.
+ *
+ * Currently, every method just copies storage from one object to the other,
+ * but this method of encapsulation opens the path to future techniques of
+ * sharing storage together with reference counting.
+ */
+void
+STV_dup(const struct sess *sp, struct object *src, struct object *target)
+{
+        struct stevedore *stv;
+
+        CHECK_OBJ_NOTNULL(src, OBJECT_MAGIC);
+        CHECK_OBJ_NOTNULL(target, OBJECT_MAGIC);
+        CHECK_OBJ_NOTNULL(src->objstore, STORAGE_MAGIC);
+        CHECK_OBJ_NOTNULL(src->objstore->stevedore, STEVEDORE_MAGIC);
+
+        stv = src->objstore->stevedore;
+        AN(stv->dup);
+        
+        stv->dup(sp, src, target);
+}
+
 void
 STV_free(struct storage *st)
 {
@@ -580,3 +604,24 @@ VRT_Stv_##nm(const char *nm)			\
 
 #include "vrt_stv_var.h"
 #undef VRTSTVVAR
+
+/*
+ * Default object store dup just copies the storage.
+ */
+void
+default_dup(const struct sess *sp, struct object *src, struct object *target)
+{
+        struct storage *st, *st2;
+        unsigned cl;
+
+        VTAILQ_FOREACH(st2, &src->store, list) {
+		cl = st2->len;
+		st = STV_alloc(sp, cl);
+		XXXAN(st);
+                assert(st->space >= cl);
+		VTAILQ_INSERT_TAIL(&target->store, st, list);
+		st->len = cl;
+		target->len += cl;
+		memcpy(st->ptr, st2->ptr, cl);
+	}
+}
diff --git a/bin/varnishd/stevedore.h b/bin/varnishd/stevedore.h
index e97d70f..6cc86bf 100644
--- a/bin/varnishd/stevedore.h
+++ b/bin/varnishd/stevedore.h
@@ -39,6 +39,7 @@ struct stv_objsecrets;
 typedef void storage_init_f(struct stevedore *, int ac, char * const *av);
 typedef void storage_open_f(const struct stevedore *);
 typedef struct storage *storage_alloc_f(struct stevedore *, size_t size);
+typedef void storage_dup_f(const struct sess *sp, struct object *src, struct object *target);
 typedef void storage_trim_f(struct storage *, size_t size);
 typedef void storage_free_f(struct storage *);
 typedef struct object *storage_allocobj_f(struct stevedore *, struct sess *sp,
@@ -70,6 +71,7 @@ struct stevedore {
 	storage_open_f		*open;		/* called by cache process */
 	storage_alloc_f		*alloc;		/* --//-- */
 	storage_trim_f		*trim;		/* --//-- */
+        storage_dup_f           *dup;           /* --//-- */
 	storage_free_f		*free;		/* --//-- */
 	storage_close_f		*close;		/* --//-- */
 	storage_allocobj_f	*allocobj;	/* --//-- */
@@ -93,6 +95,7 @@ struct object *STV_MkObject(struct sess *sp, void *ptr, unsigned ltot,
 struct object *STV_NewObject(struct sess *sp, const char *hint, unsigned len,
     double ttl, unsigned nhttp);
 struct storage *STV_alloc(const struct sess *sp, size_t size);
+void STV_dup(const struct sess *sp, struct object *src, struct object *target);
 void STV_trim(struct storage *st, size_t size);
 void STV_free(struct storage *st);
 void STV_open(void);
@@ -117,3 +120,6 @@ extern const struct stevedore smp_stevedore;
 #ifdef HAVE_LIBUMEM
 extern const struct stevedore smu_stevedore;
 #endif
+
+/* Default dup method */
+void default_dup(const struct sess *sp, struct object *src, struct object *target);
diff --git a/bin/varnishd/storage_file.c b/bin/varnishd/storage_file.c
index c334b6b..e6181f5 100644
--- a/bin/varnishd/storage_file.c
+++ b/bin/varnishd/storage_file.c
@@ -552,6 +552,7 @@ const struct stevedore smf_stevedore = {
 	.alloc	=	smf_alloc,
 	.trim	=	smf_trim,
 	.free	=	smf_free,
+        .dup    =       default_dup,
 };
 
 #ifdef INCLUDE_TEST_DRIVER
diff --git a/bin/varnishd/storage_malloc.c b/bin/varnishd/storage_malloc.c
index c934ab8..372df91 100644
--- a/bin/varnishd/storage_malloc.c
+++ b/bin/varnishd/storage_malloc.c
@@ -219,4 +219,5 @@ const struct stevedore sma_stevedore = {
 	.trim	=	sma_trim,
 	.var_free_space =	sma_free_space,
 	.var_used_space =	sma_used_space,
+        .dup    =       default_dup,
 };
diff --git a/bin/varnishd/storage_persistent.c b/bin/varnishd/storage_persistent.c
index 0fd2ee4..ca6205b 100644
--- a/bin/varnishd/storage_persistent.c
+++ b/bin/varnishd/storage_persistent.c
@@ -582,6 +582,7 @@ const struct stevedore smp_stevedore = {
 	.allocobj =	smp_allocobj,
 	.free	=	smp_free,
 	.trim	=	smp_trim,
+        .dup    =       default_dup,
 };
 
 /*--------------------------------------------------------------------
diff --git a/bin/varnishd/storage_synth.c b/bin/varnishd/storage_synth.c
index 4ee4337..e4ad7e3 100644
--- a/bin/varnishd/storage_synth.c
+++ b/bin/varnishd/storage_synth.c
@@ -70,6 +70,7 @@ static struct stevedore sms_stevedore = {
 	.magic	=	STEVEDORE_MAGIC,
 	.name	=	"synth",
 	.free	=	sms_free,
+        .dup    =       default_dup,
 };
 
 struct vsb *
diff --git a/bin/varnishd/storage_umem.c b/bin/varnishd/storage_umem.c
index 4ec6e1d..6d1b3f2 100644
--- a/bin/varnishd/storage_umem.c
+++ b/bin/varnishd/storage_umem.c
@@ -166,6 +166,7 @@ const struct stevedore smu_stevedore = {
 	.alloc	=	smu_alloc,
 	.free	=	smu_free,
 	.trim	=	smu_trim,
+        .dup    =       default_dup,
 };
 
 #endif /* HAVE_UMEM_H */
diff --git a/bin/varnishtest/tests/i00000.vtc b/bin/varnishtest/tests/i00000.vtc
new file mode 100644
index 0000000..e227423
--- /dev/null
+++ b/bin/varnishtest/tests/i00000.vtc
@@ -0,0 +1,405 @@
+# $Id$
+
+test "VCL compiler coverage test: conditional refresh extension"
+
+## stale_obj is r/o in miss, fetch and error, illegal everywhere else
+
+varnish v1 -vcl {
+        backend b { .host = "127.0.0.1"; }
+        sub vcl_miss { if (stale_obj) { return(pass); } }
+}
+
+varnish v1 -badvcl {
+        backend b { .host = "127.0.0.1"; }
+        sub vcl_miss { set stale_obj = true; }
+}
+
+varnish v1 -vcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_miss { if (stale_obj.proto ~ "foo") { return(pass); } }
+}
+
+varnish v1 -badvcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_miss { set stale_obj.proto = "foo"; }
+}
+
+varnish v1 -vcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_miss { if (stale_obj.status == 200) { return(pass); } }
+}
+
+varnish v1 -badvcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_miss { set stale_obj.status = 200; }
+}
+
+varnish v1 -vcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_miss { if (stale_obj.response ~ "foo") { return(pass); } }
+}
+
+varnish v1 -badvcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_miss { set stale_obj.response = "foo"; }
+}
+
+varnish v1 -vcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_miss { if (stale_obj.hits > 100) { return(pass); } }
+}
+
+varnish v1 -badvcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_miss { set stale_obj.hits = 100; }
+}
+
+varnish v1 -vcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_miss { if (stale_obj.http.Foo ~ "foo") { return(pass); } }
+}
+
+varnish v1 -badvcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_miss { set stale_obj.http.Foo = "foo"; }
+}
+
+varnish v1 -vcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_miss { if (stale_obj.ttl > 1m) { return(pass); } }
+}
+
+varnish v1 -badvcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_miss { set stale_obj.ttl = 1m; }
+}
+
+varnish v1 -vcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_miss { if (stale_obj.grace > 1m) { return(pass); } }
+}
+
+varnish v1 -badvcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_miss { set stale_obj.grace = 1m; }
+}
+
+varnish v1 -vcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_miss { if (stale_obj.lastuse > 1m) { return(pass); } }
+}
+
+varnish v1 -badvcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_miss { set stale_obj.lastuse = 1m; }
+}
+
+varnish v1 -vcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_miss { if (stale_obj.conditional_timeout > 1m) { return(pass); } }
+}
+
+varnish v1 -badvcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_miss { set stale_obj.conditional_timeout = 1m; }
+}
+
+varnish v1 -vcl {
+        backend b { .host = "127.0.0.1"; }
+        sub vcl_fetch { if (stale_obj) { return(deliver); } }
+}
+
+varnish v1 -badvcl {
+        backend b { .host = "127.0.0.1"; }
+        sub vcl_fetch { set stale_obj = true; }
+}
+
+varnish v1 -vcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_fetch { if (stale_obj.proto ~ "foo") { return(deliver); } }
+}
+
+varnish v1 -badvcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_fetch { set stale_obj.proto = "foo"; }
+}
+
+varnish v1 -vcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_fetch { if (stale_obj.status == 200) { return(deliver); } }
+}
+
+varnish v1 -badvcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_fetch { set stale_obj.status = 200; }
+}
+
+varnish v1 -vcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_fetch { if (stale_obj.response ~ "foo") { return(deliver); } }
+}
+
+varnish v1 -badvcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_fetch { set stale_obj.response = "foo"; }
+}
+
+varnish v1 -vcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_fetch { if (stale_obj.hits > 100) { return(deliver); } }
+}
+
+varnish v1 -badvcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_fetch { set stale_obj.hits = 100; }
+}
+
+varnish v1 -vcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_fetch { if (stale_obj.http.Foo ~ "foo") { return(deliver); } }
+}
+
+varnish v1 -badvcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_fetch { set stale_obj.http.Foo = "foo"; }
+}
+
+varnish v1 -vcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_fetch { if (stale_obj.ttl > 1m) { return(deliver); } }
+}
+
+varnish v1 -badvcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_fetch { set stale_obj.ttl = 1m; }
+}
+
+varnish v1 -vcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_fetch { if (stale_obj.grace > 1m) { return(deliver); } }
+}
+
+varnish v1 -badvcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_fetch { set stale_obj.grace = 1m; }
+}
+
+varnish v1 -vcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_fetch { if (stale_obj.lastuse > 1m) { return(deliver); } }
+}
+
+varnish v1 -badvcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_fetch { set stale_obj.lastuse = 1m; }
+}
+
+varnish v1 -vcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_fetch { if (stale_obj.conditional_timeout > 1m) { return(deliver); } }
+}
+
+varnish v1 -badvcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_fetch { set stale_obj.conditional_timeout = 1m; }
+}
+
+varnish v1 -vcl {
+        backend b { .host = "127.0.0.1"; }
+        sub vcl_error { if (stale_obj) { return(deliver); } }
+}
+
+varnish v1 -badvcl {
+        backend b { .host = "127.0.0.1"; }
+        sub vcl_error { set stale_obj = true; }
+}
+
+varnish v1 -vcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_error { if (stale_obj.proto ~ "foo") { return(deliver); } }
+}
+
+varnish v1 -badvcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_error { set stale_obj.proto = "foo"; }
+}
+
+varnish v1 -vcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_error { if (stale_obj.status == 200) { return(deliver); } }
+}
+
+varnish v1 -badvcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_error { set stale_obj.status = 200; }
+}
+
+varnish v1 -vcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_error { if (stale_obj.response ~ "foo") { return(deliver); } }
+}
+
+varnish v1 -badvcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_error { set stale_obj.response = "foo"; }
+}
+
+varnish v1 -vcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_error { if (stale_obj.hits > 100) { return(deliver); } }
+}
+
+varnish v1 -badvcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_error { set stale_obj.hits = 100; }
+}
+
+varnish v1 -vcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_error { if (stale_obj.http.Foo ~ "foo") { return(deliver); } }
+}
+
+varnish v1 -badvcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_error { set stale_obj.http.Foo = "foo"; }
+}
+
+varnish v1 -vcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_error { if (stale_obj.ttl > 1m) { return(deliver); } }
+}
+
+varnish v1 -badvcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_error { set stale_obj.ttl = 1m; }
+}
+
+varnish v1 -vcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_error { if (stale_obj.grace > 1m) { return(deliver); } }
+}
+
+varnish v1 -badvcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_error { set stale_obj.grace = 1m; }
+}
+
+varnish v1 -vcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_error { if (stale_obj.lastuse > 1m) { return(deliver); } }
+}
+
+varnish v1 -badvcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_error { set stale_obj.lastuse = 1m; }
+}
+
+varnish v1 -vcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_error { if (stale_obj.conditional_timeout > 1m) { return(deliver); } }
+}
+
+varnish v1 -badvcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_error { set stale_obj.conditional_timeout = 1m; }
+}
+
+## conditional timeout is r/w for beresp in fetch
+##  r/w for obj in hit and error, r/o in miss and fetch (like ttl and grace)
+##  r/o for stale_obj in hit, fetch, miss and error (already tested)
+
+varnish v1 -vcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_fetch { if (beresp.conditional_timeout > 1s) { return(deliver); } }
+}
+
+varnish v1 -vcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_fetch { set beresp.conditional_timeout = 1s; }
+}
+
+varnish v1 -badvcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_fetch { if (obj.conditional_timeout > 1s) { return(deliver); } }
+}
+
+varnish v1 -vcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_hit { set obj.conditional_timeout = 1s; }
+}
+
+varnish v1 -badvcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_miss { if (obj.conditional_timeout > 1s) { return(pass); } }
+}
+
+varnish v1 -vcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_error { if (obj.conditional_timeout > 1s) { return(deliver); } }
+}
+
+varnish v1 -badvcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_miss { set obj.conditional_timeout = 1s; }
+}
+
+varnish v1 -badvcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_fetch { set obj.conditional_timeout = 1s; }
+}
+
+varnish v1 -badvcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_recv { if (obj.conditional_timeout > 1s) { return(pass); } }
+}
+
+varnish v1 -badvcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_recv { set obj.conditional_timeout = 1s; }
+}
+
+varnish v1 -badvcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_pipe { if (obj.conditional_timeout > 1s) { return(error); } }
+}
+
+varnish v1 -badvcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_pipe { set obj.conditional_timeout = 1s; }
+}
+
+varnish v1 -badvcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_pass { if (obj.conditional_timeout > 1s) { return(error); } }
+}
+
+varnish v1 -badvcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_pass { set obj.conditional_timeout = 1s; }
+}
+
+varnish v1 -badvcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_hash { if (obj.conditional_timeout > 1s) { return(hash); } }
+}
+
+varnish v1 -badvcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_hash { set obj.conditional_timeout = 1s; }
+}
+
+varnish v1 -badvcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_deliver { if (obj.conditional_timeout > 1s) { return(error); } }
+}
+
+varnish v1 -badvcl {
+	backend b { .host = "127.0.0.1"; }
+	sub vcl_deliver { set obj.conditional_timeout = 1s; }
+}
+
+varnish v1 -cliok "param.show default_conditional_timeout"
+
+varnish v1 -cliok "param.set default_conditional_timeout 30s"
+
+varnish v1 -clierr 106 "param.set default_conditional_timeout foo"
diff --git a/bin/varnishtest/tests/i00001.vtc b/bin/varnishtest/tests/i00001.vtc
new file mode 100644
index 0000000..4289899
--- /dev/null
+++ b/bin/varnishtest/tests/i00001.vtc
@@ -0,0 +1,69 @@
+# $Id$
+
+test "Test basic If-Modified-Since requests to backends"
+
+server s1 {
+        rxreq
+        expect req.url == "/foo"
+        expect req.http.If-Modified-Since == "req.http.If-Modified-Since"
+        txresp -status 200 \
+    	    -hdr "Last-Modified: Thu, 26 Jun 2008 12:00:01 GMT" \
+            -hdr "ETag: foo" \
+            -body "123456"
+
+        rxreq
+        expect req.url == "/foo"
+        expect req.http.If-Modified-Since == "Thu, 26 Jun 2008 12:00:01 GMT"
+        expect req.http.If-None-Match == "foo"
+        txresp -status 304
+
+        rxreq
+        expect req.url == "/foo"
+        expect req.http.If-Modified-Since == "Thu, 26 Jun 2008 12:00:01 GMT"
+        expect req.http.If-None-Match == "foo"
+        txresp -status 200 \
+            -hdr "Last-Modified: Wed, 27 Jun 2008 12:00:01 GMT" \
+            -hdr "ETag: foo" \
+            -body "ABCDEF"
+
+        rxreq
+        expect req.url == "/foo"
+        expect req.http.If-Modified-Since == "Wed, 27 Jun 2008 12:00:01 GMT"
+        expect req.http.If-None-Match == "foo"
+        txresp -status 304
+
+} -start
+
+varnish v1 -vcl+backend {
+        sub vcl_fetch {
+            set beresp.grace = 1m;
+            set beresp.ttl = 0.5s;
+        }
+} -start
+
+client c1 {
+        txreq -url "/foo"
+        rxresp
+        expect resp.status == 200
+        expect resp.http.ETag == "foo"
+        expect resp.http.content-length == 6
+        expect resp.http.Age == 0
+}
+
+client c1 -run
+
+delay 1
+
+client c1 -run
+
+delay 1
+
+client c1 -run
+
+delay 1
+
+client c1 -run
+
+varnish v1 -expect cache_cond_not_modified  == 2
+varnish v1 -expect cache_cond_modified == 1
+
diff --git a/bin/varnishtest/tests/i00002.vtc b/bin/varnishtest/tests/i00002.vtc
new file mode 100644
index 0000000..41abd98
--- /dev/null
+++ b/bin/varnishtest/tests/i00002.vtc
@@ -0,0 +1,113 @@
+# $Id$
+
+test "handling stale_obj in vcl_miss() and vcl_fetch()"
+
+server s1 {
+        rxreq
+        expect req.url == "/foo"
+        expect req.http.X-VT-Stale == "false"
+        expect req.http.X-VT-Stale-Proto == "req.http.X-VT-Stale-Proto"
+        expect req.http.X-VT-Stale-Status == 503
+        expect req.http.X-VT-Stale-Response == "req.http.X-VT-Stale-Response"
+        expect req.http.X-VT-Stale-Hits == 0
+        expect req.http.X-VT-Stale-Last-Modified == "req.http.X-VT-Stale-Last-Modified"
+        expect req.http.X-VT-Stale-TTL == 0.000
+        expect req.http.X-VT-Stale-Grace == 0.000
+        expect req.http.X-VT-Stale-Last-Use == 0.000
+        expect req.http.X-VT-Stale-Conditional-Timeout == 0.000
+        txresp -status 200 \
+    	    -hdr "Last-Modified: Thu, 26 Jun 2008 12:00:01 GMT" \
+            -body "1234567890"
+
+        rxreq
+        expect req.url == "/foo"
+        expect req.http.If-Modified-Since == "Thu, 26 Jun 2008 12:00:01 GMT"
+        expect req.http.X-VT-Stale == "true"
+        expect req.http.X-VT-Stale-Proto == "HTTP/1.1"
+        expect req.http.X-VT-Stale-Status == 200
+        expect req.http.X-VT-Stale-Response == "Ok"
+        expect req.http.X-VT-Stale-Last-Modified == "Thu, 26 Jun 2008 12:00:01 GMT"
+        expect req.http.X-VT-Stale-TTL != 0.000
+        expect req.http.X-VT-Stale-Grace == 3600.000
+        expect req.http.X-VT-Stale-Last-Use != 0.000
+        expect req.http.X-VT-Stale-Conditional-Timeout == 10.000
+        txresp -status 304
+} -start
+
+varnish v1 -vcl+backend {
+        sub vcl_fetch {
+            set beresp.grace = 1h;
+            set beresp.ttl = 1s;
+
+            if (stale_obj) {
+                set beresp.http.X-VT-Stale = "true";
+            }
+            else {
+                set beresp.http.X-VT-Stale = "false";
+            }
+            set beresp.http.X-VT-Stale-Proto = stale_obj.proto;
+            set beresp.http.X-VT-Stale-Status = stale_obj.status;
+            set beresp.http.X-VT-Stale-Response = stale_obj.response;
+            set beresp.http.X-VT-Stale-Hits = stale_obj.hits;
+            set beresp.http.X-VT-Stale-Last-Modified = stale_obj.http.Last-Modified;
+            set beresp.http.X-VT-Stale-TTL = stale_obj.ttl;
+            set beresp.http.X-VT-Stale-Grace = stale_obj.grace;
+            set beresp.http.X-VT-Stale-Last-Use = stale_obj.lastuse;
+            set beresp.http.X-VT-Stale-Conditional-Timeout = stale_obj.conditional_timeout;
+        }
+
+        sub vcl_miss {
+            if (stale_obj) {
+                set bereq.http.X-VT-Stale = "true";
+            }
+            else {
+                set bereq.http.X-VT-Stale = "false";
+            }
+            set bereq.http.X-VT-Stale-Proto = stale_obj.proto;
+            set bereq.http.X-VT-Stale-Status = stale_obj.status;
+            set bereq.http.X-VT-Stale-Response = stale_obj.response;
+            set bereq.http.X-VT-Stale-Hits = stale_obj.hits;
+            set bereq.http.X-VT-Stale-Last-Modified = stale_obj.http.Last-Modified;
+            set bereq.http.X-VT-Stale-TTL = stale_obj.ttl;
+            set bereq.http.X-VT-Stale-Grace = stale_obj.grace;
+            set bereq.http.X-VT-Stale-Last-Use = stale_obj.lastuse;
+            set bereq.http.X-VT-Stale-Conditional-Timeout = stale_obj.conditional_timeout;
+        }
+} -start
+
+client c1 {
+        txreq -url "/foo"
+        rxresp
+        expect resp.status == 200
+        expect resp.http.content-length == 10
+        expect resp.http.X-VT-Stale == "false"
+        expect resp.http.X-VT-Stale-Proto == "resp.http.X-VT-Stale-Proto"
+        expect resp.http.X-VT-Stale-Status == 503
+        expect resp.http.X-VT-Stale-Response == "resp.http.X-VT-Stale-Response"
+        expect resp.http.X-VT-Stale-Hits == 0
+        expect resp.http.X-VT-Stale-Last-Modified == "resp.http.X-VT-Stale-Last-Modified"
+        expect resp.http.X-VT-Stale-TTL == 0.000
+        expect resp.http.X-VT-Stale-Grace == 0.000
+        expect resp.http.X-VT-Stale-Last-Use == 0.000
+        expect resp.http.X-VT-Stale-Conditional-Timeout == 0.000
+} -run
+
+delay 2
+
+client c1 {
+        txreq -url "/foo"
+        rxresp
+        expect resp.status == 200
+        expect resp.bodylen == 10
+        expect resp.http.Content-Length == 10
+        expect resp.http.X-VT-Stale == "true"
+        expect resp.http.X-VT-Stale-Proto == "HTTP/1.1"
+        expect resp.http.X-VT-Stale-Status == 200
+        expect resp.http.X-VT-Stale-Response == "Ok"
+        expect resp.http.X-VT-Stale-Hits == 0
+        expect resp.http.X-VT-Stale-Last-Modified == "Thu, 26 Jun 2008 12:00:01 GMT"
+        expect resp.http.X-VT-Stale-TTL != 0.000
+        expect resp.http.X-VT-Stale-Grace == 3600.000
+        expect resp.http.X-VT-Stale-Last-Use != 0.000
+        expect resp.http.X-VT-Stale-Conditional-Timeout == 10.000
+} -run
diff --git a/bin/varnishtest/tests/i00003.vtc b/bin/varnishtest/tests/i00003.vtc
new file mode 100644
index 0000000..02624e6
--- /dev/null
+++ b/bin/varnishtest/tests/i00003.vtc
@@ -0,0 +1,90 @@
+# $Id$
+
+test "Test some anticipated use cases for conditional backend requests"
+
+## In vcl_miss(), it is possible to veto a conditional request by removing any
+## If-Modified-Since or If-None-Match header.
+
+server s1 {
+        rxreq
+        expect req.url == "/foo"
+        expect req.http.If-Modified-Since == "req.http.If-Modified-Since"
+        txresp -status 200 \
+    	    -hdr "Last-Modified: Thu, 26 Jun 2008 12:00:01 GMT" \
+            -hdr "ETag: foo" \
+            -body "abcdefghijklmonpqrstuvwxyz"
+
+        rxreq
+        expect req.url == "/foo"
+        expect req.http.If-Modified-Since == "req.http.If-Modified-Since"
+        expect req.http.If-None-Match == "req.http.If-None-Match"
+        txresp -status 200 -body "abcdefghijklmonpqrstuvwxyz"
+} -start
+
+varnish v1 -vcl+backend {
+        sub vcl_fetch {
+            set beresp.grace = 1m;
+            set beresp.ttl = 0.5s;
+        }
+
+        sub vcl_miss {
+            unset bereq.http.If-Modified-Since;
+            unset bereq.http.If-None-Match;
+        }
+} -start
+
+client c1 {
+        txreq -url "/foo"
+        rxresp
+        expect resp.status == 200
+        expect resp.http.content-length == 26
+}
+
+client c1 -run
+
+delay 1
+
+client c1 -run
+
+server s1 -wait
+varnish v1 -stop
+
+## Verify that if a client sends a conditional request to Varnish, then Varnish
+## can return a 304 response to the client after it got 304 from the backend
+
+server s2 {
+        rxreq
+        expect req.url == "/foo"
+        txresp -status 200 \
+    	    -hdr "Last-Modified: Thu, 26 Jun 2008 12:00:01 GMT" \
+            -body "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+
+        rxreq
+        expect req.url == "/foo"
+        expect req.http.If-Modified-Since == "Thu, 26 Jun 2008 12:00:01 GMT"
+        txresp -status 304
+} -start
+
+varnish v1 -vcl {
+        backend s2 {
+                .host = "${s2_addr}"; .port = "${s2_port}";
+        }
+
+        sub vcl_fetch {
+            set beresp.grace = 1m;
+            set beresp.ttl = 0.5s;
+        }
+} -start
+
+client c2 {
+        txreq -url "/foo" \
+            -hdr "If-Modified-Since: Thu, 26 Jun 2008 12:00:01 GMT"
+        rxresp
+        expect resp.status == 304
+}
+
+client c1 -run
+
+delay 1
+
+client c2 -run
diff --git a/bin/varnishtest/tests/i00004.vtc b/bin/varnishtest/tests/i00004.vtc
new file mode 100644
index 0000000..7a9c703
--- /dev/null
+++ b/bin/varnishtest/tests/i00004.vtc
@@ -0,0 +1,153 @@
+# $Id$
+
+test "Verify the semantics of conditional timeout"
+
+server s1 {
+        rxreq
+        expect req.url == "/foo"
+        txresp -status 200 \
+    	    -hdr "Last-Modified: Thu, 26 Jun 2008 12:00:01 GMT" \
+            -body "foo bar baz quux"
+
+        rxreq
+        expect req.url == "/foo"
+        expect req.http.If-Modified-Since == "Thu, 26 Jun 2008 12:00:01 GMT"
+        txresp -status 304
+
+        rxreq
+        expect req.url == "/foo"
+        expect req.http.If-Modified-Since == "req.http.If-Modified-Since"
+        txresp -status 200 -body "foo bar baz quux"
+} -start
+
+varnish v1 -vcl+backend {
+        sub vcl_fetch {
+            set beresp.grace = 1h;
+            set beresp.ttl = 1s;
+        }
+} -start
+
+client c1 {
+        txreq -url "/foo"
+        rxresp
+        expect resp.status == 200
+        expect resp.http.content-length == 16
+        expect resp.http.Age == 0
+}
+
+varnish v1 -cli "param.set default_conditional_timeout 1"
+
+client c1 -run
+
+delay 1
+
+client c1 -run
+
+delay 2
+
+client c1 -run
+
+server s1 -wait
+varnish v1 -stop
+
+## Start a new varnish (now using the default default_conditional_timeout),
+## and manipulate beresp.conditional_timeout in VCL
+
+server s1 -start
+
+varnish v1 -vcl {
+    backend s1 {
+            .host = "${s1_addr}"; .port = "${s1_port}";
+    }
+
+    sub vcl_fetch {
+        set beresp.grace = 1h;
+        set beresp.ttl = 1s;
+        set beresp.conditional_timeout = 1s;
+    }
+} -start
+
+client c1 -run
+
+delay 1
+
+client c1 -run
+
+delay 2
+
+client c1 -run
+
+server s1 -wait
+varnish v1 -stop
+
+## Manipulate obj.conditional_timeout in vcl_hit()
+
+server s1 -start
+
+varnish v1 -vcl {
+    backend s1 {
+            .host = "${s1_addr}"; .port = "${s1_port}";
+    }
+
+    sub vcl_fetch {
+        set beresp.grace = 1h;
+        set beresp.ttl = 1s;
+    }
+
+    sub vcl_hit {
+        set obj.conditional_timeout = 1s;
+    }
+} -start
+
+client c1 -run
+
+delay 1
+
+client c1 -run
+
+delay 2
+
+client c1 -run
+
+server s1 -wait
+varnish v1 -stop
+
+## Manipulate obj.conditional_timeout in vcl_error()
+
+server s1 -start
+
+varnish v1 -vcl {
+    backend s1 {
+            .host = "${s1_addr}"; .port = "${s1_port}";
+    }
+
+    sub vcl_fetch {
+        set beresp.grace = 1h;
+        set beresp.ttl = 1s;
+    }
+
+    sub vcl_hit {
+        error 200 "Ok";
+    }
+
+    sub vcl_error {
+        set obj.conditional_timeout = 1s;
+        return(deliver);
+    }
+} -start
+
+client c1 {
+        txreq -url "/foo"
+        rxresp
+        expect resp.status == 200
+}
+
+client c1 -run
+
+delay 1
+
+client c1 -run
+
+delay 2
+
+client c1 -run
diff --git a/bin/varnishtest/tests/i00005.vtc b/bin/varnishtest/tests/i00005.vtc
new file mode 100644
index 0000000..f7a1b95
--- /dev/null
+++ b/bin/varnishtest/tests/i00005.vtc
@@ -0,0 +1,124 @@
+# $Id$
+
+test "Verify interactions of ttl, conditional_timeout, grace and bans"
+
+## Banned objects are not used for conditional requests
+
+server s1 {
+        rxreq
+        expect req.url == "/foo"
+        txresp -status 200 \
+    	    -hdr "Last-Modified: Thu, 26 Jun 2008 12:00:01 GMT" \
+            -body "The quick brown fox jumps over the lazy dog."
+
+        rxreq
+        expect req.url == "/foo"
+        expect req.http.If-Modified-Since == "req.http.If-Modified-Since"
+        txresp -status 200 -body "The quick brown fox jumps over the lazy dog."
+} -start
+
+varnish v1 -vcl+backend {
+        sub vcl_fetch {
+            set beresp.grace = 1h;
+            set beresp.ttl = 0.5s;
+        }
+} -start
+
+client c1 {
+        txreq -url "/foo"
+        rxresp
+        expect resp.status == 200
+        expect resp.http.content-length == 44
+}
+
+client c1 -run
+
+delay 1
+
+varnish v1 -cli "ban.url \"/foo\""
+
+client c1 -run
+
+server s1 -wait
+varnish v1 -stop
+
+## If ttl is set to 0 without setting non-zero conditional_timeout,
+## then the object is not used for conditional requests
+
+server s1 -start
+
+varnish v1 -vcl {
+    backend s2 {
+            .host = "${s1_addr}"; .port = "${s1_port}";
+    }
+
+    sub vcl_fetch {
+        set beresp.ttl = 0s;
+    }
+} -start
+
+client c1 -run
+
+delay 1
+
+client c1 -run
+
+server s1 -wait
+varnish v1 -stop
+
+## If ttl is set to 0 but conditional_timeout is non-zero,
+## then the object is used for conditional requests
+## First test using beresp in vcl_fetch()
+
+server s2 {
+        rxreq
+        expect req.url == "/foo"
+        txresp -status 200 \
+    	    -hdr "Last-Modified: Thu, 26 Jun 2008 12:00:01 GMT" \
+            -body "The quick brown fox jumps over the lazy dog."
+
+        rxreq
+        expect req.url == "/foo"
+        expect req.http.If-Modified-Since == "Thu, 26 Jun 2008 12:00:01 GMT"
+        txresp -status 304
+} -start
+
+varnish v1 -vcl {
+    backend s2 {
+            .host = "${s2_addr}"; .port = "${s2_port}";
+    }
+
+    # NB: return(deliver) necessary here, because default vcl_fetch() returns
+    # pass if beresp.ttl <= 0
+    sub vcl_fetch {
+        set beresp.ttl = 0s;
+        set beresp.conditional_timeout = 1m;
+        return (deliver);
+    }
+} -start
+
+client c1 -run
+
+client c1 -run
+
+server s2 -wait
+varnish v1 -stop
+
+## Verify using obj.ttl in vcl_hit()
+
+server s2 -start
+
+varnish v1 -vcl {
+    backend s2 {
+            .host = "${s2_addr}"; .port = "${s2_port}";
+    }
+
+    sub vcl_hit {
+        set obj.ttl = 0s;
+        set obj.conditional_timeout = 1m;
+    }
+} -start
+
+client c1 -run
+
+client c1 -run
diff --git a/bin/varnishtest/tests/i00006.vtc b/bin/varnishtest/tests/i00006.vtc
new file mode 100644
index 0000000..5102fe2
--- /dev/null
+++ b/bin/varnishtest/tests/i00006.vtc
@@ -0,0 +1,88 @@
+# $Id$
+
+test "Verify effects of ttl, conditional_timeout, grace on expiration"
+
+## Verify that an object's lifetime in the cache is
+## obj.ttl + max(obj.grace, obj.conditional_timeout)
+
+## First with obj.conditional_timeout > obj.grace
+
+server s1 {
+        rxreq
+        expect req.url == "/foo"
+        txresp -status 200 \
+    	    -hdr "Last-Modified: Thu, 26 Jun 2008 12:00:01 GMT" \
+            -body "fred garply waldo xyzzy"
+
+        rxreq
+        expect req.url == "/foo"
+        expect req.http.If-Modified-Since == "Thu, 26 Jun 2008 12:00:01 GMT"
+        txresp -status 304
+
+        rxreq
+        expect req.url == "/foo"
+        expect req.http.If-Modified-Since == "req.http.If-Modified-Since"
+        txresp -status 200 -body "fred garply waldo xyzzy"
+} -start
+
+varnish v1 -vcl+backend {
+        sub vcl_fetch {
+            set beresp.ttl = 0.5s;
+            set beresp.grace = 0.5s;
+            set beresp.conditional_timeout = 1s;
+        }
+} -start
+
+client c1 {
+        txreq -url "/foo"
+        rxresp
+        expect resp.status == 200
+        expect resp.http.content-length == 23
+} -run
+
+delay 0.5
+
+client c2 {
+        txreq -url "/foo"
+        rxresp
+        expect resp.status == 200
+        expect resp.http.content-length == 23
+        expect resp.msg == "OK Not Modified"
+} -run
+
+delay 1
+
+client c3 {
+        txreq -url "/foo"
+        rxresp
+        expect resp.msg == "Ok"
+} -run
+
+server s1 -wait
+varnish v1 -stop
+
+## Now with obj.conditional_timeout < obj.grace
+
+server s1 -start
+
+varnish v1 -vcl {
+        backend s1 {
+                .host = "${s1_addr}"; .port = "${s1_port}";
+        }
+
+        sub vcl_fetch {
+            set beresp.ttl = 0.5s;
+            set beresp.grace = 1s;
+            set beresp.conditional_timeout = 0.5s;
+        }
+} -start
+
+client c1 -run
+
+delay 0.5
+
+client c2 -run
+
+delay 1
+
+client c3 -run
diff --git a/bin/varnishtest/tests/i00007.vtc b/bin/varnishtest/tests/i00007.vtc
new file mode 100644
index 0000000..7791bbc
--- /dev/null
+++ b/bin/varnishtest/tests/i00007.vtc
@@ -0,0 +1,126 @@
+# $Id$
+
+test "Passes through responses to backend conditionals to the client if status != 304 or 200"
+
+# Testing a sample from each of the nxx status codes
+
+server s1 {
+        rxreq
+        expect req.url == "/foo"
+        txresp -status 200 \
+    	    -hdr "Last-Modified: Thu, 26 Jun 2008 12:00:01 GMT" \
+            -body "Varnish has poked you"
+
+        rxreq
+        expect req.url == "/foo"
+        expect req.http.If-Modified-Since == "Thu, 26 Jun 2008 12:00:01 GMT"
+        txresp -status 304
+
+        rxreq
+        expect req.url == "/foo"
+        expect req.http.If-Modified-Since == "Thu, 26 Jun 2008 12:00:01 GMT"
+        txresp -status 201 -msg "Created" \
+    	    -hdr "Last-Modified: Thu, 26 Jun 2008 12:00:01 GMT" \
+            -body "Varnish has poked you"
+
+        # 3xx responses typically do not include Last-Modified or a body
+        rxreq
+        expect req.url == "/foo"
+        expect req.http.If-Modified-Since == "Thu, 26 Jun 2008 12:00:01 GMT"
+        txresp -status 301 -msg "Moved Permanently"
+
+        # Restore a cached object with Last-Modified
+        rxreq
+        expect req.url == "/foo"
+        expect req.http.If-Modified-Since == "req.http.If-Modified-Since"
+        txresp -status 200 \
+    	    -hdr "Last-Modified: Thu, 26 Jun 2008 12:00:01 GMT" \
+            -body "Varnish has poked you"
+
+        rxreq
+        expect req.url == "/foo"
+        expect req.http.If-Modified-Since == "Thu, 26 Jun 2008 12:00:01 GMT"
+        txresp -status 400 -msg "Bad Request" \
+            -body "Varnish has poked you"
+
+        # Restore a cached object with Last-Modified
+        rxreq
+        expect req.url == "/foo"
+        expect req.http.If-Modified-Since == "req.http.If-Modified-Since"
+        txresp -status 200 \
+    	    -hdr "Last-Modified: Thu, 26 Jun 2008 12:00:01 GMT" \
+            -body "Varnish has poked you"
+
+        rxreq
+        expect req.url == "/foo"
+        expect req.http.If-Modified-Since == "Thu, 26 Jun 2008 12:00:01 GMT"
+        txresp -status 500 -msg "Internal Server Error" \
+            -body "Varnish has poked you"
+} -start
+
+varnish v1 -vcl+backend {
+        sub vcl_fetch {
+            set beresp.ttl = 0.5s;
+            set beresp.grace = 1m;
+            set beresp.conditional_timeout = 1m;
+        }
+} -start
+
+client c1 {
+        txreq -url "/foo"
+        rxresp
+        expect resp.status == 200
+        expect resp.http.content-length == 21
+} -run
+
+delay 1
+
+client c1 -run
+
+delay 1
+
+client c2 {
+        txreq -url "/foo"
+        rxresp
+        expect resp.status == 201
+        expect resp.msg == "Created"
+        expect resp.http.Last-Modified == "Thu, 26 Jun 2008 12:00:01 GMT"
+        expect resp.http.content-length == 21
+} -run
+
+delay 1
+
+client c2 {
+        txreq -url "/foo"
+        rxresp
+        expect resp.status == 301
+        expect resp.msg == "Moved Permanently"
+}  -run
+
+delay 1
+
+client c1 -run
+
+delay 1
+
+client c2 {
+        txreq -url "/foo"
+        rxresp
+        expect resp.status == 400
+        expect resp.msg == "Bad Request"
+        expect resp.http.content-length == 21
+}  -run
+
+delay 1
+
+client c1 -run
+
+delay 1
+
+client c2 {
+        txreq -url "/foo"
+        rxresp
+        expect resp.status == 500
+        expect resp.msg == "Internal Server Error"
+        expect resp.http.content-length == 21
+}  -run
diff --git a/include/vrt.h b/include/vrt.h
index 4ed137e..b865862 100644
--- a/include/vrt.h
+++ b/include/vrt.h
@@ -161,7 +161,7 @@ int VRT_rewrite(const char *, const char *);
 void VRT_error(struct sess *, unsigned, const char *);
 int VRT_switch_config(const char *);
 
-enum gethdr_e { HDR_REQ, HDR_RESP, HDR_OBJ, HDR_BEREQ, HDR_BERESP };
+enum gethdr_e { HDR_REQ, HDR_RESP, HDR_OBJ, HDR_STALE_OBJ, HDR_BEREQ, HDR_BERESP };
 char *VRT_GetHdr(const struct sess *, enum gethdr_e where, const char *);
 void VRT_SetHdr(const struct sess *, enum gethdr_e where, const char *,
     const char *, ...);
diff --git a/include/vsc_fields.h b/include/vsc_fields.h
index ffb56db..44274fa 100644
--- a/include/vsc_fields.h
+++ b/include/vsc_fields.h
@@ -159,6 +159,9 @@ VSC_F(vmods,		uint64_t, 0, 'i', "Loaded VMODs")
 VSC_F(n_gzip,			uint64_t, 0, 'a', "Gzip operations")
 VSC_F(n_gunzip,			uint64_t, 0, 'a', "Gunzip operations")
 
+VSC_F(cache_cond_not_modified,  uint64_t, 1, 'a', "Cond. reqs., not modified")
+VSC_F(cache_cond_modified,      uint64_t, 1, 'a', "Cond. reqs., modified")
+
 #endif
 
 /**********************************************************************/
diff --git a/lib/libvcl/generate.py b/lib/libvcl/generate.py
index 6e27946..ad3a190 100755
--- a/lib/libvcl/generate.py
+++ b/lib/libvcl/generate.py
@@ -346,6 +346,12 @@ sp_variables = (
 		( 'fetch',),
 		'struct sess *'
 	),
+	('beresp.conditional_timeout',
+		'DURATION',
+		( 'fetch',),
+		( 'fetch',),
+		'const struct sess *'
+	),
 	('obj.proto',
 		'STRING',
 		( 'hit', 'error',),
@@ -394,6 +400,72 @@ sp_variables = (
 		( ),
 		'const struct sess *'
 	),
+	('obj.conditional_timeout',
+		'DURATION',
+		( 'hit', 'error',),
+		( 'hit', 'error',),
+		'const struct sess *'
+	),
+	('stale_obj',
+		'BOOL',
+ 		( 'error', 'miss', 'fetch',),
+		( ),
+		'const struct sess *'
+	),
+	('stale_obj.proto',
+		'STRING',
+ 		( 'error', 'miss', 'fetch',),
+		( ),
+		'const struct sess *'
+	),
+	('stale_obj.status',
+		'INT',
+ 		( 'error', 'miss', 'fetch',),
+		( ),
+		'const struct sess *'
+	),
+	('stale_obj.response',
+		'STRING',
+ 		( 'error', 'miss', 'fetch',),
+		( ),
+		'const struct sess *'
+	),
+	('stale_obj.hits',
+		'INT',
+ 		( 'error', 'miss', 'fetch',),
+		( ),
+		'const struct sess *'
+	),
+	('stale_obj.http.',
+		'HDR_STALE_OBJ',
+ 		( 'error', 'miss', 'fetch',),
+		( ),		# XXX ?
+		'const struct sess *'
+	),
+	('stale_obj.ttl',
+		'DURATION',
+ 		( 'error', 'miss', 'fetch',),
+		( ),
+		'const struct sess *'
+	),
+	('stale_obj.grace',
+		'DURATION',
+ 		( 'error', 'miss', 'fetch',),
+		( ),
+		'struct sess *'
+	),
+	('stale_obj.lastuse',
+		'DURATION',
+ 		( 'error', 'miss', 'fetch',),
+		( ),
+		'const struct sess *'
+	),
+	('stale_obj.conditional_timeout',
+		'DURATION',
+ 		( 'error', 'miss', 'fetch',),
+		( ),
+		'const struct sess *'
+	),
 	('resp.proto',
 		'STRING',
 		( 'deliver',),
-- 
1.7.2

