summaryrefslogtreecommitdiffstats
path: root/drivers/md/dm-vdo/action-manager.c
blob: a0e5e7077d13867e094cad8cc2dfbd3a14308513 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright 2023 Red Hat
 */

#include "action-manager.h"

#include "memory-alloc.h"
#include "permassert.h"

#include "admin-state.h"
#include "completion.h"
#include "status-codes.h"
#include "types.h"
#include "vdo.h"

/**
 * struct action - An action to be performed in each of a set of zones.
 * @in_use: Whether this structure is in use.
 * @operation: The admin operation associated with this action.
 * @preamble: The method to run on the initiator thread before the action is applied to each zone.
 * @zone_action: The action to be performed in each zone.
 * @conclusion: The method to run on the initiator thread after the action is applied to each zone.
 * @parent: The object to notify when the action is complete.
 * @context: The action specific context.
 * @next: The action to perform after this one.
 */
struct action {
	bool in_use;
	const struct admin_state_code *operation;
	vdo_action_preamble_fn preamble;
	vdo_zone_action_fn zone_action;
	vdo_action_conclusion_fn conclusion;
	struct vdo_completion *parent;
	void *context;
	struct action *next;
};

/**
 * struct action_manager - Definition of an action manager.
 * @completion: The completion for performing actions.
 * @state: The state of this action manager.
 * @actions: The two action slots.
 * @current_action: The current action slot.
 * @zones: The number of zones in which an action is to be applied.
 * @Scheduler: A function to schedule a default next action.
 * @get_zone_thread_id: A function to get the id of the thread on which to apply an action to a
 *                      zone.
 * @initiator_thread_id: The ID of the thread on which actions may be initiated.
 * @context: Opaque data associated with this action manager.
 * @acting_zone: The zone currently being acted upon.
 */
struct action_manager {
	struct vdo_completion completion;
	struct admin_state state;
	struct action actions[2];
	struct action *current_action;
	zone_count_t zones;
	vdo_action_scheduler_fn scheduler;
	vdo_zone_thread_getter_fn get_zone_thread_id;
	thread_id_t initiator_thread_id;
	void *context;
	zone_count_t acting_zone;
};

static inline struct action_manager *as_action_manager(struct vdo_completion *completion)
{
	vdo_assert_completion_type(completion, VDO_ACTION_COMPLETION);
	return container_of(completion, struct action_manager, completion);
}

/* Implements vdo_action_scheduler_fn. */
static bool no_default_action(void *context __always_unused)
{
	return false;
}

/* Implements vdo_action_preamble_fn. */
static void no_preamble(void *context __always_unused, struct vdo_completion *completion)
{
	vdo_finish_completion(completion);
}

/* Implements vdo_action_conclusion_fn. */
static int no_conclusion(void *context __always_unused)
{
	return VDO_SUCCESS;
}

/**
 * vdo_make_action_manager() - Make an action manager.
 * @zones: The number of zones to which actions will be applied.
 * @get_zone_thread_id: A function to get the thread id associated with a zone.
 * @initiator_thread_id: The thread on which actions may initiated.
 * @context: The object which holds the per-zone context for the action.
 * @scheduler: A function to schedule a next action after an action concludes if there is no
 *             pending action (may be NULL).
 * @vdo: The vdo used to initialize completions.
 * @manager_ptr: A pointer to hold the new action manager.
 *
 * Return: VDO_SUCCESS or an error code.
 */
int vdo_make_action_manager(zone_count_t zones,
			    vdo_zone_thread_getter_fn get_zone_thread_id,
			    thread_id_t initiator_thread_id, void *context,
			    vdo_action_scheduler_fn scheduler, struct vdo *vdo,
			    struct action_manager **manager_ptr)
{
	struct action_manager *manager;
	int result = vdo_allocate(1, struct action_manager, __func__, &manager);

	if (result != VDO_SUCCESS)
		return result;

	*manager = (struct action_manager) {
		.zones = zones,
		.scheduler =
			((scheduler == NULL) ? no_default_action : scheduler),
		.get_zone_thread_id = get_zone_thread_id,
		.initiator_thread_id = initiator_thread_id,
		.context = context,
	};

	manager->actions[0].next = &manager->actions[1];
	manager->current_action = manager->actions[1].next =
		&manager->actions[0];
	vdo_set_admin_state_code(&manager->state, VDO_ADMIN_STATE_NORMAL_OPERATION);
	vdo_initialize_completion(&manager->completion, vdo, VDO_ACTION_COMPLETION);
	*manager_ptr = manager;
	return VDO_SUCCESS;
}

