]>
Commit | Line | Data |
---|---|---|
9d5b5245 SD |
1 | /* |
2 | * CDDL HEADER START | |
3 | * | |
4 | * This file and its contents are supplied under the terms of the | |
5 | * Common Development and Distribution License ("CDDL"), version 1.0. | |
6 | * You may only use this file in accordance with the terms of version | |
7 | * 1.0 of the CDDL. | |
8 | * | |
9 | * A full copy of the text of the CDDL should have accompanied this | |
10 | * source. A copy of the CDDL is also available via the Internet at | |
11 | * http://www.illumos.org/license/CDDL. | |
12 | * | |
13 | * CDDL HEADER END | |
14 | */ | |
15 | ||
16 | /* | |
17 | * Copyright (c) 2017 by Delphix. All rights reserved. | |
18 | */ | |
19 | ||
20 | /* | |
21 | * ZTHR Infrastructure | |
22 | * =================== | |
23 | * | |
24 | * ZTHR threads are used for isolated operations that span multiple txgs | |
25 | * within a SPA. They generally exist from SPA creation/loading and until | |
26 | * the SPA is exported/destroyed. The ideal requirements for an operation | |
27 | * to be modeled with a zthr are the following: | |
28 | * | |
29 | * 1] The operation needs to run over multiple txgs. | |
30 | * 2] There is be a single point of reference in memory or on disk that | |
31 | * indicates whether the operation should run/is running or is | |
32 | * stopped. | |
33 | * | |
34 | * If the operation satisfies the above then the following rules guarantee | |
35 | * a certain level of correctness: | |
36 | * | |
37 | * 1] Any thread EXCEPT the zthr changes the work indicator from stopped | |
38 | * to running but not the opposite. | |
39 | * 2] Only the zthr can change the work indicator from running to stopped | |
40 | * (e.g. when it is done) but not the opposite. | |
41 | * | |
42 | * This way a normal zthr cycle should go like this: | |
43 | * | |
44 | * 1] An external thread changes the work indicator from stopped to | |
45 | * running and wakes up the zthr. | |
46 | * 2] The zthr wakes up, checks the indicator and starts working. | |
47 | * 3] When the zthr is done, it changes the indicator to stopped, allowing | |
48 | * a new cycle to start. | |
49 | * | |
50 | * == ZTHR creation | |
51 | * | |
52 | * Every zthr needs three inputs to start running: | |
53 | * | |
54 | * 1] A user-defined checker function (checkfunc) that decides whether | |
55 | * the zthr should start working or go to sleep. The function should | |
56 | * return TRUE when the zthr needs to work or FALSE to let it sleep, | |
57 | * and should adhere to the following signature: | |
58 | * boolean_t checkfunc_name(void *args, zthr_t *t); | |
59 | * | |
60 | * 2] A user-defined ZTHR function (func) which the zthr executes when | |
61 | * it is not sleeping. The function should adhere to the following | |
62 | * signature type: | |
63 | * int func_name(void *args, zthr_t *t); | |
64 | * | |
65 | * 3] A void args pointer that will be passed to checkfunc and func | |
66 | * implicitly by the infrastructure. | |
67 | * | |
68 | * The reason why the above API needs two different functions, | |
69 | * instead of one that both checks and does the work, has to do with | |
70 | * the zthr's internal lock (zthr_lock) and the allowed cancellation | |
71 | * windows. We want to hold the zthr_lock while running checkfunc | |
72 | * but not while running func. This way the zthr can be cancelled | |
73 | * while doing work and not while checking for work. | |
74 | * | |
75 | * To start a zthr: | |
76 | * zthr_t *zthr_pointer = zthr_create(checkfunc, func, args); | |
77 | * | |
78 | * After that you should be able to wakeup, cancel, and resume the | |
79 | * zthr from another thread using zthr_pointer. | |
80 | * | |
81 | * NOTE: ZTHR threads could potentially wake up spuriously and the | |
82 | * user should take this into account when writing a checkfunc. | |
83 | * [see ZTHR state transitions] | |
84 | * | |
85 | * == ZTHR cancellation | |
86 | * | |
87 | * ZTHR threads must be cancelled when their SPA is being exported | |
88 | * or when they need to be paused so they don't interfere with other | |
89 | * operations. | |
90 | * | |
91 | * To cancel a zthr: | |
92 | * zthr_cancel(zthr_pointer); | |
93 | * | |
94 | * To resume it: | |
95 | * zthr_resume(zthr_pointer); | |
96 | * | |
97 | * A zthr will implicitly check if it has received a cancellation | |
98 | * signal every time func returns and everytime it wakes up [see ZTHR | |
99 | * state transitions below]. | |
100 | * | |
101 | * At times, waiting for the zthr's func to finish its job may take | |
102 | * time. This may be very time-consuming for some operations that | |
103 | * need to cancel the SPA's zthrs (e.g spa_export). For this scenario | |
104 | * the user can explicitly make their ZTHR function aware of incoming | |
105 | * cancellation signals using zthr_iscancelled(). A common pattern for | |
106 | * that looks like this: | |
107 | * | |
108 | * int | |
109 | * func_name(void *args, zthr_t *t) | |
110 | * { | |
111 | * ... <unpack args> ... | |
112 | * while (!work_done && !zthr_iscancelled(t)) { | |
113 | * ... <do more work> ... | |
114 | * } | |
115 | * return (0); | |
116 | * } | |
117 | * | |
118 | * == ZTHR exit | |
119 | * | |
120 | * For the rare cases where the zthr wants to stop running voluntarily | |
121 | * while running its ZTHR function (func), we provide zthr_exit(). | |
122 | * When a zthr has voluntarily stopped running, it can be resumed with | |
123 | * zthr_resume(), just like it would if it was cancelled by some other | |
124 | * thread. | |
125 | * | |
126 | * == ZTHR cleanup | |
127 | * | |
128 | * Cancelling a zthr doesn't clean up its metadata (internal locks, | |
129 | * function pointers to func and checkfunc, etc..). This is because | |
130 | * we want to keep them around in case we want to resume the execution | |
131 | * of the zthr later. Similarly for zthrs that exit themselves. | |
132 | * | |
133 | * To completely cleanup a zthr, cancel it first to ensure that it | |
134 | * is not running and then use zthr_destroy(). | |
135 | * | |
136 | * == ZTHR state transitions | |
137 | * | |
138 | * zthr creation | |
139 | * + | |
140 | * | | |
141 | * | woke up | |
142 | * | +--------------+ sleep | |
143 | * | | ^ | |
144 | * | | | | |
145 | * | | | FALSE | |
146 | * | | | | |
147 | * v v FALSE + | |
148 | * cancelled? +---------> checkfunc? | |
149 | * + ^ + | |
150 | * | | | | |
151 | * | | | TRUE | |
152 | * | | | | |
153 | * | | func returned v | |
154 | * | +---------------+ func | |
155 | * | | |
156 | * | TRUE | |
157 | * | | |
158 | * v | |
159 | * zthr stopped running | |
160 | * | |
161 | */ | |
162 | ||
163 | #include <sys/zfs_context.h> | |
164 | #include <sys/zthr.h> | |
165 | ||
166 | void | |
167 | zthr_exit(zthr_t *t, int rc) | |
168 | { | |
169 | ASSERT3P(t->zthr_thread, ==, curthread); | |
170 | mutex_enter(&t->zthr_lock); | |
171 | t->zthr_thread = NULL; | |
172 | t->zthr_rc = rc; | |
173 | cv_broadcast(&t->zthr_cv); | |
174 | mutex_exit(&t->zthr_lock); | |
175 | thread_exit(); | |
176 | } | |
177 | ||
178 | static void | |
179 | zthr_procedure(void *arg) | |
180 | { | |
181 | zthr_t *t = arg; | |
182 | int rc = 0; | |
183 | ||
184 | mutex_enter(&t->zthr_lock); | |
185 | while (!t->zthr_cancel) { | |
186 | if (t->zthr_checkfunc(t->zthr_arg, t)) { | |
187 | mutex_exit(&t->zthr_lock); | |
188 | rc = t->zthr_func(t->zthr_arg, t); | |
189 | mutex_enter(&t->zthr_lock); | |
190 | } else { | |
191 | /* go to sleep */ | |
5284f43a | 192 | cv_wait_sig(&t->zthr_cv, &t->zthr_lock); |
9d5b5245 SD |
193 | } |
194 | } | |
195 | mutex_exit(&t->zthr_lock); | |
196 | ||
197 | zthr_exit(t, rc); | |
198 | } | |
199 | ||
200 | zthr_t * | |
201 | zthr_create(zthr_checkfunc_t *checkfunc, zthr_func_t *func, void *arg) | |
202 | { | |
203 | zthr_t *t = kmem_zalloc(sizeof (*t), KM_SLEEP); | |
204 | mutex_init(&t->zthr_lock, NULL, MUTEX_DEFAULT, NULL); | |
205 | cv_init(&t->zthr_cv, NULL, CV_DEFAULT, NULL); | |
206 | ||
207 | mutex_enter(&t->zthr_lock); | |
208 | t->zthr_checkfunc = checkfunc; | |
209 | t->zthr_func = func; | |
210 | t->zthr_arg = arg; | |
211 | ||
212 | t->zthr_thread = thread_create(NULL, 0, zthr_procedure, t, | |
213 | 0, &p0, TS_RUN, minclsyspri); | |
214 | mutex_exit(&t->zthr_lock); | |
215 | ||
216 | return (t); | |
217 | } | |
218 | ||
219 | void | |
220 | zthr_destroy(zthr_t *t) | |
221 | { | |
222 | VERIFY3P(t->zthr_thread, ==, NULL); | |
223 | mutex_destroy(&t->zthr_lock); | |
224 | cv_destroy(&t->zthr_cv); | |
225 | kmem_free(t, sizeof (*t)); | |
226 | } | |
227 | ||
228 | /* | |
229 | * Note: If the zthr is not sleeping and misses the wakeup | |
230 | * (e.g it is running its ZTHR function), it will check if | |
231 | * there is work to do before going to sleep using its checker | |
232 | * function [see ZTHR state transition in ZTHR block comment]. | |
233 | * Thus, missing the wakeup still yields the expected behavior. | |
234 | */ | |
235 | void | |
236 | zthr_wakeup(zthr_t *t) | |
237 | { | |
9d5b5245 SD |
238 | mutex_enter(&t->zthr_lock); |
239 | cv_broadcast(&t->zthr_cv); | |
240 | mutex_exit(&t->zthr_lock); | |
241 | } | |
242 | ||
243 | /* | |
244 | * Note: If the zthr is not running (e.g. has been cancelled | |
245 | * already), this is a no-op. | |
246 | */ | |
247 | int | |
248 | zthr_cancel(zthr_t *t) | |
249 | { | |
250 | int rc = 0; | |
251 | ||
252 | mutex_enter(&t->zthr_lock); | |
253 | ||
254 | /* broadcast in case the zthr is sleeping */ | |
255 | cv_broadcast(&t->zthr_cv); | |
256 | ||
257 | t->zthr_cancel = B_TRUE; | |
258 | while (t->zthr_thread != NULL) | |
259 | cv_wait(&t->zthr_cv, &t->zthr_lock); | |
260 | t->zthr_cancel = B_FALSE; | |
261 | rc = t->zthr_rc; | |
262 | mutex_exit(&t->zthr_lock); | |
263 | ||
264 | return (rc); | |
265 | } | |
266 | ||
267 | void | |
268 | zthr_resume(zthr_t *t) | |
269 | { | |
270 | ASSERT3P(t->zthr_thread, ==, NULL); | |
271 | ||
272 | mutex_enter(&t->zthr_lock); | |
273 | ||
274 | ASSERT3P(&t->zthr_checkfunc, !=, NULL); | |
275 | ASSERT3P(&t->zthr_func, !=, NULL); | |
276 | ASSERT(!t->zthr_cancel); | |
277 | ||
278 | t->zthr_thread = thread_create(NULL, 0, zthr_procedure, t, | |
279 | 0, &p0, TS_RUN, minclsyspri); | |
280 | ||
281 | mutex_exit(&t->zthr_lock); | |
282 | } | |
283 | ||
284 | /* | |
285 | * This function is intended to be used by the zthr itself | |
286 | * to check if another thread has signal it to stop running. | |
287 | * | |
288 | * returns TRUE if we are in the middle of trying to cancel | |
289 | * this thread. | |
290 | * | |
291 | * returns FALSE otherwise. | |
292 | */ | |
293 | boolean_t | |
294 | zthr_iscancelled(zthr_t *t) | |
295 | { | |
296 | boolean_t cancelled; | |
297 | ||
298 | ASSERT3P(t->zthr_thread, ==, curthread); | |
299 | ||
300 | mutex_enter(&t->zthr_lock); | |
301 | cancelled = t->zthr_cancel; | |
302 | mutex_exit(&t->zthr_lock); | |
303 | ||
304 | return (cancelled); | |
305 | } | |
306 | ||
307 | boolean_t | |
308 | zthr_isrunning(zthr_t *t) | |
309 | { | |
310 | boolean_t running; | |
311 | ||
312 | mutex_enter(&t->zthr_lock); | |
313 | running = (t->zthr_thread != NULL); | |
314 | mutex_exit(&t->zthr_lock); | |
315 | ||
316 | return (running); | |
317 | } |