Hello,

these three bugs were found accidentally while analyzing requirements for
https://fedorahosted.org/bind-dyndb-ldap/ticket/125

All three should go to master before the release because they were source of
subtle bugs.

-- 
Petr^2 Spacek
From 578e72ffa221f320acfa1a4f7eadb8d97996476f Mon Sep 17 00:00:00 2001
From: Petr Spacek <pspa...@redhat.com>
Date: Tue, 14 Jun 2016 15:16:41 +0200
Subject: [PATCH] Rework hack for synchronous event processing to make it
 reliable.

Previously a pointer to event was used as unique ID which was a terrible
idea. The address was sometimes reused by allocator. Address re-use
lead to false positives in event tracing logic and thus some events
were processed asynchronously even if synchronous processing was requested.

https://fedorahosted.org/bind-dyndb-ldap/ticket/125
---
 src/ldap_helper.c | 32 +++++++++-----------------------
 src/syncrepl.c    | 29 ++++++++++++++++++++---------
 src/syncrepl.h    |  5 +++--
 src/types.h       | 14 ++++++++++++++
 4 files changed, 46 insertions(+), 34 deletions(-)

diff --git a/src/ldap_helper.c b/src/ldap_helper.c
index 5264e2d75c27864055ab1c3836b4ffaf98270af7..4610a79cb89f270af1bac799c3e17662267eecc0 100644
--- a/src/ldap_helper.c
+++ b/src/ldap_helper.c
@@ -198,18 +198,6 @@ const ldap_auth_pair_t supported_ldap_auth[] = {
 	{ AUTH_INVALID, NULL		},
 };
 
-#define LDAPDB_EVENT_SYNCREPL_UPDATE	(LDAPDB_EVENTCLASS + 1)
-
-typedef struct ldap_syncreplevent ldap_syncreplevent_t;
-struct ldap_syncreplevent {
-	ISC_EVENT_COMMON(ldap_syncreplevent_t);
-	isc_mem_t *mctx;
-	char *dbname;
-	char *prevdn;
-	int chgtype;
-	ldap_entry_t *entry;
-};
-
 extern const settings_set_t settings_default_set;
 
 /** Local configuration file */
@@ -3696,7 +3684,7 @@ update_zone(isc_task_t *task, isc_event_t *event)
 cleanup:
 	if (inst != NULL) {
 		sync_concurr_limit_signal(inst->sctx);
-		sync_event_signal(inst->sctx, event);
+		sync_event_signal(inst->sctx, pevent);
 		if (dns_name_dynamic(&prevname))
 			dns_name_free(&prevname, inst->mctx);
 	}
@@ -3732,7 +3720,7 @@ update_config(isc_task_t * task, isc_event_t *event)
 cleanup:
 	if (inst != NULL) {
 		sync_concurr_limit_signal(inst->sctx);
-		sync_event_signal(inst->sctx, event);
+		sync_event_signal(inst->sctx, pevent);
 	}
 	if (result != ISC_R_SUCCESS)
 		log_error_r("update_config (syncrepl) failed for %s. "
@@ -3764,7 +3752,7 @@ update_serverconfig(isc_task_t * task, isc_event_t *event)
 cleanup:
 	if (inst != NULL) {
 		sync_concurr_limit_signal(inst->sctx);
-		sync_event_signal(inst->sctx, event);
+		sync_event_signal(inst->sctx, pevent);
 	}
 	if (result != ISC_R_SUCCESS)
 		log_error_r("update_serverconfig (syncrepl) failed for %s. "
@@ -4069,15 +4057,15 @@ syncrepl_update(ldap_instance_t *inst, ldap_entry_t **entryp, int chgtype)
 	isc_result_t result = ISC_R_SUCCESS;
 	ldap_syncreplevent_t *pevent = NULL;
 	ldap_entry_t *entry = NULL;
-	isc_event_t *wait_event = NULL;
 	dns_name_t *zone_name = NULL;
 	dns_zone_t *zone_ptr = NULL;
 	char *dn = NULL;
 	char *dbname = NULL;
 	isc_mem_t *mctx = NULL;
 	isc_taskaction_t action = NULL;
 	isc_task_t *task = NULL;
 	sync_state_t sync_state;
+	isc_boolean_t synchronous;
 
 	REQUIRE(entryp != NULL);
 	entry = *entryp;
@@ -4113,10 +4101,12 @@ syncrepl_update(ldap_instance_t *inst, ldap_entry_t **entryp, int chgtype)
 			isc_task_attach(inst->task, &task);
 			result = ISC_R_SUCCESS;
 		}
+		synchronous = ISC_FALSE;
 	} else {
 		/* For configuration object and zone object use single task
 		 * to make sure that the exclusive mode actually works. */
 		isc_task_attach(inst->task, &task);
+		synchronous = ISC_TRUE;
 	}
 	REQUIRE(task != NULL);
 
