diff options
author | Lennart Poettering <lennart@poettering.net> | 2015-12-18 14:23:48 +0100 |
---|---|---|
committer | Lennart Poettering <lennart@poettering.net> | 2015-12-18 14:48:49 +0100 |
commit | f7014757fd3be2af31543484f583f5c1484c7b73 (patch) | |
tree | 8ee2946cfd08c61e6395e1d4bf1c01b4df8745cb /src | |
parent | a0c888c78c419cd49c05ee6d226568e6fea0a4f3 (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')
-rw-r--r-- | src/resolve/resolved-dns-transaction.c | 34 |
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); } |