1 | The following changes since commit ca61fa4b803e5d0abaf6f1ceb690f23bb78a4def: | 1 | The following changes since commit ddc27d2ad9361a81c2b3800d14143bf420dae172: |
---|---|---|---|
2 | 2 | ||
3 | Merge remote-tracking branch 'remotes/quic/tags/pull-hex-20211006' into staging (2021-10-06 12:11:14 -0700) | 3 | Merge tag 'pull-request-2024-03-18' of https://gitlab.com/thuth/qemu into staging (2024-03-19 10:25:25 +0000) |
4 | 4 | ||
5 | are available in the Git repository at: | 5 | are available in the Git repository at: |
6 | 6 | ||
7 | https://gitlab.com/stefanha/qemu.git tags/block-pull-request | 7 | https://gitlab.com/stefanha/qemu.git tags/block-pull-request |
8 | 8 | ||
9 | for you to fetch changes up to 1cc7eada97914f090125e588497986f6f7900514: | 9 | for you to fetch changes up to 86a637e48104ae74d8be53bed6441ce32be33433: |
10 | 10 | ||
11 | iothread: use IOThreadParamInfo in iothread_[set|get]_param() (2021-10-07 15:29:50 +0100) | 11 | coroutine: cap per-thread local pool size (2024-03-19 10:49:31 -0400) |
12 | 12 | ||
13 | ---------------------------------------------------------------- | 13 | ---------------------------------------------------------------- |
14 | Pull request | 14 | Pull request |
15 | 15 | ||
16 | This fix solves the "failed to set up stack guard page" error that has been | ||
17 | reported on Linux hosts where the QEMU coroutine pool exceeds the | ||
18 | vm.max_map_count limit. | ||
19 | |||
16 | ---------------------------------------------------------------- | 20 | ---------------------------------------------------------------- |
17 | 21 | ||
18 | Stefano Garzarella (2): | 22 | Stefan Hajnoczi (1): |
19 | iothread: rename PollParamInfo to IOThreadParamInfo | 23 | coroutine: cap per-thread local pool size |
20 | iothread: use IOThreadParamInfo in iothread_[set|get]_param() | ||
21 | 24 | ||
22 | iothread.c | 28 +++++++++++++++------------- | 25 | util/qemu-coroutine.c | 282 +++++++++++++++++++++++++++++++++--------- |
23 | 1 file changed, 15 insertions(+), 13 deletions(-) | 26 | 1 file changed, 223 insertions(+), 59 deletions(-) |
24 | 27 | ||
25 | -- | 28 | -- |
26 | 2.31.1 | 29 | 2.44.0 |
27 | |||
28 | |||
29 | diff view generated by jsdifflib |
Deleted patch | |||
---|---|---|---|
1 | From: Stefano Garzarella <sgarzare@redhat.com> | ||
2 | 1 | ||
3 | Commit 1793ad0247 ("iothread: add aio-max-batch parameter") added | ||
4 | a new parameter (aio-max-batch) to IOThread and used PollParamInfo | ||
5 | structure to handle it. | ||
6 | |||
7 | Since it is not a parameter of the polling mechanism, we rename the | ||
8 | structure to a more generic IOThreadParamInfo. | ||
9 | |||
10 | Suggested-by: Kevin Wolf <kwolf@redhat.com> | ||
11 | Signed-off-by: Stefano Garzarella <sgarzare@redhat.com> | ||
12 | Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com> | ||
13 | Message-id: 20210727145936.147032-2-sgarzare@redhat.com | ||
14 | Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com> | ||
15 | --- | ||
16 | iothread.c | 14 +++++++------- | ||
17 | 1 file changed, 7 insertions(+), 7 deletions(-) | ||
18 | |||
19 | diff --git a/iothread.c b/iothread.c | ||
20 | index XXXXXXX..XXXXXXX 100644 | ||
21 | --- a/iothread.c | ||
22 | +++ b/iothread.c | ||
23 | @@ -XXX,XX +XXX,XX @@ static void iothread_complete(UserCreatable *obj, Error **errp) | ||
24 | typedef struct { | ||
25 | const char *name; | ||
26 | ptrdiff_t offset; /* field's byte offset in IOThread struct */ | ||
27 | -} PollParamInfo; | ||
28 | +} IOThreadParamInfo; | ||
29 | |||
30 | -static PollParamInfo poll_max_ns_info = { | ||
31 | +static IOThreadParamInfo poll_max_ns_info = { | ||
32 | "poll-max-ns", offsetof(IOThread, poll_max_ns), | ||
33 | }; | ||
34 | -static PollParamInfo poll_grow_info = { | ||
35 | +static IOThreadParamInfo poll_grow_info = { | ||
36 | "poll-grow", offsetof(IOThread, poll_grow), | ||
37 | }; | ||
38 | -static PollParamInfo poll_shrink_info = { | ||
39 | +static IOThreadParamInfo poll_shrink_info = { | ||
40 | "poll-shrink", offsetof(IOThread, poll_shrink), | ||
41 | }; | ||
42 | -static PollParamInfo aio_max_batch_info = { | ||
43 | +static IOThreadParamInfo aio_max_batch_info = { | ||
44 | "aio-max-batch", offsetof(IOThread, aio_max_batch), | ||
45 | }; | ||
46 | |||
47 | @@ -XXX,XX +XXX,XX @@ static void iothread_get_param(Object *obj, Visitor *v, | ||
48 | const char *name, void *opaque, Error **errp) | ||
49 | { | ||
50 | IOThread *iothread = IOTHREAD(obj); | ||
51 | - PollParamInfo *info = opaque; | ||
52 | + IOThreadParamInfo *info = opaque; | ||
53 | int64_t *field = (void *)iothread + info->offset; | ||
54 | |||
55 | visit_type_int64(v, name, field, errp); | ||
56 | @@ -XXX,XX +XXX,XX @@ static bool iothread_set_param(Object *obj, Visitor *v, | ||
57 | const char *name, void *opaque, Error **errp) | ||
58 | { | ||
59 | IOThread *iothread = IOTHREAD(obj); | ||
60 | - PollParamInfo *info = opaque; | ||
61 | + IOThreadParamInfo *info = opaque; | ||
62 | int64_t *field = (void *)iothread + info->offset; | ||
63 | int64_t value; | ||
64 | |||
65 | -- | ||
66 | 2.31.1 | ||
67 | |||
68 | diff view generated by jsdifflib |
1 | From: Stefano Garzarella <sgarzare@redhat.com> | 1 | The coroutine pool implementation can hit the Linux vm.max_map_count |
---|---|---|---|
2 | 2 | limit, causing QEMU to abort with "failed to allocate memory for stack" | |
3 | Commit 0445409d74 ("iothread: generalize | 3 | or "failed to set up stack guard page" during coroutine creation. |
4 | iothread_set_param/iothread_get_param") moved common code to set and | 4 | |
5 | get IOThread parameters in two new functions. | 5 | This happens because per-thread pools can grow to tens of thousands of |
6 | 6 | coroutines. Each coroutine causes 2 virtual memory areas to be created. | |
7 | These functions are called inside callbacks, so we don't need to use an | 7 | Eventually vm.max_map_count is reached and memory-related syscalls fail. |
8 | opaque pointer. Let's replace `void *opaque` parameter with | 8 | The per-thread pool sizes are non-uniform and depend on past coroutine |
9 | `IOThreadParamInfo *info`. | 9 | usage in each thread, so it's possible for one thread to have a large |
10 | 10 | pool while another thread's pool is empty. | |
11 | Suggested-by: Kevin Wolf <kwolf@redhat.com> | 11 | |
12 | Signed-off-by: Stefano Garzarella <sgarzare@redhat.com> | 12 | Switch to a new coroutine pool implementation with a global pool that |
13 | Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com> | 13 | grows to a maximum number of coroutines and per-thread local pools that |
14 | Message-id: 20210727145936.147032-3-sgarzare@redhat.com | 14 | are capped at hardcoded small number of coroutines. |
15 | |||
16 | This approach does not leave large numbers of coroutines pooled in a | ||
17 | thread that may not use them again. In order to perform well it | ||
18 | amortizes the cost of global pool accesses by working in batches of | ||
19 | coroutines instead of individual coroutines. | ||
20 | |||
21 | The global pool is a list. Threads donate batches of coroutines to when | ||
22 | they have too many and take batches from when they have too few: | ||
23 | |||
24 | .-----------------------------------. | ||
25 | | Batch 1 | Batch 2 | Batch 3 | ... | global_pool | ||
26 | `-----------------------------------' | ||
27 | |||
28 | Each thread has up to 2 batches of coroutines: | ||
29 | |||
30 | .-------------------. | ||
31 | | Batch 1 | Batch 2 | per-thread local_pool (maximum 2 batches) | ||
32 | `-------------------' | ||
33 | |||
34 | The goal of this change is to reduce the excessive number of pooled | ||
35 | coroutines that cause QEMU to abort when vm.max_map_count is reached | ||
36 | without losing the performance of an adequately sized coroutine pool. | ||
37 | |||
38 | Here are virtio-blk disk I/O benchmark results: | ||
39 | |||
40 | RW BLKSIZE IODEPTH OLD NEW CHANGE | ||
41 | randread 4k 1 113725 117451 +3.3% | ||
42 | randread 4k 8 192968 198510 +2.9% | ||
43 | randread 4k 16 207138 209429 +1.1% | ||
44 | randread 4k 32 212399 215145 +1.3% | ||
45 | randread 4k 64 218319 221277 +1.4% | ||
46 | randread 128k 1 17587 17535 -0.3% | ||
47 | randread 128k 8 17614 17616 +0.0% | ||
48 | randread 128k 16 17608 17609 +0.0% | ||
49 | randread 128k 32 17552 17553 +0.0% | ||
50 | randread 128k 64 17484 17484 +0.0% | ||
51 | |||
52 | See files/{fio.sh,test.xml.j2} for the benchmark configuration: | ||
53 | https://gitlab.com/stefanha/virt-playbooks/-/tree/coroutine-pool-fix-sizing | ||
54 | |||
55 | Buglink: https://issues.redhat.com/browse/RHEL-28947 | ||
56 | Reported-by: Sanjay Rao <srao@redhat.com> | ||
57 | Reported-by: Boaz Ben Shabat <bbenshab@redhat.com> | ||
58 | Reported-by: Joe Mario <jmario@redhat.com> | ||
59 | Reviewed-by: Kevin Wolf <kwolf@redhat.com> | ||
15 | Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com> | 60 | Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com> |
61 | Message-ID: <20240318183429.1039340-1-stefanha@redhat.com> | ||
16 | --- | 62 | --- |
17 | iothread.c | 18 ++++++++++-------- | 63 | util/qemu-coroutine.c | 282 +++++++++++++++++++++++++++++++++--------- |
18 | 1 file changed, 10 insertions(+), 8 deletions(-) | 64 | 1 file changed, 223 insertions(+), 59 deletions(-) |
19 | 65 | ||
20 | diff --git a/iothread.c b/iothread.c | 66 | diff --git a/util/qemu-coroutine.c b/util/qemu-coroutine.c |
21 | index XXXXXXX..XXXXXXX 100644 | 67 | index XXXXXXX..XXXXXXX 100644 |
22 | --- a/iothread.c | 68 | --- a/util/qemu-coroutine.c |
23 | +++ b/iothread.c | 69 | +++ b/util/qemu-coroutine.c |
24 | @@ -XXX,XX +XXX,XX @@ static IOThreadParamInfo aio_max_batch_info = { | 70 | @@ -XXX,XX +XXX,XX @@ |
71 | #include "qemu/atomic.h" | ||
72 | #include "qemu/coroutine_int.h" | ||
73 | #include "qemu/coroutine-tls.h" | ||
74 | +#include "qemu/cutils.h" | ||
75 | #include "block/aio.h" | ||
76 | |||
77 | -/** | ||
78 | - * The minimal batch size is always 64, coroutines from the release_pool are | ||
79 | - * reused as soon as there are 64 coroutines in it. The maximum pool size starts | ||
80 | - * with 64 and is increased on demand so that coroutines are not deleted even if | ||
81 | - * they are not immediately reused. | ||
82 | - */ | ||
83 | enum { | ||
84 | - POOL_MIN_BATCH_SIZE = 64, | ||
85 | - POOL_INITIAL_MAX_SIZE = 64, | ||
86 | + COROUTINE_POOL_BATCH_MAX_SIZE = 128, | ||
25 | }; | 87 | }; |
26 | 88 | ||
27 | static void iothread_get_param(Object *obj, Visitor *v, | 89 | -/** Free list to speed up creation */ |
28 | - const char *name, void *opaque, Error **errp) | 90 | -static QSLIST_HEAD(, Coroutine) release_pool = QSLIST_HEAD_INITIALIZER(pool); |
29 | + const char *name, IOThreadParamInfo *info, Error **errp) | 91 | -static unsigned int pool_max_size = POOL_INITIAL_MAX_SIZE; |
92 | -static unsigned int release_pool_size; | ||
93 | +/* | ||
94 | + * Coroutine creation and deletion is expensive so a pool of unused coroutines | ||
95 | + * is kept as a cache. When the pool has coroutines available, they are | ||
96 | + * recycled instead of creating new ones from scratch. Coroutines are added to | ||
97 | + * the pool upon termination. | ||
98 | + * | ||
99 | + * The pool is global but each thread maintains a small local pool to avoid | ||
100 | + * global pool contention. Threads fetch and return batches of coroutines from | ||
101 | + * the global pool to maintain their local pool. The local pool holds up to two | ||
102 | + * batches whereas the maximum size of the global pool is controlled by the | ||
103 | + * qemu_coroutine_inc_pool_size() API. | ||
104 | + * | ||
105 | + * .-----------------------------------. | ||
106 | + * | Batch 1 | Batch 2 | Batch 3 | ... | global_pool | ||
107 | + * `-----------------------------------' | ||
108 | + * | ||
109 | + * .-------------------. | ||
110 | + * | Batch 1 | Batch 2 | per-thread local_pool (maximum 2 batches) | ||
111 | + * `-------------------' | ||
112 | + */ | ||
113 | +typedef struct CoroutinePoolBatch { | ||
114 | + /* Batches are kept in a list */ | ||
115 | + QSLIST_ENTRY(CoroutinePoolBatch) next; | ||
116 | |||
117 | -typedef QSLIST_HEAD(, Coroutine) CoroutineQSList; | ||
118 | -QEMU_DEFINE_STATIC_CO_TLS(CoroutineQSList, alloc_pool); | ||
119 | -QEMU_DEFINE_STATIC_CO_TLS(unsigned int, alloc_pool_size); | ||
120 | -QEMU_DEFINE_STATIC_CO_TLS(Notifier, coroutine_pool_cleanup_notifier); | ||
121 | + /* This batch holds up to @COROUTINE_POOL_BATCH_MAX_SIZE coroutines */ | ||
122 | + QSLIST_HEAD(, Coroutine) list; | ||
123 | + unsigned int size; | ||
124 | +} CoroutinePoolBatch; | ||
125 | |||
126 | -static void coroutine_pool_cleanup(Notifier *n, void *value) | ||
127 | +typedef QSLIST_HEAD(, CoroutinePoolBatch) CoroutinePool; | ||
128 | + | ||
129 | +/* Host operating system limit on number of pooled coroutines */ | ||
130 | +static unsigned int global_pool_hard_max_size; | ||
131 | + | ||
132 | +static QemuMutex global_pool_lock; /* protects the following variables */ | ||
133 | +static CoroutinePool global_pool = QSLIST_HEAD_INITIALIZER(global_pool); | ||
134 | +static unsigned int global_pool_size; | ||
135 | +static unsigned int global_pool_max_size = COROUTINE_POOL_BATCH_MAX_SIZE; | ||
136 | + | ||
137 | +QEMU_DEFINE_STATIC_CO_TLS(CoroutinePool, local_pool); | ||
138 | +QEMU_DEFINE_STATIC_CO_TLS(Notifier, local_pool_cleanup_notifier); | ||
139 | + | ||
140 | +static CoroutinePoolBatch *coroutine_pool_batch_new(void) | ||
141 | +{ | ||
142 | + CoroutinePoolBatch *batch = g_new(CoroutinePoolBatch, 1); | ||
143 | + | ||
144 | + QSLIST_INIT(&batch->list); | ||
145 | + batch->size = 0; | ||
146 | + return batch; | ||
147 | +} | ||
148 | + | ||
149 | +static void coroutine_pool_batch_delete(CoroutinePoolBatch *batch) | ||
30 | { | 150 | { |
31 | IOThread *iothread = IOTHREAD(obj); | 151 | Coroutine *co; |
32 | - IOThreadParamInfo *info = opaque; | 152 | Coroutine *tmp; |
33 | int64_t *field = (void *)iothread + info->offset; | 153 | - CoroutineQSList *alloc_pool = get_ptr_alloc_pool(); |
34 | 154 | ||
35 | visit_type_int64(v, name, field, errp); | 155 | - QSLIST_FOREACH_SAFE(co, alloc_pool, pool_next, tmp) { |
156 | - QSLIST_REMOVE_HEAD(alloc_pool, pool_next); | ||
157 | + QSLIST_FOREACH_SAFE(co, &batch->list, pool_next, tmp) { | ||
158 | + QSLIST_REMOVE_HEAD(&batch->list, pool_next); | ||
159 | qemu_coroutine_delete(co); | ||
160 | } | ||
161 | + g_free(batch); | ||
162 | +} | ||
163 | + | ||
164 | +static void local_pool_cleanup(Notifier *n, void *value) | ||
165 | +{ | ||
166 | + CoroutinePool *local_pool = get_ptr_local_pool(); | ||
167 | + CoroutinePoolBatch *batch; | ||
168 | + CoroutinePoolBatch *tmp; | ||
169 | + | ||
170 | + QSLIST_FOREACH_SAFE(batch, local_pool, next, tmp) { | ||
171 | + QSLIST_REMOVE_HEAD(local_pool, next); | ||
172 | + coroutine_pool_batch_delete(batch); | ||
173 | + } | ||
174 | +} | ||
175 | + | ||
176 | +/* Ensure the atexit notifier is registered */ | ||
177 | +static void local_pool_cleanup_init_once(void) | ||
178 | +{ | ||
179 | + Notifier *notifier = get_ptr_local_pool_cleanup_notifier(); | ||
180 | + if (!notifier->notify) { | ||
181 | + notifier->notify = local_pool_cleanup; | ||
182 | + qemu_thread_atexit_add(notifier); | ||
183 | + } | ||
184 | +} | ||
185 | + | ||
186 | +/* Helper to get the next unused coroutine from the local pool */ | ||
187 | +static Coroutine *coroutine_pool_get_local(void) | ||
188 | +{ | ||
189 | + CoroutinePool *local_pool = get_ptr_local_pool(); | ||
190 | + CoroutinePoolBatch *batch = QSLIST_FIRST(local_pool); | ||
191 | + Coroutine *co; | ||
192 | + | ||
193 | + if (unlikely(!batch)) { | ||
194 | + return NULL; | ||
195 | + } | ||
196 | + | ||
197 | + co = QSLIST_FIRST(&batch->list); | ||
198 | + QSLIST_REMOVE_HEAD(&batch->list, pool_next); | ||
199 | + batch->size--; | ||
200 | + | ||
201 | + if (batch->size == 0) { | ||
202 | + QSLIST_REMOVE_HEAD(local_pool, next); | ||
203 | + coroutine_pool_batch_delete(batch); | ||
204 | + } | ||
205 | + return co; | ||
206 | +} | ||
207 | + | ||
208 | +/* Get the next batch from the global pool */ | ||
209 | +static void coroutine_pool_refill_local(void) | ||
210 | +{ | ||
211 | + CoroutinePool *local_pool = get_ptr_local_pool(); | ||
212 | + CoroutinePoolBatch *batch; | ||
213 | + | ||
214 | + WITH_QEMU_LOCK_GUARD(&global_pool_lock) { | ||
215 | + batch = QSLIST_FIRST(&global_pool); | ||
216 | + | ||
217 | + if (batch) { | ||
218 | + QSLIST_REMOVE_HEAD(&global_pool, next); | ||
219 | + global_pool_size -= batch->size; | ||
220 | + } | ||
221 | + } | ||
222 | + | ||
223 | + if (batch) { | ||
224 | + QSLIST_INSERT_HEAD(local_pool, batch, next); | ||
225 | + local_pool_cleanup_init_once(); | ||
226 | + } | ||
227 | +} | ||
228 | + | ||
229 | +/* Add a batch of coroutines to the global pool */ | ||
230 | +static void coroutine_pool_put_global(CoroutinePoolBatch *batch) | ||
231 | +{ | ||
232 | + WITH_QEMU_LOCK_GUARD(&global_pool_lock) { | ||
233 | + unsigned int max = MIN(global_pool_max_size, | ||
234 | + global_pool_hard_max_size); | ||
235 | + | ||
236 | + if (global_pool_size < max) { | ||
237 | + QSLIST_INSERT_HEAD(&global_pool, batch, next); | ||
238 | + | ||
239 | + /* Overshooting the max pool size is allowed */ | ||
240 | + global_pool_size += batch->size; | ||
241 | + return; | ||
242 | + } | ||
243 | + } | ||
244 | + | ||
245 | + /* The global pool was full, so throw away this batch */ | ||
246 | + coroutine_pool_batch_delete(batch); | ||
247 | +} | ||
248 | + | ||
249 | +/* Get the next unused coroutine from the pool or return NULL */ | ||
250 | +static Coroutine *coroutine_pool_get(void) | ||
251 | +{ | ||
252 | + Coroutine *co; | ||
253 | + | ||
254 | + co = coroutine_pool_get_local(); | ||
255 | + if (!co) { | ||
256 | + coroutine_pool_refill_local(); | ||
257 | + co = coroutine_pool_get_local(); | ||
258 | + } | ||
259 | + return co; | ||
260 | +} | ||
261 | + | ||
262 | +static void coroutine_pool_put(Coroutine *co) | ||
263 | +{ | ||
264 | + CoroutinePool *local_pool = get_ptr_local_pool(); | ||
265 | + CoroutinePoolBatch *batch = QSLIST_FIRST(local_pool); | ||
266 | + | ||
267 | + if (unlikely(!batch)) { | ||
268 | + batch = coroutine_pool_batch_new(); | ||
269 | + QSLIST_INSERT_HEAD(local_pool, batch, next); | ||
270 | + local_pool_cleanup_init_once(); | ||
271 | + } | ||
272 | + | ||
273 | + if (unlikely(batch->size >= COROUTINE_POOL_BATCH_MAX_SIZE)) { | ||
274 | + CoroutinePoolBatch *next = QSLIST_NEXT(batch, next); | ||
275 | + | ||
276 | + /* Is the local pool full? */ | ||
277 | + if (next) { | ||
278 | + QSLIST_REMOVE_HEAD(local_pool, next); | ||
279 | + coroutine_pool_put_global(batch); | ||
280 | + } | ||
281 | + | ||
282 | + batch = coroutine_pool_batch_new(); | ||
283 | + QSLIST_INSERT_HEAD(local_pool, batch, next); | ||
284 | + } | ||
285 | + | ||
286 | + QSLIST_INSERT_HEAD(&batch->list, co, pool_next); | ||
287 | + batch->size++; | ||
36 | } | 288 | } |
37 | 289 | ||
38 | static bool iothread_set_param(Object *obj, Visitor *v, | 290 | Coroutine *qemu_coroutine_create(CoroutineEntry *entry, void *opaque) |
39 | - const char *name, void *opaque, Error **errp) | 291 | @@ -XXX,XX +XXX,XX @@ Coroutine *qemu_coroutine_create(CoroutineEntry *entry, void *opaque) |
40 | + const char *name, IOThreadParamInfo *info, Error **errp) | 292 | Coroutine *co = NULL; |
293 | |||
294 | if (IS_ENABLED(CONFIG_COROUTINE_POOL)) { | ||
295 | - CoroutineQSList *alloc_pool = get_ptr_alloc_pool(); | ||
296 | - | ||
297 | - co = QSLIST_FIRST(alloc_pool); | ||
298 | - if (!co) { | ||
299 | - if (release_pool_size > POOL_MIN_BATCH_SIZE) { | ||
300 | - /* Slow path; a good place to register the destructor, too. */ | ||
301 | - Notifier *notifier = get_ptr_coroutine_pool_cleanup_notifier(); | ||
302 | - if (!notifier->notify) { | ||
303 | - notifier->notify = coroutine_pool_cleanup; | ||
304 | - qemu_thread_atexit_add(notifier); | ||
305 | - } | ||
306 | - | ||
307 | - /* This is not exact; there could be a little skew between | ||
308 | - * release_pool_size and the actual size of release_pool. But | ||
309 | - * it is just a heuristic, it does not need to be perfect. | ||
310 | - */ | ||
311 | - set_alloc_pool_size(qatomic_xchg(&release_pool_size, 0)); | ||
312 | - QSLIST_MOVE_ATOMIC(alloc_pool, &release_pool); | ||
313 | - co = QSLIST_FIRST(alloc_pool); | ||
314 | - } | ||
315 | - } | ||
316 | - if (co) { | ||
317 | - QSLIST_REMOVE_HEAD(alloc_pool, pool_next); | ||
318 | - set_alloc_pool_size(get_alloc_pool_size() - 1); | ||
319 | - } | ||
320 | + co = coroutine_pool_get(); | ||
321 | } | ||
322 | |||
323 | if (!co) { | ||
324 | @@ -XXX,XX +XXX,XX @@ static void coroutine_delete(Coroutine *co) | ||
325 | co->caller = NULL; | ||
326 | |||
327 | if (IS_ENABLED(CONFIG_COROUTINE_POOL)) { | ||
328 | - if (release_pool_size < qatomic_read(&pool_max_size) * 2) { | ||
329 | - QSLIST_INSERT_HEAD_ATOMIC(&release_pool, co, pool_next); | ||
330 | - qatomic_inc(&release_pool_size); | ||
331 | - return; | ||
332 | - } | ||
333 | - if (get_alloc_pool_size() < qatomic_read(&pool_max_size)) { | ||
334 | - QSLIST_INSERT_HEAD(get_ptr_alloc_pool(), co, pool_next); | ||
335 | - set_alloc_pool_size(get_alloc_pool_size() + 1); | ||
336 | - return; | ||
337 | - } | ||
338 | + coroutine_pool_put(co); | ||
339 | + } else { | ||
340 | + qemu_coroutine_delete(co); | ||
341 | } | ||
342 | - | ||
343 | - qemu_coroutine_delete(co); | ||
344 | } | ||
345 | |||
346 | void qemu_aio_coroutine_enter(AioContext *ctx, Coroutine *co) | ||
347 | @@ -XXX,XX +XXX,XX @@ AioContext *qemu_coroutine_get_aio_context(Coroutine *co) | ||
348 | |||
349 | void qemu_coroutine_inc_pool_size(unsigned int additional_pool_size) | ||
41 | { | 350 | { |
42 | IOThread *iothread = IOTHREAD(obj); | 351 | - qatomic_add(&pool_max_size, additional_pool_size); |
43 | - IOThreadParamInfo *info = opaque; | 352 | + QEMU_LOCK_GUARD(&global_pool_lock); |
44 | int64_t *field = (void *)iothread + info->offset; | 353 | + global_pool_max_size += additional_pool_size; |
45 | int64_t value; | 354 | } |
46 | 355 | ||
47 | @@ -XXX,XX +XXX,XX @@ static bool iothread_set_param(Object *obj, Visitor *v, | 356 | void qemu_coroutine_dec_pool_size(unsigned int removing_pool_size) |
48 | static void iothread_get_poll_param(Object *obj, Visitor *v, | ||
49 | const char *name, void *opaque, Error **errp) | ||
50 | { | 357 | { |
51 | + IOThreadParamInfo *info = opaque; | 358 | - qatomic_sub(&pool_max_size, removing_pool_size); |
52 | 359 | + QEMU_LOCK_GUARD(&global_pool_lock); | |
53 | - iothread_get_param(obj, v, name, opaque, errp); | 360 | + global_pool_max_size -= removing_pool_size; |
54 | + iothread_get_param(obj, v, name, info, errp); | 361 | +} |
362 | + | ||
363 | +static unsigned int get_global_pool_hard_max_size(void) | ||
364 | +{ | ||
365 | +#ifdef __linux__ | ||
366 | + g_autofree char *contents = NULL; | ||
367 | + int max_map_count; | ||
368 | + | ||
369 | + /* | ||
370 | + * Linux processes can have up to max_map_count virtual memory areas | ||
371 | + * (VMAs). mmap(2), mprotect(2), etc fail with ENOMEM beyond this limit. We | ||
372 | + * must limit the coroutine pool to a safe size to avoid running out of | ||
373 | + * VMAs. | ||
374 | + */ | ||
375 | + if (g_file_get_contents("/proc/sys/vm/max_map_count", &contents, NULL, | ||
376 | + NULL) && | ||
377 | + qemu_strtoi(contents, NULL, 10, &max_map_count) == 0) { | ||
378 | + /* | ||
379 | + * This is a conservative upper bound that avoids exceeding | ||
380 | + * max_map_count. Leave half for non-coroutine users like library | ||
381 | + * dependencies, vhost-user, etc. Each coroutine takes up 2 VMAs so | ||
382 | + * halve the amount again. | ||
383 | + */ | ||
384 | + return max_map_count / 4; | ||
385 | + } | ||
386 | +#endif | ||
387 | + | ||
388 | + return UINT_MAX; | ||
389 | +} | ||
390 | + | ||
391 | +static void __attribute__((constructor)) qemu_coroutine_init(void) | ||
392 | +{ | ||
393 | + qemu_mutex_init(&global_pool_lock); | ||
394 | + global_pool_hard_max_size = get_global_pool_hard_max_size(); | ||
55 | } | 395 | } |
56 | |||
57 | static void iothread_set_poll_param(Object *obj, Visitor *v, | ||
58 | const char *name, void *opaque, Error **errp) | ||
59 | { | ||
60 | IOThread *iothread = IOTHREAD(obj); | ||
61 | + IOThreadParamInfo *info = opaque; | ||
62 | |||
63 | - if (!iothread_set_param(obj, v, name, opaque, errp)) { | ||
64 | + if (!iothread_set_param(obj, v, name, info, errp)) { | ||
65 | return; | ||
66 | } | ||
67 | |||
68 | @@ -XXX,XX +XXX,XX @@ static void iothread_set_poll_param(Object *obj, Visitor *v, | ||
69 | static void iothread_get_aio_param(Object *obj, Visitor *v, | ||
70 | const char *name, void *opaque, Error **errp) | ||
71 | { | ||
72 | + IOThreadParamInfo *info = opaque; | ||
73 | |||
74 | - iothread_get_param(obj, v, name, opaque, errp); | ||
75 | + iothread_get_param(obj, v, name, info, errp); | ||
76 | } | ||
77 | |||
78 | static void iothread_set_aio_param(Object *obj, Visitor *v, | ||
79 | const char *name, void *opaque, Error **errp) | ||
80 | { | ||
81 | IOThread *iothread = IOTHREAD(obj); | ||
82 | + IOThreadParamInfo *info = opaque; | ||
83 | |||
84 | - if (!iothread_set_param(obj, v, name, opaque, errp)) { | ||
85 | + if (!iothread_set_param(obj, v, name, info, errp)) { | ||
86 | return; | ||
87 | } | ||
88 | |||
89 | -- | 396 | -- |
90 | 2.31.1 | 397 | 2.44.0 |
91 | |||
92 | diff view generated by jsdifflib |