const struct admin_state_code *vdo_get_current_manager_operation(struct action_manager *manager)
{
	return vdo_get_admin_state_code(&manager->state);
}

void *vdo_get_current_action_context(struct action_manager *manager)
{
	return manager->current_action->in_use ? manager->current_action->context : NULL;
}

static void finish_action_callback(struct vdo_completion *completion);
static void apply_to_zone(struct vdo_completion *completion);

static thread_id_t get_acting_zone_thread_id(struct action_manager *manager)
{
	return manager->get_zone_thread_id(manager->context, manager->acting_zone);
}

static void preserve_error(struct vdo_completion *completion)
{
	if (completion->parent != NULL)
		vdo_set_completion_result(completion->parent, completion->result);

	vdo_reset_completion(completion);
	vdo_run_completion(completion);
}

static void prepare_for_next_zone(struct action_manager *manager)
{
	vdo_prepare_completion_for_requeue(&manager->completion, apply_to_zone,
					   preserve_error,
					   get_acting_zone_thread_id(manager),
					   manager->current_action->parent);
}

static void prepare_for_conclusion(struct action_manager *manager)
{
	vdo_prepare_completion_for_requeue(&manager->completion, finish_action_callback,
					   preserve_error, manager->initiator_thread_id,
					   manager->current_action->parent);
}

static void apply_to_zone(struct vdo_completion *completion)
{
	zone_count_t zone;
	struct action_manager *manager = as_action_manager(completion);

	VDO_ASSERT_LOG_ONLY((vdo_get_callback_thread_id() == get_acting_zone_thread_id(manager)),
			    "%s() called on acting zones's thread", __func__);

	zone = manager->acting_zone++;
	if (manager->acting_zone == manager->zones) {
		/*
		 * We are about to apply to the last zone. Once that is finished, we're done, so go
		 * back to the initiator thread and finish up.
		 */
		prepare_for_conclusion(manager);
	} else {
		/* Prepare to come back on the next zone */
		prepare_for_next_zone(manager);
	}

	manager->current_action->zone_action(manager->context, zone, completion);
}

static void handle_preamble_error(struct vdo_completion *completion)
{
	/* Skip the zone actions since the preamble failed. */
	completion->callback = finish_action_callback;
	preserve_error(completion);
}

static void launch_current_action(struct action_manager *manager)
{
	struct action *action = manager->current_action;
	int result = vdo_start_operation(&manager->state, action->operation);

	if (result != VDO_SUCCESS) {
		if (action->parent != NULL)
			vdo_set_completion_result(action->parent, result);

		/* We aren't going to run the preamble, so don't run the conclusion */
		action->conclusion = no_conclusion;
		finish_action_callback(&manager->completion);
		return;
	}

	if (action->zone_action == NULL) {
		prepare_for_conclusion(manager);
	} else {
		manager->acting_zone = 0;
		vdo_prepare_completion_for_requeue(&manager->completion, apply_to_zone,
						   handle_preamble_error,
						   get_acting_zone_thread_id(manager),
						   manager->current_action->parent);
	}

	action->preamble(manager->context, &manager->completion);
}

/**
 * vdo_schedule_default_action() - Attempt to schedule the default action.
 * @manager: The action manager.
 *
 * If the manager is not operating normally, the action will not be scheduled.
 *
 * Return: true if an action was scheduled.
 */
bool vdo_schedule_default_action(struct action_manager *manager)
{
	/* Don't schedule a default action if we are operating or not in normal operation. */
	const struct admin_state_code *code = vdo_get_current_manager_operation(manager);

	return ((code == VDO_ADMIN_STATE_NORMAL_OPERATION) &&
		manager->scheduler(manager->context));
}

static void finish_action_callback(struct vdo_completion *completion)
{
	bool has_next_action;
	int result;
	struct action_manager *manager = as_action_manager(completion);
	struct action action = *(manager->current_action);

	manager->current_action->in_use = false;
	manager->current_action = manager->current_action->next;

	/*
	 * We need to check this now to avoid use-after-free issues if running the conclusion or
	 * notifying the parent results in the manager being freed.
	 */
	has_next_action =
		(manager->current_action->in_use || vdo_schedule_default_action(manager));
	result = action.conclusion(manager->context);
	vdo_finish_operation(&manager->state, VDO_SUCCESS);
	if (action.parent != NULL)
		vdo_continue_completion(action.parent, result);

	if (has_next_action)
		launch_current_action(manager);
}

