On 11/11/2024 8:56 PM, Paul E. McKenney wrote:
> On Mon, Nov 11, 2024 at 06:24:58PM +0530, Neeraj Upadhyay wrote:
>>
>>>  
>>>  /*
>>> - * Returns approximate total of the readers' ->srcu_lock_count[] values
>>> - * for the rank of per-CPU counters specified by idx.
>>> + * Computes approximate total of the readers' ->srcu_lock_count[] values
>>> + * for the rank of per-CPU counters specified by idx, and returns true if
>>> + * the caller did the proper barrier (gp), and if the count of the locks
>>> + * matches that of the unlocks passed in.
>>>   */
>>> -static unsigned long srcu_readers_lock_idx(struct srcu_struct *ssp, int 
>>> idx)
>>> +static bool srcu_readers_lock_idx(struct srcu_struct *ssp, int idx, bool 
>>> gp, unsigned long unlocks)
>>>  {
>>>     int cpu;
>>> +   unsigned long mask = 0;
>>>     unsigned long sum = 0;
>>>  
>>>     for_each_possible_cpu(cpu) {
>>>             struct srcu_data *sdp = per_cpu_ptr(ssp->sda, cpu);
>>>  
>>>             sum += atomic_long_read(&sdp->srcu_lock_count[idx]);
>>> +           if (IS_ENABLED(CONFIG_PROVE_RCU))
>>> +                   mask = mask | READ_ONCE(sdp->srcu_reader_flavor);
>>>     }
>>> -   return sum;
>>> +   WARN_ONCE(IS_ENABLED(CONFIG_PROVE_RCU) && (mask & (mask - 1)),
>>> +             "Mixed reader flavors for srcu_struct at %ps.\n", ssp);
>>
>> I am trying to understand the (unlikely) case where synchronize_srcu() is 
>> done before any
>> srcu reader lock/unlock lite call is done. Can new SRCU readers fail to 
>> observe the
>> updates?
> 
> If a SRCU reader fail to observe the index flip, then isn't it the case
> that the synchronize_rcu() invoked from srcu_readers_active_idx_check()
> must wait on it?
> 

Below is the sequence of operations I was thinking of, where at step 4 CPU2
reads old pointer

ptr = old


CPU1                                         CPU2

1. Update ptr = new

2. synchronize_srcu()

<Does not use synchronize_rcu()
 as SRCU_READ_FLAVOR_LITE is not
 set for any sdp as srcu_read_lock_lite()
 hasn't been called by any CPU>

                                      3. srcu_read_lock_lite()
                                        <No smp_mb() ordering>

                                      4.  Can read ptr == old ?


>>> +   if (mask & SRCU_READ_FLAVOR_LITE && !gp)
>>> +           return false;
>>
>> So, srcu_readers_active_idx_check() can potentially return false for very 
>> long
>> time, until the CPU executing srcu_readers_active_idx_check() does
>> at least one read lock/unlock lite call?
> 
> That is correct.  The theory is that until after an srcu_read_lock_lite()
> has executed, there is no need to wait on it.  Does the practice match the
> theory in this case, or is there some sequence of events that I missed?
> 

Below sequence

CPU1                     CPU2     
                       1. srcu_read_lock_lite()
                       
                       
                       2. srcu_read_unlock_lite()

3. synchronize_srcu()

3.1 srcu_readers_lock_idx() is
called with gp = false as
srcu_read_lock_lite() was never
called on this CPU for this
srcu_struct. So
ssp->sda->srcu_reader_flavor is not
set for CPU1's sda.

3.2 Inside srcu_readers_lock_idx()
"mask" contains SRCU_READ_FLAVOR_LITE
as CPU2's sdp->srcu_reader_flavor has it.

3.3 CPU1 keeps returning false from
below check until CPU1 does at least
one srcu_read_lock_lite() call or
the thread migrates.

if (mask & SRCU_READ_FLAVOR_LITE && !gp)
  return false;


>>> +   return sum == unlocks;
>>>  }
>>>  
>>>  /*
>>> @@ -473,6 +482,7 @@ static unsigned long srcu_readers_unlock_idx(struct 
>>> srcu_struct *ssp, int idx)
>>>   */
>>>  static bool srcu_readers_active_idx_check(struct srcu_struct *ssp, int idx)
>>>  {
>>> +   bool did_gp = !!(raw_cpu_read(ssp->sda->srcu_reader_flavor) & 
>>> SRCU_READ_FLAVOR_LITE);
>>
>> sda->srcu_reader_flavor is only set when CONFIG_PROVE_RCU is enabled. But we
>> need the reader flavor information for srcu lite variant to work. So, lite
>> variant does not work when CONFIG_PROVE_RCU is disabled. Am I missing 
>> something
>> obvious here?
> 
> At first glance, it appears that I am the one who missed something obvious.
> Including in testing, which failed to uncover this issue.
> 
> Thank you for the careful reviews!
> 

Sure thing, no problem!


- Neeraj

>                                                       Thanx, Paul
> 
>> - Neeraj
>>
>>>     unsigned long unlocks;
>>>  
>>>     unlocks = srcu_readers_unlock_idx(ssp, idx);
>>> @@ -482,13 +492,16 @@ static bool srcu_readers_active_idx_check(struct 
>>> srcu_struct *ssp, int idx)
>>>      * unlock is counted. Needs to be a smp_mb() as the read side may
>>>      * contain a read from a variable that is written to before the
>>>      * synchronize_srcu() in the write side. In this case smp_mb()s
>>> -    * A and B act like the store buffering pattern.
>>> +    * A and B (or X and Y) act like the store buffering pattern.
>>>      *
>>> -    * This smp_mb() also pairs with smp_mb() C to prevent accesses
>>> -    * after the synchronize_srcu() from being executed before the
>>> -    * grace period ends.
>>> +    * This smp_mb() also pairs with smp_mb() C (or, in the case of X,
>>> +    * Z) to prevent accesses after the synchronize_srcu() from being
>>> +    * executed before the grace period ends.
>>>      */
>>> -   smp_mb(); /* A */
>>> +   if (!did_gp)
>>> +           smp_mb(); /* A */
>>> +   else
>>> +           synchronize_rcu(); /* X */
>>>  
>>>     /*
>>>      * If the locks are the same as the unlocks, then there must have
>>> @@ -546,7 +559,7 @@ static bool srcu_readers_active_idx_check(struct 
>>> srcu_struct *ssp, int idx)
>>>      * which are unlikely to be configured with an address space fully
>>>      * populated with memory, at least not anytime soon.
>>>      */
>>> -   return srcu_readers_lock_idx(ssp, idx) == unlocks;
>>> +   return srcu_readers_lock_idx(ssp, idx, did_gp, unlocks);
>>>  }
>>>  
>>

Reply via email to