From: Eric W. Biederman <hidden> Date: 2014-08-29 21:57:18
Julian Anastasov [off-list ref] writes:
Hello,
On Thu, 28 Aug 2014, Simon Kirby wrote:
quoted
I noticed that [kworker/u16:0]'s stack is often:
[<ffffffff810942a6>] wait_rcu_gp+0x46/0x50
[<ffffffff8109607e>] synchronize_sched+0x2e/0x50
[<ffffffffa00385ac>] nf_nat_net_exit+0x2c/0x50 [nf_nat]
I guess the problem is in nf_nat_net_exit,
may be other nf exit handlers too. pernet-exit handlers
should avoid synchronize_rcu and rcu_barrier.
A RCU callback and rcu_barrier in module-exit is the way
to go. cleanup_net includes rcu_barrier, so pernet-exit
does not need such calls.
In principle I agree, however in this particular case it looks a bit
tricky because a separate hash table to track nat state per network
namespace.
At the same time all of the packets should be drained before
we get to nf_nat_net_exit so it doesn't look the synchronize_rcu
in nf_nat_exit is actually protecting anything.
Further calling a rcu delay function in net_exit methods largely
destroys the batched cleanup of network namespaces, so it is very
unpleasant.
Could someone who knows nf_nat_core.c better than I do look and
see if we can just remove the synchronize_rcu in nf_nat_exit?
I noticed that [kworker/u16:0]'s stack is often:
[<ffffffff810942a6>] wait_rcu_gp+0x46/0x50
[<ffffffff8109607e>] synchronize_sched+0x2e/0x50
[<ffffffffa00385ac>] nf_nat_net_exit+0x2c/0x50 [nf_nat]
I guess the problem is in nf_nat_net_exit,
may be other nf exit handlers too. pernet-exit handlers
should avoid synchronize_rcu and rcu_barrier.
A RCU callback and rcu_barrier in module-exit is the way
to go. cleanup_net includes rcu_barrier, so pernet-exit
does not need such calls.
In principle I agree, however in this particular case it looks a bit
tricky because a separate hash table to track nat state per network
namespace.
At the same time all of the packets should be drained before
we get to nf_nat_net_exit so it doesn't look the synchronize_rcu
in nf_nat_exit is actually protecting anything.
Hmm, the problem is with the conntrack entries living in the netns being
destroyed.
I don't think they are guaranteed to be removed by the time
the nat netns exit function runs.
Further calling a rcu delay function in net_exit methods largely
destroys the batched cleanup of network namespaces, so it is very
unpleasant.
Could someone who knows nf_nat_core.c better than I do look and
see if we can just remove the synchronize_rcu in nf_nat_exit?
If I remember correctly its needed to ensure that
all conntracks with nat extensions that might still be referenced
on other cpu have finished (i.e., nf_conntrack_destroy() has been
called, which calls nf_nat_cleanup_conntrack() which deletes
the extension from the hash table).
As we remove the ct from that table ourselves EXCEPT in the
case where we cannot steal the timers' reference we should
be able to avoid that call virtually every time.
Perhaps this is worth a shot (not even compile tested):
@@ -528,23 +529,32 @@ static int nf_nat_proto_remove(struct nf_conn *i, void *data)staticintnf_nat_proto_clean(structnf_conn*ct,void*data){+structnf_nat_proto_clean*clean=data;structnf_conn_nat*nat=nfct_nat(ct);-if(nf_nat_proto_remove(ct,data))-return1;-if(!nat||!nat->ct)return0;-/* This netns is being destroyed, and conntrack has nat null binding.+/* This netns is being destroyed, and conntrack has nat binding.*Removeitfrombysourcehash,asthetablewillbefreedsoon.*-*Else,whentheconntrackisdestoyed,nf_nat_cleanup_conntrack()+*Else,whentheconntrackisdestroyed,nf_nat_cleanup_conntrack()*willdeleteentryfromalready-freedtable.*/-if(!del_timer(&ct->timeout))+if(!del_timer(&ct->timeout)){+/* We have nat binding, but destruction+*mightalreadybeinprogress.+*+*natentryisremovedonlyafterlast+*nf_ct_put().+*/+clean->need_sync_rcu=true;return1;+}+/* We stole refcount owned by timer;+*conntrackcannotgoaway.+*/spin_lock_bh(&nf_nat_lock);hlist_del_rcu(&nat->bysource);ct->status&=~IPS_NAT_DONE_MASK;
@@ -553,6 +563,9 @@ static int nf_nat_proto_clean(struct nf_conn *ct, void *data)add_timer(&ct->timeout);+if(nf_nat_proto_remove(ct,data))+return1;+/* don't delete conntrack. Although that would make things a lot*simpler,we'dendupflushingallconntracksonnatrmmod.*/
@@ -830,7 +843,8 @@ static void __net_exit nf_nat_net_exit(struct net *net)structnf_nat_proto_cleanclean={};nf_ct_iterate_cleanup(net,nf_nat_proto_clean,&clean,0,0);-synchronize_rcu();+if(clean.need_sync_rcu)+synchronize_rcu();nf_ct_free_hashtable(net->ct.nat_bysource,net->ct.nat_htable_size);}
From: Paul E. McKenney <hidden> Date: 2014-08-30 02:56:46
On Sat, Aug 30, 2014 at 01:52:06AM +0200, Florian Westphal wrote:
quoted hunk
Eric W. Biederman [off-list ref] wrote:
quoted
Julian Anastasov [off-list ref] writes:
quoted
Hello,
On Thu, 28 Aug 2014, Simon Kirby wrote:
quoted
I noticed that [kworker/u16:0]'s stack is often:
[<ffffffff810942a6>] wait_rcu_gp+0x46/0x50
[<ffffffff8109607e>] synchronize_sched+0x2e/0x50
[<ffffffffa00385ac>] nf_nat_net_exit+0x2c/0x50 [nf_nat]
I guess the problem is in nf_nat_net_exit,
may be other nf exit handlers too. pernet-exit handlers
should avoid synchronize_rcu and rcu_barrier.
A RCU callback and rcu_barrier in module-exit is the way
to go. cleanup_net includes rcu_barrier, so pernet-exit
does not need such calls.
In principle I agree, however in this particular case it looks a bit
tricky because a separate hash table to track nat state per network
namespace.
At the same time all of the packets should be drained before
we get to nf_nat_net_exit so it doesn't look the synchronize_rcu
in nf_nat_exit is actually protecting anything.
Hmm, the problem is with the conntrack entries living in the netns being
destroyed.
I don't think they are guaranteed to be removed by the time
the nat netns exit function runs.
quoted
Further calling a rcu delay function in net_exit methods largely
destroys the batched cleanup of network namespaces, so it is very
unpleasant.
Could someone who knows nf_nat_core.c better than I do look and
see if we can just remove the synchronize_rcu in nf_nat_exit?
If I remember correctly its needed to ensure that
all conntracks with nat extensions that might still be referenced
on other cpu have finished (i.e., nf_conntrack_destroy() has been
called, which calls nf_nat_cleanup_conntrack() which deletes
the extension from the hash table).
As we remove the ct from that table ourselves EXCEPT in the
case where we cannot steal the timers' reference we should
be able to avoid that call virtually every time.
Perhaps this is worth a shot (not even compile tested):
@@ -528,23 +529,32 @@ static int nf_nat_proto_remove(struct nf_conn *i, void *data)staticintnf_nat_proto_clean(structnf_conn*ct,void*data){+structnf_nat_proto_clean*clean=data;structnf_conn_nat*nat=nfct_nat(ct);-if(nf_nat_proto_remove(ct,data))-return1;-if(!nat||!nat->ct)return0;-/* This netns is being destroyed, and conntrack has nat null binding.+/* This netns is being destroyed, and conntrack has nat binding.*Removeitfrombysourcehash,asthetablewillbefreedsoon.*-*Else,whentheconntrackisdestoyed,nf_nat_cleanup_conntrack()+*Else,whentheconntrackisdestroyed,nf_nat_cleanup_conntrack()*willdeleteentryfromalready-freedtable.*/-if(!del_timer(&ct->timeout))+if(!del_timer(&ct->timeout)){+/* We have nat binding, but destruction+*mightalreadybeinprogress.+*+*natentryisremovedonlyafterlast+*nf_ct_put().+*/+clean->need_sync_rcu=true;
So this happens only if we race with the timer handler? If so, this
patch might give good speedups. (Can't comment on any other correctness
issues due to unfamiliarity with the code and what it is trying to do.)
Thanx, Paul
quoted hunk
return 1;
+ }
+ /* We stole refcount owned by timer;
+ * conntrack cannot go away.
+ */
spin_lock_bh(&nf_nat_lock);
hlist_del_rcu(&nat->bysource);
ct->status &= ~IPS_NAT_DONE_MASK;
@@ -553,6 +563,9 @@ static int nf_nat_proto_clean(struct nf_conn *ct, void *data) add_timer(&ct->timeout);+ if (nf_nat_proto_remove(ct, data))+ return 1;+ /* don't delete conntrack. Although that would make things a lot * simpler, we'd end up flushing all conntracks on nat rmmod. */
Hello,
On Fri, 29 Aug 2014, Eric W. Biederman wrote:
quoted
I guess the problem is in nf_nat_net_exit,
may be other nf exit handlers too. pernet-exit handlers
should avoid synchronize_rcu and rcu_barrier.
A RCU callback and rcu_barrier in module-exit is the way
to go. cleanup_net includes rcu_barrier, so pernet-exit
does not need such calls.
In principle I agree, however in this particular case it looks a bit
tricky because a separate hash table to track nat state per network
namespace.
It is still possible module's pernet-init handler to
attach in net->ct... special structure with all pointers that
should be freed by RCU callback for the module, like the hash table.
For example:
struct netns_ct_nat_rcu_allocs {
struct rcu_head rcu_head;
struct hlist_head *nat_bysource;
unsigned int nat_htable_size;
};
- pernet-init:
- allocate structure, attach it to net->ct.nat_rcu_allocs
- the original nat_bysource place remains because
we want to avoid net->ct.nat_rcu_allocs dereference.
- pernet-exit:
- copy nat_bysource and nat_htable_size to net->ct.nat_rcu_allocs,
this can be done even in above pernet-init function
- call_rcu(&net->ct.nat_rcu_allocs->rcu_head, nat_rcu_free);
- cleanup_net:
- rcu_barrier()
- RCU callback (nat_rcu_free):
- call nf_ct_free_hashtable
- kfree the structure
- cleanup_net:
- drop netns after rcu_barrier
Due to the rcu_barrier in cleanup_net it is even
possible to provide per-module rcu_head instead of using
allocated structure, for example:
call_rcu(&net->ct.nat_rcu_head, nat_rcu_free);
Then the nat_rcu_free function will just call
nf_ct_free_hashtable before cleanup_net drops the netns struct.
In this case the memory price is just one rcu_head for every module
that uses RCU callback.
At the same time all of the packets should be drained before
we get to nf_nat_net_exit so it doesn't look the synchronize_rcu
in nf_nat_exit is actually protecting anything.
It is true for cleanup_net. I don't remember, can we
see packets while the particular module-exit calls
unregister_pernet_subsys(), may be yes?
Regards
--
Julian Anastasov [off-list ref]