Index: examples/hashtable2.cfa
===================================================================
--- examples/hashtable2.cfa	(revision 8fc6e92a94eca513939b0d56129c4a8673416898)
+++ examples/hashtable2.cfa	(revision df40a5609f2a0d7e3d2ad2acbfe04866705db329)
@@ -32,4 +32,6 @@
 DATA_EXCEPTION(ht_fill_limit_crossed)(
 	void * theHashtable;
+    bool want_throwResume_ht_auto_resize_pending;
+    size_t size_for_ht_auto_resize_pending;
 );
 
@@ -37,4 +39,6 @@
 	VTABLE_INIT(this, ht_fill_limit_crossed);
 	this.theHashtable = theHashtable;
+    this.want_throwResume_ht_auto_resize_pending = false;
+    this.size_for_ht_auto_resize_pending = 0;
 }
 
@@ -45,4 +49,21 @@
 VTABLE_INSTANCE(ht_fill_limit_crossed)(ht_fill_limit_crossed_msg);
 
+
+DATA_EXCEPTION(ht_auto_resize_pending)(
+	void * theHashtable;
+    size_t new_size;
+);
+
+void ?{}(ht_auto_resize_pending & this, void * theHashtable, size_t new_size) {
+	VTABLE_INIT(this, ht_auto_resize_pending);
+	this.theHashtable = theHashtable;
+    this.new_size = new_size;
+}
+
+const char * ht_auto_resize_pending_msg(ht_auto_resize_pending * this) {
+	return "ht_auto_resize_pending";
+}
+
+VTABLE_INSTANCE(ht_auto_resize_pending)(ht_auto_resize_pending_msg);
 
 
@@ -75,4 +96,5 @@
         size_t item_count;
         float ff_next_warn_up;
+        float ff_warn_step_factor;
 
         void (*defaultResumptionHandler) (ht_fill_limit_crossed &);
