1d0431
From 44e8e5f79616fb80edf8af010332c18a628af861 Mon Sep 17 00:00:00 2001
1d0431
From: Alexander Bokovoy <abokovoy@redhat.com>
1d0431
Date: Thu, 29 Oct 2015 17:34:48 +0100
1d0431
Subject: [PATCH 1/2] slapi-nis: delay sending responses from compat tree after
1d0431
 map search
1d0431
1d0431
When slapi-nis plugin responds on a search query, it holds read lock for
1d0431
the internal structure called 'map cache'. The map cache lock can also be taken
1d0431
for write when modification would be required like responding to DELETE, ADD, or
1d0431
MODIFY operations.
1d0431
1d0431
As result of the lock semantics, write lock owner is blocked until all read lock
1d0431
owners release their locks. This is generally not a problem but when readers sent
1d0431
out LDAP query results, they call into SLAPI function that might take long time
1d0431
to send out the data due to external reasons (network latencies, clients being
1d0431
blocked, etc) and all this time map cache is locked for write operations.
1d0431
1d0431
When Kerberos KDC issues a TGT, it needs to modify few Kerberos-related attributes
1d0431
in the principal's LDAP entry. These updates are generating MOD operations visible
1d0431
by slapi-nis plugin which triggers re-scan of map cache to potentially replace
1d0431
the affected entries. To perform potential replacement, slapi-nis has to take a write
1d0431
lock and be blocked by outstanding readers.
1d0431
1d0431
Therefore, it is possible to encounter a situation where an LDAP client uses
1d0431
SASL GSSAPI authentication and existing Kerberos ticket did expire in a course
1d0431
of outstanding search request. According to LDAPv3 protocol specification, an
1d0431
LDAP client must perform re-negotiation before reading any outstanding PDUs. It
1d0431
would ask Kerberos KDC for a new (or renewed) TGT, that would cause MOD updates
1d0431
for the primary tree which is tracked for changes by slapi-nis. These changes
1d0431
would be blocked by a slapi-nis reader as the client cannot finish reading
1d0431
outstanding PDUs yet.
1d0431
1d0431
To solve this problem, we avoid sending LDAP entries while keeping map cache
1d0431
lock. Instead, we generate a linked list of copies of entries which will be
1d0431
sent out. To allow sharing of entries between multiple parallel queries, we
1d0431
hash the entry and reference the cached entry in the linked list with increased
1d0431
reference count. Once entry is actually sent, its reference count decreased and
1d0431
on reaching zero it is removed from the hash.
1d0431
1d0431
o solve this problem, we avoid sending LDAP entries while keeping map cache
1d0431
lock. Instead, we generate a linked list of copies of entries which will be
1d0431
sent out. To allow sharing of entries between multiple parallel queries, we
1d0431
hash the entry and reference the cached entry in the linked list with increased
1d0431
reference count. Once entry is actually sent, its reference count decreased and
1d0431
on reaching zero it is removed from the hash.
1d0431
1d0431
The entry in the hash table might become outdated. This is detected by comparing
1d0431
both modifyTimestamp and entryUSN values of the entry to be sent and entry in the
1d0431
hash table. If new version of the entry is different, hash table's entry reference
1d0431
is replaced with a new copy. The old entry is not removed because it is still
1d0431
referenced by some outstanding query processing. Thus, the hash table always
1d0431
references the most recent version of an entry but there might be multiple copies
1d0431
in possesion of the linked lists from the separate parallel queries.
1d0431
1d0431
An entry sharing via hash table can be disabled by setting
1d0431
        slapi-entry-cache: 0
1d0431
in the definition, cn=Schema Compatibility,cn=plugins,cn=config
1d0431
1d0431
Resolves: rhbz#1273587
1d0431
1d0431
https://bugzilla.redhat.com/show_bug.cgi?id=1273587
1d0431
---
1d0431
 doc/sch-configuration.txt |   7 ++
1d0431
 src/back-sch.c            | 159 ++++++++++++++++++++++++++++++++++++++++++----
1d0431
 src/back-sch.h            |  20 ++++++
1d0431
 src/plug-sch.c            |  34 ++++++++++
1d0431
 src/plugin.h              |   3 +
1d0431
 5 files changed, 212 insertions(+), 11 deletions(-)
1d0431
1d0431
diff --git a/doc/sch-configuration.txt b/doc/sch-configuration.txt
1d0431
index e07a4af..dd8b3c4 100644
1d0431
--- a/doc/sch-configuration.txt
1d0431
+++ b/doc/sch-configuration.txt
1d0431
@@ -16,6 +16,13 @@ look like this:
1d0431
   nsslapd-version: 0.0
