]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
resolved: make sure we don't get confused when notifying transactions while they...
authorLennart Poettering <lennart@poettering.net>
Fri, 18 Dec 2015 13:23:48 +0000 (14:23 +0100)
committerLennart Poettering <lennart@poettering.net>
Fri, 18 Dec 2015 13:48:49 +0000 (14:48 +0100)
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.

src/resolve/resolved-dns-transaction.c

index 16740d40f4435d9f929482429da4fde94cdb92b9..f3418191279c4314c328e167922209a980e3b48c 100644 (file)
@@ -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);
 }