/**
 * vdo_schedule_action() - Schedule an action to be applied to all zones.
 * @manager: The action manager to schedule the action on.
 * @preamble: A method to be invoked on the initiator thread once this action is started but before
 *            applying to each zone; may be NULL.
 * @action: The action to apply to each zone; may be NULL.
 * @conclusion: A method to be invoked back on the initiator thread once the action has been
 *              applied to all zones; may be NULL.
 * @parent: The object to notify once the action is complete or if the action can not be scheduled;
 *          may be NULL.
 *
 * The action will be launched immediately if there is no current action, or as soon as the current
 * action completes. If there is already a pending action, this action will not be scheduled, and,
 * if it has a parent, that parent will be notified. At least one of the preamble, action, or
 * conclusion must not be NULL.
 *
 * Return: true if the action was scheduled.
 */
bool vdo_schedule_action(struct action_manager *manager, vdo_action_preamble_fn preamble,
			 vdo_zone_action_fn action, vdo_action_conclusion_fn conclusion,
			 struct vdo_completion *parent)
{
	return vdo_schedule_operation(manager, VDO_ADMIN_STATE_OPERATING, preamble,
				      action, conclusion, parent);
}

/**
 * vdo_schedule_operation() - Schedule an operation to be applied to all zones.
 * @manager: The action manager to schedule the action on.
 * @operation: The operation this action will perform
 * @preamble: A method to be invoked on the initiator thread once this action is started but before
 *            applying to each zone; may be NULL.
 * @action: The action to apply to each zone; may be NULL.
 * @conclusion: A method to be invoked back on the initiator thread once the action has been
 *              applied to all zones; may be NULL.
 * @parent: The object to notify once the action is complete or if the action can not be scheduled;
 *          may be NULL.
 *
 * The operation's action will be launched immediately if there is no current action, or as soon as
 * the current action completes. If there is already a pending action, this operation will not be
 * scheduled, and, if it has a parent, that parent will be notified. At least one of the preamble,
 * action, or conclusion must not be NULL.
 *
 * Return: true if the action was scheduled.
 */
bool vdo_schedule_operation(struct action_manager *manager,
			    const struct admin_state_code *operation,
			    vdo_action_preamble_fn preamble, vdo_zone_action_fn action,
			    vdo_action_conclusion_fn conclusion,
			    struct vdo_completion *parent)
{
	return vdo_schedule_operation_with_context(manager, operation, preamble, action,
						   conclusion, NULL, parent);
}

/**
 * vdo_schedule_operation_with_context() - Schedule an operation on all zones.
 * @manager: The action manager to schedule the action on.
 * @operation: The operation this action will perform.
 * @preamble: A method to be invoked on the initiator thread once this action is started but before
 *            applying to each zone; may be NULL.
 * @action: The action to apply to each zone; may be NULL.
 * @conclusion: A method to be invoked back on the initiator thread once the action has been
 *              applied to all zones; may be NULL.
 * @context: An action-specific context which may be retrieved via
 *           vdo_get_current_action_context(); may be NULL.
 * @parent: The object to notify once the action is complete or if the action can not be scheduled;
 *          may be NULL.
 *
 * The operation's action will be launched immediately if there is no current action, or as soon as
 * the current action completes. If there is already a pending action, this operation will not be
 * scheduled, and, if it has a parent, that parent will be notified. At least one of the preamble,
 * action, or conclusion must not be NULL.
 *
 * Return: true if the action was scheduled
 */
bool vdo_schedule_operation_with_context(struct action_manager *manager,
					 const struct admin_state_code *operation,
					 vdo_action_preamble_fn preamble,
					 vdo_zone_action_fn action,
					 vdo_action_conclusion_fn conclusion,
					 void *context, struct vdo_completion *parent)
{
	struct action *current_action;

	VDO_ASSERT_LOG_ONLY((vdo_get_callback_thread_id() == manager->initiator_thread_id),
			    "action initiated from correct thread");
	if (!manager->current_action->in_use) {
		current_action = manager->current_action;
	} else if (!manager->current_action->next->in_use) {
		current_action = manager->current_action->next;
	} else {
		if (parent != NULL)
			vdo_continue_completion(parent, VDO_COMPONENT_BUSY);

		return false;
	}

	*current_action = (struct action) {
		.in_use = true,
		.operation = operation,
		.preamble = (preamble == NULL) ? no_preamble : preamble,
		.zone_action = action,
		.conclusion = (conclusion == NULL) ? no_conclusion : conclusion,
		.context = context,
		.parent = parent,
		.next = current_action->next,
	};

	if (current_action == manager->current_action)
		launch_current_action(manager);

	return true;
}