locking/percpu-rwsem: Optimizations/tweaks
Subsystems (4)
Peter Zijlstra
maintainercover locking/percpu-rwsem: Optimizations/tweaks
As reported by John and Dmitry, Android tickles bad behaviour in cgroup_threadgroup_rwsem.
These two patches attempt to address the issue by optimizing the percpu-rwsem slow path and second add a bias knob to the percpu-rwsem that allows disabling the fast path which improves writer latency by getting rid of a synchronize_sched() call.
--- fs/super.c | 3 +- include/linux/percpu-rwsem.h | 96 +++++++++++++++-- kernel/cgroup.c | 2 +- kernel/locking/percpu-rwsem.c | 246 +++++++++++++++++++++++++----------------- 4 files changed, 239 insertions(+), 108 deletions(-)
Peter Zijlstra
maintainerPATCH 1/2 locking/percpu-rwsem: Optimize readers and reduce global impact
Currently the percpu-rwsem switches to (global) atomic ops while a writer is waiting; which could be quite a while and slows down releasing the readers.
This patch cures this problem by ordering the reader-state vs reader-count (see the comments in __percpu_down_read() and percpu_down_write()). This changes a global atomic op into a full memory barrier, which doesn't have the global cacheline contention.
--- include/linux/percpu-rwsem.h | 85 +++++++++++++-- kernel/locking/percpu-rwsem.c | 236 ++++++++++++++++++++++++------------------ 2 files changed, 216 insertions(+), 105 deletions(-)
include/linux/percpu-rwsem.h +76 -9
@@ -10,30 +10,97 @@struct percpu_rw_semaphore {struct rcu_sync rss; -unsigned int __percpu *fast_read_ctr; +unsigned int __percpu *read_count;struct rw_semaphore rw_sem; -atomic_t slow_read_ctr; -wait_queue_head_t write_waitq; +wait_queue_head_t writer; +int state; }; -extern void percpu_down_read (struct percpu_rw_semaphore *); -extern int percpu_down_read_trylock (struct percpu_rw_semaphore *); -extern void percpu_up_read (struct percpu_rw_semaphore *); +extern void __percpu_down_read (struct percpu_rw_semaphore *); +extern int __percpu_down_read_trylock (struct percpu_rw_semaphore *); +extern void __percpu_up_read (struct percpu_rw_semaphore *); + +static inline void percpu_down_read (struct percpu_rw_semaphore *sem) +{ +might_sleep (); + +rwsem_acquire_read (&sem->rw_sem.dep_map,0 ,0 , _RET_IP_); + +preempt_disable (); +/* +* We are in an RCU-sched read-side critical section, so the writer +* cannot both change sem->state from readers_fast and start checking +* counters while we are here. So if we see !sem->state, we know that +* the writer won't be checking until we're past the preempt_enable() +* and that one the synchronize_sched() is done, the writer will see +* anything we did within this RCU-sched read-size critical section. +*/ +__this_cpu_inc (*sem->read_count); +if (unlikely (!rcu_sync_is_idle (&sem->rss))) +__percpu_down_read (sem);/* Unconditional memory barrier */ +preempt_enable (); +/* +* The barrier() from preempt_enable() prevents the compiler from +* bleeding the critical section out. +*/ +} + +static inline int percpu_down_read_trylock (struct percpu_rw_semaphore *sem) +{ +int ret =1 ; + +preempt_disable (); +/* +* Same as in percpu_down_read(). +*/ +__this_cpu_inc (*sem->read_count); +if (unlikely (!rcu_sync_is_idle (&sem->rss))) + ret =__percpu_down_read_trylock (sem); +preempt_enable (); +/* +* The barrier() from preempt_enable() prevents the compiler from +* bleeding the critical section out. +*/ + +if (ret) +rwsem_acquire_read (&sem->rw_sem.dep_map,0 ,1 , _RET_IP_); + +return ret; +} + +static inline void percpu_up_read (struct percpu_rw_semaphore *sem) +{ +/* +* The barrier() in preempt_disable() prevents the compiler from +* bleeding the critical section out. +*/ +preempt_disable (); +/* +* Same as in percpu_down_read(). +*/ +if (likely (rcu_sync_is_idle (&sem->rss))) +__this_cpu_dec (*sem->read_count); +else +__percpu_up_read (sem);/* Unconditional memory barrier */ +preempt_enable (); + +rwsem_release (&sem->rw_sem.dep_map,1 , _RET_IP_); +}extern void percpu_down_write (struct percpu_rw_semaphore *);extern void percpu_up_write (struct percpu_rw_semaphore *);extern int __percpu_init_rwsem (struct percpu_rw_semaphore *,const char *,struct lock_class_key *); +extern void percpu_free_rwsem (struct percpu_rw_semaphore *); -#define percpu_init_rwsem (brw) \ +#define percpu_init_rwsem (sem) \ ({ \ static struct lock_class_key rwsem_key; \ - __percpu_init_rwsem(brw, #brw, &rwsem_key); \ + __percpu_init_rwsem(sem, #sem, &rwsem_key); \ }) -#define percpu_rwsem_is_held (sem) lockdep_is_held(&(sem)->rw_sem) static inline void percpu_rwsem_release(struct percpu_rw_semaphore *sem,
kernel/locking/percpu-rwsem.c +140 -96
292 lines not on this page · raw message
Re: [PATCH 1/2] locking/percpu-rwsem: Optimize readers and reduce global impact
Quoted 9 lines · Peter Zijlstra
Currently the percpu-rwsem switches to (global) atomic ops while a writer is waiting; which could be quite a while and slows down releasing the readers.
This patch cures this problem by ordering the reader-state vs reader-count (see the comments in
__percpu_down_read()andpercpu_down_write()). This changes a global atomic op into a full memory barrier, which doesn't have the global cacheline contention.
I've applied this patch + another change you sent on top of it.
Everything looks good to me except the __this_cpu_inc() in __percpu_down_read(),
kernel/locking/percpu-rwsem.c line 87
Quoted 3 lines
kernel/locking/percpu-rwsem.c +3
-0+__down_read (&sem->rw_sem); +__this_cpu_inc (*sem->read_count); +__up_read (&sem->rw_sem);
Preemption is already enabled, don't we need this_cpu_inc() ?
kernel/locking/percpu-rwsem.c line 203
Quoted 2 lines
kernel/locking/percpu-rwsem.c +1
-1-EXPORT_SYMBOL_GPL (percpu_up_write); +EXPORT_SYMBOL (percpu_up_write);
and this one ;) I do not really care, but it seems you did this change by accident.
Actually, I _think_ we can do some cleanups/improvements on top of this change, but we can do this later. In particular, _perhaps_ we can avoid the unconditional wakeup in __percpu_up_read(), but I am not sure and in any case this needs another change.
Peter Zijlstra
maintainerRe: [PATCH 1/2] locking/percpu-rwsem: Optimize readers and reduce global impact
Quoted 13 lines · Oleg Nesterov
Quoted 9 lines · Peter Zijlstra
Currently the percpu-rwsem switches to (global) atomic ops while a writer is waiting; which could be quite a while and slows down releasing the readers.
This patch cures this problem by ordering the reader-state vs reader-count (see the comments in
__percpu_down_read()andpercpu_down_write()). This changes a global atomic op into a full memory barrier, which doesn't have the global cacheline contention.I've applied this patch + another change you sent on top of it.
Quoted 8 lines
Everything looks good to me except the
__this_cpu_inc()in__percpu_down_read(),kernel/locking/percpu-rwsem.c line 87
Quoted 3 lines
kernel/locking/percpu-rwsem.c +3
-0+__down_read (&sem->rw_sem); +__this_cpu_inc (*sem->read_count); +__up_read (&sem->rw_sem);Preemption is already enabled, don't we need
this_cpu_inc()?
Ah indeed. This mistake is quite old it seems, good catch.
Quoted 5 lines
kernel/locking/percpu-rwsem.c line 203
Quoted 2 lines
kernel/locking/percpu-rwsem.c +1
-1-EXPORT_SYMBOL_GPL (percpu_up_write); +EXPORT_SYMBOL (percpu_up_write);and this one ;) I do not really care, but it seems you did this change by accident.
Yep, oops ;-)
Quoted 6 lines
Actually, I _think_ we can do some cleanups/improvements on top of this change, but we can do this later. In particular, _perhaps_ we can avoid the unconditional wakeup in
__percpu_up_read(), but I am not sure and in any case this needs another change.Reviewed-by Oleg Nesterov <[email protected]>
Thanks!
kbuild test robot
build botRe: [PATCH 1/2] locking/percpu-rwsem: Optimize readers and reduce global impact
In reply to Peter Zijlstraurl: https://github.com/0day-ci/linux/commits/Peter-Zijlstra/locking-percpu-rwsem-Optimize-readers-and-reduce-global-impact/20160715-142422 config: x86_64-randconfig-i0-07182210 (attached as…
Hi,
[auto build test ERROR on v4.7-rc7]
[also build test ERROR on next-20160718]
[cannot apply to tip/core/locking]
[if your patch is applied to the wrong git tree, please drop us a note to help improve the system]
url: https://github.com/0day-ci/linux/commits/Peter-Zijlstra/locking-percpu-rwsem-Optimize-readers-and-reduce-global-impact/20160715-142422
config: x86_64-randconfig-i0-07182210 (attached as .config)
compiler: gcc-6 (Debian 6.1.1-1) 6.1.1 20160430 reproduce:
# save the attached .config to linux build tree
make ARCH=x86_64 All errors (new ones prefixed by >>):
>> ERROR: "rcu_sync_lockdep_assert" [kernel/locking/locktorture.ko] undefined!
---
0-DAY kernel test infrastructure Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all Intel Corporation
kbuild test robot
build botRe: [PATCH 1/2] locking/percpu-rwsem: Optimize readers and reduce global impact
In reply to Peter Zijlstraurl: https://github.com/0day-ci/linux/commits/Peter-Zijlstra/locking-percpu-rwsem-Optimize-readers-and-reduce-global-impact/20160715-142422 config: x86_64-allmodconfig (attached as .config)…
Hi,
[auto build test ERROR on v4.7-rc7]
[also build test ERROR on next-20160718]
[cannot apply to tip/core/locking]
[if your patch is applied to the wrong git tree, please drop us a note to help improve the system]
url: https://github.com/0day-ci/linux/commits/Peter-Zijlstra/locking-percpu-rwsem-Optimize-readers-and-reduce-global-impact/20160715-142422
config: x86_64-allmodconfig (attached as .config)
compiler: gcc-6 (Debian 6.1.1-1) 6.1.1 20160430
reproduce:
# save the attached .config to linux build tree
make ARCH=x86_64 All errors (new ones prefixed by >>):
ERROR: "rcu_sync_lockdep_assert" [kernel/locking/locktorture.ko] undefined!
>> ERROR: "rcu_sync_lockdep_assert" [fs/ext4/ext4.ko] undefined!
---
0-DAY kernel test infrastructure Open Source Technology Center
https://lists.01.org/pipermail/kbuild-all Intel Corporation
Peter Zijlstra
maintainerPATCH 2/2 locking/percpu-rwsem: Introduce bias knob
In reply to Peter ZijlstraThe current percpu-rwsem read side is entirely free of serializing instructions at the cost of having a synchronize_sched() in the write path.
The latency of the synchronize_sched() is too high for some users (cgroups), so provide a __percpu_init_rwsem(.bias) argument to forgot this synchronize_sched() at the cost of forcing all readers into the slow path, which has serializing instructions.
--- fs/super.c | 3 ++- include/linux/percpu-rwsem.h | 15 +++++++++++++-- kernel/cgroup.c | 2 +- kernel/locking/percpu-rwsem.c | 10 +++++++++- 4 files changed, 25 insertions(+), 5 deletions(-)
fs/super.c +2 -1
@@ -195,7 +195,8 @@ static struct super_block *alloc_super(sfor (i =0 ; i <SB_FREEZE_LEVELS ; i++) {if (__percpu_init_rwsem (&s->s_writers.rw_sem[i], sb_writers_name[i], - &type->s_writers_key[i])) + &type->s_writers_key[i], +PERCPU_RWSEM_READER )) goto fail; }init_waitqueue_head (&s->s_writers.wait_unfrozen);
include/linux/percpu-rwsem.h +13 -2
@@ -90,15 +90,26 @@ static inline void percpu_up_read(structextern void percpu_down_write (struct percpu_rw_semaphore *);extern void percpu_up_write (struct percpu_rw_semaphore *); +enum percpu_rwsem_bias {PERCPU_RWSEM_READER ,PERCPU_RWSEM_WRITER }; +extern int __percpu_init_rwsem (struct percpu_rw_semaphore *, -const char *,struct lock_class_key *); +const char *,struct lock_class_key *, +enum percpu_rwsem_bias bias);extern void percpu_free_rwsem (struct percpu_rw_semaphore *);#define percpu_init_rwsem (sem) \ ({ \ static struct lock_class_key rwsem_key; \ - __percpu_init_rwsem(sem, #sem, &rwsem_key); \ + __percpu_init_rwsem(sem, #sem, &rwsem_key, \ + PERCPU_RWSEM_READER); \ +}) + +#define percpu_init_rwsem_writer (sem) \ +({ \ + static struct lock_class_key rwsem_key; \ + __percpu_init_rwsem(sem, #sem, &rwsem_key, \ + PERCPU_RWSEM_WRITER); \ })#define percpu_rwsem_is_held (sem) lockdep_is_held(&(sem)->rw_sem)
kernel/cgroup.c +1 -1
@@ -5605,7 +5605,7 @@ int __init cgroup_init(void)int ssid;BUILD_BUG_ON (CGROUP_SUBSYS_COUNT >16 ); -BUG_ON (percpu_init_rwsem (&cgroup_threadgroup_rwsem)); +BUG_ON (percpu_init_rwsem_writer (&cgroup_threadgroup_rwsem));BUG_ON (cgroup_init_cftypes (NULL , cgroup_dfl_base_files));BUG_ON (cgroup_init_cftypes (NULL , cgroup_legacy_base_files));
kernel/locking/percpu-rwsem.c +9 -1
@@ -11,7 +11,8 @@enum { readers_slow, readers_block };int __percpu_init_rwsem (struct percpu_rw_semaphore *sem, -const char *name,struct lock_class_key *rwsem_key) +const char *name,struct lock_class_key *rwsem_key, +enum percpu_rwsem_bias bias) { sem->read_count =alloc_percpu (int);if (unlikely (!sem->read_count))
@@ -19,6 +20,13 @@ int __percpu_init_rwsem(struct percpu_rw/* ->rw_sem represents the whole percpu_rw_semaphore for lockdep */ rcu_sync_init (&sem->rss,RCU_SCHED_SYNC ); +if (bias ==PERCPU_RWSEM_WRITER ) { +/* +* Disable rcu_sync() and force slow path. +*/ + sem->rss.gp_count++; + sem->rss.gp_state = !0 ; + }__init_rwsem (&sem->rw_sem, name, rwsem_key);init_waitqueue_head (&sem->writer); sem->state = readers_slow;
Peter Zijlstra
maintainerRe: [PATCH 2/2] locking/percpu-rwsem: Introduce bias knob
Quoted 4 lines · Peter Zijlstra
kernel/locking/percpu-rwsem.c +0
-0@@ -19,6 +20,13 @@ int __percpu_init_rwsem(struct percpu_rw/* ->rw_sem represents the whole percpu_rw_semaphore for lockdep */ rcu_sync_init (&sem->rss,RCU_SCHED_SYNC );
kernel/locking/percpu-rwsem.c int __percpu_init_rwsem(struct percpu_rw · line 32
Quoted 10 lines
kernel/locking/percpu-rwsem.c +7
-0+if (bias ==PERCPU_RWSEM_WRITER ) { +/* +* Disable rcu_sync() and force slow path. +*/ + sem->rss.gp_count++; + sem->rss.gp_state = !0 ; + }__init_rwsem (&sem->rw_sem, name, rwsem_key);init_waitqueue_head (&sem->writer); sem->state = readers_slow;
So this seemed like a better deal than calling rcu_sync_enter(), because that would still incur a (pointless) synchronize_sched() at init time and people do tend to complain about things like that.
Re: [PATCH 2/2] locking/percpu-rwsem: Introduce bias knob
In reply to Peter ZijlstraQuoted 9 lines · Peter Zijlstra
The current percpu-rwsem read side is entirely free of serializing instructions at the cost of having a
synchronize_sched()in the write path.The latency of the
synchronize_sched()is too high for some users (cgroups), so provide a__percpu_init_rwsem(.bias)argument to forgot thissynchronize_sched()at the cost of forcing all readers into the slow path, which has serializing instructions.
Oh well... I personally do not think this is what we want... Can't we just add the stupid rcu_sync_enter() into cgroup_init() at least for now? Yes, this means the unnecessary .sync() at boot time, but it will go away after cleanups I am going to send.
Because, again, we will probably want to change this bias dynamically.
Oleg.
Peter Zijlstra
maintainerRe: [PATCH 2/2] locking/percpu-rwsem: Introduce bias knob
Quoted 11 lines · Oleg Nesterov
Quoted 9 lines · Peter Zijlstra
The current percpu-rwsem read side is entirely free of serializing instructions at the cost of having a
synchronize_sched()in the write path.The latency of the
synchronize_sched()is too high for some users (cgroups), so provide a__percpu_init_rwsem(.bias)argument to forgot thissynchronize_sched()at the cost of forcing all readers into the slow path, which has serializing instructions.
Quoted 6 lines
Oh well... I personally do not think this is what we want... Can't we just add the stupid
rcu_sync_enter()intocgroup_init()at least for now?Yes, this means the unnecessary .sync() at boot time, but it will go away after cleanups I am going to send.
Those would have to hit the same merge window though; some people (like Arjan) really care about boot times and hunt and kill people adding pointless delays..
Quoted 1 line
Because, again, we will probably want to change this bias dynamically.
Hmm, how so?
Peter Zijlstra
maintainerRe: [PATCH 2/2] locking/percpu-rwsem: Introduce bias knob
In reply to Oleg NesterovQuoted 5 lines · Oleg Nesterov
Oh well... I personally do not think this is what we want... Can't we just add the stupid
rcu_sync_enter()intocgroup_init()at least for now? Yes, this means the unnecessary .sync() at boot time, but it will go away after cleanups I am going to send.
Something like so then?
---
include/linux/rcu_sync.h +1 -0
@@ -59,6 +59,7 @@ static inline bool rcu_sync_is_idle(stru }extern void rcu_sync_init (struct rcu_sync *,enum rcu_sync_type ); +extern void rcu_sync_sabotage (struct rcu_sync *);extern void rcu_sync_enter (struct rcu_sync *);extern void rcu_sync_exit (struct rcu_sync *);extern void rcu_sync_dtor (struct rcu_sync *);
kernel/cgroup.c +2 -0
@@ -5609,6 +5609,8 @@ int __init cgroup_init(void)BUG_ON (cgroup_init_cftypes (NULL , cgroup_dfl_base_files));BUG_ON (cgroup_init_cftypes (NULL , cgroup_legacy_base_files)); +rcu_sync_sabotage (&cgroup_threadgroup_rwsem.rss); +get_user_ns (init_cgroup_ns.user_ns);mutex_lock (&cgroup_mutex);
kernel/rcu/sync.c +15 -0
@@ -83,6 +83,21 @@ void rcu_sync_init(struct rcu_sync *rsp, }/** +* rcu_sync_sabotage() - Sabotage a fresh rcu_sync instance +* @rsp: Pointer to rcu_sync structure to be sabotaged +* +* Must be called after rcu_sync_init() and before first use. +* +* Ensures rcu_sync_is_idle() returns false and rcu_sync_{enter,exit}() pairs +* turn into NO-OPs. +*/ +void rcu_sync_sabotage (struct rcu_sync *rsp) +{ + rsp->gp_count++; + rsp->gp_state = !GP_IDLE ; +} + +/** * rcu_sync_enter() - Force readers onto slowpath * @rsp: Pointer to rcu_sync structure to use for synchronization *
Re: [PATCH 2/2] locking/percpu-rwsem: Introduce bias knob
Quoted 23 lines · Peter Zijlstra
Quoted 5 lines · Oleg Nesterov
Oh well... I personally do not think this is what we want... Can't we just add the stupid
rcu_sync_enter()intocgroup_init()at least for now? Yes, this means the unnecessary .sync() at boot time, but it will go away after cleanups I am going to send.Something like so then?
---
include/linux/rcu_sync.h +1
-0@@ -59,6 +59,7 @@ static inline bool rcu_sync_is_idle(stru }extern void rcu_sync_init (struct rcu_sync *,enum rcu_sync_type ); +extern void rcu_sync_sabotage (struct rcu_sync *);extern void rcu_sync_enter (struct rcu_sync *);extern void rcu_sync_exit (struct rcu_sync *);extern void rcu_sync_dtor (struct rcu_sync *);
kernel/cgroup.c int __init cgroup_init(void) · line 5612
Quoted 4 lines
kernel/cgroup.c +1
-0@@ -5609,6 +5609,8 @@ int __init cgroup_init(void)BUG_ON (cgroup_init_cftypes (NULL , cgroup_dfl_base_files));BUG_ON (cgroup_init_cftypes (NULL , cgroup_legacy_base_files)); +rcu_sync_sabotage (&cgroup_threadgroup_rwsem.rss);
With a name like that...
Quoted 12 lines
kernel/cgroup.c +1
-0+get_user_ns (init_cgroup_ns.user_ns);mutex_lock (&cgroup_mutex);kernel/rcu/sync.c +2
-0@@ -83,6 +83,21 @@ void rcu_sync_init(struct rcu_sync *rsp, }/** +* rcu_sync_sabotage() - Sabotage a fresh rcu_sync instance +* @rsp: Pointer to rcu_sync structure to be sabotaged
kernel/rcu/sync.c void rcu_sync_init(struct rcu_sync *rsp, · line 97
Quoted 10 lines
kernel/rcu/sync.c +10
-0+* +* Must be called after rcu_sync_init() and before first use. +* +* Ensures rcu_sync_is_idle() returns false and rcu_sync_{enter,exit}() pairs +* turn into NO-OPs. +*/ +void rcu_sync_sabotage (struct rcu_sync *rsp) +{ + rsp->gp_count++; + rsp->gp_state = !GP_IDLE ;
??? A very strange way to say GP_PENDING. A new GP_DISABLED, perhaps?
Thanx, Paul
Quoted 7 lines
kernel/rcu/sync.c +3
-0+} + +/** * rcu_sync_enter() - Force readers onto slowpath * @rsp: Pointer to rcu_sync structure to use for synchronization *
Peter Zijlstra
maintainerRe: [PATCH 2/2] locking/percpu-rwsem: Introduce bias knob
Quoted 16 lines · Paul E. McKenney
kernel/rcu/sync.c void rcu_sync_init(struct rcu_sync *rsp, · line 97
Quoted 13 lines · Peter Zijlstra
/**
+ *rcu_sync_sabotage()- Sabotage a freshrcu_syncinstance
+ * @rsp: Pointer torcu_syncstructure to be sabotaged
+ *
+ * Must be called afterrcu_sync_init()and before first use.
+ *
+ * Ensuresrcu_sync_is_idle()returns false and rcu_sync_{enter,exit}() pairs
+ * turn into NO-OPs.
+ */kernel/rcu/sync.c +4
-0+void rcu_sync_sabotage (struct rcu_sync *rsp) +{ + rsp->gp_count++; + rsp->gp_state = !GP_IDLE ;??? A very strange way to say
GP_PENDING. A newGP_DISABLED, perhaps?
Right, so the important thing is that its not GP_IDLE, the rest doesn't really matter.
This forces rcu_sync_is_idle() to return false. The skewed gp_count ensures rcu_sync_{enter,exit}() pairs no-op.
Re: [PATCH 2/2] locking/percpu-rwsem: Introduce bias knob
Quoted 18 lines · Peter Zijlstra
Quoted 16 lines · Paul E. McKenney
kernel/rcu/sync.c void rcu_sync_init(struct rcu_sync *rsp, · line 97
Quoted 13 lines · Peter Zijlstra
/**
+ *rcu_sync_sabotage()- Sabotage a freshrcu_syncinstance
+ * @rsp: Pointer torcu_syncstructure to be sabotaged
+ *
+ * Must be called afterrcu_sync_init()and before first use.
+ *
+ * Ensuresrcu_sync_is_idle()returns false and rcu_sync_{enter,exit}() pairs
+ * turn into NO-OPs.
+ */kernel/rcu/sync.c +4
-0+void rcu_sync_sabotage (struct rcu_sync *rsp) +{ + rsp->gp_count++; + rsp->gp_state = !GP_IDLE ;??? A very strange way to say
GP_PENDING. A newGP_DISABLED, perhaps?
Quoted 5 lines
Right, so the important thing is that its not
GP_IDLE, the rest doesn't really matter.This forces
rcu_sync_is_idle()to return false. The skewedgp_countensures rcu_sync_{enter,exit}() pairs no-op.
Understood. But let's have at least some pity on the poor people who might one day read this code.
Thanx, Paul
Re: [PATCH 2/2] locking/percpu-rwsem: Introduce bias knob
In reply to Peter ZijlstraQuoted 9 lines · Peter Zijlstra
Quoted 5 lines · Oleg Nesterov
Oh well... I personally do not think this is what we want... Can't we just add the stupid
rcu_sync_enter()intocgroup_init()at least for now? Yes, this means the unnecessary .sync() at boot time, but it will go away after cleanups I am going to send.Something like so then?
OK, agreed,
Quoted 14 lines
---
include/linux/rcu_sync.h +1
-0@@ -59,6 +59,7 @@ static inline bool rcu_sync_is_idle(stru }extern void rcu_sync_init (struct rcu_sync *,enum rcu_sync_type ); +extern void rcu_sync_sabotage (struct rcu_sync *);extern void rcu_sync_enter (struct rcu_sync *);extern void rcu_sync_exit (struct rcu_sync *);extern void rcu_sync_dtor (struct rcu_sync *);
kernel/cgroup.c int __init cgroup_init(void) · line 5612
Quoted 4 lines
kernel/cgroup.c +1
-0@@ -5609,6 +5609,8 @@ int __init cgroup_init(void)BUG_ON (cgroup_init_cftypes (NULL , cgroup_dfl_base_files));BUG_ON (cgroup_init_cftypes (NULL , cgroup_legacy_base_files)); +rcu_sync_sabotage (&cgroup_threadgroup_rwsem.rss);
Heh ;) I too think it should be renamed. I'd suggest
__rcu_sync_enter(rss);
although I do not really mind and agree with any name.
Hopefully I'll send some cleanups soon, rcu_sync_enter() will be re-implemented as
rcu_sync_enter(rss)
{
if (__rcu_sync_enter(rss))
__rcu_sync_wait(rss);
}kernel/cgroup.c int __init cgroup_init(void) · line 5616
Quoted 4 lines
kernel/cgroup.c +4
-0+* rcu_sync_sabotage() - Sabotage a fresh rcu_sync instance +* @rsp: Pointer to rcu_sync structure to be sabotaged +* +* Must be called after rcu_sync_init() and before first use.
OK. And after those cleanups __rcu_sync_enter() can be called at any time.
Oleg.
Re: [PATCH 2/2] locking/percpu-rwsem: Introduce bias knob
Quoted 30 lines · Oleg Nesterov
Quoted 9 lines · Peter Zijlstra
Quoted 5 lines · Oleg Nesterov
Oh well... I personally do not think this is what we want... Can't we just add the stupid
rcu_sync_enter()intocgroup_init()at least for now? Yes, this means the unnecessary .sync() at boot time, but it will go away after cleanups I am going to send.Something like so then?
OK, agreed,
Quoted 17 lines
---
include/linux/rcu_sync.h +1
-0@@ -59,6 +59,7 @@ static inline bool rcu_sync_is_idle(stru }extern void rcu_sync_init (struct rcu_sync *,enum rcu_sync_type ); +extern void rcu_sync_sabotage (struct rcu_sync *);extern void rcu_sync_enter (struct rcu_sync *);extern void rcu_sync_exit (struct rcu_sync *);extern void rcu_sync_dtor (struct rcu_sync *);kernel/cgroup.c +0
-0@@ -5609,6 +5609,8 @@ int __init cgroup_init(void)BUG_ON (cgroup_init_cftypes (NULL , cgroup_dfl_base_files));BUG_ON (cgroup_init_cftypes (NULL , cgroup_legacy_base_files));
Quoted 7 lines
kernel/cgroup.c int __init cgroup_init(void) · line 5612
Quoted 1 line
kernel/cgroup.c +1
-0+rcu_sync_sabotage (&cgroup_threadgroup_rwsem.rss);Heh ;) I too think it should be renamed. I'd suggest
__rcu_sync_enter(rss);although I do not really mind and agree with any name.
Hmmm... Why not just move the checks out into the caller? That would make the intent much more clear.
Thanx, Paul
Quoted 19 lines
Hopefully I'll send some cleanups soon,
rcu_sync_enter()will be re-implemented asrcu_sync_enter(rss) { if (__rcu_sync_enter(rss)) __rcu_sync_wait(rss); }kernel/cgroup.c int __init cgroup_init(void) · line 5616
Quoted 4 lines
kernel/cgroup.c +4
-0+* rcu_sync_sabotage() - Sabotage a fresh rcu_sync instance +* @rsp: Pointer to rcu_sync structure to be sabotaged +* +* Must be called after rcu_sync_init() and before first use. OK. And after those cleanups
__rcu_sync_enter()can be called at any time.Oleg.
Re: [PATCH 2/2] locking/percpu-rwsem: Introduce bias knob
Quoted 11 lines · Paul E. McKenney
Quoted 6 lines · Oleg Nesterov
Heh ;) I too think it should be renamed. I'd suggest
__rcu_sync_enter(rss);although I do not really mind and agree with any name.
Hmmm... Why not just move the checks out into the caller? That would make the intent much more clear.
Hmm. which caller?
Oleg.
Re: [PATCH 2/2] locking/percpu-rwsem: Introduce bias knob
Quoted 6 lines · Oleg Nesterov
Quoted 5 lines · Paul E. McKenney
Quoted 3 lines · Oleg Nesterov
Heh ;) I too think it should be renamed. I'd suggest
Quoted 8 lines
Quoted 6 lines
Quoted 3 lines
__rcu_sync_enter(rss);although I do not really mind and agree with any name.
Hmmm... Why not just move the checks out into the caller? That would make the intent much more clear.
Hmm. which caller?
The ones associated with a percpu_rwsem_bias of PERCPU_RWSEM_READER.
Thanx, Paul
Re: [PATCH 2/2] locking/percpu-rwsem: Introduce bias knob
Quoted 9 lines · Paul E. McKenney
Quoted 8 lines · Oleg Nesterov
Quoted 7 lines · Paul E. McKenney
Quoted 5 lines · Oleg Nesterov
Heh ;) I too think it should be renamed. I'd suggest
__rcu_sync_enter(rss);
Quoted 8 lines
Quoted 6 lines
Quoted 4 lines
Quoted 1 line
although I do not really mind and agree with any name.
Hmmm... Why not just move the checks out into the caller? That would make the intent much more clear.
Hmm. which caller?
The ones associated with a
percpu_rwsem_biasofPERCPU_RWSEM_READER.
Ah. But if we add __rcu_sync_enter() instead of bias/PERCPU_RWSEM_READER.
IOW, please ignore 2/2 which adds PERCPU_RWSEM_READER, the new version just adds rcu_sync_sabotage() which should be renamed (and use GP_PASSED).
Oleg.
Re: [PATCH 2/2] locking/percpu-rwsem: Introduce bias knob
Quoted 15 lines · Oleg Nesterov
Quoted 14 lines · Paul E. McKenney
Quoted 13 lines · Oleg Nesterov
Quoted 11 lines · Paul E. McKenney
Quoted 6 lines · Oleg Nesterov
Heh ;) I too think it should be renamed. I'd suggest
__rcu_sync_enter(rss);although I do not really mind and agree with any name.
Hmmm... Why not just move the checks out into the caller? That would make the intent much more clear.
Quoted 8 lines
Quoted 3 lines
Quoted 1 line
Hmm. which caller?
The ones associated with a
percpu_rwsem_biasofPERCPU_RWSEM_READER.Ah. But if we add
__rcu_sync_enter()instead of bias/PERCPU_RWSEM_READER.IOW, please ignore 2/2 which adds
PERCPU_RWSEM_READER, the new version just addsrcu_sync_sabotage()which should be renamed (and useGP_PASSED).
OK, then move the checks out into the callers that would have used __rcu_sync_enter(). ;-)
Thanx, Paul
PATCH 1/1 rcu_sync: simplify the state machine, introduce __rcu_sync_enter()
Quoted 8 lines · Paul E. McKenney
Quoted 3 lines · Oleg Nesterov
IOW, please ignore 2/2 which adds
PERCPU_RWSEM_READER, the new version just addsrcu_sync_sabotage()which should be renamed (and useGP_PASSED).OK, then move the checks out into the callers that would have used
__rcu_sync_enter(). ;-)
Cough, now I can't understand which check do you mean ;) OK, let me show the code, then we will hopefully understand each other.
----------------------------------------------------------------------
So, as you can see I have fooled you ;) I'll send the patch on top of Peter's changes, this is the (UNTESTED) code with the patch applied.
Peter, Paul, could you review? Do you see any hole?
Why. Firstly, note that the state machine was greatly simplified, and rsp->cb_state has gone, we have a single "state" variable, gp_state. Note also the ->sync() op has gone (and actually ->wait() too, see the "TODO" comment).
GP_IDLE - owned by __rcu_sync_enter() which can only move this state to
GP_ENTER - owned by rcu-callback which moves it to
GP_PASSED - owned by the last rcu_sync_exit() which moves it to
GP_EXIT - owned by rcu-callback which moves it back to GP_IDLE.
Yes, this is a bit simplified, we also have GP_REPLAY, but hopefully clear.
And, there is another possible transition, GP_ENTER -> GP_IDLE, because not it is possible to call __rcu_sync_enter() and rcu_sync_exit() in any state (except obviously they should be balanced), and they do not block.
The only blocking call is __rcu_sync_wait() which actually waits for GP. Obviously should only be called if gp_count != 0, iow after __rcu_sync_enter.
--------------------------------------------------------------------------
Now, cgroup_init() can simply call __rcu_sync_enter(&cgroup_threadgroup_rwsem) and switch this sem into the slow mode. Compared to "sabotage" from Peter this implies the unnecessary call_rcu_sched(), but I hope we can tolerate this.
And we can even add a runtime knob to switch between "fast" and "slow aka writer-biased" modes for cgroup_threadgroup_rwsem.
--------------------------------------------------------------------------
And I think __rcu_sync_enter() can have more users. Let's look at freeze_super(). It calls percpu_down_write() 3 times, and waits for 3 GP's sequentally.
Now we can add 3 __rcu_sync_enter's at the start and 3 rcu_sync_exit's at the end (actually we can do better, just to simplify). And again, note that rcu_sync_exit() will work correctly even if we (say) return -EBUSY, so rcu_sync_wait and/or percpu_down_write() was not called in between, and in this case we won't block waiting for GP.
What do you think?
Oleg.
---
// rcu_sync.h: ----------------------------------------------------------------
struct rcu_sync {
int gp_state;
int gp_count;
wait_queue_head_t gp_wait;
struct rcu_head cb_head;
enum rcu_sync_type gp_type;
};// sync.c --------------------------------------------------------------------- #include <linux/rcu_sync.h>
#include <linux/sched.h>
#ifdef CONFIG_PROVE_RCU
#define __INIT_HELD(func) .held = func,
#else
#define __INIT_HELD(func)
#endif
static const struct {
void (*call)(struct rcu_head *, void (*)(struct rcu_head *));
void (*wait)(void); // TODO: remove this, see the comment in dtor
#ifdef CONFIG_PROVE_RCU
int (*held)(void);
#endif
} gp_ops[] = {
[RCU_SYNC] = {
.call = call_rcu,
.wait = rcu_barrier,
__INIT_HELD(rcu_read_lock_held)
},
[RCU_SCHED_SYNC] = {
.call = call_rcu_sched,
.wait = rcu_barrier_sched,
__INIT_HELD(rcu_read_lock_sched_held)
},
[RCU_BH_SYNC] = {
.call = call_rcu_bh,
.wait = rcu_barrier_bh,
__INIT_HELD(rcu_read_lock_bh_held)
},
};#define rss_lock gp_wait.lock
enum { GP_IDLE = 0, GP_ENTER, GP_PASSED, GP_EXIT, GP_REPLAY };
static void rcu_sync_func(struct rcu_head *rcu);
static void rcu_sync_call(struct rcu_sync *rsp)
{
// TODO: THIS IS SUBOPTIMAL. We want to call it directly
// if rcu_blocking_is_gp() == T, but it has might_sleep().
gp_ops[rsp->gp_type].call(&rsp->cb_head, rcu_sync_func);
}static void rcu_sync_func(struct rcu_head *rcu)
{
struct rcu_sync *rsp = container_of(rcu, struct rcu_sync, cb_head);
unsigned long flags;
BUG_ON(rsp->gp_state == GP_IDLE);
BUG_ON(rsp->gp_state == GP_PASSED);
spin_lock_irqsave(&rsp->rss_lock, flags);
if (rsp->gp_count) {
/*
* We're at least a GP after the first __rcu_sync_enter().
*/
rsp->gp_state = GP_PASSED;
} else if (rsp->gp_state == GP_REPLAY) {
/*
* A new rcu_sync_exit() has happened; requeue the callback
* to catch a later GP.
*/
rsp->gp_state = GP_EXIT;
rcu_sync_call(rsp);
} else {
/*
* We're at least a GP after the last rcu_sync_exit();
* eveybody will now have observed the write side critical
* section. Let 'em rip!.
*
* OR. ->gp_state can be still GP_ENTER if __rcu_sync_wait()
* wasn't called after __rcu_sync_enter(), abort.
*/
rsp->gp_state = GP_IDLE;
}
spin_unlock_irqrestore(&rsp->rss_lock, flags);
}bool __rcu_sync_enter(struct rcu_sync *rsp)
{
int gp_count, gp_state;
spin_lock_irq(&rsp->rss_lock);
gp_count = rsp->gp_count++;
gp_state = rsp->gp_state;
if (gp_state == GP_IDLE) {
rsp->gp_state = GP_ENTER;
rcu_sync_call(rsp);
}
spin_unlock_irq(&rsp->rss_lock);
BUG_ON(gp_count != 0 && gp_state == GP_IDLE);
BUG_ON(gp_count == 0 && gp_state == GP_PASSED);
return gp_state < GP_PASSED;
}void __rcu_sync_wait(struct rcu_sync *rsp)
{
BUG_ON(rsp->gp_state == GP_IDLE);
BUG_ON(rsp->gp_count == 0);
wait_event(rsp->gp_wait, rsp->gp_state >= GP_PASSED);
}void rcu_sync_enter(struct rcu_sync *rsp)
{
if (__rcu_sync_enter(rsp))
__rcu_sync_wait(rsp);
}void rcu_sync_exit(struct rcu_sync *rsp)
{
BUG_ON(rsp->gp_state == GP_IDLE);
BUG_ON(rsp->gp_count == 0);
spin_lock_irq(&rsp->rss_lock);
if (!--rsp->gp_count) {
if (rsp->gp_state == GP_PASSED) {
rsp->gp_state = GP_EXIT;
rcu_sync_call(rsp);
} else if (rsp->gp_state == GP_EXIT) {
rsp->gp_state = GP_REPLAY;
}
}
spin_unlock_irq(&rsp->rss_lock);
}void rcu_sync_dtor(struct rcu_sync *rsp)
{
int gp_state;
BUG_ON(rsp->gp_count);
BUG_ON(rsp->gp_state == GP_PASSED);
spin_lock_irq(&rsp->rss_lock);
if (rsp->gp_state == GP_REPLAY)
rsp->gp_state = GP_EXIT;
gp_state = rsp->gp_state;
spin_unlock_irq(&rsp->rss_lock);
// TODO: add another wake_up_locked() into rcu_sync_func(),
// use wait_event + spin_lock_wait, remove gp_ops->wait().
if (gp_state != GP_IDLE) {
gp_ops[rsp->gp_type].wait();
BUG_ON(rsp->gp_state != GP_IDLE);
}
}Re: [PATCH] rcu_sync: simplify the state machine, introduce __rcu_sync_enter()
Damn, sorry for noise...
Quoted 3 lines · Oleg Nesterov
And, there is another possible transition,
GP_ENTER->GP_IDLE, because not it is possible to call__rcu_sync_enter()andrcu_sync_exit()in any
^^^
I tried to say "now it is possible ..."
Oleg.
Peter Zijlstra
maintainerRe: [PATCH] rcu_sync: simplify the state machine, introduce __rcu_sync_enter()
In reply to Oleg NesterovQuoted 6 lines · Oleg Nesterov
Peter, Paul, could you review? Do you see any hole?
Why. Firstly, note that the state machine was greatly simplified, and
rsp->cb_statehas gone, we have a single "state" variable,gp_state. Note also the ->sync() op has gone (and actually ->wait() too, see the "TODO" comment).
Yes, that seems like a nice simplification.
Quoted 11 lines
GP_IDLE- owned by__rcu_sync_enter()which can only move this state to
GP_ENTER- owned by rcu-callback which moves it to
GP_PASSED- owned by the lastrcu_sync_exit()which moves it to
GP_EXIT- owned by rcu-callback which moves it back toGP_IDLE.Yes, this is a bit simplified, we also have
GP_REPLAY, but hopefully clear.
Quoted 6 lines
And, there is another possible transition,
GP_ENTER->GP_IDLE, because not it is possible to call__rcu_sync_enter()andrcu_sync_exit()in any state (except obviously they should be balanced), and they do not block.The only blocking call is
__rcu_sync_wait()which actually waits for GP. Obviously should only be called ifgp_count!= 0, iow after__rcu_sync_enter.
So I'm a complete moron today, to aid the failing brain I draw pictures.
I think I ended up with this:
.----> GP_IDLE <--------------.
| | |
| | __rcu_sync_enter() | <GP> / rcu_sync_exit()
| v |
| GP_ENTER --------------'
| |
| <GP> |
| v
| GP_PASSED <---------.
| | |
| | rcu_sync_exit() | <GP> / __rcu_sync_enter()
| v |
`----- GP_EXIT ------------'
^
<GP> | __rcu_sync_enter() + rcu_sync_exit()
v
GP_RETRYQuoted 8 lines
enum {
GP_IDLE= 0,GP_ENTER,GP_PASSED,GP_EXIT,GP_REPLAY};static void
rcu_sync_func(struct rcu_head *rcu);static void
rcu_sync_call(struct rcu_sync *rsp){ // TODO: THIS IS SUBOPTIMAL. We want to call it directly // if rcu_blocking_is_gp() == T, but it has might_sleep().
Not sure I get that comment..
Quoted 8 lines
gp_ops[rsp->gp_type].call(&rsp->cb_head, rcu_sync_func); }static void
rcu_sync_func(struct rcu_head *rcu){ struct rcu_sync *rsp = container_of(rcu, struct rcu_sync, cb_head); unsigned long flags;
Right, those are 'stable' states and must be advanced through explicit calls to __rcu_sync_enter()/rcu_sync_exit() respectively.
Quoted 9 lines
BUG_ON(rsp->gp_state == GP_IDLE);
BUG_ON(rsp->gp_state == GP_PASSED);
spin_lock_irqsave(&rsp->rss_lock, flags);
if (rsp->gp_count) {
/*
* We're at least a GP after the first __rcu_sync_enter().
*/
rsp->gp_state = GP_PASSED;So we can end up here in two ways afaict.
The simple way: someone called __rcu_sync_enter(), we go IDLE -> ENTER with a raised count. Once the GP passes, we get here, observe the raised count and advance to PASSED.
The more involved way: we were EXIT and someone calls __rcu_sync_enter() to raise the count again. The callback from rcu_sync_exit() was still pending and once we get here we observe the raised count and voila.
Now, since state != IDLE, I suppose this is valid, but it does hurt my brain.
Else, !count:
Quoted 1 line
} else if (rsp->gp_state == GP_REPLAY) {Fairly straight forward: during EXIT someone did __rcu_sync_enter + rcu_sync_exit() and we need to wait longer still.
Quoted 7 lines
/*
* A new rcu_sync_exit() has happened; requeue the callback
* to catch a later GP.
*/
rsp->gp_state = GP_EXIT;
rcu_sync_call(rsp);
} else {Again two ways here, simple: we were EXIT, GP passed, we let em rip.
But its also possible, as you noted, that someone called rcu_sync_exit() during ENTER and we ended up with !count which gets us back to IDLE.
Quoted 4 lines
/* * We're at least a GP after the last rcu_sync_exit(); * eveybody will now have observed the write side critical * section. Let 'em rip!.
Quoted 8 lines
* * OR. ->gp_state can be still GP_ENTER if __rcu_sync_wait() * wasn't called after __rcu_sync_enter(), abort. */ rsp->gp_state = GP_IDLE; } spin_unlock_irqrestore(&rsp->rss_lock, flags); }
Agreed on the rest.
So I think its solid, but you failed to mention one state transition, which seems ok in any case.
Re: [PATCH] rcu_sync: simplify the state machine, introduce __rcu_sync_enter()
Quoted 4 lines · Peter Zijlstra
I think I ended up with this:
.---->
GP_IDLE<--------------.
Quoted 16 lines
| | |
| | __rcu_sync_enter() | <GP> / rcu_sync_exit()
| v |
| GP_ENTER --------------'
| |
| <GP> |
| v
| GP_PASSED <---------.
| | |
| | rcu_sync_exit() | <GP> / __rcu_sync_enter()
| v |
`----- GP_EXIT ------------'
^
<GP> | __rcu_sync_enter() + rcu_sync_exit()
v
GP_RETRYThanks! I'll include this into the changelog.
Quoted 6 lines
Quoted 4 lines
static void
rcu_sync_call(struct rcu_sync *rsp){ // TODO: THIS IS SUBOPTIMAL. We want to call it directly // if rcu_blocking_is_gp() == T, but it has might_sleep().Not sure I get that comment..
I meant, we actually want
static void rcu_sync_call(struct rcu_sync *rsp)
{
if (rcu_blocking_is_gp())
rcu_sync_func(rsp);
else
gp_ops[rsp->gp_type].call(&rsp->cb_head, rcu_sync_func)
}but this needs some other simple changes. rcu_sync_func() needs the same spinlock and rcu_blocking_is_gp() calls might_sleep().
We can simply move rcu_sync_call() outside of rsp->rss_lock, but then we will need more comments to explain why we can't race with enter/exit from someone else. Or introduce __rcu_blocking_is_gp() without might_sleep(), but this needs a trivial change outside of rcu/sync.c.
We will see. This is simple anyway.
Quoted 9 lines
Quoted 6 lines
spin_lock_irqsave(&rsp->rss_lock, flags); if (rsp->gp_count) { /* * We're at least a GP after the first __rcu_sync_enter(). */ rsp->gp_state = GP_PASSED;So we can end up here in two ways afaict.
Quoted 7 lines
The simple way: someone called
__rcu_sync_enter(), we go IDLE -> ENTER with a raised count. Once the GP passes, we get here, observe the raised count and advance to PASSED.The more involved way: we were EXIT and someone calls
__rcu_sync_enter()to raise the count again. The callback fromrcu_sync_exit()was still pending and once we get here we observe the raised count and voila.
Yes, yes.
Quoted 2 lines
Now, since state != IDLE, I suppose this is valid, but it does hurt my brain.
Simply put, if rsp->gp_count != 0 we do not care about the history and GP_PASSED is always correct when rcu callback is called, this obviously means that we passed a GP.
Except GP_IDLE -> GP_PASSED transition is wrong, but this must not be possible because only rcu callback can set GP_IDLE and only if !gp_count, so we must always have at least one GP in between. Note also BUG_ON() checks at the start.
Quoted 2 lines
So I think its solid, but you failed to mention one state transition, which seems ok in any case.
Great, thanks for review.
I'll send the actual patch on top of your changes.
Oleg.
Re: [PATCH] rcu_sync: simplify the state machine, introduce __rcu_sync_enter()
In reply to Oleg NesterovQuoted 4 lines · Oleg Nesterov
Quoted 3 lines · Paul E. McKenney
Quoted 1 line · Oleg Nesterov
Quoted 8 lines
Quoted 5 lines
Quoted 2 lines
IOW, please ignore 2/2 which adds
PERCPU_RWSEM_READER, the new version just addsrcu_sync_sabotage()which should be renamed (and useGP_PASSED).OK, then move the checks out into the callers that would have used
__rcu_sync_enter(). ;-)Cough, now I can't understand which check do you mean ;) OK, let me show the code, then we will hopefully understand each other.
The check in an earlier patch having to do with some _NONE value. I don't see any such checks here. Which might be a good thing, who knows? ;-)
Quoted 5 lines
----------------------------------------------------------------------
So, as you can see I have fooled you ;) I'll send the patch on top of Peter's changes, this is the (UNTESTED) code with the patch applied.Peter, Paul, could you review? Do you see any hole?
Please see below. I see what looks like a missing wakeup and something that could fail to wait for pre-existing RCU readers. At the very end, I have a somewhat more elaborate state diagram, along with assumptions I made during review. Please do check these assumptions carefully.
And the state table, for that matter...
Quoted 7 lines
Why. Firstly, note that the state machine was greatly simplified, and
rsp->cb_statehas gone, we have a single "state" variable,gp_state. Note also the ->sync() op has gone (and actually ->wait() too, see the "TODO" comment).
GP_IDLE- owned by__rcu_sync_enter()which can only move this state to
Quoted 8 lines
GP_ENTER- owned by rcu-callback which moves it to
GP_PASSED- owned by the lastrcu_sync_exit()which moves it to
GP_EXIT- owned by rcu-callback which moves it back toGP_IDLE.Yes, this is a bit simplified, we also have
GP_REPLAY, but hopefully clear.
Agreed, given that you are ignoring GP_REPLAY.
Quoted 3 lines
And, there is another possible transition,
GP_ENTER->GP_IDLE, because not it is possible to call__rcu_sync_enter()andrcu_sync_exit()in any state (except obviously they should be balanced), and they do not block.
Hmmm... I am assuming that the only threads allowed to invoke rcu_sync_exit() are those that have executed an unpaired rcu_sync_enter(). With that assumption, rcu_sync_enter() is illegal if GP_IDLE or GP_ENTER, because the rcu_sync_enter() calls won't return until the state reaches GP_PASSED.
If you feel strongly about allowing rcu_sync_exit() in GP_ENTER state, could you please tell me your use case? Or am I confused?
Quoted 2 lines
The only blocking call is
__rcu_sync_wait()which actually waits for GP. Obviously should only be called ifgp_count!= 0, iow after__rcu_sync_enter.
Well, rcu_sync_dtor() as well. But to your point, it is blocking in rcu_barrier(), not in wait_event().
Quoted 5 lines
--------------------------------------------------------------------------
Now,cgroup_init()can simply call__rcu_sync_enter(&cgroup_threadgroup_rwsem)and switch this sem into the slow mode. Compared to "sabotage" from Peter this implies the unnecessarycall_rcu_sched(), but I hope we can tolerate this.
Or you could add a flag to rcu_sync_init() and friends, but I vaguely recall some objection to that.
Quoted 2 lines
And we can even add a runtime knob to switch between "fast" and "slow aka writer-biased" modes for
cgroup_threadgroup_rwsem.
This might well be the check that I was objecting to earlier. ;-)
Quoted 5 lines
--------------------------------------------------------------------------
And I think__rcu_sync_enter()can have more users. Let's look atfreeze_super(). It callspercpu_down_write()3 times, and waits for 3 GP's sequentally.
Quoted 7 lines
Now we can add 3 __rcu_sync_enter's at the start and 3 rcu_sync_exit's at the end (actually we can do better, just to simplify). And again, note that
rcu_sync_exit()will work correctly even if we (say) return-EBUSY, sorcu_sync_waitand/orpercpu_down_write()was not called in between, and in this case we won't block waiting for GP.What do you think?
I am not going to claim to understand freeze_super(), but it does seem to have a fair amount of waiting.
But yes, you could put rcu_sync_enter() and rcu_sync_exit() before and after a series of write-side enter/exit pairs in order to force things to stay in writer mode, if that is what you are suggesting.
Quoted 48 lines
Oleg.
---//
rcu_sync.h: ----------------------------------------------------------------struct rcu_sync { int gp_state; int gp_count; wait_queue_head_t gp_wait; struct rcu_head cb_head; enum rcu_sync_type gp_type; };//
sync.c--------------------------------------------------------------------- #include <linux/rcu_sync.h>
#include <linux/sched.h>#ifdef
CONFIG_PROVE_RCU
#define__INIT_HELD(func).held = func,
#else
#define__INIT_HELD(func)
#endifstatic const struct { void (*call)(struct rcu_head *, void (*)(struct rcu_head *)); void (*wait)(void); // TODO: remove this, see the comment in dtor #ifdef CONFIG_PROVE_RCU int (*held)(void); #endif } gp_ops[] = { [RCU_SYNC] = { .call = call_rcu, .wait = rcu_barrier, __INIT_HELD(rcu_read_lock_held) }, [RCU_SCHED_SYNC] = { .call = call_rcu_sched, .wait = rcu_barrier_sched, __INIT_HELD(rcu_read_lock_sched_held) }, [RCU_BH_SYNC] = { .call = call_rcu_bh, .wait = rcu_barrier_bh, __INIT_HELD(rcu_read_lock_bh_held) }, };
Quoted 1 line
#define
rss_lockgp_wait.lock
Interesting... Should there instead be a wait_queue_lock() and wait_queue_unlock()? If someone changes how wait-queue locking work, this one might be a bit tricky.
Quoted 8 lines
enum {
GP_IDLE= 0,GP_ENTER,GP_PASSED,GP_EXIT,GP_REPLAY};static void
rcu_sync_func(struct rcu_head *rcu);static void
rcu_sync_call(struct rcu_sync *rsp){ // TODO: THIS IS SUBOPTIMAL. We want to call it directly // if rcu_blocking_is_gp() == T, but it has might_sleep().
Careful! This optimization works only for RCU-sched and RCU-bh. With normal RCU, you can be tripped up by tasks preempted within RCU read-side critical sections should CONFIG_PREEMPT=y.
Quoted 9 lines
gp_ops[rsp->gp_type].call(&rsp->cb_head, rcu_sync_func); }static void
rcu_sync_func(struct rcu_head *rcu){ struct rcu_sync *rsp = container_of(rcu, struct rcu_sync, cb_head); unsigned long flags; BUG_ON(rsp->gp_state == GP_IDLE);
If you really want rcu_sync_exit() to be invoked before the corresponding rcu_sync_enter() returns, and if you want that to set the state to GP_IDLE, then the above BUG_ON() can fire. This is why I was assuming that rcu_sync_enter() must return before the corresponding rcu_sync_exit() can start.
Quoted 1 line
BUG_ON(rsp->gp_state == GP_PASSED);
READ_ONCE() would feel better in both of the above BUG_ON()s, given that we are not yet holding the lock.
Quoted 5 lines
spin_lock_irqsave(&rsp->rss_lock, flags);
if (rsp->gp_count) {
/*
* We're at least a GP after the first __rcu_sync_enter().
*/Don't we need a wakeup here?
Quoted 23 lines
rsp->gp_state = GP_PASSED; } else if (rsp->gp_state == GP_REPLAY) { /* * A new rcu_sync_exit() has happened; requeue the callback * to catch a later GP. */ rsp->gp_state = GP_EXIT; rcu_sync_call(rsp); } else { /* * We're at least a GP after the last rcu_sync_exit(); * EVeybody will now have observed the write side critical * section. Let 'em rip!. * * OR. ->gp_state can be still GP_ENTER if __rcu_sync_wait() * wasn't called after __rcu_sync_enter(), abort. */ rsp->gp_state = GP_IDLE; } spin_unlock_irqrestore(&rsp->rss_lock, flags); }bool
__rcu_sync_enter(struct rcu_sync *rsp)
Quoted 14 lines
{
int gp_count, gp_state;
spin_lock_irq(&rsp->rss_lock);
gp_count = rsp->gp_count++;
gp_state = rsp->gp_state;
if (gp_state == GP_IDLE) {
rsp->gp_state = GP_ENTER;
rcu_sync_call(rsp);
}
spin_unlock_irq(&rsp->rss_lock);
BUG_ON(gp_count != 0 && gp_state == GP_IDLE);
BUG_ON(gp_count == 0 && gp_state == GP_PASSED);Isn't gp_count==0 illegal here regardless of gp_state value?
Quoted 1 line
return gp_state < GP_PASSED;
And the above statement is another reason why I believe that it should be illegal to invoke rcu_sync_exit() until after the matching rcu_sync_enter() has returned. If we were preempted just before the above "return" statement, we might be in GP_IDLE state upon return, which could fatally disappoint the caller.
And ditto for the next BUG_ON().
Quoted 8 lines
}
void
__rcu_sync_wait(struct rcu_sync *rsp){ BUG_ON(rsp->gp_state == GP_IDLE); BUG_ON(rsp->gp_count == 0); wait_event(rsp->gp_wait, rsp->gp_state >= GP_PASSED);
I would feel better if all the references to ->gp_state and ->gp_count outside of the lock be READ_ONCE(). Compilers can be tricky beasts.
Quoted 1 line
}
Here is what I believe rcu_sync_enter() does:
rcu_sync_enter() checks to see if the rcu_sync structure is in writer mode, and, if not, initiates a transition to writer mode and waits for that transition to complete. Either way, a writer reference is acquired.
Quoted 5 lines
void
rcu_sync_enter(struct rcu_sync *rsp){ if (__rcu_sync_enter(rsp)) __rcu_sync_wait(rsp); }
Here is what I believe rcu_sync_exit() does:
rcu_sync_exit() removes an updater reference, and if there are no more, starts the transition to reader mode. Note that this must handle transitions back to writer mode that occur before the transition to reader mode has fully completed.
Quoted 3 lines
void
rcu_sync_exit(struct rcu_sync *rsp){ BUG_ON(rsp->gp_state == GP_IDLE);
BUG_ON(READ_ONCE(rsp->gp_state) == GP_ENTER);
If you do allow rcu_sync_exit() while GP_ENTER, then all the readers can do rcu_sync_exit() in that state, leaving the state at GP_PASSED instead of the desired GP_IDLE.
Quoted 5 lines
BUG_ON(rsp->gp_count == 0);
spin_lock_irq(&rsp->rss_lock);
if (!--rsp->gp_count) {
if (rsp->gp_state == GP_PASSED) {Quoted 8 lines
rsp->gp_state = GP_EXIT;
rcu_sync_call(rsp);
} else if (rsp->gp_state == GP_EXIT) {
rsp->gp_state = GP_REPLAY;
}
}
spin_unlock_irq(&rsp->rss_lock);
}And here is what I believe rcu_sync_dtor() does:
rcu_sync_dtor() cleans up at end, waiting for a full transition back into reader mode. Anything invoking rcu_sync_dtor() must have ensured that there will be no more calls to rcu_sync_enter() and rcu_sync_exit().
Quoted 10 lines
void
rcu_sync_dtor(struct rcu_sync *rsp){ int gp_state; BUG_ON(rsp->gp_count); BUG_ON(rsp->gp_state == GP_PASSED); spin_lock_irq(&rsp->rss_lock); if (rsp->gp_state == GP_REPLAY) rsp->gp_state = GP_EXIT;
OK, this ensures that the .wait() below will wait for the callback, but it might result in some RCU read-side critical sections still being in flight after rcu_sync_dtor() completes. In other words, any transition started by an rcu_sync_exit() will normally wait until all pre-existing RCU readers complete -- unless that rcu_sync_exit() set the state to GP_REPLAY and was quickly followed by rcu_sync_dtor().
Is this really OK???
You might get a more robust API if you left the value of ->gp_state alone and rechecked ->gp_state below (under the lock). The additional overhead is incurred only at cleanup, so should not be a problem.
Or am I missing some subtle constraint on use cases here?
Quoted 4 lines
gp_state = rsp->gp_state; spin_unlock_irq(&rsp->rss_lock); // TODO: add another wake_up_locked() into rcu_sync_func(),
We need to add the first wakeup before we can add another one. ;-)
But I don't see why we need a second wakeup, given that rcu_barrier() wakes itself up.
Quoted 7 lines
// use wait_event + spin_lock_wait, remove gp_ops->wait().
if (gp_state != GP_IDLE) {
gp_ops[rsp->gp_type].wait();
BUG_ON(rsp->gp_state != GP_IDLE);
}
}And please see below for the state table and assumptions.
So, what am I missing?
Thanx, Paul
------------------------------------------------------------------------
o "count" is the value of the rcu_sync structure's ->gp_count field.
o "state" is the value of the rcu_sync structure's ->gp_state field.
o "CB?" is "yes" if there is an RCU callback pending and "no" otherwise.
| count | state | CB? | next state
---+-------+-----------+-----+------------------------------------
| | | | A | 0 | GP_IDLE | no | rcu_sync_enter() -> B (wait) | | | | rcu_sync_exit() -> illegal | | | | rcu_sync_dtor() -> H | | | | callback -> cannot happen | | | | ---+-------+-----------+-----+------------------------------------ | | | | B | 1+ | GP_ENTER | yes | rcu_sync_enter() -> B (wait) | | | | rcu_sync_exit() -> illegal | | | | rcu_sync_dtor() -> illegal | | | | callback -> C (ends _enter() wait) | | | | ---+-------+-----------+-----+------------------------------------ | | | | C | 1+ | GP_PASSED | no | rcu_sync_enter() -> C | | | | last rcu_sync_exit() -> E | | | | non-last rcu_sync_exit() -> C | | | | rcu_sync_dtor() -> illegal | | | | callback -> cannot happen | | | | ---+-------+-----------+-----+------------------------------------ | | | | D | 0 | GP_REPLAY | yes | rcu_sync_enter() -> F | | | | rcu_sync_exit() -> illegal | | | | rcu_sync_dtor() -> I (wait ???) | | | | callback -> E | | | | ---+-------+-----------+-----+------------------------------------ | | | | E | 0 | GP_EXIT | yes | rcu_sync_enter() -> G | | | | rcu_sync_exit() -> illegal | | | | rcu_sync_dtor() -> I (wait) | | | | callback -> A | | | | ---+-------+-----------+-----+------------------------------------ | | | | F | 1+ | GP_REPLAY | yes | rcu_sync_enter() -> F | | | | last rcu_sync_exit() -> D | | | | non-last rcu_sync_exit() -> F | | | | rcu_sync_dtor() -> illegal | | | | callback -> C | | | | ---+-------+-----------+-----+------------------------------------ | | | | G | 1+ | GP_EXIT | yes | rcu_sync_enter() -> G | | | | last rcu_sync_exit() -> D | | | | non-last rcu_sync_exit() -> F | | | | rcu_sync_dtor() -> illegal | | | | callback -> C | | | | ---+-------+-----------+-----+------------------------------------ | | | | H | 0 | GP_IDLE | no | rcu_sync_enter() -> illegal | | | | rcu_sync_exit() -> illegal | | | | rcu_sync_dtor() -> illegal | | | | callback -> cannot happen | | | | ---+-------+-----------+-----+------------------------------------ | | | | I | 0 | GP_EXIT | yes | rcu_sync_enter() -> illegal | | | | rcu_sync_exit() -> illegal | | | | rcu_sync_dtor() -> illegal | | | | callback -> H (ends dtor() wait)
Assumptions:
o Initial state is A.
o Final state is H.
o There will never be enough unpaired rcu_sync_enter() calls to
overflow ->gp_count.
o All calls to rcu_sync_exit() must pair with a preceding call
to rcu_sync_enter() by that same thread.
o It is illegal to invoke rcu_sync_dtor() until after the caller
has ensured that there will be no future calls to either rcu_sync_enter() or rcu_sync_exit().
o It is illegal to invoke rcu_sync_dtor() while there are any
unpaired calls to rcu_sync_enter().
Re: [PATCH] rcu_sync: simplify the state machine, introduce __rcu_sync_enter()
Quoted 7 lines · Paul E. McKenney
Quoted 3 lines · Oleg Nesterov
And, there is another possible transition,
GP_ENTER->GP_IDLE, because not it is possible to call__rcu_sync_enter()andrcu_sync_exit()in any state (except obviously they should be balanced), and they do not block.
Quoted 5 lines
Hmmm... I am assuming that the only threads allowed to invoke
rcu_sync_exit()are those that have executed an unpairedrcu_sync_enter(). With that assumption,rcu_sync_enter()is illegal ifGP_IDLEorGP_ENTER, because thercu_sync_enter()calls won't return until the state reachesGP_PASSED.
Not sure I understand... Obviously rcu_sync_exit() can only be used if it pairs with the preceeding __rcu_sync_enter() or rcu_sync_enter(), if nothing else rsp->gp_count-- must not underflow.
rcu_sync_enter() or __rcu_sync_enter() is legal in any state, the latter won't block.
Quoted 2 lines
If you feel strongly about allowing
rcu_sync_exit()inGP_ENTERstate, could you please tell me your use case? Or am I confused?
I'll write another email, let me reply to the code review first. And thanks for your review!
Quoted 5 lines
Quoted 2 lines
The only blocking call is
__rcu_sync_wait()which actually waits for GP. Obviously should only be called ifgp_count!= 0, iow after__rcu_sync_enter.Well,
rcu_sync_dtor()as well. But to your point, it is blocking inrcu_barrier(), not inwait_event().
Yes, yes, sure. I mean't the only blocking call in enter/exit paths.
Quoted 5 lines
Quoted 1 line
#define
rss_lockgp_wait.lockInteresting... Should there instead be a
wait_queue_lock()andwait_queue_unlock()? If someone changes how wait-queue locking work, this one might be a bit tricky.
Well, the current version does the same... I do not mind to add wait_queue_lock(), but personally I do not think this would be better. Either way the code should not use gp_wait.lock directly, to make it clear that we abuse the spinlock we already have in rcu_sync->gp_wait.
To me
spin_lock_irq(&rsp->rss_lock); spin_unlock_irq(&rsp->rss_lock);
looks a bit better than
wait_queue_lock(); wait_queue_unlock();
because we need to protect the state, not the wait-queue. And if someone changes how wait-queue locking work, we can just actually add "spinlock_t rss_lock" into rcu_sync and remove that "#define".
Quoted 8 lines
Quoted 4 lines
static void
rcu_sync_call(struct rcu_sync *rsp){ // TODO: THIS IS SUBOPTIMAL. We want to call it directly // if rcu_blocking_is_gp() == T, but it has might_sleep().Careful! This optimization works only for RCU-sched and RCU-bh. With normal RCU, you can be tripped up by tasks preempted within RCU read-side critical sections should
CONFIG_PREEMPT=y.
Yes, thanks, I understand ;) another reason why I do not want to add this optimization into the initial version.
Quoted 5 lines
Quoted 5 lines
static void
rcu_sync_func(struct rcu_head *rcu){ struct rcu_sync *rsp = container_of(rcu, struct rcu_sync, cb_head); unsigned long flags;
Quoted 7 lines
Quoted 1 line
BUG_ON(rsp->gp_state == GP_IDLE);If you really want
rcu_sync_exit()to be invoked before the correspondingrcu_sync_enter()returns, and if you want that to set the state toGP_IDLE, then the aboveBUG_ON()can fire. This is why I was assuming thatrcu_sync_enter()must return before the correspondingrcu_sync_exit()can start.
Again, can't understand. Let me repeat, of course rcu_sync_exit() can only be called after __rcu_sync_enter() (or rcu_sync_enter() which does __rcu_sync_enter + wait), they obviously should be balanced.
But, it should be fine to call rcu_sync_exit() right after __rcu_sync_enter() returns, without waiting for GP_PASSED, iow without __rcu_sync_wait().
But probably I misunderstood you... Either way, rcu_sync_func() can never hit gp_state == GP_IDLE. Because this state can only be set by rcu_sync_func(), nobody else can set this state. Ignoring the initial state of course.
And every caller of rcu_sync_call() (which does call_rcu(rcu_sync_func)) updates the state first:
rsp->gp_state = GP_ENTER; // __rcu_sync_enter() rcu_sync_call(rsp); rsp->gp_state = GP_EXIT; // rcu_sync_exit() or REPLAY rcu_sync_call(rsp);
so I simply can't understand your concern.
Quoted 4 lines
Quoted 1 line
BUG_ON(rsp->gp_state == GP_PASSED);
READ_ONCE()would feel better in both of the above BUG_ON()s, given that we are not yet holding the lock.
The lockless checks are fine, both states can only be set by the callback. As for READ_ONCE(), please see below.
Quoted 7 lines
Quoted 5 lines
spin_lock_irqsave(&rsp->rss_lock, flags); if (rsp->gp_count) { /* * We're at least a GP after the first __rcu_sync_enter(). */Don't we need a wakeup here?
Heh ;) let me show you what I have in kernel/rcu/sync.c~
if (rsp->gp_count) {
/*
* COMMENT.
*/
rsp->gp_state = GP_PASSED;
wake_up_locked(&rsp->gp_wait);
} else if (rsp->gp_state == GP_REPLAY) {apparently I removed this wake_up_locked() by accident when I edited the "COMMENT" placeholder ;)
Thanks!
Quoted 13 lines
Quoted 13 lines
bool
__rcu_sync_enter(struct rcu_sync *rsp){ int gp_count, gp_state; spin_lock_irq(&rsp->rss_lock); gp_count = rsp->gp_count++; gp_state = rsp->gp_state; if (gp_state == GP_IDLE) { rsp->gp_state = GP_ENTER; rcu_sync_call(rsp); } spin_unlock_irq(&rsp->rss_lock);
Quoted 4 lines
Quoted 2 lines
BUG_ON(gp_count != 0 && gp_state == GP_IDLE); BUG_ON(gp_count == 0 && gp_state == GP_PASSED);Isn't gp_count==0 illegal here regardless of
gp_statevalue?
No. gp_count is the old value of rsp->gp_count, before it was incremented by us.
Quoted 6 lines
Quoted 1 line
return gp_state < GP_PASSED;And the above statement is another reason why I believe that it should be illegal to invoke
rcu_sync_exit()until after the matchingrcu_sync_enter()has returned. If we were preempted just before the above "return" statement, we might be inGP_IDLEstate upon return,
How? Note the GP_IDLE -> GP_ENTER transition above, it can't be GP_IDLE. And since we incremented rsp->gp_count, GP_IDLE is not possibly until rcu_sync_exit() which pairs with this "enter" decrements the counter and fires the RCU callback which will set GP_IDLE after GP.
Quoted 9 lines
Quoted 6 lines
void
__rcu_sync_wait(struct rcu_sync *rsp){ BUG_ON(rsp->gp_state == GP_IDLE); BUG_ON(rsp->gp_count == 0); wait_event(rsp->gp_wait, rsp->gp_state >= GP_PASSED);I would feel better if all the references to ->gp_state and ->gp_count outside of the lock be
READ_ONCE(). Compilers can be tricky beasts.
Oh. I won't argue too much, but I do not agree.
Because I started to hate these _ONCE() helpers a long ago. Why? Because (imo) we have too many XXX_ONCE() added "just in case, because compiler can be buggy".
Now, when I look some particular READ_ONCE() I can almost never understand the reason: do we really need it for correctness? or this is another "it does not hurt" case?
So why do we need READ_ONCE() here? How this wait_event() differs from any other wait_event() which does a plain LOAD? Should we "fix" them all?
Quoted 9 lines
rcu_sync_enter()checks to see if thercu_syncstructure is in writer mode, and, if not, initiates a transition to writer mode and waits for that transition to complete. Either way, a writer reference is acquired.Quoted 5 lines
void
rcu_sync_enter(struct rcu_sync *rsp){ if (__rcu_sync_enter(rsp)) __rcu_sync_wait(rsp); }
Yes. And of course it could call __rcu_sync_wait() unconditionally:
__rcu_sync_enter(rsp); __rcu_sync_wait(rsp);
This "if" is more the documentation than optimization.
Quoted 11 lines
Here is what I believe
rcu_sync_exit()does:
rcu_sync_exit()removes an updater reference, and if there are no more, starts the transition to reader mode. Note that this must handle transitions back to writer mode that occur before the transition to reader mode has fully completed.Quoted 3 lines
void
rcu_sync_exit(struct rcu_sync *rsp){ BUG_ON(rsp->gp_state == GP_IDLE);
Quoted 5 lines
BUG_ON(READ_ONCE(rsp->gp_state) == GP_ENTER);If you do allow
rcu_sync_exit()whileGP_ENTER, then all the readers can dorcu_sync_exit()in that state, leaving the state atGP_PASSEDinstead of the desiredGP_IDLE.
Can't understand this too...
Firstly, readers do not do rcu_sync_enter/exit. They only use rcu_sync_is_idle() which checks gp_state == GP_IDLE.
As for enter() path, we only need to ensure that after __rcu_sync_wait() returns, all CPU's must see gp_state != GP_IDLE. Yes, yes, this is not really true, we also need the memory barriers implied by RCU, lets ignore this to simplify the discussion.
Now lets suppose that rcu_sync_exit() is called while GP_ENTER. This is only possible if __rcu_sync_wait() was not called by us. IOW, this thread does
__rcu_sync_enter(); rcu_sync_exit();
by any reason.
Now. If gp_count is not zero after we decrement it, rcu_sync_exit() does nothing.
If it is zero, we could set GP_IDLE right now. We do not care about the readers in this case, exactly because __rcu_sync_wait() was not called and thus we can't even know if other CPU's had any chance to observe the gp_state != GP_IDLE state set by __rcu_sync_enter().
But. We can't actually set GP_IDLE, because GP_ENTER means that the RCU callback is already pending and this can break the next enter(). IOW, this would break the "GP_IDLE is owned by __rcu_sync_enter" contract.
And we do not need to do this! We rely on the already pending rcu_sync_func() which will set GP_IDLE _if_ the counter is still zero.
Now suppose that another writer (__rcu_sync_enter) comes and increments gp_count right before rcu_sync_func() is called by RCU. In this case rcu_sync_func() will notice rsp->gp_count != 0 and correctly set GP_PASSED.
Note that in this case that new writer won't wait for a full GP, quite possibly __rcu_sync_wait() won't even block. And this is correct, we know that we have at least one full GP after the previous GP_IDLE -> GP_ENTER transition.
Quoted 5 lines
And here is what I believe
rcu_sync_dtor()does:
rcu_sync_dtor()cleans up at end, waiting for a full transition back into reader mode. Anything invokingrcu_sync_dtor()must have ensured that there will be no more calls torcu_sync_enter()andrcu_sync_exit().
Sure, we are going to destroy/free this object.
Quoted 7 lines
Quoted 7 lines
void
rcu_sync_dtor(struct rcu_sync *rsp){ int gp_state; BUG_ON(rsp->gp_count); BUG_ON(rsp->gp_state == GP_PASSED);
Quoted 7 lines
Quoted 3 lines
spin_lock_irq(&rsp->rss_lock); if (rsp->gp_state == GP_REPLAY) rsp->gp_state = GP_EXIT;OK, this ensures that the .wait() below will wait for the callback, but it might result in some RCU read-side critical sections still being in flight after
rcu_sync_dtor()completes.
Hmm. Obviously, the caller should prevent this somehow or it is simply buggy. Or I misunderstood.
Quoted 4 lines
In other words, any transition
started by anrcu_sync_exit()will normally wait until all pre-existing RCU readers complete -- unless thatrcu_sync_exit()set the state toGP_REPLAYand was quickly followed byrcu_sync_dtor().
Again, we are going to (say) free this memory. The caller must ensure that rcu_sync_is_idle(rsp) or anything else which can touch this object is not possible.
The only reason for rcu_sync_dtor() is that we can't free/destroy this memory until we ensure that the pending rcu_sync_func() finishes. And of course we need to ensure it won't re-arm itself, that is why we do GP_REPLAY -> GP_EXIT transition.
And in any case, this patch doesn't change rcu_sync_dtor() logic, it is the same even if ->cb_state goes away.
Quoted 1 line
We need to add the first wakeup before we can add another one. ;-)
Yes ;) see above.
Quoted 9 lines
But I don't see why we need a second wakeup, given that
rcu_barrier()wakes itself up.Quoted 6 lines
// use wait_event + spin_lock_wait, remove gp_ops->wait(). if (gp_state != GP_IDLE) { gp_ops[rsp->gp_type].wait(); BUG_ON(rsp->gp_state != GP_IDLE); } }
Because we can remove .wait() op and simplify the code a bit more. This also can be more efficient, altough this doesn't really matter. The main reason is that this would be more clear: we only need to sync with one particular callback, we do not need to wait for all outstanding RCU callbacks to complete. Anyway, this is TODO and needs another patch, we will discuss this later.
Quoted 1 line
And please see below for the state table and assumptions.
I'll try to read it carefully later.
Thanks Paul!
Oleg.
Re: [PATCH] rcu_sync: simplify the state machine, introduce __rcu_sync_enter()
Quoted 14 lines · Oleg Nesterov
Quoted 7 lines · Paul E. McKenney
Quoted 3 lines · Oleg Nesterov
And, there is another possible transition,
GP_ENTER->GP_IDLE, because not it is possible to call__rcu_sync_enter()andrcu_sync_exit()in any state (except obviously they should be balanced), and they do not block.Quoted 5 lines
Hmmm... I am assuming that the only threads allowed to invoke
rcu_sync_exit()are those that have executed an unpairedrcu_sync_enter(). With that assumption,rcu_sync_enter()is illegal ifGP_IDLEorGP_ENTER, because thercu_sync_enter()calls won't return until the state reachesGP_PASSED.
Quoted 3 lines
Not sure I understand... Obviously
rcu_sync_exit()can only be used if it pairs with the preceeding__rcu_sync_enter()orrcu_sync_enter(), if nothing else rsp->gp_count-- must not underflow.
Agreed, just wondering what the no-wait use case is.
Quoted 2 lines
rcu_sync_enter()or__rcu_sync_enter()is legal in any state, the latter won't block.
Actually, I had no idea that __rcu_sync_enter() was intended for anything other than internal use.
Other than that, agreed, with the exception that it is illegal after rcu_sync_dtor() has been called.
Quoted 5 lines
Quoted 2 lines
If you feel strongly about allowing
rcu_sync_exit()inGP_ENTERstate, could you please tell me your use case? Or am I confused?I'll write another email, let me reply to the code review first. And thanks for your review!
Fair enough, hope it is helpful.
Quoted 7 lines
Quoted 5 lines
Quoted 2 lines
The only blocking call is
__rcu_sync_wait()which actually waits for GP. Obviously should only be called ifgp_count!= 0, iow after__rcu_sync_enter.Well,
rcu_sync_dtor()as well. But to your point, it is blocking inrcu_barrier(), not inwait_event().Yes, yes, sure. I mean't the only blocking call in enter/exit paths.
Pedantic of me, isn't it? ;-)
Quoted 16 lines
Quoted 5 lines
Quoted 1 line
#define
rss_lockgp_wait.lockInteresting... Should there instead be a
wait_queue_lock()andwait_queue_unlock()? If someone changes how wait-queue locking work, this one might be a bit tricky.Well, the current version does the same... I do not mind to add
wait_queue_lock(), but personally I do not think this would be better. Either way the code should not use gp_wait.lock directly, to make it clear that we abuse the spinlock we already have inrcu_sync->gp_wait.To me
spin_lock_irq(&rsp->rss_lock); spin_unlock_irq(&rsp->rss_lock);
Quoted 8 lines
looks a bit better than
wait_queue_lock(); wait_queue_unlock();because we need to protect the state, not the wait-queue. And if someone changes how wait-queue locking work, we can just actually add "
spinlock_trss_lock" intorcu_syncand remove that "#define".
I don't feel all that strongly about it, but it did look strange.
In any case, please don't reach inside RCU in this way without asking me first! ;-)
Quoted 11 lines
Quoted 8 lines
Quoted 4 lines
static void
rcu_sync_call(struct rcu_sync *rsp){ // TODO: THIS IS SUBOPTIMAL. We want to call it directly // if rcu_blocking_is_gp() == T, but it has might_sleep().Careful! This optimization works only for RCU-sched and RCU-bh. With normal RCU, you can be tripped up by tasks preempted within RCU read-side critical sections should
CONFIG_PREEMPT=y.Yes, thanks, I understand ;) another reason why I do not want to add this optimization into the initial version.
So I should take this as a request to export rcu_blocking_is_gp()?
I would probably need to change the name. Or comment it more heavily. Or hand out coccinelle scripts looking for this pattern:
if (!rcu_blocking_is_gp()) synchronize_rcu();
Or maybe all of the above.
Quoted 27 lines
Quoted 5 lines
Quoted 5 lines
static void
rcu_sync_func(struct rcu_head *rcu){ struct rcu_sync *rsp = container_of(rcu, struct rcu_sync, cb_head); unsigned long flags;Quoted 7 lines
Quoted 1 line
BUG_ON(rsp->gp_state == GP_IDLE);If you really want
rcu_sync_exit()to be invoked before the correspondingrcu_sync_enter()returns, and if you want that to set the state toGP_IDLE, then the aboveBUG_ON()can fire. This is why I was assuming thatrcu_sync_enter()must return before the correspondingrcu_sync_exit()can start.Again, can't understand. Let me repeat, of course
rcu_sync_exit()can only be called after__rcu_sync_enter()(orrcu_sync_enter()which does__rcu_sync_enter+ wait), they obviously should be balanced.But, it should be fine to call
rcu_sync_exit()right after__rcu_sync_enter()returns, without waiting forGP_PASSED, iow without__rcu_sync_wait().But probably I misunderstood you... Either way,
rcu_sync_func()can never hitgp_state==GP_IDLE. Because this state can only be set byrcu_sync_func(), nobody else can set this state. Ignoring the initial state of course.And every caller of
rcu_sync_call()(which doescall_rcu(rcu_sync_func)) updates the state first:
Quoted 7 lines
rsp->gp_state = GP_ENTER; // __rcu_sync_enter() rcu_sync_call(rsp); rsp->gp_state = GP_EXIT; // rcu_sync_exit() or REPLAY rcu_sync_call(rsp);so I simply can't understand your concern.
You are right, the state is always set to something other than GP_IDLE before the callback is registered. Never mind!
Quoted 26 lines
Quoted 4 lines
Quoted 1 line
BUG_ON(rsp->gp_state == GP_PASSED);
READ_ONCE()would feel better in both of the above BUG_ON()s, given that we are not yet holding the lock.The lockless checks are fine, both states can only be set by the callback. As for
READ_ONCE(), please see below.Quoted 7 lines
Quoted 5 lines
spin_lock_irqsave(&rsp->rss_lock, flags); if (rsp->gp_count) { /* * We're at least a GP after the first __rcu_sync_enter(). */Don't we need a wakeup here?
Heh ;) let me show you what I have in
kernel/rcu/sync.c~if (rsp->gp_count) { /* * COMMENT. */ rsp->gp_state = GP_PASSED; wake_up_locked(&rsp->gp_wait); } else if (rsp->gp_state == GP_REPLAY) {
Quoted 2 lines
apparently I removed this
wake_up_locked()by accident when I edited the "COMMENT" placeholder ;)
Sounds like something that I might do! ;-)
Quoted 1 line
Thanks!
No problem -- you have done the same for me many times!
Quoted 13 lines
Quoted 13 lines
Quoted 13 lines
bool
__rcu_sync_enter(struct rcu_sync *rsp){ int gp_count, gp_state; spin_lock_irq(&rsp->rss_lock); gp_count = rsp->gp_count++; gp_state = rsp->gp_state; if (gp_state == GP_IDLE) { rsp->gp_state = GP_ENTER; rcu_sync_call(rsp); } spin_unlock_irq(&rsp->rss_lock);
Quoted 7 lines
Quoted 4 lines
Quoted 2 lines
BUG_ON(gp_count != 0 && gp_state == GP_IDLE); BUG_ON(gp_count == 0 && gp_state == GP_PASSED);Isn't gp_count==0 illegal here regardless of
gp_statevalue?No.
gp_countis the old value ofrsp->gp_count, before it was incremented by us.
Got it -- blindness on my part, I guess.
Quoted 11 lines
Quoted 6 lines
Quoted 1 line
return gp_state < GP_PASSED;And the above statement is another reason why I believe that it should be illegal to invoke
rcu_sync_exit()until after the matchingrcu_sync_enter()has returned. If we were preempted just before the above "return" statement, we might be inGP_IDLEstate upon return,How? Note the
GP_IDLE->GP_ENTERtransition above, it can't beGP_IDLE. And since we incrementedrsp->gp_count,GP_IDLEis not possibly untilrcu_sync_exit()which pairs with this "enter" decrements the counter and fires the RCU callback which will setGP_IDLEafter GP.
Right -- I was again confusing gp_state with rsp->gp_state.
Quoted 16 lines
Quoted 9 lines
Quoted 6 lines
void
__rcu_sync_wait(struct rcu_sync *rsp){ BUG_ON(rsp->gp_state == GP_IDLE); BUG_ON(rsp->gp_count == 0); wait_event(rsp->gp_wait, rsp->gp_state >= GP_PASSED);I would feel better if all the references to ->gp_state and ->gp_count outside of the lock be
READ_ONCE(). Compilers can be tricky beasts.Oh. I won't argue too much, but I do not agree.
Because I started to hate these
_ONCE()helpers a long ago. Why? Because (imo) we have too manyXXX_ONCE()added "just in case, because compiler can be buggy".
Quoted 6 lines
Now, when I look some particular
READ_ONCE()I can almost never understand the reason: do we really need it for correctness? or this is another "it does not hurt" case?So why do we need
READ_ONCE()here? How thiswait_event()differs from any otherwait_event()which does a plain LOAD? Should we "fix" them all?
I think of them as "this accesses a shared variable". I have been learning more about what the compiler is permitted to do to normal variables, and I am paranoid. The default compiler assumption that no other thread is looking at or modifying the variable is a very powerful and profound assumption, and it points to a rather stunning array of "creative" optimizations.
It has been suggested that gcc implement a -std=kernel, which might be a decent alternative. However, we would need to specify what we want gcc to do (and not do) in that case.
This is not an easy thing, nor will it go away. Though I suppose that doesn't mean we cannot ignore it. For awhile, anyway. ;-)
Quoted 10 lines
Quoted 9 lines
rcu_sync_enter()checks to see if thercu_syncstructure is in writer mode, and, if not, initiates a transition to writer mode and waits for that transition to complete. Either way, a writer reference is acquired.Quoted 5 lines
void
rcu_sync_enter(struct rcu_sync *rsp){ if (__rcu_sync_enter(rsp)) __rcu_sync_wait(rsp); }
Quoted 6 lines
Yes. And of course it could call
__rcu_sync_wait()unconditionally:__rcu_sync_enter(rsp); __rcu_sync_wait(rsp);This "if" is more the documentation than optimization.
Understood.
Quoted 7 lines
Quoted 7 lines
Here is what I believe
rcu_sync_exit()does:
rcu_sync_exit()removes an updater reference, and if there are no more, starts the transition to reader mode. Note that this must handle transitions back to writer mode that occur before the transition to reader mode has fully completed.
Quoted 7 lines
Quoted 7 lines
Quoted 3 lines
void
rcu_sync_exit(struct rcu_sync *rsp){ BUG_ON(rsp->gp_state == GP_IDLE);BUG_ON(READ_ONCE(rsp->gp_state) == GP_ENTER);If you do allow
rcu_sync_exit()whileGP_ENTER, then all the readers
s/readers/writers/, sorry!
Quoted 37 lines
Quoted 2 lines
can do
rcu_sync_exit()in that state, leaving the state atGP_PASSEDinstead of the desiredGP_IDLE.Can't understand this too...
Firstly, readers do not do rcu_sync_enter/exit. They only use
rcu_sync_is_idle()which checksgp_state==GP_IDLE.As for
enter()path, we only need to ensure that after__rcu_sync_wait()returns, all CPU's must seegp_state!=GP_IDLE. Yes, yes, this is not really true, we also need the memory barriers implied by RCU, lets ignore this to simplify the discussion.Now lets suppose that
rcu_sync_exit()is called whileGP_ENTER. This is only possible if__rcu_sync_wait()was not called by us. IOW, this thread does__rcu_sync_enter(); rcu_sync_exit();by any reason.
Now. If
gp_countis not zero after we decrement it,rcu_sync_exit()does nothing.If it is zero, we could set
GP_IDLEright now. We do not care about the readers in this case, exactly because__rcu_sync_wait()was not called and thus we can't even know if other CPU's had any chance to observe thegp_state!=GP_IDLEstate set by__rcu_sync_enter().But. We can't actually set
GP_IDLE, becauseGP_ENTERmeans that the RCU callback is already pending and this can break the nextenter(). IOW, this would break the "GP_IDLEis owned by__rcu_sync_enter" contract.And we do not need to do this! We rely on the already pending
rcu_sync_func()which will setGP_IDLE_if_ the counter is still zero.
Quoted 8 lines
Now suppose that another writer (
__rcu_sync_enter) comes and incrementsgp_countright beforercu_sync_func()is called by RCU. In this casercu_sync_func()will noticersp->gp_count!= 0 and correctly setGP_PASSED.Note that in this case that new writer won't wait for a full GP, quite possibly
__rcu_sync_wait()won't even block. And this is correct, we know that we have at least one full GP after the previousGP_IDLE->GP_ENTERtransition.
Let me try laying out the sequence of events that I was concerned about, which will hopefully either prove my point or identify my error:
o The rcu_sync structure is in state GP_IDLE with count 0 and
no CB posted. (State A in my table.)
o Task A invokes __rcu_sync_enter(), which puts the state at
GP_ENTER with count 1 and a CB posted.
o Task B also invokes __rcu_sync_enter(), which increments count
to 2, but leaves the state otherwise unchanged.
o Both Task A and Task B invoke rcu_sync_exit(), which decrements
count back down to zero, but otherwise leaves the state unchanged. So we are at GP_ENTER with count 0.
Ah, but the CB is still pending, and when it is invoked, as you say above, it will see that count==0 and therefore set the state to GP_IDLE.
OK, you are quite right.
Quoted 19 lines
Quoted 5 lines
And here is what I believe
rcu_sync_dtor()does:
rcu_sync_dtor()cleans up at end, waiting for a full transition back into reader mode. Anything invokingrcu_sync_dtor()must have ensured that there will be no more calls torcu_sync_enter()andrcu_sync_exit().Sure, we are going to destroy/free this object.
Quoted 11 lines
Quoted 10 lines
void
rcu_sync_dtor(struct rcu_sync *rsp){ int gp_state; BUG_ON(rsp->gp_count); BUG_ON(rsp->gp_state == GP_PASSED); spin_lock_irq(&rsp->rss_lock); if (rsp->gp_state == GP_REPLAY) rsp->gp_state = GP_EXIT;
Quoted 6 lines
Quoted 3 lines
OK, this ensures that the .wait() below will wait for the callback, but it might result in some RCU read-side critical sections still being in flight after
rcu_sync_dtor()completes.Hmm. Obviously, the caller should prevent this somehow or it is simply buggy. Or I misunderstood.
Hard to say without knowing what the permitted use cases are...
Me, I would make rcu_sync_dtor() wait the extra grace period in this case. It should be a low-probability race, and it reduces the _dtor-time state space.
What it looks like you are saying is that the caller must not only ensure that there will never again be a __rcu_sync_enter(), rcu_sync_enter(), or rcu_sync_exit() (or, I suppose, rcu_sync_dtor()) for this rcu_sync structure, but must also ensure that any relevant RCU read-side critical sections have completed.
I guess that either way is OK, but whatever the rules are, they do need to be clearly communicated. Of course, this does indicate a bug in rcu_sync_dtor()'s current comment header. ;-)
Quoted 9 lines
Quoted 4 lines
In other words, any transition
started by anrcu_sync_exit()will normally wait until all pre-existing RCU readers complete -- unless thatrcu_sync_exit()set the state toGP_REPLAYand was quickly followed byrcu_sync_dtor().Again, we are going to (say) free this memory. The caller must ensure that
rcu_sync_is_idle(rsp)or anything else which can touch this object is not possible.
Quoted 7 lines
The only reason for
rcu_sync_dtor()is that we can't free/destroy this memory until we ensure that the pendingrcu_sync_func()finishes. And of course we need to ensure it won't re-arm itself, that is why we doGP_REPLAY->GP_EXITtransition.And in any case, this patch doesn't change
rcu_sync_dtor()logic, it is the same even if ->cb_state goes away.
Agreed, this is something that I should have complained about eleven months ago. So I am slow! ;-)
Quoted 14 lines
Quoted 1 line
We need to add the first wakeup before we can add another one. ;-)
Yes ;) see above.
Quoted 9 lines
But I don't see why we need a second wakeup, given that
rcu_barrier()wakes itself up.Quoted 6 lines
// use wait_event + spin_lock_wait, remove gp_ops->wait(). if (gp_state != GP_IDLE) { gp_ops[rsp->gp_type].wait(); BUG_ON(rsp->gp_state != GP_IDLE); } }
Quoted 6 lines
Because we can remove .wait() op and simplify the code a bit more. This also can be more efficient, altough this doesn't really matter. The main reason is that this would be more clear: we only need to sync with one particular callback, we do not need to wait for all outstanding RCU callbacks to complete. Anyway, this is TODO and needs another patch, we will discuss this later.
Yes, you could have a flag that the callback manipulates and a wait_event()/wake_up() or similar, which would be quite a bit lighter weight than rcu_barrier(). So it does sound worthwhile.
The only concern would be scalability, but we should run into that problem before solving it, and the callback will be the least of our worries.
Thanx, Paul
Quoted 8 lines
Quoted 1 line
And please see below for the state table and assumptions.
I'll try to read it carefully later.
Thanks Paul!
Oleg.
Re: [PATCH] rcu_sync: simplify the state machine, introduce __rcu_sync_enter()
Quoted 11 lines · Paul E. McKenney
Quoted 2 lines · Oleg Nesterov
rcu_sync_enter()or__rcu_sync_enter()is legal in any state, the latter won't block.Actually, I had no idea that
__rcu_sync_enter()was intended for anything other than internal use.Other than that, agreed, with the exception that it is illegal after
rcu_sync_dtor()has been called.
Yes, sure. rcu_sync_dtor() "destroys" struct rcu_sync, and currently it is only called by destroy_super_work() right before kfree(). Nothing is legal after rcu_sync_dtor().
Quoted 5 lines
Quoted 5 lines
Quoted 5 lines
Quoted 4 lines
static void
rcu_sync_call(struct rcu_sync *rsp){ // TODO: THIS IS SUBOPTIMAL. We want to call it directly // if rcu_blocking_is_gp() == T, but it has might_sleep().
Quoted 8 lines
Quoted 6 lines
Quoted 3 lines
Careful! This optimization works only for RCU-sched and RCU-bh. With normal RCU, you can be tripped up by tasks preempted within RCU read-side critical sections should
CONFIG_PREEMPT=y.Yes, thanks, I understand ;) another reason why I do not want to add this optimization into the initial version.
So I should take this as a request to export
rcu_blocking_is_gp()?
Would be nice ;) Or we can do something else. Nevermind, this needs another discussion.
Quoted 20 lines
Quoted 11 lines
Quoted 11 lines
Quoted 10 lines
void
rcu_sync_dtor(struct rcu_sync *rsp){ int gp_state; BUG_ON(rsp->gp_count); BUG_ON(rsp->gp_state == GP_PASSED); spin_lock_irq(&rsp->rss_lock); if (rsp->gp_state == GP_REPLAY) rsp->gp_state = GP_EXIT;Quoted 6 lines
Quoted 3 lines
OK, this ensures that the .wait() below will wait for the callback, but it might result in some RCU read-side critical sections still being in flight after
rcu_sync_dtor()completes.Hmm. Obviously, the caller should prevent this somehow or it is simply buggy. Or I misunderstood.
Hard to say without knowing what the permitted use cases are...
Quoted 8 lines
Me, I would make
rcu_sync_dtor()wait the extra grace period in this case. It should be a low-probability race, and it reduces the _dtor-time state space.What it looks like you are saying is that the caller must not only ensure that there will never again be a
__rcu_sync_enter(),rcu_sync_enter(), orrcu_sync_exit()(or, I suppose,rcu_sync_dtor()) for thisrcu_syncstructure,
Yes, and
Quoted 2 lines
but must also ensure that any relevant RCU read-side critical sections have completed.
Ah, now I understand your concerns. Yes, yes, sure. The caller must ensure that all RCU read-side critical sections which might look at this rcu_sync via rcu_sync_is_idle() have completed.
Currently the only caller of dtor() is percpu_free_rwsem(). So if you do, say,
struct percpu_rw_semaphore *sem = kmalloc(...); ... percpu_free_rwsem(sem); kfree(sem);
you obviously need to ensure that percpu_free_rwsem() can't be called before all readers fully complete their percpu_down_read/percpu_up_read critical sections, this includes the RCU read-side critical sections.
And this doesn't doesn't really differ from the plain rw_semaphore.
And can't resist... let me add another "TODO" note ;) we actually want to improve it a bit (probably just a "bool wait" arg) and kill the ugly super_block->destroy_work which currently does percpu_free_rwsem(). This should be simple.
Oleg.
Re: [PATCH] rcu_sync: simplify the state machine, introduce __rcu_sync_enter()
In reply to Paul E. McKenneyPaul, I had to switch to internal bugzillas after the first email, and now I feel I can't read... I'll try to answer one question right now, tomorrow I'll reread your email, probably I need to answer something else...
Quoted 6 lines · Paul E. McKenney
Quoted 3 lines · Oleg Nesterov
And, there is another possible transition,
GP_ENTER->GP_IDLE, because not it is possible to call__rcu_sync_enter()andrcu_sync_exit()in any state (except obviously they should be balanced), and they do not block.
...
Quoted 2 lines
If you feel strongly about allowing
rcu_sync_exit()inGP_ENTERstate, could you please tell me your use case? Or am I confused?
See below,
Quoted 11 lines
And I think
__rcu_sync_enter()can have more users. Let's look atfreeze_super(). It callspercpu_down_write()3 times, and waits for 3 GP's sequentally.Quoted 6 lines
Now we can add 3 __rcu_sync_enter's at the start and 3 rcu_sync_exit's at the end (actually we can do better, just to simplify). And again, note that
rcu_sync_exit()will work correctly even if we (say) return-EBUSY, sorcu_sync_waitand/orpercpu_down_write()was not called in between, and in this case we won't block waiting for GP.
Quoted 4 lines
I am not going to claim to understand
freeze_super(), but it does seem to have a fair amount of waiting.But yes, you could put
rcu_sync_enter()
^^^^^^^^^^^^^^
__rcu_sync_enter, see below,
Quoted 3 lines
and
rcu_sync_exit()before and
after a series of write-side enter/exit pairs in order to force things to stay in writer mode, if that is what you are suggesting.
No, no, this is not what I am trying to suggest.
The problem is that freeze_super() takes 3 semaphores for writing in row, this means that it needs to wait for 3 GP's sequentally, and it does this with sb->s_umount held. This is just ugly.
OK, lets suppose it simply does
freeze_super(sb)
{
down_write(&sb->s_umount);
if (NEED_TO_FREEZE) {
percpu_down_write(SEM1);
percpu_down_write(SEM2);
percpu_down_write(SEM3);
}
up_write(&sb->s_umount);
}and every percpu_down_write() waits for GP.
Now, suppose we add the additional enter/exit's:
freeze_super(sb)
{
// this doesn't block
__rcu_sync_enter(SEM3);
__rcu_sync_enter(SEM2);
__rcu_sync_enter(SEM1);
down_write(&sb->s_umount);
if (NEED_TO_FREEZE) {
percpu_down_write(SEM1);
percpu_down_write(SEM2);
percpu_down_write(SEM3);
}
up_write(&sb->s_umount);
rcu_sync_exit(SEM1);
rcu_sync_exit(SEM2);
rcu_sync_exit(SEM3);
}Again, actually we can do better, just to simplify.
Now. the fisrt percpu_down_write(SEM1) can block waiting for GP or not, this depends on how many time it spends in down_write().
But the 2nd and the 3rd percpu_down_write() most likely won't block, so in the likely case freeze_super() will need a single GP pass.
And note that NEED_TO_FREEZE can be false, in this case rcu_sync_exit() will be called in GP_ENTER state.
To some degree, this is like get_state_synchronize_rcu/cond_synchronize_rcu. But obviously percpu_down_write() can not use these helpers, and in this particular case __rcu_sync_enter() is better because it forces the start of GP pass.
-----------------------------------------------------------------------
As for cgroups, we want to switch cgroup_threadgroup_rwsem into the slow mode, at least for now.
We could add the additional hooks/hacks into rcu/sync.c but why? We can do this without any changes outside of cgroup.c right now, just add rcu_sync_enter() into cgroup_init().
But we do not want to add a pointless synchronize_sched() at boot time, __rcu_sync_enter() looks much better.
------------------------------------------------------------------------
And even __cgroup_procs_write() could use __rcu_sync_enter(). But lets ignore this for now.
Oleg.
Re: [PATCH] rcu_sync: simplify the state machine, introduce __rcu_sync_enter()
Quoted 3 lines · Oleg Nesterov
Paul, I had to switch to internal bugzillas after the first email, and now I feel I can't read... I'll try to answer one question right now, tomorrow I'll reread your email, probably I need to answer something else...
I know that feeling of distraction!
Quoted 35 lines · Paul E. McKenney
Quoted 6 lines · Oleg Nesterov
Quoted 4 lines
And, there is another possible transition,
GP_ENTER->GP_IDLE, because not it is possible to call__rcu_sync_enter()andrcu_sync_exit()in any state (except obviously they should be balanced), and they do not block....
Quoted 2 lines
If you feel strongly about allowing
rcu_sync_exit()inGP_ENTERstate, could you please tell me your use case? Or am I confused?See below,
Quoted 11 lines
And I think
__rcu_sync_enter()can have more users. Let's look atfreeze_super(). It callspercpu_down_write()3 times, and waits for 3 GP's sequentally.Quoted 6 lines
Now we can add 3 __rcu_sync_enter's at the start and 3 rcu_sync_exit's at the end (actually we can do better, just to simplify). And again, note that
rcu_sync_exit()will work correctly even if we (say) return-EBUSY, sorcu_sync_waitand/orpercpu_down_write()was not called in between, and in this case we won't block waiting for GP.Quoted 4 lines
I am not going to claim to understand
freeze_super(), but it does seem to have a fair amount of waiting.But yes, you could put
rcu_sync_enter()^^^^^^^^^^^^^^
__rcu_sync_enter, see below,Quoted 3 lines
and
rcu_sync_exit()before and
after a series of write-side enter/exit pairs in order to force things to stay in writer mode, if that is what you are suggesting.
Quoted 5 lines
No, no, this is not what I am trying to suggest.
The problem is that
freeze_super()takes 3 semaphores for writing in row, this means that it needs to wait for 3 GP's sequentally, and it does this withsb->s_umountheld. This is just ugly.
Slow, too. ;-)
Quoted 17 lines
OK, lets suppose it simply does
freeze_super(sb) { down_write(&sb->s_umount); if (NEED_TO_FREEZE) { percpu_down_write(SEM1); percpu_down_write(SEM2); percpu_down_write(SEM3); } up_write(&sb->s_umount); }and every
percpu_down_write()waits for GP.Now, suppose we add the additional enter/exit's:
Quoted 10 lines
freeze_super(sb)
{
// this doesn't block
__rcu_sync_enter(SEM3);
__rcu_sync_enter(SEM2);
__rcu_sync_enter(SEM1);
down_write(&sb->s_umount);
if (NEED_TO_FREEZE) {
percpu_down_write(SEM1);The above waits for the grace period initiated by __rcu_sync_enter(), correct? Presumably "yes", because it will invoke rcu_sync_enter(), which will see the state as GP_ENTER, and will thus wait. Or am I confused?
Quoted 6 lines
percpu_down_write(SEM2); percpu_down_write(SEM3); } up_write(&sb->s_umount); rcu_sync_exit(SEM1);
But your point is that if !NEED_TO_FREEZE, we will get here without waiting for a grace period.
But why aren't the __rcu_sync_enter() and rcu_sync_exit() calls inside the "if" statement?
Quoted 2 lines
rcu_sync_exit(SEM2); rcu_sync_exit(SEM3);
And the reason it is OK to invoke rcu_sync_exit() before doing the percpu_up_write() calls is that those calls will do their own rcu_sync_exit() calls, and the above calls really don't do anything other than decrement the count.
Quoted 5 lines
}Again, actually we can do better, just to simplify.
Quoted 8 lines
Now. the fisrt
percpu_down_write(SEM1)can block waiting for GP or not, this depends on how many time it spends indown_write().But the 2nd and the 3rd
percpu_down_write()most likely won't block, so in the likely casefreeze_super()will need a single GP pass.And note that
NEED_TO_FREEZEcan be false, in this casercu_sync_exit()will be called inGP_ENTERstate.
Understood, the above approach allows the three grace periods to elapse concurrently, at least assuming RCU is busy at the time. (If RCU is not busy, the first one gets its own grace period, and the other two share the next grace period.)
But again why aren't the __rcu_sync_enter() and rcu_sync_exit() calls inside that "if" statement?
That aside, would it make sense to name __rcu_sync_enter() something like rcu_sync_begin_to_enter(), rcu_sync_pre_enter() or some such? Something to make it clear that it just starts the job and that something else is needed to finish it.
Quoted 4 lines
To some degree, this is like get_state_synchronize_rcu/cond_synchronize_rcu. But obviously
percpu_down_write()can not use these helpers, and in this particular case__rcu_sync_enter()is better because it forces the start of GP pass.
OK, I now understand the motivation, thank you for your patience!
Quoted 10 lines
-----------------------------------------------------------------------
As for cgroups, we want to switchcgroup_threadgroup_rwseminto the slow mode, at least for now.We could add the additional hooks/hacks into
rcu/sync.cbut why? We can do this without any changes outside ofcgroup.cright now, just addrcu_sync_enter()intocgroup_init().But we do not want to add a pointless
synchronize_sched()at boot time,__rcu_sync_enter()looks much better.
I do like that approach much better than an rcu_sync_sabotage().
I still have concern with the __rcu_sync_enter() name, and would prefer something that clearly indicates that it starts the rcu_sync() entry job, but does not synchronously complete it.
Quoted 3 lines
------------------------------------------------------------------------
And even__cgroup_procs_write()could use__rcu_sync_enter(). But lets ignore this for now.
And here is an updated state table. I do not yet separately call out __rcu_sync_enter(), though without it the rcu_sync_exit() transition out of state B cannot happen.
Thanx, Paul
------------------------------------------------------------------------
o "count" is the value of the rcu_sync structure's ->gp_count field.
o "state" is the value of the rcu_sync structure's ->gp_state field.
o "CB?" is "yes" if there is an RCU callback pending and "no" otherwise.
| count | state | CB? | next state
---+-------+-----------+-----+------------------------------------
| | | | A | 0 | GP_IDLE | no | rcu_sync_enter() -> B (wait) | | | | rcu_sync_exit() -> illegal | | | | rcu_sync_dtor() -> H | | | | callback -> cannot happen | | | | ---+-------+-----------+-----+------------------------------------ | | | | B | 1+ | GP_ENTER | yes | rcu_sync_enter() -> B (wait) | | | | last rcu_sync_exit() -> J | | | | non-last rcu_sync_exit() -> B | | | | rcu_sync_dtor() -> illegal | | | | callback -> C (ends _enter() wait) | | | | ---+-------+-----------+-----+------------------------------------ | | | | C | 1+ | GP_PASSED | no | rcu_sync_enter() -> C | | | | last rcu_sync_exit() -> E | | | | non-last rcu_sync_exit() -> C | | | | rcu_sync_dtor() -> illegal | | | | callback -> cannot happen | | | | ---+-------+-----------+-----+------------------------------------ | | | | D | 0 | GP_REPLAY | yes | rcu_sync_enter() -> F | | | | rcu_sync_exit() -> illegal | | | | rcu_sync_dtor() -> I (wait ???) | | | | callback -> E | | | | ---+-------+-----------+-----+------------------------------------ | | | | E | 0 | GP_EXIT | yes | rcu_sync_enter() -> G | | | | rcu_sync_exit() -> illegal | | | | rcu_sync_dtor() -> I (wait) | | | | callback -> A | | | | ---+-------+-----------+-----+------------------------------------ | | | | F | 1+ | GP_REPLAY | yes | rcu_sync_enter() -> F | | | | last rcu_sync_exit() -> D | | | | non-last rcu_sync_exit() -> F | | | | rcu_sync_dtor() -> illegal | | | | callback -> C | | | | ---+-------+-----------+-----+------------------------------------ | | | | G | 1+ | GP_EXIT | yes | rcu_sync_enter() -> G | | | | last rcu_sync_exit() -> D | | | | non-last rcu_sync_exit() -> F | | | | rcu_sync_dtor() -> illegal | | | | callback -> C | | | | ---+-------+-----------+-----+------------------------------------ | | | | H | 0 | GP_IDLE | no | rcu_sync_enter() -> illegal | | | | rcu_sync_exit() -> illegal | | | | rcu_sync_dtor() -> illegal | | | | callback -> cannot happen | | | | ---+-------+-----------+-----+------------------------------------ | | | | I | 0 | GP_EXIT | yes | rcu_sync_enter() -> illegal | | | | rcu_sync_exit() -> illegal | | | | rcu_sync_dtor() -> illegal | | | | callback -> H (ends dtor() wait) ---+-------+-----------+-----+------------------------------------ | | | | J | 0 | GP_ENTER | yes | rcu_sync_enter() -> B (wait) | | | | rcu_sync_exit() -> illegal | | | | rcu_sync_dtor() -> illegal | | | | callback -> A | | | |
Assumptions:
o Initial state is A.
o Final state is H.
o There will never be enough unpaired rcu_sync_enter() calls to
overflow ->gp_count.
o All calls to rcu_sync_exit() must pair with a preceding call
to rcu_sync_enter() by that same thread.
o It is illegal to invoke rcu_sync_dtor() until after the caller
has ensured that there will be no future calls to either rcu_sync_enter() or rcu_sync_exit().
o It is illegal to invoke rcu_sync_dtor() while there are any
unpaired calls to rcu_sync_enter().
Re: [PATCH] rcu_sync: simplify the state machine, introduce __rcu_sync_enter()
Quoted 12 lines · Paul E. McKenney
Quoted 9 lines · Oleg Nesterov
Now, suppose we add the additional enter/exit's:
freeze_super(sb) { // this doesn't block __rcu_sync_enter(SEM3); __rcu_sync_enter(SEM2); __rcu_sync_enter(SEM1);
Quoted 7 lines
Quoted 3 lines
down_write(&sb->s_umount); if (NEED_TO_FREEZE) { percpu_down_write(SEM1);The above waits for the grace period initiated by
__rcu_sync_enter(), correct? Presumably "yes", because it will invokercu_sync_enter(), which will see the state asGP_ENTER, and will thus wait.
But if down_write() blocks and/or NEED_TO_FREEZE takes some time it could already see the GP_PASSED state, or at least it can sleep less.
Quoted 5 lines
But your point is that if !NEED_TO_FREEZE, we will get here without waiting for a grace period.
But why aren't the
__rcu_sync_enter()andrcu_sync_exit()calls inside the "if" statement?
Yes, if we do __rcu_sync_enter() inside "if", then rcu_sync_exit() can't hit GP_ENTER.
But why we should disallow this use-case? It does not complicate the code at all.
And see above, we want to initiate the GP "asap", so that we will sleep less later. Although yes, freeze_super() is not the best example. And __cgroup_procs_write() too, but note that cgroup_kn_lock_live() is rather heavy, takes the global locks, and can fail. So (ignoring the fact we are going to switch cgroup_threadgroup_rwsem into the slow mode for now) __rcu_sync_enter() at the start could help to lessen the time percpu_down_write(cgroup_threadgroup_rwsem) sleeps with the cgroup_mutex held.
Quoted 4 lines
That aside, would it make sense to name
__rcu_sync_enter()something likercu_sync_begin_to_enter(),rcu_sync_pre_enter()or some such? Something to make it clear that it just starts the job and that something else is needed to finish it.
Sure. Agreed, will rename.
Quoted 3 lines
And here is an updated state table. I do not yet separately call out
__rcu_sync_enter(), though without it thercu_sync_exit()transition out of state B cannot happen.
Thanks! I'll try to double-check it.
Oleg.
Re: [PATCH] rcu_sync: simplify the state machine, introduce __rcu_sync_enter()
Quoted 27 lines · Oleg Nesterov
Quoted 12 lines · Paul E. McKenney
Quoted 9 lines · Oleg Nesterov
Now, suppose we add the additional enter/exit's:
freeze_super(sb) { // this doesn't block __rcu_sync_enter(SEM3); __rcu_sync_enter(SEM2); __rcu_sync_enter(SEM1);Quoted 7 lines
Quoted 3 lines
down_write(&sb->s_umount); if (NEED_TO_FREEZE) { percpu_down_write(SEM1);The above waits for the grace period initiated by
__rcu_sync_enter(), correct? Presumably "yes", because it will invokercu_sync_enter(), which will see the state asGP_ENTER, and will thus wait.But if
down_write()blocks and/orNEED_TO_FREEZEtakes some time it could already see theGP_PASSEDstate, or at least it can sleep less.Quoted 3 lines
But your point is that if !NEED_TO_FREEZE, we will get here without waiting for a grace period.
Quoted 8 lines
Quoted 2 lines
But why aren't the
__rcu_sync_enter()andrcu_sync_exit()calls inside the "if" statement?Yes, if we do
__rcu_sync_enter()inside "if", thenrcu_sync_exit()can't hitGP_ENTER.But why we should disallow this use-case? It does not complicate the code at all.
I do agree that it doesn't complicate the current implementation. But it relies on a global lock, so I am not at all confident that this implementation is the final word. I therefore tend to try to avoid supporting more than is required.
And speaking of global locks, failing to discourage the pattern above means that the code is unnecessarily acquiring three global locks, which doesn't seem like a good thing to me.
Quoted 8 lines
And see above, we want to initiate the GP "asap", so that we will sleep less later. Although yes,
freeze_super()is not the best example. And__cgroup_procs_write()too, but note thatcgroup_kn_lock_live()is rather heavy, takes the global locks, and can fail. So (ignoring the fact we are going to switchcgroup_threadgroup_rwseminto the slow mode for now)__rcu_sync_enter()at the start could help to lessen the timepercpu_down_write(cgroup_threadgroup_rwsem)sleeps with thecgroup_mutexheld.
I agree that there are use cases for beginning-of-time __rcu_sync_enter() or whatever we end up naming it.
Quoted 6 lines
Quoted 4 lines
That aside, would it make sense to name
__rcu_sync_enter()something likercu_sync_begin_to_enter(),rcu_sync_pre_enter()or some such? Something to make it clear that it just starts the job and that something else is needed to finish it.Sure. Agreed, will rename.
Thank you!
Quoted 5 lines
Quoted 3 lines
And here is an updated state table. I do not yet separately call out
__rcu_sync_enter(), though without it thercu_sync_exit()transition out of state B cannot happen.Thanks! I'll try to double-check it.
And thank you again!
Thanx, Paul
Re: [PATCH] rcu_sync: simplify the state machine, introduce __rcu_sync_enter()
Paul, sorry for delay.
Quoted 35 lines · Paul E. McKenney
Quoted 33 lines · Oleg Nesterov
Quoted 12 lines · Paul E. McKenney
Quoted 9 lines · Oleg Nesterov
Now, suppose we add the additional enter/exit's:
freeze_super(sb) { // this doesn't block __rcu_sync_enter(SEM3); __rcu_sync_enter(SEM2); __rcu_sync_enter(SEM1);Quoted 7 lines
Quoted 3 lines
down_write(&sb->s_umount); if (NEED_TO_FREEZE) { percpu_down_write(SEM1);The above waits for the grace period initiated by
__rcu_sync_enter(), correct? Presumably "yes", because it will invokercu_sync_enter(), which will see the state asGP_ENTER, and will thus wait.But if
down_write()blocks and/orNEED_TO_FREEZEtakes some time it could already see theGP_PASSEDstate, or at least it can sleep less.Quoted 5 lines
But your point is that if !NEED_TO_FREEZE, we will get here without waiting for a grace period.
But why aren't the
__rcu_sync_enter()andrcu_sync_exit()calls inside the "if" statement?Yes, if we do
__rcu_sync_enter()inside "if", thenrcu_sync_exit()can't hitGP_ENTER.
Quoted 6 lines
Quoted 2 lines
But why we should disallow this use-case? It does not complicate the code at all.
I do agree that it doesn't complicate the current implementation. But it relies on a global lock, so I am not at all confident that this implementation is the final word.
Hmm. which global lock? Or did you mean freeze_super(), not rcu_sync?
Quoted 3 lines
And speaking of global locks, failing to discourage the pattern above means that the code is unnecessarily acquiring three global locks, which doesn't seem like a good thing to me.
Well, I do not agree, but this wasn't written by me. Just in case, all these locks above are not really global, they are per-sb, but this is minor.
And the patches which changed sb->s_writers to use percpu_rw_semaphore/rcu_sync didn't change this logic.
Except the old implementation was buggy, and the readers were slower than now.
Quoted 2 lines
I agree that there are use cases for beginning-of-time
__rcu_sync_enter()or whatever we end up naming it.
OK, at least iiuc you agree that cgroup_init() can use __rcu_sync_enter(). As for other potential use-cases, we will disccuss this later. I will have to CC you anyway ;)
So I'll send v2 with renames after I test it. Thanks again.
Oleg.
Re: [PATCH] rcu_sync: simplify the state machine, introduce __rcu_sync_enter()
Quoted 59 lines · Oleg Nesterov
Paul, sorry for delay.
Quoted 35 lines · Paul E. McKenney
Quoted 33 lines · Oleg Nesterov
Quoted 12 lines · Paul E. McKenney
Quoted 9 lines · Oleg Nesterov
Now, suppose we add the additional enter/exit's:
freeze_super(sb) { // this doesn't block __rcu_sync_enter(SEM3); __rcu_sync_enter(SEM2); __rcu_sync_enter(SEM1);Quoted 7 lines
Quoted 3 lines
down_write(&sb->s_umount); if (NEED_TO_FREEZE) { percpu_down_write(SEM1);The above waits for the grace period initiated by
__rcu_sync_enter(), correct? Presumably "yes", because it will invokercu_sync_enter(), which will see the state asGP_ENTER, and will thus wait.But if
down_write()blocks and/orNEED_TO_FREEZEtakes some time it could already see theGP_PASSEDstate, or at least it can sleep less.Quoted 5 lines
But your point is that if !NEED_TO_FREEZE, we will get here without waiting for a grace period.
But why aren't the
__rcu_sync_enter()andrcu_sync_exit()calls inside the "if" statement?Yes, if we do
__rcu_sync_enter()inside "if", thenrcu_sync_exit()can't hitGP_ENTER.Quoted 6 lines
Quoted 2 lines
But why we should disallow this use-case? It does not complicate the code at all.
I do agree that it doesn't complicate the current implementation. But it relies on a global lock, so I am not at all confident that this implementation is the final word.
Hmm. which global lock? Or did you mean
freeze_super(), notrcu_sync?Quoted 3 lines
And speaking of global locks, failing to discourage the pattern above means that the code is unnecessarily acquiring three global locks, which doesn't seem like a good thing to me.
Well, I do not agree, but this wasn't written by me. Just in case, all these locks above are not really global, they are per-sb, but this is minor.
And the patches which changed
sb->s_writersto use percpu_rw_semaphore/rcu_sync didn't change this logic.Except the old implementation was buggy, and the readers were slower than now.
Quoted 8 lines
Quoted 2 lines
I agree that there are use cases for beginning-of-time
__rcu_sync_enter()or whatever we end up naming it.OK, at least iiuc you agree that
cgroup_init()can use__rcu_sync_enter(). As for other potential use-cases, we will disccuss this later. I will have to CC you anyway ;)So I'll send v2 with renames after I test it. Thanks again.
Can you also make clear which patches of PeterZ's I should be adding as well for testing? I've lost the plot as to what goes with what..
thanks
-john
Re: [PATCH] rcu_sync: simplify the state machine, introduce __rcu_sync_enter()
Quoted 3 lines · John Stultz
Can you also make clear which patches of PeterZ's I should be adding as well for testing? I've lost the plot as to what goes with what..
Well, my understanding is that Peter is going to send v2 with some minor changes, I am going to send this patch on top of his series.
In particular 2/2 should set GP_PASSED rather than !GP_IDLE. It would be nice to also rename rcu_sync_sabotage() to rcu_sync_enter_start(), this will save another change in cgroup.c, but this is minor.
Oleg.
Peter Zijlstra
maintainerRe: [PATCH] rcu_sync: simplify the state machine, introduce __rcu_sync_enter()
Quoted 7 lines · Oleg Nesterov
Quoted 3 lines · John Stultz
Can you also make clear which patches of PeterZ's I should be adding as well for testing? I've lost the plot as to what goes with what..
Well, my understanding is that Peter is going to send v2 with some minor changes, I am going to send this patch on top of his series.
OK, back from holidays and I finally got around to reading this again.
So let me send out the rwsem optmization patch v2 in a new thread and lets take it from there.
Re: [PATCH] rcu_sync: simplify the state machine, introduce __rcu_sync_enter()
In reply to Oleg NesterovQuoted 38 lines · Oleg Nesterov
Paul, sorry for delay.
Quoted 35 lines · Paul E. McKenney
Quoted 33 lines · Oleg Nesterov
Quoted 12 lines · Paul E. McKenney
Quoted 9 lines · Oleg Nesterov
Now, suppose we add the additional enter/exit's:
freeze_super(sb) { // this doesn't block __rcu_sync_enter(SEM3); __rcu_sync_enter(SEM2); __rcu_sync_enter(SEM1);Quoted 7 lines
Quoted 3 lines
down_write(&sb->s_umount); if (NEED_TO_FREEZE) { percpu_down_write(SEM1);The above waits for the grace period initiated by
__rcu_sync_enter(), correct? Presumably "yes", because it will invokercu_sync_enter(), which will see the state asGP_ENTER, and will thus wait.But if
down_write()blocks and/orNEED_TO_FREEZEtakes some time it could already see theGP_PASSEDstate, or at least it can sleep less.Quoted 5 lines
But your point is that if !NEED_TO_FREEZE, we will get here without waiting for a grace period.
But why aren't the
__rcu_sync_enter()andrcu_sync_exit()calls inside the "if" statement?Yes, if we do
__rcu_sync_enter()inside "if", thenrcu_sync_exit()can't hitGP_ENTER.
Quoted 8 lines
Quoted 6 lines
Quoted 2 lines
But why we should disallow this use-case? It does not complicate the code at all.
I do agree that it doesn't complicate the current implementation. But it relies on a global lock, so I am not at all confident that this implementation is the final word.
Hmm. which global lock? Or did you mean
freeze_super(), notrcu_sync?
OK, you are right, they are per-superblock locks rather than being global locks. Still, given that workloads that hammer a single filesystem hard are quite common, it might still eventually be of some concern.
Quoted 6 lines
Quoted 3 lines
And speaking of global locks, failing to discourage the pattern above means that the code is unnecessarily acquiring three global locks, which doesn't seem like a good thing to me.
Well, I do not agree, but this wasn't written by me. Just in case, all these locks above are not really global, they are per-sb, but this is minor.
Agreed.
Quoted 5 lines
And the patches which changed
sb->s_writersto use percpu_rw_semaphore/rcu_sync didn't change this logic.Except the old implementation was buggy, and the readers were slower than now.
Quoted 8 lines
Quoted 2 lines
I agree that there are use cases for beginning-of-time
__rcu_sync_enter()or whatever we end up naming it.OK, at least iiuc you agree that
cgroup_init()can use__rcu_sync_enter(). As for other potential use-cases, we will disccuss this later. I will have to CC you anyway ;)So I'll send v2 with renames after I test it. Thanks again.
Sounds good!
Thanx, Paul
Oleg Nesterov
Reviewed-byRe: [PATCH 2/2] locking/percpu-rwsem: Introduce bias knob
In reply to Peter Zijlstrakernel/rcu/sync.c void rcu_sync_init(struct rcu_sync *rsp, · line 98
Quoted 6 lines · Peter Zijlstra
kernel/rcu/sync.c +5
-0+void rcu_sync_sabotage (struct rcu_sync *rsp) +{ + rsp->gp_count++; + rsp->gp_state = !GP_IDLE ; +}
Ah, I didn't notice this !GP_IDLE...
Please use GP_PASSED, this is what this actually means. And note the wait_event(GP_PASSED) in rcu_sync_enter().
Otherwise