@@ -84,5 +106,8 @@
 forall( otype Tt_unused | pretendsToMatter(Tt_unused) | { void defaultResumptionHandler(ht_fill_limit_crossed &); } ) {
 
-    void ?{}( hashtable_rbs(Tt_unused) & this, size_t n_buckets, dlist(request_in_ht_by_src, request) *buckets ) {
+    void ?{}( hashtable_rbs(Tt_unused) & this, size_t n_buckets, dlist(request_in_ht_by_src, request) *buckets,
+        float ff_next_warn_up, float ff_warn_step_factor ) {
+
+        printf( "base hashtable ctor with %ld buckets at %p\n", n_buckets, buckets);
 
         this.n_buckets = n_buckets;
@@ -90,5 +115,6 @@
 
         this.item_count = 0;
-        this.ff_next_warn_up = 0.5;
+        this.ff_next_warn_up = ff_next_warn_up;
+        this.ff_warn_step_factor = ff_warn_step_factor;
 
         this.defaultResumptionHandler = defaultResumptionHandler;
@@ -98,6 +124,14 @@
         }
     }
-}
-
+
+    void ?{}( hashtable_rbs(Tt_unused) & this, size_t n_buckets, dlist(request_in_ht_by_src, request) *buckets ) {
+        printf( "base hashtable ctor with default warning steps\n" );
+        ( this ) { n_buckets, buckets, 0.5, 2 };
+    }
+
+}
+
+// this fwd declaration is artifact of workaround trac#192
+void defaultResumptionHandler( ht_auto_resize_pending & ex );
 
 forall( otype Tt_unused | pretendsToMatter(Tt_unused) ) {
@@ -126,5 +160,11 @@
     void check_ff_warning( hashtable_rbs(Tt_unused) & this ) with (this) {
         if (fill_frac(this) > ff_next_warn_up) {
-            throwResume (ht_fill_limit_crossed){ &this };
+            ht_fill_limit_crossed ex1 = { &this };
+            throwResume ex1;
+            // workaround trac#192: want the second throwResume to be in __dynamic_defaultResumptionHandler
+            // ... want base hashtable decoupled from resize
+            if ( ex1.want_throwResume_ht_auto_resize_pending ) {
+                throwResume( (ht_auto_resize_pending) { & this, ex1.size_for_ht_auto_resize_pending } );
+            }
         }
     }
@@ -170,7 +210,7 @@
 
 void defaultResumptionHandler(ht_fill_limit_crossed & ex) {
-    printf("default resumption ht_fill_limit_crossed\n");
     hashtable_rbs(t_unused) & ht = *(hashtable_rbs(t_unused) *)ex.theHashtable;
-    ht.ff_next_warn_up *= 2;
+    printf("base default resumption handler ht_fill_limit_crossed with ht filled at %f\n", fill_frac(ht));
+    ht.ff_next_warn_up *= ht.ff_warn_step_factor;
 }
 
@@ -192,4 +232,20 @@
     struct hashtable_rbs_dynamic { 
         inline hashtable_rbs(Tt_unused);
+
+        struct resize_policy {
+            // When fill factor exceeds grow limit, grow big enough for
+            // resulting fill factor to be lower than grow_target.  Vice versa.
+            // Using different grow and shrink limits prevents noisy current
+            // size from triggering grow-shrink oscillation.  OK to use same
+            // grow and shrink targets.
+            float grow_limit, shrink_limit, grow_target, shrink_target;
+
+            // warn with exception but do nothing, this many -1 times, then actually resize
+            unsigned short int warns_per_grow, warns_per_shrink;
+
+            // Don't shrink below.
+            size_t nbuckets_floor;
+        } policy;
+
         dlist(request_in_ht_by_src, request) * (*alloc)( size_t );
         void (*free)( void * ); 
@@ -209,10 +265,36 @@
 forall( otype Tt_unused | heaped( dlist(request_in_ht_by_src, request) ) ) {
 
-    void ?{}( hashtable_rbs_dynamic(Tt_unused) & this, size_t n_buckets )  {
+    void ?{}( hashtable_rbs_dynamic(Tt_unused).resize_policy & this, size_t nbuckets_floor ) {
+        printf("default dynamic policy ctor\n");
+
+        (this.grow_limit)      {2.0};
+        (this.shrink_limit)    {0.5};
+        (this.grow_target)     {1.0};
+        (this.shrink_target)   {1.0};
+        (this.warns_per_grow)  {4};
+        (this.warns_per_shrink){4};
+        (this.nbuckets_floor)  {nbuckets_floor};
+    }
+
+    void ?{}( hashtable_rbs_dynamic(Tt_unused) & this, size_t n_buckets, hashtable_rbs_dynamic(Tt_unused).resize_policy rp )  {
+        printf("ctor hashtable_rbs_dynamic{ size_t, resize_policy }\n");
+
+        float first_first_warn_up = rp.grow_target;
+        float ff_warn_step_factor = (rp.grow_limit / rp.grow_target) \ ( 1. / rp.warns_per_grow );
+
         void (*defaultResumptionHandler) (ht_fill_limit_crossed &) = __dynamic_defaultResumptionHandler;
         dlist(request_in_ht_by_src, request) *buckets = alloc(n_buckets);
-        ((hashtable_rbs(Tt_unused) &)this){ n_buckets, buckets };
+        ( ( hashtable_rbs( Tt_unused ) & ) this ){ n_buckets, buckets, first_first_warn_up, ff_warn_step_factor };
+        ( this.policy ){ rp };
         this.alloc = alloc;
         this.free = free;
+    }
+    void ?{}( hashtable_rbs_dynamic(Tt_unused) & this, hashtable_rbs_dynamic(Tt_unused).resize_policy rp )  {
+        printf("ctor hashtable_rbs_dynamic{ resize_policy }\n");
+        ( this ) { rp.nbuckets_floor, rp };
+    }
+    void ?{}( hashtable_rbs_dynamic(Tt_unused) & this, size_t n_buckets )  {
+        printf("ctor hashtable_rbs_dynamic{ size_t }\n");
+        ( this ) { n_buckets, (hashtable_rbs_dynamic(Tt_unused).resize_policy){ n_buckets } };
     }
     void ^?{}( hashtable_rbs_dynamic(Tt_unused) & this ) {
@@ -230,7 +312,9 @@
         // make empty hash table of new size
         dlist(request_in_ht_by_src, request) *oldBuckets = buckets;
+        float oldFfWarnStepFactor = ff_warn_step_factor;
+        float newFfNextWarnUp = ((float)item_count) / ((float) new_n_buckets);
         ^?{}( (hashtable_rbs(Tt_unused) &)this );
         free( oldBuckets );
-        ?{}( (hashtable_rbs(Tt_unused) &)this, new_n_buckets, alloc(new_n_buckets) );
+        ?{}( (hashtable_rbs(Tt_unused) &)this, new_n_buckets, alloc(new_n_buckets), newFfNextWarnUp, oldFfWarnStepFactor );
 
         // fill new table with old items
@@ -247,8 +331,21 @@
 }
 
+void defaultResumptionHandler( ht_auto_resize_pending & ex ) {
+    hashtable_rbs_dynamic(t_unused) & ht = *(hashtable_rbs_dynamic(t_unused) *)ex.theHashtable;
+    printf("auto-resize unhandled: proceeding with resize\n");
+    rehashToLarger_STEP( ht, ex.new_size );
+}
+
 void __dynamic_defaultResumptionHandler(ht_fill_limit_crossed & ex) {
     hashtable_rbs_dynamic(t_unused) & ht = *(hashtable_rbs_dynamic(t_unused) *)ex.theHashtable;
-    printf("dynamic limit crossed with fill_frac = %f and buckets at %p\n", fill_frac(ht), ht.buckets);
-    rehashToLarger_STEP( ht, 2 * ht.n_buckets );
+    printf("dynamic warning received with fill_frac = %f and buckets at %p\n", fill_frac(ht), ht.buckets);
+    if ( fill_frac( ht ) >= ht.policy.grow_limit ) {
+        float grow_amount =  ht.policy.grow_limit / ht.policy.grow_target;
+        ex.want_throwResume_ht_auto_resize_pending = true;
+        ex.size_for_ht_auto_resize_pending = ( size_t )( grow_amount * ht.n_buckets );
+    } else {
+        // base handler, not specialized for dynamic
+        defaultResumptionHandler( ex );
+    }
 }
 
@@ -260,10 +357,7 @@
 #include <stdlib.hfa>
 
-int main() {
-
-
-    HASHTABLE_RBS_STATIC(67, h_src)
-
-    request & wasnt_found = get(h_src, 17);
+void basicFillingTestHelper( hashtable_rbs(t_unused) & ht, size_t n_elems ) {
+
+    request & wasnt_found = get(ht, 17);
     assert( &wasnt_found == 0p );
 
@@ -272,31 +366,33 @@
     r.tgt_id = 998;
 
-    put(h_src, r);
-
-    request & found = get(h_src, 117);
+    put(ht, r);
+
+    request & found = get(ht, 117);
     assert( &found == &r );
 
-    & wasnt_found = & get(h_src, 998);
+    & wasnt_found = & get(ht, 998);
     assert( &wasnt_found == 0p );
 
-    printf( "%f\n", fill_frac(h_src) );
-
-
-    request rs[500];
-    try {
-        for (i; 500) {
-            rs[i].src_id = 8000 * i;
-            put(h_src, rs[i]);
-        }
-    } catchResume(ht_fill_limit_crossed*) {
-        printf("fill limit tripped with h_src filled at %f\n", fill_frac(h_src));
-        throwResume;
-    }
-
-    assert(  & get(h_src, 117      ) );
-    assert(  & get(h_src, 8000*25  ) );
-    assert(! & get(h_src, 8000*25+1) );
-
-
+    request rs[n_elems];
+    for (i; n_elems) {
+        rs[i].src_id = 8000 * i;
+        put(ht, rs[i]);
+    }
+
+    assert(  & get(ht, 117      ) );
+    assert(  & get(ht, 8000*25  ) );
+    assert(! & get(ht, 8000*25+1) );
+}
+
+void basicFillingTest_static() {
+
+    printf("---start basic fill test static ----\n");
+
+    HASHTABLE_RBS_STATIC(67, ht)
+
+    basicFillingTestHelper(ht, 500);
+}
+
+void basicFillingTest_dynamic() {
 
     dlist(request_in_ht_by_src, request) * (*old_alloc)( size_t ) = alloc;
@@ -313,17 +409,58 @@
     }
 
-    hashtable_rbs_dynamic(t_unused) ht2 = { 113 };
-    request rs2[500];
+    printf("---start basic fill test dynamic ----\n");
+
+    hashtable_rbs_dynamic(t_unused) ht = { 113 };
+
+    basicFillingTestHelper(ht, 500);
+}
+
+// Demonstrates user-provided instrumentation monitoring a fixed-size hash table
+void logTest() {
+
+    printf("---start log test ----\n");
+
+    HASHTABLE_RBS_STATIC(67, ht)
+
     try {
-        for (i; 500) {
-            if (i % 10 == 0) {printf("%d(%f),", i, fill_frac(ht2));}
-            rs2[i].src_id = 8000 * i;
-            put(ht2, rs2[i]);
-        }
-    } catchResume(ht_fill_limit_crossed*) {
-        printf("fill limit tripped with ht2 filled at %f\n", fill_frac(ht2));
+        basicFillingTestHelper(ht, 500);
+    } catchResume( ht_fill_limit_crossed * ) {
+        printf("log test instrumentation runs\n");
         throwResume;
     }
-
-
-}
+}
+
+// Demonstrates "snoozing" a growing hash table's auto-resize event,
+// in that that next call to put will get the resize exception instead.
+void snoozeTest() {
+
+    printf("---start snooze test ----\n");
+
+    hashtable_rbs_dynamic(t_unused) ht = { 113 };
+
+    bool lastResizeSnoozed = false;
+
+    try {
+        basicFillingTestHelper(ht, 500);
+    } catchResume( ht_auto_resize_pending * ) {
+
+        if ( lastResizeSnoozed == false ) {
+            lastResizeSnoozed = true;
+            printf("snooze test intervention decides to snooze this time\n");
+        } else {
+            lastResizeSnoozed = false;
+            printf("snooze test intervention decides to allow the resize\n");
+            throwResume;
+        }
+
+    }
+}
+
+int main() {
+
+    basicFillingTest_static();
+    basicFillingTest_dynamic();
+
+    logTest();
+    snoozeTest();
+}