1d0431
   nsslapd-pluginvendor: redhat.com
1d0431
   nsslapd-plugindescription: Schema Compatibility Plugin
1d0431
+  slapi-entry-cache: 1
1d0431
+
1d0431
+The only optional attribute is 'slapi-entry-cache' (default to 1)
1d0431
+controls whether the plugin should use an entry cache for outstanding
1d0431
+query requests. The entry cache is an optimization technique to
1d0431
+help reduce memory pressure during parallel requests. Specify 0 to disable
1d0431
+an entry cache.
1d0431
 
1d0431
 Configuration for individual sets should be stored in entries directly
1d0431
 beneath the plugin's entry.  These attributes are recognized:
1d0431
diff --git a/src/back-sch.c b/src/back-sch.c
1d0431
index dd6f92d..b2362d0 100644
1d0431
--- a/src/back-sch.c
1d0431
+++ b/src/back-sch.c
1d0431
@@ -32,6 +32,7 @@
1d0431
 
1d0431
 #ifdef HAVE_DIRSRV_SLAPI_PLUGIN_H
1d0431
 #include <nspr.h>
1d0431
+#include <plhash.h>
1d0431
 #include <nss.h>
1d0431
 #include <dirsrv/slapi-plugin.h>
1d0431
 #else
1d0431
@@ -53,6 +54,9 @@
1d0431
 #include "map.h"
1d0431
 #include "back-sch.h"
1d0431
 
1d0431
+static void
1d0431
+backend_entries_to_return_push(struct backend_search_cbdata *cbdata, Slapi_Entry *e);
1d0431
+
1d0431
 #define SCH_CONTAINER_CONFIGURATION_FILTER "(&(" SCH_CONTAINER_CONFIGURATION_GROUP_ATTR "=*)(" SCH_CONTAINER_CONFIGURATION_BASE_ATTR "=*)(" SCH_CONTAINER_CONFIGURATION_FILTER_ATTR "=*)(" SCH_CONTAINER_CONFIGURATION_RDN_ATTR "=*))"
1d0431
 
1d0431
 /* Read the name of the NIS master. A dummy function for the schema
1d0431
@@ -996,7 +1000,7 @@ backend_search_entry_cb(const char *domain, const char *map, bool_t secure,
1d0431
 			void *backend_data, void *cb_data)
1d0431
 {
1d0431
 	Slapi_DN *sdn;
1d0431
-	Slapi_Entry *entry;
1d0431
+	Slapi_Entry *entry = NULL; /* prevent to free an uninitialized entry */
1d0431
 	Slapi_Attr *attr = NULL;
1d0431
 	struct backend_search_cbdata *cbdata;
1d0431
 	struct backend_entry_data *entry_data;
1d0431
@@ -1052,8 +1056,7 @@ backend_search_entry_cb(const char *domain, const char *map, bool_t secure,
1d0431
 			slapi_entry_delete_string(entry, "objectClass", "ipaOverrideTarget");
1d0431
 		}
1d0431
 #endif
1d0431
-		slapi_send_ldap_search_entry(cbdata->pb, entry, NULL,
1d0431
-					     cbdata->attrs, cbdata->attrsonly);
1d0431
+		backend_entries_to_return_push(cbdata, entry);
1d0431
 		cbdata->n_entries++;
1d0431
 
1d0431
 		if (entry != entry_data->e) {
1d0431
@@ -1070,7 +1073,7 @@ backend_search_set_cb(const char *group, const char *set, bool_t flag,
1d0431
 {
1d0431
 	struct backend_search_cbdata *cbdata;
1d0431
 	struct backend_set_data *set_data;
1d0431
-	Slapi_Entry *set_entry;
1d0431
+	Slapi_Entry *set_entry = NULL ; /* prevent to free an uninitialized entry */
1d0431
 	int result, n_entries;
1d0431
 	int n_entries_without_nsswitch;
1d0431
 	const char *ndn;
1d0431
@@ -1124,12 +1127,11 @@ backend_search_set_cb(const char *group, const char *set, bool_t flag,
1d0431
 							 set_data->common.group, set_entry);
1d0431
 			}
1d0431
 #endif
1d0431
-			slapi_send_ldap_search_entry(cbdata->pb, set_entry,
1d0431
-						     NULL, cbdata->attrs,
1d0431
-						     cbdata->attrsonly);
1d0431
+			backend_entries_to_return_push(cbdata, set_entry);
1d0431
 			cbdata->n_entries++;
1d0431
 			break;
1d0431
 		}
1d0431
+
1d0431
 		slapi_entry_free(set_entry);
1d0431
 	}
