summaryrefslogtreecommitdiff
path: root/src/resolve/resolved-dns-transaction.c
diff options
context:
space:
mode:
authorLennart Poettering <lennart@poettering.net>2015-12-18 14:23:48 +0100
committerLennart Poettering <lennart@poettering.net>2015-12-18 14:48:49 +0100
commitf7014757fd3be2af31543484f583f5c1484c7b73 (patch)
tree8ee2946cfd08c61e6395e1d4bf1c01b4df8745cb /src/resolve/resolved-dns-transaction.c
parenta0c888c78c419cd49c05ee6d226568e6fea0a4f3 (diff)
resolved: make sure we don't get confused when notifying transactions while they are destroyed
A failing transaction might cause other transactions to fail too, and thus the set of transactions to notify for a transaction might change while we are notifying them. Protect against that.
Diffstat (limited to 'src/resolve/resolved-dns-transaction.c')
-rw-r--r--src/resolve/resolved-dns-transaction.c34
1 files changed, 31 insertions, 3 deletions
diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c
index 16740d40f4..f341819127 100644
--- a/src/resolve/resolved-dns-transaction.c
+++ b/src/resolve/resolved-dns-transaction.c
@@ -245,14 +245,42 @@ void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state) {
/* Notify all queries that are interested, but make sure the
* transaction isn't freed while we are still looking at it */
t->block_gc++;
+
SET_FOREACH(c, t->notify_query_candidates, i)
dns_query_candidate_notify(c);
SET_FOREACH(z, t->notify_zone_items, i)
dns_zone_item_notify(z);
- SET_FOREACH(d, t->notify_transactions, i)
- dns_transaction_notify(d, t);
- t->block_gc--;
+ if (!set_isempty(t->notify_transactions)) {
+ DnsTransaction **nt;
+ unsigned j, n = 0;
+
+ /* We need to be careful when notifying other
+ * transactions, as that might destroy other
+ * transactions in our list. Hence, in order to be
+ * able to safely iterate through the list of
+ * transactions, take a GC lock on all of them
+ * first. Then, in a second loop, notify them, but
+ * first unlock that specific transaction. */
+
+ nt = newa(DnsTransaction*, set_size(t->notify_transactions));
+ SET_FOREACH(d, t->notify_transactions, i) {
+ nt[n++] = d;
+ d->block_gc++;
+ }
+
+ assert(n == set_size(t->notify_transactions));
+
+ for (j = 0; j < n; j++) {
+ if (set_contains(t->notify_transactions, nt[j]))
+ dns_transaction_notify(nt[j], t);
+
+ nt[j]->block_gc--;
+ dns_transaction_gc(nt[j]);
+ }
+ }
+
+ t->block_gc--;
dns_transaction_gc(t);
}