diff --git a/CHANGES b/CHANGES
index c17ec21748a0f244f1e2f43f505cb329d015239c..f841ae536a3211a0aeda782c1982b3b115767a70 100644
--- a/CHANGES
+++ b/CHANGES
@@ -3,6 +3,7 @@ OpenLDAP 2.4 Change Log
 OpenLDAP 2.4.16 Engineering
 	Fixed libldap GnuTLS TLSVerifyCilent try (ITS#5981)
 	Fixed libldap segfault in checking cert/DN (ITS#5976)
+	Fixed slapd-bdb/hdb cachesize handling (ITS#5860)
 	Fixed slapd-ldap/meta with broken AD results (ITS#5977)
 	Fixed slapd-ldap/meta with invalid attrs again (ITS#5959)
 	Fixed slapo-accesslog interaction with ppolicy (ITS#5979)
diff --git a/servers/slapd/back-bdb/cache.c b/servers/slapd/back-bdb/cache.c
index 4b5edcfac3c71b952a52e3973564495f21944895..6eddd00856cfc1cd8a68d02de4789aa411803229 100644
--- a/servers/slapd/back-bdb/cache.c
+++ b/servers/slapd/back-bdb/cache.c
@@ -658,6 +658,10 @@ bdb_cache_lru_purge( struct bdb_info *bdb )
 	DB_LOCK		lock, *lockp;
 	EntryInfo *elru, *elnext = NULL;
 	int count, islocked, eimax;
+	int efree = 0, eifree = 0, eicount, ecount;
+#ifdef LDAP_DEBUG
+	int iter;
+#endif
 
 	/* Wait for the mutex; we're the only one trying to purge. */
 	ldap_pvt_thread_mutex_lock( &bdb->bi_cache.c_lru_mutex );
@@ -670,8 +674,15 @@ bdb_cache_lru_purge( struct bdb_info *bdb )
 	else
 		eimax = bdb->bi_cache.c_eimax;
 
-	if ( bdb->bi_cache.c_cursize <= bdb->bi_cache.c_maxsize &&
-		bdb->bi_cache.c_leaves <= eimax ) {
+	if ( bdb->bi_cache.c_cursize > bdb->bi_cache.c_maxsize )
+		efree = bdb->bi_cache.c_minfree;
+	if ( bdb->bi_cache.c_leaves > eimax ) {
+		eifree = bdb->bi_cache.c_minfree * 10;
+		if ( eifree >= eimax )
+			eifree = eimax / 2;
+	}
+
+	if ( !efree && !eifree ) {
 		ldap_pvt_thread_mutex_unlock( &bdb->bi_cache.c_lru_mutex );
 		bdb->bi_cache.c_purging = 0;
 		return;
@@ -684,6 +695,11 @@ bdb_cache_lru_purge( struct bdb_info *bdb )
 	}
 
 	count = 0;
+	eicount = 0;
+	ecount = 0;
+#ifdef LDAP_DEBUG
+	iter = 0;
+#endif
 
 	/* Look for an unused entry to remove */
 	for ( elru = bdb->bi_cache.c_lruhead; elru; elru = elnext ) {
@@ -720,8 +736,8 @@ bdb_cache_lru_purge( struct bdb_info *bdb )
 
 			/* Free entry for this node if it's present */
 			if ( elru->bei_e ) {
-				if ( bdb->bi_cache.c_cursize > bdb->bi_cache.c_maxsize &&
-					count < bdb->bi_cache.c_minfree ) {
+				ecount++;
+				if ( count < efree ) {
 					elru->bei_e->e_private = NULL;
 #ifdef SLAP_ZONE_ALLOC
 					bdb_entry_return( bdb, elru->bei_e, elru->bei_zseq );
@@ -744,11 +760,12 @@ bdb_cache_lru_purge( struct bdb_info *bdb )
 			if ( elru->bei_kids ) {
 				/* Drop from list, we ignore it... */
 				LRU_DEL( &bdb->bi_cache, elru );
-			} else if ( bdb->bi_cache.c_leaves > eimax ) {
+			} else if ( eicount < eifree ) {
 				/* Too many leaf nodes, free this one */
 				bdb_cache_delete_internal( &bdb->bi_cache, elru, 0 );
 				bdb_cache_delete_cleanup( &bdb->bi_cache, elru );
 				islocked = 0;
+				eicount++;
 			}	/* Leave on list until we need to free it */
 		}
 
@@ -756,10 +773,12 @@ next:
 		if ( islocked )
 			bdb_cache_entryinfo_unlock( elru );
 
-		if (( bdb->bi_cache.c_cursize <= bdb->bi_cache.c_maxsize ||
-			(unsigned) count >= bdb->bi_cache.c_minfree ) && bdb->bi_cache.c_leaves <= eimax ) {
-			if ( count ) {
+		if ( count >= efree && eicount >= eifree ) {
+			if ( count || ecount > bdb->bi_cache.c_cursize ) {
 				ldap_pvt_thread_mutex_lock( &bdb->bi_cache.c_count_mutex );
+				/* HACK: we seem to be losing track, fix up now */
+				if ( ecount > bdb->bi_cache.c_cursize )
+					bdb->bi_cache.c_cursize = ecount;
 				bdb->bi_cache.c_cursize -= count;
 				ldap_pvt_thread_mutex_unlock( &bdb->bi_cache.c_count_mutex );
 			}
@@ -768,6 +787,9 @@ next:
 bottom:
 		if ( elnext == bdb->bi_cache.c_lruhead )
 			break;
+#ifdef LDAP_DEBUG
+		iter++;
+#endif
 	}
 
 	bdb->bi_cache.c_lruhead = elnext;