1d0431
 
1d0431
@@ -1244,7 +1246,7 @@ backend_search_group_cb(const char *group, void *cb_data)
1d0431
 {
1d0431
 	struct backend_search_cbdata *cbdata;
1d0431
 	Slapi_DN *group_dn;
1d0431
-	Slapi_Entry *group_entry;
1d0431
+	Slapi_Entry *group_entry = NULL; /* prevent to free an uninitialized entry */
1d0431
 	int result, n_maps;
1d0431
 
1d0431
 	cbdata = cb_data;
1d0431
@@ -1279,12 +1281,11 @@ backend_search_group_cb(const char *group, void *cb_data)
1d0431
 				idview_process_overrides(cbdata, NULL, NULL, group, group_entry);
1d0431
 			}
1d0431
 #endif
1d0431
-			slapi_send_ldap_search_entry(cbdata->pb, group_entry,
1d0431
-						     NULL, cbdata->attrs,
1d0431
-						     cbdata->attrsonly);
1d0431
+			backend_entries_to_return_push(cbdata, group_entry);
1d0431
 			cbdata->n_entries++;
1d0431
 			break;
1d0431
 		}
1d0431
+
1d0431
 		slapi_entry_free(group_entry);
1d0431
 	}
1d0431
 
1d0431
@@ -1343,6 +1344,138 @@ backend_sch_scope_as_string(int scope)
1d0431
 	return "";
1d0431
 }
1d0431
 
