@@ -188,9 +188,12 @@ static int rhashtable_rehash_one(struct rhashtable *ht, unsigned int old_hash)new_tbl,new_hash);if(rht_is_a_nulls(head))-INIT_RHT_NULLS_HEAD(entry->next,ht,new_hash);-else-RCU_INIT_POINTER(entry->next,head);+head=(structrhash_head*)rht_marker(ht,new_hash);+/* We don't insert any new nodes that were not previously accessible+*toreaders,sowedon'tneedtousercu_assign_pointer().+*Butentryisbeingconcurrentlyaccessedbyreaders,soweneedto+*useatleastWRITE_ONCE.*/+WRITE_ONCE(entry->next,head);rcu_assign_pointer(new_tbl->buckets[new_hash],entry);spin_unlock(new_bucket_lock);
@@ -188,9 +188,12 @@ static int rhashtable_rehash_one(struct rhashtable *ht, unsigned int old_hash)new_tbl,new_hash);if(rht_is_a_nulls(head))-INIT_RHT_NULLS_HEAD(entry->next,ht,new_hash);-else-RCU_INIT_POINTER(entry->next,head);+head=(structrhash_head*)rht_marker(ht,new_hash);+/* We don't insert any new nodes that were not previously accessible+*toreaders,sowedon'tneedtousercu_assign_pointer().+*Butentryisbeingconcurrentlyaccessedbyreaders,soweneedto+*useatleastWRITE_ONCE.*/
This is bogus.
1) Linux is certainly not working if some arch or compiler is not doing
single word writes. WRITE_ONCE() would not help at all to enforce this.
2) If new node is not yet visible, we don't care if we write
entry->next using any kind of operation.
So the WRITE_ONCE() is not needed at all.
+ WRITE_ONCE(entry->next, head);
The rcu_assign_pointer() immediately following is enough in this case.
We have hundred of similar cases in the kernel.
From: Eric Dumazet <hidden> Date: 2015-09-21 14:51:52
On Mon, 2015-09-21 at 06:31 -0700, Eric Dumazet wrote:
On Mon, 2015-09-21 at 10:08 +0200, Dmitry Vyukov wrote:
quoted
rhashtable_rehash_one() uses plain writes to update entry->next,
while it is being concurrently accessed by readers.
Unfortunately, the compiler is within its rights to (for example) use
byte-at-a-time writes to update the pointer, which would fatally confuse
concurrent readers.
This is bogus.
1) Linux is certainly not working if some arch or compiler is not doing
single word writes. WRITE_ONCE() would not help at all to enforce this.
2) If new node is not yet visible, we don't care if we write
entry->next using any kind of operation.
So the WRITE_ONCE() is not needed at all.
quoted
+ WRITE_ONCE(entry->next, head);
The rcu_assign_pointer() immediately following is enough in this case.
We have hundred of similar cases in the kernel.
The changelog and comment are totally confusing.
Please remove the bogus parts in them, and/or rephrase.
The important part here is that we rehash an item, so we need to make
sure to maintain consistent ->next field, and need to prevent compiler
from using ->next as a temporary variable.
ptr->next = 1UL | ((base + offset) << 1);
Is dangerous because compiler could issue :
ptr->next = (base + offset);
ptr->next <<= 1;
ptr->next += 1UL;
Frankly, all this looks like an oversight in this code.
Not sure why the NULLS value is even recomputed.
On Mon, Sep 21, 2015 at 4:51 PM, Eric Dumazet [off-list ref] wrote:
On Mon, 2015-09-21 at 06:31 -0700, Eric Dumazet wrote:
quoted
On Mon, 2015-09-21 at 10:08 +0200, Dmitry Vyukov wrote:
quoted
rhashtable_rehash_one() uses plain writes to update entry->next,
while it is being concurrently accessed by readers.
Unfortunately, the compiler is within its rights to (for example) use
byte-at-a-time writes to update the pointer, which would fatally confuse
concurrent readers.
This is bogus.
1) Linux is certainly not working if some arch or compiler is not doing
single word writes. WRITE_ONCE() would not help at all to enforce this.
2) If new node is not yet visible, we don't care if we write
entry->next using any kind of operation.
So the WRITE_ONCE() is not needed at all.
quoted
+ WRITE_ONCE(entry->next, head);
The rcu_assign_pointer() immediately following is enough in this case.
We have hundred of similar cases in the kernel.
The changelog and comment are totally confusing.
Please remove the bogus parts in them, and/or rephrase.
The important part here is that we rehash an item, so we need to make
sure to maintain consistent ->next field, and need to prevent compiler
from using ->next as a temporary variable.
ptr->next = 1UL | ((base + offset) << 1);
Is dangerous because compiler could issue :
ptr->next = (base + offset);
ptr->next <<= 1;
ptr->next += 1UL;
Frankly, all this looks like an oversight in this code.
Not sure why the NULLS value is even recomputed.
I have not looked in detail yet, but the NULLS recomputation uses
new_hash, which obviously wasn't available when the value was
previously computed. Don't know yet whether it is important or not.
--
Dmitry Vyukov, Software Engineer, dvyukov@google.com
Google Germany GmbH, Dienerstraße 12, 80331, München
Geschäftsführer: Graham Law, Christine Elizabeth Flores
Registergericht und -nummer: Hamburg, HRB 86891
Sitz der Gesellschaft: Hamburg
Diese E-Mail ist vertraulich. Wenn Sie nicht der richtige Adressat
sind, leiten Sie diese bitte nicht weiter, informieren Sie den
Absender und löschen Sie die E-Mail und alle Anhänge. Vielen Dank.
This e-mail is confidential. If you are not the right addressee please
do not forward it, please inform the sender, and please erase this
e-mail including any attachments. Thanks.
From: Eric Dumazet <hidden> Date: 2015-09-21 15:15:35
On Mon, 2015-09-21 at 17:10 +0200, Dmitry Vyukov wrote:
On Mon, Sep 21, 2015 at 4:51 PM, Eric Dumazet [off-list ref] wrote:
quoted
On Mon, 2015-09-21 at 06:31 -0700, Eric Dumazet wrote:
quoted
On Mon, 2015-09-21 at 10:08 +0200, Dmitry Vyukov wrote:
quoted
rhashtable_rehash_one() uses plain writes to update entry->next,
while it is being concurrently accessed by readers.
Unfortunately, the compiler is within its rights to (for example) use
byte-at-a-time writes to update the pointer, which would fatally confuse
concurrent readers.
This is bogus.
1) Linux is certainly not working if some arch or compiler is not doing
single word writes. WRITE_ONCE() would not help at all to enforce this.
2) If new node is not yet visible, we don't care if we write
entry->next using any kind of operation.
So the WRITE_ONCE() is not needed at all.
quoted
+ WRITE_ONCE(entry->next, head);
The rcu_assign_pointer() immediately following is enough in this case.
We have hundred of similar cases in the kernel.
The changelog and comment are totally confusing.
Please remove the bogus parts in them, and/or rephrase.
The important part here is that we rehash an item, so we need to make
sure to maintain consistent ->next field, and need to prevent compiler
from using ->next as a temporary variable.
ptr->next = 1UL | ((base + offset) << 1);
Is dangerous because compiler could issue :
ptr->next = (base + offset);
ptr->next <<= 1;
ptr->next += 1UL;
Frankly, all this looks like an oversight in this code.
Not sure why the NULLS value is even recomputed.
I have not looked in detail yet, but the NULLS recomputation uses
new_hash, which obviously wasn't available when the value was
previously computed. Don't know yet whether it is important or not.
Well, head already contains the right value, set in bucket_table_alloc()
for (i = 0; i < nbuckets; i++)
INIT_RHT_NULLS_HEAD(tbl->buckets[i], ht, i);
Think of this nulls value as a special NULL pointer.
If hash table is properly allocated/initialized, all the chains are
correctly ending with a proper NULL pointer.
From: Thomas Graf <tgraf@suug.ch> Date: 2015-09-21 22:25:42
On 09/21/15 at 07:51am, Eric Dumazet wrote:
The important part here is that we rehash an item, so we need to make
sure to maintain consistent ->next field, and need to prevent compiler
from using ->next as a temporary variable.
ptr->next = 1UL | ((base + offset) << 1);
Is dangerous because compiler could issue :
ptr->next = (base + offset);
ptr->next <<= 1;
ptr->next += 1UL;
Frankly, all this looks like an oversight in this code.
Not sure why the NULLS value is even recomputed.
The hash of the chain is part of the NULLS value. Since the
entry might have been moved to a different chain, the NULLS
value must be recalculated to contain the proper hash.
However, nobody is using the hash today as far as I can
see so we could as well just remove it and use the base
value only for the nulls marker.
From: Eric Dumazet <hidden> Date: 2015-09-21 23:03:42
On Tue, 2015-09-22 at 00:25 +0200, Thomas Graf wrote:
On 09/21/15 at 07:51am, Eric Dumazet wrote:
quoted
The important part here is that we rehash an item, so we need to make
sure to maintain consistent ->next field, and need to prevent compiler
from using ->next as a temporary variable.
ptr->next = 1UL | ((base + offset) << 1);
Is dangerous because compiler could issue :
ptr->next = (base + offset);
ptr->next <<= 1;
ptr->next += 1UL;
Frankly, all this looks like an oversight in this code.
Not sure why the NULLS value is even recomputed.
The hash of the chain is part of the NULLS value. Since the
entry might have been moved to a different chain, the NULLS
value must be recalculated to contain the proper hash.
However, nobody is using the hash today as far as I can
see so we could as well just remove it and use the base
value only for the nulls marker.
What I said is :
In @head you already have the correct nulls value, from hash table.
You do not need to recompute this value, and/or test if hash table chain
is empty.
If hash bucket is empty, it contains the appropriate NULLS value.
If you are paranoiac add this debugging check :
if (rht_is_a_nulls(head))
BUG_ON(head != (struct rhash_head *)rht_marker(ht, new_hash));
Therefore, simply fix the bug and unnecessary code with :
From: Thomas Graf <tgraf@suug.ch> Date: 2015-09-22 08:20:05
On 09/21/15 at 04:03pm, Eric Dumazet wrote:
What I said is :
In @head you already have the correct nulls value, from hash table.
You do not need to recompute this value, and/or test if hash table chain
is empty.
If hash bucket is empty, it contains the appropriate NULLS value.
If you are paranoiac add this debugging check :
if (rht_is_a_nulls(head))
BUG_ON(head != (struct rhash_head *)rht_marker(ht, new_hash));
Therefore, simply fix the bug and unnecessary code with :
You are absolutely right Eric. Do you want to revise your patch Dmitry?
Eric's proposed fix absolutely the best way to fix this.
On Tue, Sep 22, 2015 at 10:19 AM, Thomas Graf [off-list ref] wrote:
On 09/21/15 at 04:03pm, Eric Dumazet wrote:
quoted
What I said is :
In @head you already have the correct nulls value, from hash table.
You do not need to recompute this value, and/or test if hash table chain
is empty.
If hash bucket is empty, it contains the appropriate NULLS value.
If you are paranoiac add this debugging check :
if (rht_is_a_nulls(head))
BUG_ON(head != (struct rhash_head *)rht_marker(ht, new_hash));
Therefore, simply fix the bug and unnecessary code with :
You are absolutely right Eric. Do you want to revise your patch Dmitry?
Eric's proposed fix absolutely the best way to fix this.
Mailed v2 of the patch.
--
Dmitry Vyukov, Software Engineer, dvyukov@google.com
Google Germany GmbH, Dienerstraße 12, 80331, München
Geschäftsführer: Graham Law, Christine Elizabeth Flores
Registergericht und -nummer: Hamburg, HRB 86891
Sitz der Gesellschaft: Hamburg
Diese E-Mail ist vertraulich. Wenn Sie nicht der richtige Adressat
sind, leiten Sie diese bitte nicht weiter, informieren Sie den
Absender und löschen Sie die E-Mail und alle Anhänge. Vielen Dank.
This e-mail is confidential. If you are not the right addressee please
do not forward it, please inform the sender, and please erase this
e-mail including any attachments. Thanks.
From: Herbert Xu <herbert@gondor.apana.org.au> Date: 2015-09-22 15:19:04
Eric Dumazet [off-list ref] wrote:
What I said is :
In @head you already have the correct nulls value, from hash table.
You do not need to recompute this value, and/or test if hash table chain
is empty.
If hash bucket is empty, it contains the appropriate NULLS value.
If you are paranoiac add this debugging check :
if (rht_is_a_nulls(head))
BUG_ON(head != (struct rhash_head *)rht_marker(ht, new_hash));
Therefore, simply fix the bug and unnecessary code with :