From 83aa524becc6db7a987a498d5c747cf0a21d5d1a Mon Sep 17 00:00:00 2001 From: Jens Maurer Date: Fri, 23 Jun 2023 01:54:40 +0200 Subject: [PATCH 1/4] P2545R4 Read-Copy Update (RCU) --- source/lib-intro.tex | 1 + source/support.tex | 1 + source/threads.tex | 359 ++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 360 insertions(+), 1 deletion(-) diff --git a/source/lib-intro.tex b/source/lib-intro.tex index 944afab25e..27004e1e41 100644 --- a/source/lib-intro.tex +++ b/source/lib-intro.tex @@ -1114,6 +1114,7 @@ \tcode{} \\ \tcode{} \\ \tcode{} \\ +\tcode{} \\ \tcode{} \\ \tcode{} \\ \tcode{} \\ diff --git a/source/support.tex b/source/support.tex index 7628f2861c..9fa6a3c561 100644 --- a/source/support.tex +++ b/source/support.tex @@ -706,6 +706,7 @@ #define @\defnlibxname{cpp_lib_ranges_to_container}@ 202202L // also in \libheader{ranges} #define @\defnlibxname{cpp_lib_ranges_zip}@ 202110L // also in \libheader{ranges}, \libheader{tuple}, \libheader{utility} #define @\defnlibxname{cpp_lib_raw_memory_algorithms}@ 201606L // also in \libheader{memory} +#define @\defnlibxname{cpp_lib_rcu}@ 202306L // also in \libheader{rcu} #define @\defnlibxname{cpp_lib_reference_from_temporary}@ 202202L // also in \libheader{type_traits} #define @\defnlibxname{cpp_lib_remove_cvref}@ 201711L // also in \libheader{type_traits} #define @\defnlibxname{cpp_lib_result_of_sfinae}@ 201210L // also in \libheader{functional}, \libheader{type_traits} diff --git a/source/threads.tex b/source/threads.tex index 527bf57e7a..2e28982f72 100644 --- a/source/threads.tex +++ b/source/threads.tex @@ -20,7 +20,8 @@ \ref{thread.condition}& Condition variables & \tcode{} \\ \rowsep \ref{thread.sema} & Semaphores & \tcode{} \\ \rowsep \ref{thread.coord} & Coordination types & \tcode{} \tcode{} \\ \rowsep -\ref{futures} & Futures & \tcode{} \\ +\ref{futures} & Futures & \tcode{} \\ \rowsep +\ref{saferecl} & Safe reclamation & \tcode{} \\ \end{libsumtab} \rSec1[thread.req]{Requirements} @@ -11661,3 +11662,359 @@ \effects As if by \tcode{x.swap(y)}. \end{itemdescr} + +\rSec1[saferecl]{Safe reclamation} + +\rSec2[saferecl.general]{General} + +\pnum +Subclause \ref{saferecl} contains safe-reclamation techniques, which are most +frequently used to straightforwardly resolve access-deletion races. + +\rSec2[saferecl.rcu]{Read-copy update (RCU)} + +\rSec3[saferecl.rcu.general]{General} + +\pnum +RCU is a synchronization mechanism +that can be used for linked data structures +that are frequently read, but seldom updated. +RCU does not provide mutual exclusion, +but instead allows the user to schedule specified actions +such as deletion at some later time. + +\pnum +A class type \tcode{T} is \defn{rcu-protectable} +if it has exactly one base class of type \tcode{rcu_obj_base} +for some \tcode{D}, and that base is public and non-virtual, and +it has no base classes of type \tcode{rcu_obj_base} +for any other combination \tcode{X}, \tcode{Y}. +An object is rcu-protectable if it is of rcu-protectable type. + +\pnum +An invocation of \tcode{unlock} $U$ on an \tcode{rcu_domain dom} +corresponds to an invocation of \tcode{lock} $L$ on \tcode{dom} +if $L$ is sequenced before $U$ and either +\begin{itemize} +\item +no other invocation of \tcode{lock} on \tcode{dom} +is sequenced after $L$ and before $U$, or +\item +every invocation of \tcode{unlock} $U2$ on \tcode{dom} +such that $L$ is sequenced before $U2$ and $U2$ is sequenced before $U$ +corresponds to an invocation of \tcode{lock} $L2$ on \tcode{dom} +such that $L$ is sequenced before $L2$ and $L2$ is sequenced before $U2$. +\end{itemize} +\begin{note} +This pairs nested locks and unlocks on a given domain in each thread. +\end{note} + +\pnum +A \defn{region of RCU protection} on a domain \tcode{dom} +starts with a \tcode{lock} $L$ on \tcode{dom} and +ends with its corresponding \tcode{unlock} $U$. + +\pnum +Given a region of RCU protection $R$ on a domain \tcode{dom} and +given an evaluation $E$ that scheduled another evaluation $F$ in \tcode{dom}, +if $E$ does not strongly happen before the start of $R$, +the end of $R$ strongly happens before evaluating $F$. + +\pnum +The evaluation of a scheduled evaluation is potentially concurrent with +any other scheduled evaluation. +Each scheduled evaluation is evaluated at most once. + +\rSec3[rcu.syn]{Header \tcode{} synopsis} + +\indexheader{rcu} +\begin{codeblock} +namespace std { + // \ref{saferecl.rcu.base}, class template \tcode{rcu_obj_base} + template> class rcu_obj_base; + + // \ref{saferecl.rcu.domain}, class \tcode{rcu_domain} + class rcu_domain; + rcu_domain& rcu_default_domain() noexcept; + void rcu_synchronize(rcu_domain& dom = rcu_default_domain()) noexcept; + void rcu_barrier(rcu_domain& dom = rcu_default_domain()) noexcept; + template> + void rcu_retire(T* p, D d = D(), rcu_domain& dom = rcu_default_domain()); +} +\end{codeblock} + +\rSec3[saferecl.rcu.base]{Class template \tcode{rcu_obj_base}} + +\pnum +Objects of type \tcode{T} to be protected by RCU inherit from +a specialization of \tcode{rcu_obj_base}. + +\begin{codeblock} +namespace std { + template> + class rcu_obj_base { + public: + void retire(D d = D(), rcu_domain& dom = rcu_default_domain()) noexcept; + protected: + rcu_obj_base() = default; + rcu_obj_base(const rcu_obj_base&) = default; + rcu_obj_base(rcu_obj_base&&) = default; + rcu_obj_base& operator=(const rcu_obj_base&) = default; + rcu_obj_base& operator=(rcu_obj_base&&) = default; + ~rcu_obj_base() = default; + private: + D @\exposid{deleter}@; // \expos + }; +} +\end{codeblock} + +\pnum +The behavior of a program that adds specializations for \tcode{rcu_obj_base} +is undefined. + +\pnum +\tcode{T} may be an incomplete type. +It shall be complete before any member of the resulting specialization of +\tcode{rcu_obj_base} is referenced. + +\pnum +\tcode{D} shall be a +function object type\iref{function.objects} for which, +given a value \tcode{d} of type \tcode{D} and +a value \tcode{ptr} of type \tcode{T*}, +the expression \tcode{d(ptr)} is valid. + +\pnum +\tcode{D} shall meet the requirements for +\oldconcept{DefaultConstructible} and \oldconcept{MoveAssignable}. + +\pnum +If \tcode{D} is trivially copyable, +all specializations of \tcode{rcu_obj_base} are trivially copyable. + +\begin{itemdecl} +void retire(D d = D(), rcu_domain& dom = rcu_default_domain()) noexcept; +\end{itemdecl} + +\begin{itemdescr} +\pnum +\mandates +\tcode{T} is an rcu-protectable type. + +\pnum +\expects +\tcode{*this} is +a base class subobject of an object \tcode{x} of type \tcode{T}. +The member function \tcode{rcu_obj_base::retire} +was not invoked on \tcode{x} before. +The assignment to \exposid{deleter} does not exit via an exception. + +\pnum +\effects +Evaluates \tcode{\exposid{deleter} = std::move(d)} and +schedules the evaluation of +the expression \tcode{\exposid{deleter}(\newline addressof(x))} +in the domain \tcode{dom}; +the behavior is undefined if that evaluation exits via an exception. +May invoke scheduled evaluations in \tcode{dom}. + +\begin{note} +If such evaluations acquire resources held across any invocation of +\tcode{retire} on \tcode{dom}, deadlock can occur. +\end{note} +\end{itemdescr} + +\rSec3[saferecl.rcu.domain]{Class \tcode{rcu_domain}} + +\rSec4[saferecl.rcu.domain.general]{General} + +\pnum +This class meets the requirements of +\oldconcept{Lockable}\iref{thread.req.lockable.req} and +provides regions of RCU protection. + +\begin{example} +\begin{codeblock} +std::scoped_lock rlock(rcu_default_domain()); +\end{codeblock} +\end{example} + +\begin{codeblock} +class rcu_domain { +public: + rcu_domain(const rcu_domain&) = delete; + rcu_domain& operator=(const rcu_domain&) = delete; + + void lock() noexcept; + bool try_lock() noexcept; + void unlock() noexcept; +}; + +rcu_domain& rcu_default_domain() noexcept; +\end{codeblock} + +\pnum +The functions \tcode{lock} and \tcode{unlock} establish +(possibly nested) regions of RCU protection. + +\rSec4[saferecl.rcu.domain.members]{Member functions} + +\indexlibrarymember{lock}{rcu_domain}% +\begin{itemdecl} +void lock() noexcept; +\end{itemdecl} + +\begin{itemdescr} +\pnum +\effects +Opens a region of RCU protection. + +\pnum +\remarks +Calls to \tcode{lock} +do not introduce a data race\iref{intro.races} involving \tcode{*this}. +\end{itemdescr} + +\indexlibrarymember{try_lock}{rcu_domain}% +\begin{itemdecl} +bool try_lock() noexcept; +\end{itemdecl} + +\begin{itemdescr} +\pnum +\effects +Equivalent to \tcode{lock()}. + +\pnum +\returns +\tcode{true}. +\end{itemdescr} + +\indexlibrarymember{unlock}{rcu_domain}% +\begin{itemdecl} +void unlock() noexcept; +\end{itemdecl} + +\begin{itemdescr} +\pnum +\expects +A call to \tcode{lock} +that opened an unclosed region of RCU protection +is sequenced before the call to \tcode{unlock}. + +\pnum +\effects +Closes the unclosed region of RCU protection +that was most recently opened. +May invoke scheduled evaluations in \tcode{*this}. + +\pnum +\begin{note} +If such evaluations acquire resources +held across any invocation of \tcode{unlock} on \tcode{*this}, +deadlock can occur. +\end{note} +Calls to \tcode{unlock} do not introduce a data race involving \tcode{*this}. +\begin{note} +Evaluation of scheduled evaluations can still cause a data race. +\end{note} +\end{itemdescr} + +\rSec4[saferecl.rcu.domain.func]{Non-member functions} + +\indexlibraryglobal{rcu_default_domain}% +\begin{itemdecl} +rcu_domain& rcu_default_domain() noexcept; +\end{itemdecl} + +\begin{itemdescr} +\pnum +\returns +A reference to a static-duration object of type \tcode{rcu_domain}. +A reference to the same object is returned every time this function is called. +\end{itemdescr} + +\indexlibraryglobal{rcu_synchronize}% +\begin{itemdecl} +void rcu_synchronize(rcu_domain& dom = rcu_default_domain()) noexcept; +\end{itemdecl} + +\begin{itemdescr} +\pnum +\effects +If the call to \tcode{rcu_synchronize} does not strongly happen before +the lock opening an RCU protection region \tcode{R} on \tcode{dom}, +blocks until the \tcode{unlock} closing \tcode{R} happens. + +\pnum +\sync +The \tcode{unlock} closing \tcode{R} +strongly happens before the return from \tcode{rcu_synchronize}. +\end{itemdescr} + +\indexlibraryglobal{rcu_barrier}% +\begin{itemdecl} +void rcu_barrier(rcu_domain& dom = rcu_default_domain()) noexcept; +\end{itemdecl} + +\begin{itemdescr} +\pnum +\effects +May evaluate any scheduled evaluations in \tcode{dom}. +For any evaluation that happens before the call to \tcode{rcu_barrier} and +that schedules an evaluation $E$ in \tcode{dom}, +blocks until $E$ has been evaluated. + +\pnum +\sync +The evaluation of any such $E$ +strongly happens before the return from \tcode{rcu_barrier}. + +\begin{note} +A call to \tcode{rcu_barrier} does not imply +a call to \tcode{rcu_synchronize} and vice versa. +\end{note} +\end{itemdescr} + +\indexlibraryglobal{rcu_retire}% +\begin{itemdecl} +template> +void rcu_retire(T* p, D d = D(), rcu_domain& dom = rcu_default_domain()); +\end{itemdecl} + +\begin{itemdescr} +\pnum +\mandates +\tcode{is_move_constructible_v} is \tcode{true} and +the expression \tcode{d(p)} is well-formed. + +\pnum +\expects +\tcode{D} meets the \oldconcept{MoveConstructible} and +\oldconcept{Destructible} requirements. + +\pnum +\effects +May allocate memory. +It is unspecified whether the memory allocation +is performed by invoking \tcode{\keyword{operator} \keyword{new}}. +Initializes an object \tcode{d1} of type \tcode{D} from \tcode{std::move(d)}. +Schedules the evaluation of \tcode{d1(p)} in the domain \tcode{dom}; +the behavior is undefined if that evaluation exits via an exception. +May invoke scheduled evaluations in \tcode{dom}. +\begin{note} +If \tcode{rcu_retire} exits via an exception, no evaluation +is scheduled. +\end{note} + +\pnum +\throws +\tcode{bad_alloc} or any exception thrown by the initialization of \tcode{d1}. + +\pnum +\begin{note} +If scheduled evaluations acquire resources +held across any invocation of \tcode{rcu_retire} on \tcode{dom}, +deadlock can occur. +\end{note} +\end{itemdescr} From 3608a50e381f85ae727c90752608e3789b6d969c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20K=C3=B6ppe?= Date: Tue, 18 Jul 2023 23:36:45 +0100 Subject: [PATCH 2/4] [saferecl.rcu.base] Fix description of "specialization" --- source/threads.tex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/threads.tex b/source/threads.tex index 2e28982f72..a4927308f5 100644 --- a/source/threads.tex +++ b/source/threads.tex @@ -11747,7 +11747,7 @@ \pnum Objects of type \tcode{T} to be protected by RCU inherit from -a specialization of \tcode{rcu_obj_base}. +a specialization \tcode{rcu_obj_base} for some \tcode{D}. \begin{codeblock} namespace std { From 8d787a2a2a62e4ebc818ec82d649240610630fa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20K=C3=B6ppe?= Date: Thu, 20 Jul 2023 00:14:03 +0100 Subject: [PATCH 3/4] [saferecl.rcu.domain] Minor reogranization In [.general, move all paragraphs below the class synopsis, add "namespace std" to the class synopsis, and remove repeated declaration of "rcu_default_domain". Add a cross reference to [.func] to the header synopsis. --- source/threads.tex | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/source/threads.tex b/source/threads.tex index a4927308f5..b5d57ca302 100644 --- a/source/threads.tex +++ b/source/threads.tex @@ -11735,6 +11735,8 @@ // \ref{saferecl.rcu.domain}, class \tcode{rcu_domain} class rcu_domain; + + // \ref{saferecl.rcu.domain.func} non-member functions rcu_domain& rcu_default_domain() noexcept; void rcu_synchronize(rcu_domain& dom = rcu_default_domain()) noexcept; void rcu_barrier(rcu_domain& dom = rcu_default_domain()) noexcept; @@ -11828,31 +11830,30 @@ \rSec4[saferecl.rcu.domain.general]{General} +\begin{codeblock} +namespace std { + class rcu_domain { + public: + rcu_domain(const rcu_domain&) = delete; + rcu_domain& operator=(const rcu_domain&) = delete; + + void lock() noexcept; + bool try_lock() noexcept; + void unlock() noexcept; + }; +} +\end{codeblock} + \pnum This class meets the requirements of \oldconcept{Lockable}\iref{thread.req.lockable.req} and provides regions of RCU protection. - \begin{example} \begin{codeblock} std::scoped_lock rlock(rcu_default_domain()); \end{codeblock} \end{example} -\begin{codeblock} -class rcu_domain { -public: - rcu_domain(const rcu_domain&) = delete; - rcu_domain& operator=(const rcu_domain&) = delete; - - void lock() noexcept; - bool try_lock() noexcept; - void unlock() noexcept; -}; - -rcu_domain& rcu_default_domain() noexcept; -\end{codeblock} - \pnum The functions \tcode{lock} and \tcode{unlock} establish (possibly nested) regions of RCU protection. From 81111d6259affc21afc2b65d3a9e028cb9d80604 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20K=C3=B6ppe?= Date: Thu, 20 Jul 2023 00:22:53 +0100 Subject: [PATCH 4/4] [saferecl.rcu.domain.members] Add missing Remarks: element/paragraph --- source/threads.tex | 3 +++ 1 file changed, 3 insertions(+) diff --git a/source/threads.tex b/source/threads.tex index b5d57ca302..dad6e316ff 100644 --- a/source/threads.tex +++ b/source/threads.tex @@ -11915,6 +11915,9 @@ held across any invocation of \tcode{unlock} on \tcode{*this}, deadlock can occur. \end{note} + +\pnum +\remarks Calls to \tcode{unlock} do not introduce a data race involving \tcode{*this}. \begin{note} Evaluation of scheduled evaluations can still cause a data race.