1d0431
+/* The entries are pushed (added) at the end of the list
1d0431
+ * so that they will be send in the head->tail order
1d0431
+ */
1d0431
+static void
1d0431
+backend_entries_to_return_push(struct backend_search_cbdata *cbdata, Slapi_Entry *e)
1d0431
+{
1d0431
+	struct entries_to_send *e_to_send = NULL;
1d0431
+	struct cached_entry *entry = NULL;
1d0431
+	bool_t dont_cache = FALSE;
1d0431
+	PLHashTable* ht = (PLHashTable*) cbdata->state->cached_entries;
1d0431
+
1d0431
+	if ((cbdata == NULL) || (e == NULL)) return;
1d0431
+
1d0431
+	e_to_send = (struct entries_to_send *) slapi_ch_calloc(1, sizeof(struct entries_to_send));
1d0431
+
1d0431
+	dont_cache = cbdata->state->use_entry_cache ? FALSE : TRUE;
1d0431
+
1d0431
+	if (!wrap_rwlock_wrlock(cbdata->state->cached_entries_lock)) {
1d0431
+		entry = PL_HashTableLookup(ht, slapi_entry_get_ndn(e));
1d0431
+		if (entry != NULL) {
1d0431
+			/* There is an entry in the hash table but is it the same? */
1d0431
+			char *e_modifyTimestamp = slapi_entry_attr_get_charptr(e, "modifyTimestamp");
1d0431
+			char *entry_modifyTimestamp = slapi_entry_attr_get_charptr(entry->entry, "modifyTimestamp");
1d0431
+			unsigned long e_usn = slapi_entry_attr_get_ulong(e, "entryUSN");
1d0431
+			unsigned long entry_usn = slapi_entry_attr_get_ulong(entry->entry, "entryUSN");
1d0431
+			int res = -1;
1d0431
+
1d0431
+			/* Our comparison strategy is following:
1d0431
+			 * - compare modifyTimestamp values first,
1d0431
+			 * - if they are the same (modifyTimestamp in slapi-nis is down to a second precision),
1d0431
+			 *   compare entryUSN values if they exist
1d0431
+			 * - default to not using the cached entry to be on safe side if both comparisons don't
1d0431
+			 *   give us a definite answer */
1d0431
+			if ((e_modifyTimestamp != NULL) && (entry_modifyTimestamp != NULL)) {
1d0431
+				res = strncmp(e_modifyTimestamp, entry_modifyTimestamp, strlen(e_modifyTimestamp));
1d0431
+			}
1d0431
+
1d0431
+			if ((res == 0) && ((e_usn != 0) && (entry_usn != 0))) {
1d0431
+				res = e_usn != entry_usn ? 1 : 0;
1d0431
+			}
1d0431
+
1d0431
+			if (res != 0) {
1d0431
+				/* Cached entry is different, evict it from the hash table */
1d0431
+				(void) PL_HashTableRemove(ht, slapi_entry_get_ndn(entry->entry));
1d0431
+
1d0431
+				/* We don't want to clear the entry because it is still in use by other thread.
1d0431
+				 * Instead, we'll insert new entry into hash table, let the linked list in other
1d0431
+				 * search to remove the entry itself, but mark it as non-cached. */
1d0431
+				entry->not_cached = TRUE;
1d0431
+				entry = NULL;
1d0431
+			} else {
1d0431
+				slapi_log_error(SLAPI_LOG_PLUGIN,
1d0431
+					cbdata->state->plugin_desc->spd_id,
1d0431
+					"referenced entry [%s], USNs: %ld vs %ld, [%s] vs [%s]\n",
1d0431
+					slapi_entry_get_ndn(e), e_usn, entry_usn, e_modifyTimestamp, entry_modifyTimestamp);
1d0431
+				/* It is the same entry, reference it for us */
1d0431
+				(void) PR_AtomicIncrement(&entry->refcount);
1d0431
+			}
1d0431
+
1d0431
+			if (e_modifyTimestamp != NULL)
1d0431
+				slapi_ch_free_string(&e_modifyTimestamp);
1d0431
+			if (entry_modifyTimestamp != NULL)
1d0431
+				slapi_ch_free_string(&entry_modifyTimestamp);
1d0431
+		}
1d0431
+
1d0431
+		if (entry == NULL) {
1d0431
+			/* no cached entry for this DN */
1d0431
+			entry = (struct cached_entry *) slapi_ch_calloc(1, sizeof(struct cached_entry));
1d0431
+			entry->entry = slapi_entry_dup(e);
1d0431
+			entry->not_cached = FALSE;
1d0431
+			(void) PR_AtomicSet(&entry->refcount, 1);
1d0431
+			if ((ht != NULL) && (entry->entry != NULL) && (!dont_cache)) {
1d0431
+				(void) PL_HashTableAdd(ht, slapi_entry_get_ndn(entry->entry), entry);
1d0431
+			}
1d0431
+		}
1d0431
+
1d0431
+		wrap_rwlock_unlock(cbdata->state->cached_entries_lock);
1d0431
+	}
1d0431
+
1d0431
+	e_to_send->entry = entry;
1d0431
+	if (cbdata->entries_tail == NULL) {
1d0431
+		/* First entry in that list */
1d0431
+		cbdata->entries_tail = e_to_send;
1d0431
+		cbdata->entries_head = e_to_send;
1d0431
+	} else {
1d0431
+		cbdata->entries_tail->next = e_to_send;
1d0431
+		cbdata->entries_tail = e_to_send;
1d0431
+	}
1d0431
+}
1d0431
+
1d0431
+static void
1d0431
+backend_send_mapped_entries(struct backend_search_cbdata *cbdata)
1d0431
+{
1d0431
+	struct entries_to_send *e_to_send, *next;
1d0431
+	PLHashTable* ht = NULL;
1d0431
+	int i = 0;
1d0431
+	PRInt32 count;
1d0431
+
1d0431
+	if (cbdata == NULL) return;
1d0431
+	ht = (PLHashTable*) cbdata->state->cached_entries;
1d0431
+
1d0431
+	/* iterate from head->tail sending the stored entries */
1d0431
+	for (e_to_send = cbdata->entries_head, i = 0; e_to_send != NULL; i++) {
1d0431
+		next = e_to_send->next;
1d0431
+		if (e_to_send->entry->refcount > 0) {
1d0431
+			slapi_send_ldap_search_entry(cbdata->pb, e_to_send->entry->entry, NULL,
1d0431
+						     cbdata->attrs, cbdata->attrsonly);
1d0431
+
1d0431
+			/* Clean up entry only if there is no reference to it any more in any outstanding request */
1d0431
+			wrap_rwlock_wrlock(cbdata->state->cached_entries_lock);
1d0431
+			count = PR_AtomicDecrement(&e_to_send->entry->refcount);
1d0431
+			if (count == 0) {
1d0431
+				if (!e_to_send->entry->not_cached) {
1d0431
+					(void) PL_HashTableRemove(ht, slapi_entry_get_ndn(e_to_send->entry->entry));
1d0431
+				}
1d0431
+				/* free this returned entry */
1d0431
+				slapi_entry_free(e_to_send->entry->entry);
1d0431
+				e_to_send->entry->entry = NULL;
1d0431
+				slapi_ch_free((void **) &e_to_send->entry);
1d0431
+				e_to_send->entry = NULL;
1d0431
+			}
1d0431
+			wrap_rwlock_unlock(cbdata->state->cached_entries_lock);
1d0431
+		}
1d0431
+
1d0431
+		/* Otherwise only free list item */
1d0431
+		slapi_ch_free((void **) &e_to_send);
1d0431
+		e_to_send = next;
1d0431
+	}
1d0431
+	cbdata->entries_head = NULL;
1d0431
+	cbdata->entries_tail = NULL;
1d0431
+}
1d0431
+
1d0431
 static int