@@ -4168,23 +4158,19 @@ syncrepl_update(ldap_instance_t *inst, ldap_entry_t **entryp, int chgtype)
 	pevent->prevdn = NULL;
 	pevent->chgtype = chgtype;
 	pevent->entry = entry;
-	wait_event = (isc_event_t *)pevent;
-	isc_task_send(task, (isc_event_t **)&pevent);
-	*entryp = NULL; /* event handler will deallocate the LDAP entry */
 
 	/* Lock syncrepl queue to prevent zone, config and resource records
 	 * from racing with each other. */
-	if (action == update_zone || action == update_config
-	    || action == update_serverconfig)
-		CHECK(sync_event_wait(inst->sctx, wait_event));
+	CHECK(sync_event_send(inst->sctx, task, &pevent, synchronous));
+	*entryp = NULL; /* event handler will deallocate the LDAP entry */
 
 cleanup:
 	if (zone_ptr != NULL)
 		dns_zone_detach(&zone_ptr);
 	if (result != ISC_R_SUCCESS)
 		log_error_r("syncrepl_update failed for %s",
 			    ldap_entry_logname(entry));
-	if (wait_event == NULL) {
+	if (pevent != NULL) {
 		/* Event was not sent */
 		sync_concurr_limit_signal(inst->sctx);
 
diff --git a/src/syncrepl.c b/src/syncrepl.c
index 1117358e9a1ac3de89f8c1504dae87ca1eaaa80a..00796448b4e5121a8cc5cb2e623b959553bb6bcb 100644
--- a/src/syncrepl.c
+++ b/src/syncrepl.c
@@ -91,10 +91,11 @@ struct sync_ctx {
 	isc_condition_t			cond;	/**< for signal when task_cnt == 0 */
 	sync_state_t			state;
 	ldap_instance_t			*inst;
-	isc_event_t			*last_ev; /**< Last processed event */
 	ISC_LIST(task_element_t)	tasks;	/**< list of tasks processing
 						     events from initial
 						     synchronization phase */
+	isc_uint32_t			next_id;  /**< next sequential id */
+	isc_uint32_t			last_id;  /**< last processed event */
 };
 
 /**
@@ -587,20 +588,29 @@ sync_concurr_limit_signal(sync_ctx_t *sctx) {
 }
 
 /**
- * Wait until given event ev is processed.
+ * Send ISC event to specified task and optionally wait until given event
+ * is processed.
  *
- * End of event processing has to be signalled by
- * sync_event_signal() call.
+ * End of event processing has to be signaled by
+ * @see sync_event_signal() call.
  */
 isc_result_t
-sync_event_wait(sync_ctx_t *sctx, isc_event_t *ev) {
+sync_event_send(sync_ctx_t *sctx, isc_task_t *task, ldap_syncreplevent_t **ev,
+		isc_boolean_t synchronous) {
 	isc_result_t result;
 	isc_time_t abs_timeout;
+	isc_uint32_t seqid;
+	isc_boolean_t locked = ISC_FALSE;
 
 	REQUIRE(sctx != NULL);
 
 	LOCK(&sctx->mutex);
-	while (sctx->last_ev != ev) {
+	locked = ISC_TRUE;
+	/* overflow is not a problem as long as the modulo is smaller than
+	 * constant used by sync_concurr_limit_wait() */
+	(*ev)->seqid = seqid = ++sctx->next_id % 0xffffffff;
+	isc_task_send(task, (isc_event_t **)ev);
+	while (synchronous == ISC_TRUE && sctx->last_id != seqid) {
 		if (ldap_instance_isexiting(sctx->inst) == ISC_TRUE)
 			CLEANUP_WITH(ISC_R_SHUTTINGDOWN);
 
@@ -613,20 +623,21 @@ sync_event_wait(sync_ctx_t *sctx, isc_event_t *ev) {
 	result = ISC_R_SUCCESS;
 
 cleanup:
-	UNLOCK(&sctx->mutex);
+	if (locked == ISC_TRUE)
+		UNLOCK(&sctx->mutex);
 	return result;
 }
 
 /**
  * Signal that given syncrepl event was processed.
  */
 void
-sync_event_signal(sync_ctx_t *sctx, isc_event_t *ev) {
+sync_event_signal(sync_ctx_t *sctx, ldap_syncreplevent_t *ev) {
 	REQUIRE(sctx != NULL);
 	REQUIRE(ev != NULL);
 
 	LOCK(&sctx->mutex);
-	sctx->last_ev = ev;
+	sctx->last_id = ev->seqid;
 	BROADCAST(&sctx->cond);
 	UNLOCK(&sctx->mutex);
 }
diff --git a/src/syncrepl.h b/src/syncrepl.h
index eb6b8d0c9a6a0ab8f7595f4735e333fce9808f26..ba3070a2cd70adcb7543148e458ed89a8b7d2bec 100644
--- a/src/syncrepl.h
+++ b/src/syncrepl.h
@@ -58,9 +58,10 @@ void
 sync_concurr_limit_signal(sync_ctx_t *sctx) ATTR_NONNULLS;
 
 isc_result_t
-sync_event_wait(sync_ctx_t *sctx, isc_event_t *ev) ATTR_NONNULLS ATTR_CHECKRESULT;
+sync_event_send(sync_ctx_t *sctx, isc_task_t *task, ldap_syncreplevent_t **ev,
+		isc_boolean_t synchronous) ATTR_NONNULLS ATTR_CHECKRESULT;
 
 void
-sync_event_signal(sync_ctx_t *sctx, isc_event_t *ev) ATTR_NONNULLS;
+sync_event_signal(sync_ctx_t *sctx, ldap_syncreplevent_t *ev) ATTR_NONNULLS;
 
 #endif /* SYNCREPL_H_ */
diff --git a/src/types.h b/src/types.h
index 92c9a7c109d2cfba635fd1373cdff76f826ab5f1..57d55796d0678925c7b2c58d730b36857d8599ee 100644
--- a/src/types.h
+++ b/src/types.h
@@ -5,6 +5,7 @@
 #ifndef _LD_TYPES_H_
 #define _LD_TYPES_H_
 
+#include <isc/event.h>
 #include <isc/refcount.h>
 #include <dns/name.h>
 
@@ -36,4 +37,17 @@ typedef struct mldapdb		mldapdb_t;
 typedef struct ldap_entry	ldap_entry_t;
 typedef struct settings_set	settings_set_t;
 
+
+#define LDAPDB_EVENT_SYNCREPL_UPDATE	(LDAPDB_EVENTCLASS + 1)
+typedef struct ldap_syncreplevent ldap_syncreplevent_t;
+struct ldap_syncreplevent {
+	ISC_EVENT_COMMON(ldap_syncreplevent_t);
+	isc_mem_t *mctx;
+	char *dbname;
+	char *prevdn;
+	int chgtype;
+	ldap_entry_t *entry;
+	isc_uint32_t seqid;
+};
+
 #endif /* !_LD_TYPES_H_ */
-- 
2.5.5

From b45ff02fd42a2e2694b0f5959b583605da5e7d93 Mon Sep 17 00:00:00 2001
From: Petr Spacek <pspa...@redhat.com>
Date: Tue, 14 Jun 2016 15:19:07 +0200
Subject: [PATCH] Avoid redundant sync_task_add() calls.

inst->task is always added to inst->sctx task list during sctx
initialization so there is no point in adding it again and again.
---
 src/ldap_helper.c | 11 -----------
 1 file changed, 11 deletions(-)

diff --git a/src/ldap_helper.c b/src/ldap_helper.c
index 4610a79cb89f270af1bac799c3e17662267eecc0..50c0277f4e578db74ba6b44d49082631c3b15692 100644
--- a/src/ldap_helper.c
+++ b/src/ldap_helper.c
@@ -4064,7 +4064,6 @@ syncrepl_update(ldap_instance_t *inst, ldap_entry_t **entryp, int chgtype)
 	isc_mem_t *mctx = NULL;
 	isc_taskaction_t action = NULL;
 	isc_task_t *task = NULL;
-	sync_state_t sync_state;
 	isc_boolean_t synchronous;
 
 	REQUIRE(entryp != NULL);
@@ -4133,16 +4132,6 @@ syncrepl_update(ldap_instance_t *inst, ldap_entry_t **entryp, int chgtype)
 		goto cleanup;
 	}
 
-	/* All events for single zone are handled by one task, so we don't
-	 * need to spend time with normal records. */
-	if (action == update_zone || action == update_config
-	    || action == update_serverconfig) {
-		INSIST(task == inst->task); /* For task-exclusive mode */
-		sync_state_get(inst->sctx, &sync_state);
-		if (sync_state == sync_configinit || sync_state == sync_datainit)
-			CHECK(sync_task_add(inst->sctx, task));
-	}
-
 	pevent = (ldap_syncreplevent_t *)isc_event_allocate(inst->mctx,
 				inst, LDAPDB_EVENT_SYNCREPL_UPDATE,
 				action, NULL,
-- 
2.5.5

From de083b965e3f73e398b1dc9ca4d47e2263c89850 Mon Sep 17 00:00:00 2001
From: Petr Spacek <pspa...@redhat.com>
Date: Tue, 14 Jun 2016 16:37:44 +0200
Subject: [PATCH] Make task selection in syncrepl_update() deterministic.

The fallback task selection logic in syncrepl_update() was
hidding problems in other parts of code and made event processing
non-deterministic, which lead to subtle bugs.

Now the fallback is removed so records destined at non-existing zones
will fail immediatelly instead of failing in later stages or causing
mayhem.

https://fedorahosted.org/bind-dyndb-ldap/ticket/125
---
 src/ldap_helper.c | 15 +++------------
 1 file changed, 3 insertions(+), 12 deletions(-)

diff --git a/src/ldap_helper.c b/src/ldap_helper.c
index 50c0277f4e578db74ba6b44d49082631c3b15692..5d19e1f6d626ec5cad97c8c78ec665d1b91633c8 100644
--- a/src/ldap_helper.c
+++ b/src/ldap_helper.c
@@ -4088,18 +4088,9 @@ syncrepl_update(ldap_instance_t *inst, ldap_entry_t **entryp, int chgtype)
 	 * See discussion about run_exclusive_begin() function in lock.c. */
 	if ((entry->class & LDAP_ENTRYCLASS_RR) != 0 &&
 	    (entry->class & LDAP_ENTRYCLASS_MASTER) == 0) {
-		result = zr_get_zone_ptr(inst->zone_register, zone_name,
-					 &zone_ptr, NULL);
-		if (result == ISC_R_SUCCESS && dns_zone_getmgr(zone_ptr) != NULL)
-			dns_zone_gettask(zone_ptr, &task);
-		else {
-			/* TODO: Fix race condition:
-			 * zone is not (yet) present in zone register */
-			log_debug(1, "TODO: %s: task fallback",
-				  ldap_entry_logname(entry));
-			isc_task_attach(inst->task, &task);
-			result = ISC_R_SUCCESS;
-		}
+		CHECK(zr_get_zone_ptr(inst->zone_register, zone_name,
+				      &zone_ptr, NULL));
+		dns_zone_gettask(zone_ptr, &task);
 		synchronous = ISC_FALSE;
 	} else {
 		/* For configuration object and zone object use single task
-- 
2.5.5

-- 
Manage your subscription for the Freeipa-devel mailing list:
https://www.redhat.com/mailman/listinfo/freeipa-devel
Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code

Reply via email to