• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Implementation of safe memory reclamation scheme using
3  * quiescent states.
4  *
5  * This is derived from the "GUS" safe memory reclamation technique
6  * in FreeBSD written by Jeffrey Roberson. It is heavily modified. Any bugs
7  * in this code are likely due to the modifications.
8  *
9  * The original copyright is preserved below.
10  *
11  * Copyright (c) 2019,2020 Jeffrey Roberson <jeff@FreeBSD.org>
12  *
13  * Redistribution and use in source and binary forms, with or without
14  * modification, are permitted provided that the following conditions
15  * are met:
16  * 1. Redistributions of source code must retain the above copyright
17  *    notice unmodified, this list of conditions, and the following
18  *    disclaimer.
19  * 2. Redistributions in binary form must reproduce the above copyright
20  *    notice, this list of conditions and the following disclaimer in the
21  *    documentation and/or other materials provided with the distribution.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
24  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
25  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
26  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
27  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
28  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
29  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
30  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
31  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
32  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33  */
34 #include "Python.h"
35 #include "pycore_initconfig.h"      // _PyStatus_NO_MEMORY()
36 #include "pycore_lock.h"            // PyMutex_Lock()
37 #include "pycore_qsbr.h"
38 #include "pycore_pystate.h"         // _PyThreadState_GET()
39 
40 
41 // Starting size of the array of qsbr thread states
42 #define MIN_ARRAY_SIZE 8
43 
44 // For _Py_qsbr_deferred_advance(): the number of deferrals before advancing
45 // the write sequence.
46 #define QSBR_DEFERRED_LIMIT 10
47 
48 // Allocate a QSBR thread state from the freelist
49 static struct _qsbr_thread_state *
qsbr_allocate(struct _qsbr_shared * shared)50 qsbr_allocate(struct _qsbr_shared *shared)
51 {
52     struct _qsbr_thread_state *qsbr = shared->freelist;
53     if (qsbr == NULL) {
54         return NULL;
55     }
56     shared->freelist = qsbr->freelist_next;
57     qsbr->freelist_next = NULL;
58     qsbr->shared = shared;
59     qsbr->allocated = true;
60     return qsbr;
61 }
62 
63 // Initialize (or reintialize) the freelist of QSBR thread states
64 static void
initialize_new_array(struct _qsbr_shared * shared)65 initialize_new_array(struct _qsbr_shared *shared)
66 {
67     for (Py_ssize_t i = 0; i != shared->size; i++) {
68         struct _qsbr_thread_state *qsbr = &shared->array[i].qsbr;
69         if (qsbr->tstate != NULL) {
70             // Update the thread state pointer to its QSBR state
71             _PyThreadStateImpl *tstate = (_PyThreadStateImpl *)qsbr->tstate;
72             tstate->qsbr = qsbr;
73         }
74         if (!qsbr->allocated) {
75             // Push to freelist
76             qsbr->freelist_next = shared->freelist;
77             shared->freelist = qsbr;
78         }
79     }
80 }
81 
82 // Grow the array of QSBR thread states. Returns 0 on success, -1 on failure.
83 static int
grow_thread_array(struct _qsbr_shared * shared)84 grow_thread_array(struct _qsbr_shared *shared)
85 {
86     Py_ssize_t new_size = shared->size * 2;
87     if (new_size < MIN_ARRAY_SIZE) {
88         new_size = MIN_ARRAY_SIZE;
89     }
90 
91     struct _qsbr_pad *array = PyMem_RawCalloc(new_size, sizeof(*array));
92     if (array == NULL) {
93         return -1;
94     }
95 
96     struct _qsbr_pad *old = shared->array;
97     if (old != NULL) {
98         memcpy(array, shared->array, shared->size * sizeof(*array));
99     }
100 
101     shared->array = array;
102     shared->size = new_size;
103     shared->freelist = NULL;
104     initialize_new_array(shared);
105 
106     PyMem_RawFree(old);
107     return 0;
108 }
109 
110 uint64_t
_Py_qsbr_advance(struct _qsbr_shared * shared)111 _Py_qsbr_advance(struct _qsbr_shared *shared)
112 {
113     // NOTE: with 64-bit sequence numbers, we don't have to worry too much
114     // about the wr_seq getting too far ahead of rd_seq, but if we ever use
115     // 32-bit sequence numbers, we'll need to be more careful.
116     return _Py_atomic_add_uint64(&shared->wr_seq, QSBR_INCR) + QSBR_INCR;
117 }
118 
119 uint64_t
_Py_qsbr_deferred_advance(struct _qsbr_thread_state * qsbr)120 _Py_qsbr_deferred_advance(struct _qsbr_thread_state *qsbr)
121 {
122     if (++qsbr->deferrals < QSBR_DEFERRED_LIMIT) {
123         return _Py_qsbr_shared_current(qsbr->shared) + QSBR_INCR;
124     }
125     qsbr->deferrals = 0;
126     return _Py_qsbr_advance(qsbr->shared);
127 }
128 
129 static uint64_t
qsbr_poll_scan(struct _qsbr_shared * shared)130 qsbr_poll_scan(struct _qsbr_shared *shared)
131 {
132     // Synchronize with store in _Py_qsbr_attach(). We need to ensure that
133     // the reads from each thread's sequence number are not reordered to see
134     // earlier "offline" states.
135     _Py_atomic_fence_seq_cst();
136 
137     // Compute the minimum sequence number of all attached threads
138     uint64_t min_seq = _Py_atomic_load_uint64(&shared->wr_seq);
139     struct _qsbr_pad *array = shared->array;
140     for (Py_ssize_t i = 0, size = shared->size; i != size; i++) {
141         struct _qsbr_thread_state *qsbr = &array[i].qsbr;
142 
143         uint64_t seq = _Py_atomic_load_uint64(&qsbr->seq);
144         if (seq != QSBR_OFFLINE && QSBR_LT(seq, min_seq)) {
145             min_seq = seq;
146         }
147     }
148 
149     // Update the shared read sequence
150     uint64_t rd_seq = _Py_atomic_load_uint64(&shared->rd_seq);
151     if (QSBR_LT(rd_seq, min_seq)) {
152         // It's okay if the compare-exchange failed: another thread updated it
153         (void)_Py_atomic_compare_exchange_uint64(&shared->rd_seq, &rd_seq, min_seq);
154         rd_seq = min_seq;
155     }
156 
157     return rd_seq;
158 }
159 
160 bool
_Py_qsbr_poll(struct _qsbr_thread_state * qsbr,uint64_t goal)161 _Py_qsbr_poll(struct _qsbr_thread_state *qsbr, uint64_t goal)
162 {
163     assert(_Py_atomic_load_int_relaxed(&_PyThreadState_GET()->state) == _Py_THREAD_ATTACHED);
164 
165     if (_Py_qbsr_goal_reached(qsbr, goal)) {
166         return true;
167     }
168 
169     uint64_t rd_seq = qsbr_poll_scan(qsbr->shared);
170     return QSBR_LEQ(goal, rd_seq);
171 }
172 
173 void
_Py_qsbr_attach(struct _qsbr_thread_state * qsbr)174 _Py_qsbr_attach(struct _qsbr_thread_state *qsbr)
175 {
176     assert(qsbr->seq == 0 && "already attached");
177 
178     uint64_t seq = _Py_qsbr_shared_current(qsbr->shared);
179     _Py_atomic_store_uint64(&qsbr->seq, seq);  // needs seq_cst
180 }
181 
182 void
_Py_qsbr_detach(struct _qsbr_thread_state * qsbr)183 _Py_qsbr_detach(struct _qsbr_thread_state *qsbr)
184 {
185     assert(qsbr->seq != 0 && "already detached");
186 
187     _Py_atomic_store_uint64_release(&qsbr->seq, QSBR_OFFLINE);
188 }
189 
190 Py_ssize_t
_Py_qsbr_reserve(PyInterpreterState * interp)191 _Py_qsbr_reserve(PyInterpreterState *interp)
192 {
193     struct _qsbr_shared *shared = &interp->qsbr;
194 
195     PyMutex_Lock(&shared->mutex);
196     // Try allocating from our internal freelist
197     struct _qsbr_thread_state *qsbr = qsbr_allocate(shared);
198 
199     // If there are no free entries, we pause all threads, grow the array,
200     // and update the pointers in PyThreadState to entries in the new array.
201     if (qsbr == NULL) {
202         _PyEval_StopTheWorld(interp);
203         if (grow_thread_array(shared) == 0) {
204             qsbr = qsbr_allocate(shared);
205         }
206         _PyEval_StartTheWorld(interp);
207     }
208     PyMutex_Unlock(&shared->mutex);
209 
210     if (qsbr == NULL) {
211         return -1;
212     }
213 
214     // Return an index rather than the pointer because the array may be
215     // resized and the pointer invalidated.
216     return (struct _qsbr_pad *)qsbr - shared->array;
217 }
218 
219 void
_Py_qsbr_register(_PyThreadStateImpl * tstate,PyInterpreterState * interp,Py_ssize_t index)220 _Py_qsbr_register(_PyThreadStateImpl *tstate, PyInterpreterState *interp,
221                   Py_ssize_t index)
222 {
223     // Associate the QSBR state with the thread state
224     struct _qsbr_shared *shared = &interp->qsbr;
225 
226     PyMutex_Lock(&shared->mutex);
227     struct _qsbr_thread_state *qsbr = &interp->qsbr.array[index].qsbr;
228     assert(qsbr->allocated && qsbr->tstate == NULL);
229     qsbr->tstate = (PyThreadState *)tstate;
230     tstate->qsbr = qsbr;
231     PyMutex_Unlock(&shared->mutex);
232 }
233 
234 void
_Py_qsbr_unregister(PyThreadState * tstate)235 _Py_qsbr_unregister(PyThreadState *tstate)
236 {
237     struct _qsbr_shared *shared = &tstate->interp->qsbr;
238     struct _PyThreadStateImpl *tstate_imp = (_PyThreadStateImpl*) tstate;
239 
240     // gh-119369: GIL must be released (if held) to prevent deadlocks, because
241     // we might not have an active tstate, which means that blocking on PyMutex
242     // locks will not implicitly release the GIL.
243     assert(!tstate->_status.holds_gil);
244 
245     PyMutex_Lock(&shared->mutex);
246     // NOTE: we must load (or reload) the thread state's qbsr inside the mutex
247     // because the array may have been resized (changing tstate->qsbr) while
248     // we waited to acquire the mutex.
249     struct _qsbr_thread_state *qsbr = tstate_imp->qsbr;
250 
251     assert(qsbr->seq == 0 && "thread state must be detached");
252     assert(qsbr->allocated && qsbr->tstate == tstate);
253 
254     tstate_imp->qsbr = NULL;
255     qsbr->tstate = NULL;
256     qsbr->allocated = false;
257     qsbr->freelist_next = shared->freelist;
258     shared->freelist = qsbr;
259     PyMutex_Unlock(&shared->mutex);
260 }
261 
262 void
_Py_qsbr_fini(PyInterpreterState * interp)263 _Py_qsbr_fini(PyInterpreterState *interp)
264 {
265     struct _qsbr_shared *shared = &interp->qsbr;
266     PyMem_RawFree(shared->array);
267     shared->array = NULL;
268     shared->size = 0;
269     shared->freelist = NULL;
270 }
271 
272 void
_Py_qsbr_after_fork(_PyThreadStateImpl * tstate)273 _Py_qsbr_after_fork(_PyThreadStateImpl *tstate)
274 {
275     struct _qsbr_thread_state *this_qsbr = tstate->qsbr;
276     struct _qsbr_shared *shared = this_qsbr->shared;
277 
278     _PyMutex_at_fork_reinit(&shared->mutex);
279 
280     for (Py_ssize_t i = 0; i != shared->size; i++) {
281         struct _qsbr_thread_state *qsbr = &shared->array[i].qsbr;
282         if (qsbr != this_qsbr && qsbr->allocated) {
283             qsbr->tstate = NULL;
284             qsbr->allocated = false;
285             qsbr->freelist_next = shared->freelist;
286             shared->freelist = qsbr;
287         }
288     }
289 }
290