1d0431
 backend_search_cb(Slapi_PBlock *pb)
1d0431
 {
1d0431
@@ -1443,6 +1576,8 @@ backend_search_cb(Slapi_PBlock *pb)
1d0431
 				cbdata.state->plugin_desc->spd_id,
1d0431
 				"unable to acquire read lock\n");
1d0431
 	}
1d0431
+	/* Return existing collected entries */
1d0431
+	backend_send_mapped_entries(&cbdata);
1d0431
 	wrap_dec_call_level();
1d0431
 #ifdef USE_NSSWITCH
1d0431
 	/* If during search of some sets we staged additional lookups, perform them. */
1d0431
@@ -1525,6 +1660,8 @@ backend_search_cb(Slapi_PBlock *pb)
1d0431
 		if (map_rdlock() == 0) {
1d0431
 			map_data_foreach_domain(cbdata.state, backend_search_group_cb, &cbdata);
1d0431
 			map_unlock();
1d0431
+			/* Return newly acquired entries */
1d0431
+			backend_send_mapped_entries(&cbdata);
1d0431
 		} else {
1d0431
 			slapi_log_error(SLAPI_LOG_PLUGIN,
1d0431
 					cbdata.state->plugin_desc->spd_id,
1d0431
diff --git a/src/back-sch.h b/src/back-sch.h
1d0431
index 1aedf36..e8ec400 100644
1d0431
--- a/src/back-sch.h
1d0431
+++ b/src/back-sch.h
1d0431
@@ -63,6 +63,24 @@ struct backend_staged_search {
1d0431
 	Slapi_Entry **entries;
1d0431
 };
1d0431
 
1d0431
+/* Entry to be send to clients is cached to allow multiple threads to re-use results.
1d0431
+ */
1d0431
+struct cached_entry {
1d0431
+	Slapi_Entry *entry;
1d0431
+	PRInt32 refcount;
1d0431
+	bool_t not_cached;
1d0431
+};
1d0431
+
1d0431
+/* list of entries to actually send, sorted as a linked list
1d0431
+ * Entries are references to the ones stored in a cache
1d0431
+ * Before sending them out one needs to refcount the entry
1d0431
+ */
1d0431
+struct entries_to_send {
1d0431
+	struct entries_to_send *next;
1d0431
+	struct entries_to_send *prev;
1d0431
+	struct cached_entry *entry;
1d0431
+};
1d0431
+
1d0431
 /* Intercept a search request, and if it belongs to one of our compatibility
1d0431
  * trees, answer from our cache before letting the default database have a
1d0431
  * crack at it. */
1d0431
@@ -88,6 +106,8 @@ struct backend_search_cbdata {
1d0431
 	int n_entries;
1d0431
 	struct backend_staged_search *staged;
1d0431
 	struct backend_staged_search *cur_staged;
1d0431
+	struct entries_to_send *entries_head;
1d0431
+	struct entries_to_send *entries_tail;
1d0431
 };
1d0431
 
1d0431
 struct backend_search_filter_config {
1d0431
diff --git a/src/plug-sch.c b/src/plug-sch.c
1d0431
index 5a6e736..f132e6d 100644
1d0431
--- a/src/plug-sch.c
1d0431
+++ b/src/plug-sch.c
1d0431
@@ -44,6 +44,7 @@
1d0431
 
1d0431
 #ifdef HAVE_DIRSRV_SLAPI_PLUGIN_H
1d0431
 #include <nspr.h>
1d0431
+#include <plhash.h>
1d0431
 #include <nss.h>
1d0431
 #include <dirsrv/slapi-plugin.h>
1d0431
 #else
1d0431
@@ -100,6 +101,7 @@ plugin_startup(Slapi_PBlock *pb)
1d0431
 {
1d0431
 	/* Populate the maps and data. */
1d0431
 	struct plugin_state *state;
1d0431
+	Slapi_Entry *plugin_entry = NULL;
1d0431
 	slapi_pblock_get(pb, SLAPI_PLUGIN_PRIVATE, &state);
1d0431
 	slapi_pblock_get(pb, SLAPI_TARGET_DN, &state->plugin_base);
1d0431
 	slapi_log_error(SLAPI_LOG_PLUGIN, state->plugin_desc->spd_id,
1d0431
@@ -111,12 +113,35 @@ plugin_startup(Slapi_PBlock *pb)
1d0431
 	backend_startup(pb, state);
1d0431
 	state->pam_lock = wrap_new_rwlock();
1d0431
 	backend_nss_init_context((struct nss_ops_ctx**) &state->nss_context);
1d0431
+	if ((slapi_pblock_get(pb, SLAPI_PLUGIN_CONFIG_ENTRY, &plugin_entry) == 0) &&
1d0431
+	    (plugin_entry != NULL)) {
1d0431
+		state->use_entry_cache = backend_shr_get_vattr_boolean(state, plugin_entry,
1d0431
+									"slapi-entry-cache",
1d0431
+									1);
1d0431
+	}
1d0431
+	state->cached_entries_lock = wrap_new_rwlock();
1d0431
+	wrap_rwlock_wrlock(state->cached_entries_lock);
1d0431
+	state->cached_entries = PL_NewHashTable(0, PL_HashString, PL_CompareStrings, PL_CompareValues, 0, 0);
1d0431
+	wrap_rwlock_unlock(state->cached_entries_lock);
1d0431
 	/* Note that the plugin is ready to go. */
1d0431
 	slapi_log_error(SLAPI_LOG_PLUGIN, plugin_description.spd_id,
1d0431
 			"plugin startup completed\n");
1d0431
 	return 0;
1d0431
 }
1d0431
 
1d0431
+static PRIntn
1d0431
+remove_cached_entries_cb(PLHashEntry *he, PRIntn i, void *arg)
1d0431
+{
1d0431
+	struct cached_entry *e = (struct cached_entry*) he->value;
1d0431
+	if (e != NULL) {
1d0431
+		if (e->entry != NULL) {
1d0431
+			slapi_entry_free(e->entry);
1d0431
+		}
1d0431
+		slapi_ch_free((void **) &e);
1d0431
+	}
1d0431
+	return HT_ENUMERATE_REMOVE;
1d0431
+}
1d0431
+
1d0431
 static int
1d0431
 plugin_shutdown(Slapi_PBlock *pb)
1d0431
 {
1d0431
@@ -126,6 +151,15 @@ plugin_shutdown(Slapi_PBlock *pb)
1d0431
 	wrap_free_rwlock(state->pam_lock);
1d0431
 	state->pam_lock = NULL;
1d0431
 	backend_nss_free_context((struct nss_ops_ctx**) &state->nss_context);
1d0431
+	if (state->cached_entries != NULL) {
1d0431
+		wrap_rwlock_wrlock(state->cached_entries_lock);
1d0431
+		PL_HashTableEnumerateEntries(state->cached_entries, remove_cached_entries_cb, NULL);
1d0431
+		PL_HashTableDestroy(state->cached_entries);
1d0431
+		state->cached_entries = NULL;
1d0431
+		wrap_rwlock_unlock(state->cached_entries_lock);
1d0431
+		wrap_free_rwlock(state->cached_entries_lock);
1d0431
+		state->cached_entries_lock = NULL;
1d0431
+	}
1d0431
 	state->plugin_base = NULL;
1d0431
 	slapi_log_error(SLAPI_LOG_PLUGIN, state->plugin_desc->spd_id,
1d0431
 			"plugin shutdown completed\n");
1d0431
diff --git a/src/plugin.h b/src/plugin.h
1d0431
index 94ad747..429e291 100644
1d0431
--- a/src/plugin.h
1d0431
+++ b/src/plugin.h
1d0431
@@ -47,6 +47,9 @@ struct plugin_state {
1d0431
 	/* Schema compat-specific data. */
1d0431
 	struct wrapped_rwlock *pam_lock;
1d0431
 	void *nss_context;
1d0431
+	int use_entry_cache;
1d0431
+	void *cached_entries;
1d0431
+	struct wrapped_rwlock *cached_entries_lock;
1d0431
 };
1d0431
 
1d0431
 #endif
1d0431
-- 
1d0431
2.5.0
1d0431