Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * datachecksum_state.c
4 : * Background worker for enabling or disabling data checksums online as
5 : * well as functionality for manipulating data checksum state
6 : *
7 : * When enabling data checksums on a cluster at initdb time or when shut down
8 : * with pg_checksums, no extra process is required as each page is checksummed,
9 : * and verified, when accessed. When enabling checksums on an already running
10 : * cluster, this worker will ensure that all pages are checksummed before
11 : * verification of the checksums is turned on. In the case of disabling
12 : * checksums, the state transition is performed only in the control file, no
13 : * changes are performed on the data pages.
14 : *
15 : * Checksums can be either enabled or disabled cluster-wide, with on/off being
16 : * the end state for data_checksums.
17 : *
18 : * 1. Enabling checksums
19 : * ---------------------
20 : * When enabling checksums in an online cluster, data_checksums will be set to
21 : * "inprogress-on" which signals that write operations MUST compute and write
22 : * the checksum on the data page, but during reading the checksum SHALL NOT be
23 : * verified. This ensures that all objects created while checksums are being
24 : * enabled will have checksums set, but reads won't fail due to missing or
25 : * invalid checksums. Invalid checksums can be present in case the cluster had
26 : * checksums enabled, then disabled them and updated the page while they were
27 : * disabled.
28 : *
29 : * The DataChecksumsWorker will compile a list of all databases at the start,
30 : * any databases created concurrently will see the in-progress state and will
31 : * be checksummed automatically. All databases from the original list MUST BE
32 : * successfully processed in order for data checksums to be enabled, the only
33 : * exception are databases which are dropped before having been processed.
34 : *
35 : * For each database, all relations which have storage are read and every data
36 : * page is marked dirty to force a write with the checksum. This will generate
37 : * a lot of WAL as the entire database is read and written.
38 : *
39 : * If the processing is interrupted by a cluster crash or restart, it needs to
40 : * be restarted from the beginning again as state isn't persisted.
41 : *
42 : * 2. Disabling checksums
43 : * ----------------------
44 : * When disabling checksums, data_checksums will be set to "inprogress-off"
45 : * which signals that checksums are written but no longer need to be verified.
46 : * This ensures that backends which have not yet transitioned to the
47 : * "inprogress-off" state will still see valid checksums on pages.
48 : *
49 : * 3. Synchronization and Correctness
50 : * ----------------------------------
51 : * The processes involved in enabling or disabling data checksums in an
52 : * online cluster must be properly synchronized with the normal backends
53 : * serving concurrent queries to ensure correctness. Correctness is defined
54 : * as the following:
55 : *
56 : * - Backends SHALL NOT violate the data_checksums state they have agreed to
57 : * by acknowledging the procsignalbarrier: This means that all backends
58 : * MUST calculate and write data checksums during all states except off;
59 : * MUST validate checksums only in the 'on' state.
60 : * - Data checksums SHALL NOT be considered enabled cluster-wide until all
61 : * currently connected backends have state "on": This means that all
62 : * backends must wait on the procsignalbarrier to be acknowledged by all
63 : * before proceeding to validate data checksums.
64 : *
65 : * There are two steps of synchronization required for changing data_checksums
66 : * in an online cluster: (i) changing state in the active backends ("on",
67 : * "off", "inprogress-on" and "inprogress-off"), and (ii) ensuring no
68 : * incompatible objects and processes are left in a database when workers end.
69 : * The former deals with cluster-wide agreement on data checksum state and the
70 : * latter with ensuring that any concurrent activity cannot break the data
71 : * checksum contract during processing.
72 : *
73 : * Synchronizing the state change is done with procsignal barriers. Before
74 : * updating the data_checksums state in the control file, all other backends must absorb the
75 : * barrier. Barrier absorption will happen during interrupt processing, which
76 : * means that connected backends will change state at different times. If
77 : * waiting for a barrier is done during startup, for example during replay, it
78 : * is important to realize that any locks held by the startup process might
79 : * cause deadlocks if backends end up waiting for those locks while startup
80 : * is waiting for a procsignalbarrier.
81 : *
82 : * 3.1 When Enabling Data Checksums
83 : * --------------------------------
84 : * A process which fails to observe data checksums being enabled can induce two
85 : * types of errors: failing to write the checksum when modifying the page and
86 : * failing to validate the data checksum on the page when reading it.
87 : *
88 : * When processing starts all backends belong to one of the below sets, with
89 : * one of Bd and Bi being empty:
90 : *
91 : * Bg: Backend updating the global state and emitting the procsignalbarrier
92 : * Bd: Backends in "off" state
93 : * Bi: Backends in "inprogress-on" state
94 : *
95 : * If processing is started in an online cluster then all backends are in Bd.
96 : * If processing was halted by the cluster shutting down (due to a crash or
97 : * intentional restart), the controlfile state "inprogress-on" will be observed
98 : * on system startup and all backends will be placed in Bd. The controlfile
99 : * state will also be set to "off".
100 : *
101 : * Backends transition Bd -> Bi via a procsignalbarrier which is emitted by the
102 : * DataChecksumsWorkerLauncherMain. When all backends have acknowledged the
103 : * barrier then Bd will be empty and the next phase can begin: calculating and
104 : * writing data checksums with DataChecksumsWorkers. When the
105 : * DataChecksumsWorker processes have finished writing checksums on all pages,
106 : * data checksums are enabled cluster-wide via another procsignalbarrier.
107 : * There are four sets of backends where Bd shall be an empty set:
108 : *
109 : * Bg: Backend updating the global state and emitting the procsignalbarrier
110 : * Bd: Backends in "off" state
111 : * Be: Backends in "on" state
112 : * Bi: Backends in "inprogress-on" state
113 : *
114 : * Backends in Bi and Be will write checksums when modifying a page, but only
115 : * backends in Be will verify the checksum during reading. The Bg backend is
116 : * blocked waiting for all backends in Bi to process interrupts and move to
117 : * Be. Any backend starting while Bg is waiting on the procsignalbarrier will
118 : * observe the global state being "on" and will thus automatically belong to
119 : * Be. Checksums are enabled cluster-wide when Bi is an empty set. Bi and Be
120 : * are compatible sets while still operating based on their local state as
121 : * both write data checksums.
122 : *
123 : * 3.2 When Disabling Data Checksums
124 : * ---------------------------------
125 : * A process which fails to observe that data checksums have been disabled
126 : * can induce two types of errors: writing the checksum when modifying the
127 : * page and validating a data checksum which is no longer correct due to
128 : * modifications to the page. The former is not an error per se as data
129 : * integrity is maintained, but it is wasteful. The latter will cause errors
130 : * in user operations. Assuming the following sets of backends:
131 : *
132 : * Bg: Backend updating the global state and emitting the procsignalbarrier
133 : * Bd: Backends in "off" state
134 : * Be: Backends in "on" state
135 : * Bo: Backends in "inprogress-off" state
136 : * Bi: Backends in "inprogress-on" state
137 : *
138 : * Backends transition from the Be state to Bd like so: Be -> Bo -> Bd. From
139 : * all other states, the transition can be straight to Bd.
140 : *
141 : * The goal is to transition all backends to Bd making the others empty sets.
142 : * Backends in Bo write data checksums, but don't validate them, such that
143 : * backends still in Be can continue to validate pages until the barrier has
144 : * been absorbed such that they are in Bo. Once all backends are in Bo, the
145 : * barrier to transition to "off" can be raised and all backends can safely
146 : * stop writing data checksums as no backend is enforcing data checksum
147 : * validation any longer.
148 : *
149 : * 4. Future opportunities for optimizations
150 : * -----------------------------------------
151 : * Below are some potential optimizations and improvements which were brought
152 : * up during reviews of this feature, but which weren't implemented in the
153 : * initial version. These are ideas listed without any validation on their
154 : * feasibility or potential payoff. More discussion on (most of) these can be
155 : * found on the -hackers threads linked to in the commit message of this
156 : * feature.
157 : *
158 : * * Launching datachecksumsworker for resuming operation from the startup
159 : * process: Currently users have to restart processing manually after a
160 : * restart since dynamic background worker cannot be started from the
161 : * postmaster. Changing the startup process could make restarting the
162 : * processing automatic on cluster restart.
163 : * * Avoid dirtying the page when checksums already match: Iff the checksum
164 : * on the page happens to already match we still dirty the page. It should
165 : * be enough to only do the log_newpage_buffer() call in that case.
166 : * * Teach pg_checksums to avoid checksummed pages when pg_checksums is used
167 : * to enable checksums on a cluster which is in inprogress-on state and
168 : * may have checksummed pages (make pg_checksums be able to resume an
169 : * online operation). This should only be attempted for wal_level minimal.
170 : * * Restartability (not necessarily with page granularity).
171 : * * Avoid processing databases which were created during inprogress-on.
172 : * Right now all databases are processed regardless to be safe.
173 : * * Teach CREATE DATABASE to calculate checksums for databases created
174 : * during inprogress-on with a template database which has yet to be
175 : * processed.
176 : *
177 : *
178 : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
179 : * Portions Copyright (c) 1994, Regents of the University of California
180 : *
181 : *
182 : * IDENTIFICATION
183 : * src/backend/postmaster/datachecksum_state.c
184 : *
185 : *-------------------------------------------------------------------------
186 : */
187 : #include "postgres.h"
188 :
189 : #include "access/genam.h"
190 : #include "access/heapam.h"
191 : #include "access/htup_details.h"
192 : #include "access/xact.h"
193 : #include "access/xlog.h"
194 : #include "access/xloginsert.h"
195 : #include "catalog/indexing.h"
196 : #include "catalog/pg_class.h"
197 : #include "catalog/pg_database.h"
198 : #include "commands/progress.h"
199 : #include "commands/vacuum.h"
200 : #include "common/relpath.h"
201 : #include "miscadmin.h"
202 : #include "pgstat.h"
203 : #include "postmaster/bgworker.h"
204 : #include "postmaster/bgwriter.h"
205 : #include "postmaster/datachecksum_state.h"
206 : #include "storage/bufmgr.h"
207 : #include "storage/checksum.h"
208 : #include "storage/ipc.h"
209 : #include "storage/latch.h"
210 : #include "storage/lmgr.h"
211 : #include "storage/lwlock.h"
212 : #include "storage/procarray.h"
213 : #include "storage/smgr.h"
214 : #include "storage/subsystems.h"
215 : #include "tcop/tcopprot.h"
216 : #include "utils/builtins.h"
217 : #include "utils/fmgroids.h"
218 : #include "utils/injection_point.h"
219 : #include "utils/lsyscache.h"
220 : #include "utils/ps_status.h"
221 : #include "utils/syscache.h"
222 : #include "utils/wait_event.h"
223 :
224 : /*
225 : * Configuration of conditions which must match when absorbing a procsignal
226 : * barrier during data checksum enable/disable operations. A single function
227 : * is used for absorbing all barriers, and the current and target states must
228 : * be defined as a from/to tuple in the checksum_barriers struct.
229 : */
230 : typedef struct ChecksumBarrierCondition
231 : {
232 : /* Current state of data checksums */
233 : int from;
234 : /* Target state for data checksums */
235 : int to;
236 : } ChecksumBarrierCondition;
237 :
238 : static const ChecksumBarrierCondition checksum_barriers[9] =
239 : {
240 : /*
241 : * Disabling checksums: If checksums are currently enabled, disabling must
242 : * go through the 'inprogress-off' state.
243 : */
244 : {PG_DATA_CHECKSUM_VERSION, PG_DATA_CHECKSUM_INPROGRESS_OFF},
245 : {PG_DATA_CHECKSUM_INPROGRESS_OFF, PG_DATA_CHECKSUM_OFF},
246 :
247 : /*
248 : * If checksums are in the process of being enabled, but are not yet being
249 : * verified, we can abort by going back to 'off' state.
250 : */
251 : {PG_DATA_CHECKSUM_INPROGRESS_ON, PG_DATA_CHECKSUM_OFF},
252 :
253 : /*
254 : * Enabling checksums must normally go through the 'inprogress-on' state.
255 : */
256 : {PG_DATA_CHECKSUM_OFF, PG_DATA_CHECKSUM_INPROGRESS_ON},
257 : {PG_DATA_CHECKSUM_INPROGRESS_ON, PG_DATA_CHECKSUM_VERSION},
258 :
259 : /*
260 : * If checksums are being disabled but all backends are still computing
261 : * checksums, we can go straight back to 'on'
262 : */
263 : {PG_DATA_CHECKSUM_INPROGRESS_OFF, PG_DATA_CHECKSUM_VERSION},
264 :
265 : /*
266 : * If checksums are being enabled when launcher_exit is executed, state is
267 : * set to off since we cannot reach on at that point.
268 : */
269 : {PG_DATA_CHECKSUM_INPROGRESS_ON, PG_DATA_CHECKSUM_INPROGRESS_OFF},
270 :
271 : /*
272 : * Transitions that can happen when a new request is made while another is
273 : * currently being processed.
274 : */
275 : {PG_DATA_CHECKSUM_INPROGRESS_OFF, PG_DATA_CHECKSUM_INPROGRESS_ON},
276 : {PG_DATA_CHECKSUM_OFF, PG_DATA_CHECKSUM_INPROGRESS_OFF},
277 : };
278 :
279 : /* Possible operations the DataChecksumsWorker can perform */
280 : typedef enum DataChecksumsWorkerOperation
281 : {
282 : ENABLE_DATACHECKSUMS,
283 : DISABLE_DATACHECKSUMS,
284 : } DataChecksumsWorkerOperation;
285 :
286 : /* Possible states for a database entry which has been processed */
287 : typedef enum
288 : {
289 : DATACHECKSUMSWORKER_SUCCESSFUL = 0,
290 : DATACHECKSUMSWORKER_ABORTED,
291 : DATACHECKSUMSWORKER_FAILED,
292 : DATACHECKSUMSWORKER_DROPDB,
293 : } DataChecksumsWorkerResult;
294 :
295 : /*
296 : * Signaling between backends calling pg_enable/disable_data_checksums, the
297 : * checksums launcher process, and the checksums worker process.
298 : *
299 : * This struct is protected by DataChecksumsWorkerLock
300 : */
301 : typedef struct DataChecksumsStateStruct
302 : {
303 : /*
304 : * These are set by pg_{enable|disable}_data_checksums, to tell the
305 : * launcher what the target state is.
306 : */
307 : DataChecksumsWorkerOperation launch_operation;
308 : int launch_cost_delay;
309 : int launch_cost_limit;
310 :
311 : /*
312 : * Is a launcher process currently running? This is set by the main
313 : * launcher process, after it has read the above launch_* parameters.
314 : */
315 : bool launcher_running;
316 :
317 : /*
318 : * Every time a new worker is launched, it's assigned a unique invocation
319 : * number by incrementing this counter.
320 : */
321 : uint64 worker_invocation_counter;
322 :
323 : /*
324 : * Information about the current worker, if it's currently running. These
325 : * are set by the worker launcher.
326 : */
327 : uint64 worker_invocation; /* unique invocation number */
328 : Oid database_oid; /* database it's processing */
329 : pid_t worker_pid; /* worker process's PID */
330 :
331 : /*
332 : * These fields indicate the target state that the worker is currently
333 : * running with. They can be different from the corresponding launch_*
334 : * fields, if a new pg_enable/disable_data_checksums() call was made while
335 : * the launcher/worker was already running. The worker will periodically
336 : * check if new cost settings have been requested, and if so will copy
337 : * them from the launch_* fields and reset cost throttling to match the
338 : * new values.
339 : */
340 : DataChecksumsWorkerOperation operation;
341 : int cost_delay;
342 : int cost_limit;
343 :
344 : /*
345 : * Signaling between the launcher and the worker process. Protected by
346 : * DataChecksumsWorkerLock.
347 : */
348 :
349 : /* result, set by worker before exiting */
350 : DataChecksumsWorkerResult worker_result;
351 :
352 : /*
353 : * Tells the worker process whether it should also process the shared
354 : * catalogs
355 : */
356 : bool process_shared_catalogs;
357 : } DataChecksumsStateStruct;
358 :
359 : /* Shared memory segment for datachecksumsworker */
360 : static DataChecksumsStateStruct *DataChecksumState;
361 :
362 : typedef struct DataChecksumsWorkerDatabase
363 : {
364 : Oid dboid;
365 : char *dbname;
366 : } DataChecksumsWorkerDatabase;
367 :
368 : /* Flag set by the interrupt handler */
369 : static volatile sig_atomic_t abort_requested = false;
370 :
371 : static uint64 worker_invocation;
372 :
373 : /*
374 : * Have we set the DataChecksumsStateStruct->launcher_running flag?
375 : * If we have, we need to clear it before exiting!
376 : */
377 : static volatile sig_atomic_t launcher_running = false;
378 :
379 : /* Are we enabling data checksums, or disabling them? */
380 : static DataChecksumsWorkerOperation operation;
381 :
382 : /* Prototypes */
383 : static void StartDataChecksumsWorkerLauncher(DataChecksumsWorkerOperation op,
384 : int cost_delay,
385 : int cost_limit);
386 : static void DataChecksumsShmemRequest(void *arg);
387 : static bool DatabaseExists(Oid dboid);
388 : static List *BuildDatabaseList(void);
389 : static List *BuildRelationList(bool temp_relations, bool include_shared);
390 : static void FreeDatabaseList(List *dblist);
391 : static DataChecksumsWorkerResult ProcessDatabase(DataChecksumsWorkerDatabase *db);
392 : static bool ProcessAllDatabases(void);
393 : static bool ProcessSingleRelationFork(Relation reln, ForkNumber forkNum, BufferAccessStrategy strategy);
394 : static void launcher_cancel_handler(SIGNAL_ARGS);
395 : static void WaitForAllTransactionsToFinish(void);
396 :
397 : const ShmemCallbacks DataChecksumsShmemCallbacks = {
398 : .request_fn = DataChecksumsShmemRequest,
399 : };
400 :
401 : #define CHECK_FOR_LAUNCHER_ABORT_REQUEST() \
402 : do { \
403 : Assert(MyBackendType == B_DATACHECKSUMSWORKER_LAUNCHER); \
404 : LWLockAcquire(DataChecksumsWorkerLock, LW_SHARED); \
405 : if (DataChecksumState->launch_operation != operation) \
406 : abort_requested = true; \
407 : LWLockRelease(DataChecksumsWorkerLock); \
408 : } while (0)
409 :
410 : #define CHECK_FOR_WORKER_ABORT_REQUEST() \
411 : do { \
412 : Assert(MyBackendType == B_DATACHECKSUMSWORKER_WORKER); \
413 : LWLockAcquire(DataChecksumsWorkerLock, LW_SHARED); \
414 : if (DataChecksumState->worker_invocation != worker_invocation || \
415 : DataChecksumState->launch_operation != operation) \
416 : abort_requested = true; \
417 : LWLockRelease(DataChecksumsWorkerLock); \
418 : } while (0)
419 :
420 :
421 : /*****************************************************************************
422 : * Functionality for manipulating the data checksum state in the cluster
423 : */
424 :
425 : void
426 8 : EmitAndWaitDataChecksumsBarrier(uint32 state)
427 : {
428 : uint64 barrier;
429 :
430 8 : switch (state)
431 : {
432 3 : case PG_DATA_CHECKSUM_INPROGRESS_ON:
433 3 : barrier = EmitProcSignalBarrier(PROCSIGNAL_BARRIER_CHECKSUM_INPROGRESS_ON);
434 3 : WaitForProcSignalBarrier(barrier);
435 3 : break;
436 :
437 1 : case PG_DATA_CHECKSUM_INPROGRESS_OFF:
438 1 : barrier = EmitProcSignalBarrier(PROCSIGNAL_BARRIER_CHECKSUM_INPROGRESS_OFF);
439 1 : WaitForProcSignalBarrier(barrier);
440 1 : break;
441 :
442 2 : case PG_DATA_CHECKSUM_VERSION:
443 2 : barrier = EmitProcSignalBarrier(PROCSIGNAL_BARRIER_CHECKSUM_ON);
444 2 : WaitForProcSignalBarrier(barrier);
445 2 : break;
446 :
447 2 : case PG_DATA_CHECKSUM_OFF:
448 2 : barrier = EmitProcSignalBarrier(PROCSIGNAL_BARRIER_CHECKSUM_OFF);
449 2 : WaitForProcSignalBarrier(barrier);
450 2 : break;
451 :
452 8 : default:
453 : Assert(false);
454 : }
455 8 : }
456 :
457 : /*
458 : * AbsorbDataChecksumsBarrier
459 : * Generic function for absorbing data checksum state changes
460 : *
461 : * All procsignalbarriers regarding data checksum state changes are absorbed
462 : * with this function. The set of conditions required for the state change to
463 : * be accepted are listed in the checksum_barriers struct, target_state is
464 : * used to look up the relevant entry.
465 : */
466 : bool
467 277 : AbsorbDataChecksumsBarrier(ProcSignalBarrierType barrier)
468 : {
469 : uint32 target_state;
470 277 : int current = data_checksums;
471 277 : bool found = false;
472 :
473 : /*
474 : * Translate the barrier condition to the target state, doing it here
475 : * instead of in the procsignal code saves the latter from knowing about
476 : * checksum states.
477 : */
478 277 : switch (barrier)
479 : {
480 95 : case PROCSIGNAL_BARRIER_CHECKSUM_INPROGRESS_ON:
481 95 : target_state = PG_DATA_CHECKSUM_INPROGRESS_ON;
482 95 : break;
483 72 : case PROCSIGNAL_BARRIER_CHECKSUM_ON:
484 72 : target_state = PG_DATA_CHECKSUM_VERSION;
485 72 : break;
486 52 : case PROCSIGNAL_BARRIER_CHECKSUM_INPROGRESS_OFF:
487 52 : target_state = PG_DATA_CHECKSUM_INPROGRESS_OFF;
488 52 : break;
489 58 : case PROCSIGNAL_BARRIER_CHECKSUM_OFF:
490 58 : target_state = PG_DATA_CHECKSUM_OFF;
491 58 : break;
492 0 : default:
493 0 : elog(ERROR, "incorrect barrier \"%i\" received", barrier);
494 : }
495 :
496 : /*
497 : * If the target state matches the current state then the barrier has been
498 : * repeated.
499 : */
500 277 : if (current == target_state)
501 1 : return true;
502 :
503 : /*
504 : * If the cluster is in recovery we skip the validation of current state
505 : * since the replay is trusted.
506 : */
507 276 : if (RecoveryInProgress())
508 : {
509 48 : SetLocalDataChecksumState(target_state);
510 48 : return true;
511 : }
512 :
513 : /*
514 : * Find the barrier condition definition for the target state. Not finding
515 : * a condition would be a grave programmer error as the states are a
516 : * discrete set.
517 : */
518 1042 : for (int i = 0; i < lengthof(checksum_barriers) && !found; i++)
519 : {
520 814 : if (checksum_barriers[i].from == current && checksum_barriers[i].to == target_state)
521 228 : found = true;
522 : }
523 :
524 : /*
525 : * If the relevant state criteria aren't satisfied, throw an error which
526 : * will be caught by the procsignal machinery for a later retry.
527 : */
528 228 : if (!found)
529 0 : ereport(ERROR,
530 : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
531 : errmsg("incorrect data checksum state %i for target state %i",
532 : current, target_state));
533 :
534 228 : SetLocalDataChecksumState(target_state);
535 228 : return true;
536 : }
537 :
538 :
539 : /*
540 : * Disables data checksums for the cluster, if applicable. Starts a background
541 : * worker which turns off the data checksums.
542 : */
543 : Datum
544 7 : disable_data_checksums(PG_FUNCTION_ARGS)
545 : {
546 7 : PreventCommandDuringRecovery("pg_disable_data_checksums()");
547 :
548 7 : if (!superuser())
549 0 : ereport(ERROR,
550 : errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
551 : errmsg("must be superuser to change data checksum state"));
552 :
553 7 : StartDataChecksumsWorkerLauncher(DISABLE_DATACHECKSUMS, 0, 0);
554 7 : PG_RETURN_VOID();
555 : }
556 :
557 : /*
558 : * Enables data checksums for the cluster, if applicable. Supports vacuum-
559 : * like cost based throttling to limit system load. Starts a background worker
560 : * which updates data checksums on existing data.
561 : */
562 : Datum
563 11 : enable_data_checksums(PG_FUNCTION_ARGS)
564 : {
565 11 : int cost_delay = PG_GETARG_INT32(0);
566 11 : int cost_limit = PG_GETARG_INT32(1);
567 :
568 11 : PreventCommandDuringRecovery("pg_enable_data_checksums()");
569 :
570 11 : if (!superuser())
571 0 : ereport(ERROR,
572 : errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
573 : errmsg("must be superuser to change data checksum state"));
574 :
575 11 : if (cost_delay < 0)
576 0 : ereport(ERROR,
577 : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
578 : errmsg("cost delay cannot be a negative value"));
579 :
580 11 : if (cost_limit <= 0)
581 0 : ereport(ERROR,
582 : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
583 : errmsg("cost limit must be greater than zero"));
584 :
585 11 : StartDataChecksumsWorkerLauncher(ENABLE_DATACHECKSUMS, cost_delay, cost_limit);
586 :
587 11 : PG_RETURN_VOID();
588 : }
589 :
590 :
591 : /*****************************************************************************
592 : * Functionality for running the datachecksumsworker and associated launcher
593 : */
594 :
595 : /*
596 : * StartDataChecksumsWorkerLauncher
597 : * Start the datachecksumsworker launcher process, if not running yet
598 : *
599 : * This is called to start data checksums processing for enabling as well as
600 : * disabling.
601 : */
602 : static void
603 18 : StartDataChecksumsWorkerLauncher(DataChecksumsWorkerOperation op,
604 : int cost_delay,
605 : int cost_limit)
606 : {
607 : BackgroundWorker bgw;
608 : BackgroundWorkerHandle *bgw_handle;
609 : bool running;
610 :
611 : #ifdef USE_ASSERT_CHECKING
612 : /* The cost delay settings have no effect when disabling */
613 : if (op == DISABLE_DATACHECKSUMS)
614 : Assert(cost_delay == 0 && cost_limit == 0);
615 : #endif
616 :
617 18 : INJECTION_POINT("datachecksumsworker-startup-delay", NULL);
618 :
619 : /* Store the desired state in shared memory */
620 18 : LWLockAcquire(DataChecksumsWorkerLock, LW_EXCLUSIVE);
621 :
622 18 : DataChecksumState->launch_operation = op;
623 18 : DataChecksumState->launch_cost_delay = cost_delay;
624 18 : DataChecksumState->launch_cost_limit = cost_limit;
625 :
626 : /* Is the launcher already running? If so, what is it doing? */
627 18 : running = DataChecksumState->launcher_running;
628 :
629 18 : LWLockRelease(DataChecksumsWorkerLock);
630 :
631 : /*
632 : * Launch a new launcher process, if it's not running already.
633 : *
634 : * If the launcher is currently busy enabling the checksums, and we want
635 : * them disabled (or vice versa), the launcher will notice that at latest
636 : * when it's about to exit, and will loop back to process the new request.
637 : * So if the launcher is already running, we don't need to do anything
638 : * more here to abort it.
639 : *
640 : * If you call pg_enable/disable_data_checksums() twice in a row, before
641 : * the launcher has had a chance to start up, we still end up launching it
642 : * twice. That's OK, the second invocation will see that a launcher is
643 : * already running and exit quickly.
644 : */
645 18 : if (!running)
646 : {
647 18 : if ((op == ENABLE_DATACHECKSUMS && DataChecksumsOn()) ||
648 7 : (op == DISABLE_DATACHECKSUMS && DataChecksumsOff()))
649 : {
650 3 : ereport(LOG,
651 : errmsg("data checksums already in desired state, exiting"));
652 3 : return;
653 : }
654 :
655 : /*
656 : * Prepare the BackgroundWorker and launch it.
657 : */
658 15 : memset(&bgw, 0, sizeof(bgw));
659 15 : bgw.bgw_flags = BGWORKER_SHMEM_ACCESS | BGWORKER_BACKEND_DATABASE_CONNECTION;
660 15 : bgw.bgw_start_time = BgWorkerStart_RecoveryFinished;
661 15 : snprintf(bgw.bgw_library_name, BGW_MAXLEN, "postgres");
662 15 : snprintf(bgw.bgw_function_name, BGW_MAXLEN, "DataChecksumsWorkerLauncherMain");
663 15 : snprintf(bgw.bgw_name, BGW_MAXLEN, "datachecksums launcher");
664 15 : snprintf(bgw.bgw_type, BGW_MAXLEN, "datachecksums launcher");
665 15 : bgw.bgw_restart_time = BGW_NEVER_RESTART;
666 15 : bgw.bgw_notify_pid = MyProcPid;
667 15 : bgw.bgw_main_arg = (Datum) 0;
668 :
669 15 : if (!RegisterDynamicBackgroundWorker(&bgw, &bgw_handle))
670 0 : ereport(ERROR,
671 : errcode(ERRCODE_INSUFFICIENT_RESOURCES),
672 : errmsg("failed to start background worker to process data checksums"));
673 : }
674 : else
675 : {
676 0 : ereport(LOG,
677 : errmsg("data checksum processing already running"));
678 : }
679 : }
680 :
681 : /*
682 : * ProcessSingleRelationFork
683 : * Enable data checksums in a single relation/fork.
684 : *
685 : * Returns true if successful, and false if *aborted*. On error, an actual
686 : * error is raised in the lower levels.
687 : */
688 : static bool
689 7704 : ProcessSingleRelationFork(Relation reln, ForkNumber forkNum, BufferAccessStrategy strategy)
690 : {
691 7704 : BlockNumber numblocks = RelationGetNumberOfBlocksInFork(reln, forkNum);
692 : char activity[NAMEDATALEN * 2 + 128];
693 : char *relns;
694 :
695 7704 : relns = get_namespace_name(RelationGetNamespace(reln));
696 :
697 : /* Report the current relation to pg_stat_activity */
698 7704 : snprintf(activity, sizeof(activity) - 1, "processing: %s.%s (%s, %u blocks)",
699 7704 : (relns ? relns : ""), RelationGetRelationName(reln), forkNames[forkNum], numblocks);
700 7704 : pgstat_report_activity(STATE_RUNNING, activity);
701 7704 : pgstat_progress_update_param(PROGRESS_DATACHECKSUMS_BLOCKS_TOTAL, numblocks);
702 7704 : if (relns)
703 7704 : pfree(relns);
704 :
705 : /*
706 : * We are looping over the blocks which existed at the time of process
707 : * start, which is safe since new blocks are created with checksums set
708 : * already due to the state being "inprogress-on".
709 : */
710 48348 : for (BlockNumber blknum = 0; blknum < numblocks; blknum++)
711 : {
712 40645 : Buffer buf = ReadBufferExtended(reln, forkNum, blknum, RBM_NORMAL, strategy);
713 :
714 : /* Need to get an exclusive lock to mark the buffer as dirty */
715 40645 : LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
716 :
717 : /*
718 : * Mark the buffer as dirty and force a full page write. We have to
719 : * re-write the page to WAL even if the checksum hasn't changed,
720 : * because if there is a replica it might have a slightly different
721 : * version of the page with an invalid checksum, caused by unlogged
722 : * changes (e.g. hint bits) on the primary happening while checksums
723 : * were off. This can happen if there was a valid checksum on the page
724 : * at one point in the past, so only when checksums are first on, then
725 : * off, and then turned on again. TODO: investigate if this could be
726 : * avoided if the checksum is calculated to be correct and wal_level
727 : * is set to "minimal".
728 : *
729 : * Unlogged relations don't need WAL since they are reset to their
730 : * init fork on recovery. We still dirty the buffer so that the
731 : * checksum is written to disk at the next checkpoint.
732 : *
733 : * The init fork is an exception: it is WAL-logged so the standby can
734 : * materialize the relation after promotion (see
735 : * ResetUnloggedRelations()). Skipping it here would leave the
736 : * standby with a stale init fork that, once copied to the main fork
737 : * on promotion, would fail checksum verification on every read.
738 : */
739 40645 : START_CRIT_SECTION();
740 40645 : MarkBufferDirty(buf);
741 40645 : if (RelationNeedsWAL(reln) || forkNum == INIT_FORKNUM)
742 40611 : log_newpage_buffer(buf, false);
743 40645 : END_CRIT_SECTION();
744 :
745 40645 : UnlockReleaseBuffer(buf);
746 :
747 : /* Check if we are asked to abort, the abortion will bubble up. */
748 : Assert(operation == ENABLE_DATACHECKSUMS);
749 40645 : CHECK_FOR_WORKER_ABORT_REQUEST();
750 40645 : if (abort_requested)
751 0 : return false;
752 :
753 : /* update the block counter */
754 40645 : pgstat_progress_update_param(PROGRESS_DATACHECKSUMS_BLOCKS_DONE,
755 40645 : (blknum + 1));
756 :
757 : /*
758 : * Processing is re-using the vacuum cost delay for process
759 : * throttling, hence why we call vacuum APIs here.
760 : */
761 40645 : vacuum_delay_point(false);
762 : }
763 :
764 7703 : return true;
765 : }
766 :
767 : /*
768 : * ProcessSingleRelationByOid
769 : * Process a single relation based on oid.
770 : *
771 : * Returns true if successful, and false if *aborted*. On error, an actual
772 : * error is raised in the lower levels.
773 : */
774 : static bool
775 5968 : ProcessSingleRelationByOid(Oid relationId, BufferAccessStrategy strategy)
776 : {
777 : Relation rel;
778 5968 : bool aborted = false;
779 :
780 5968 : StartTransactionCommand();
781 :
782 5968 : rel = try_relation_open(relationId, AccessShareLock);
783 5968 : if (rel == NULL)
784 : {
785 : /*
786 : * Relation no longer exists. We don't consider this an error since
787 : * there are no pages in it that need data checksums, and thus return
788 : * true. The worker operates off a list of relations generated at the
789 : * start of processing, so relations being dropped in the meantime is
790 : * to be expected.
791 : */
792 0 : CommitTransactionCommand();
793 0 : pgstat_report_activity(STATE_IDLE, NULL);
794 0 : return true;
795 : }
796 5968 : RelationGetSmgr(rel);
797 :
798 29836 : for (ForkNumber fnum = 0; fnum <= MAX_FORKNUM; fnum++)
799 : {
800 23869 : if (smgrexists(rel->rd_smgr, fnum))
801 : {
802 7704 : if (!ProcessSingleRelationFork(rel, fnum, strategy))
803 : {
804 0 : aborted = true;
805 0 : break;
806 : }
807 : }
808 : }
809 5967 : relation_close(rel, AccessShareLock);
810 :
811 5967 : CommitTransactionCommand();
812 5967 : pgstat_report_activity(STATE_IDLE, NULL);
813 :
814 5967 : return !aborted;
815 : }
816 :
817 : /*
818 : * ProcessDatabase
819 : * Enable data checksums in a single database.
820 : *
821 : * We do this by launching a dynamic background worker into this database, and
822 : * waiting for it to finish. We have to do this in a separate worker, since
823 : * each process can only be connected to one database during its lifetime.
824 : */
825 : static DataChecksumsWorkerResult
826 23 : ProcessDatabase(DataChecksumsWorkerDatabase *db)
827 : {
828 : BackgroundWorker bgw;
829 : BackgroundWorkerHandle *bgw_handle;
830 : BgwHandleStatus status;
831 : pid_t pid;
832 : uint64 invocation;
833 : char activity[NAMEDATALEN + 64];
834 : DataChecksumsWorkerResult result;
835 :
836 23 : LWLockAcquire(DataChecksumsWorkerLock, LW_EXCLUSIVE);
837 :
838 : /*
839 : * Initialize result to FAILED. The worker will change it to SUCCESSFUL
840 : * if it completes successfully.
841 : */
842 23 : DataChecksumState->worker_result = DATACHECKSUMSWORKER_FAILED;
843 23 : DataChecksumState->worker_pid = InvalidPid;
844 :
845 23 : invocation = ++DataChecksumState->worker_invocation_counter;
846 23 : DataChecksumState->worker_invocation = invocation;
847 23 : DataChecksumState->database_oid = db->dboid;
848 :
849 23 : LWLockRelease(DataChecksumsWorkerLock);
850 :
851 23 : memset(&bgw, 0, sizeof(bgw));
852 23 : bgw.bgw_flags = BGWORKER_SHMEM_ACCESS | BGWORKER_BACKEND_DATABASE_CONNECTION;
853 23 : bgw.bgw_start_time = BgWorkerStart_RecoveryFinished;
854 23 : snprintf(bgw.bgw_library_name, BGW_MAXLEN, "postgres");
855 23 : snprintf(bgw.bgw_function_name, BGW_MAXLEN, "%s", "DataChecksumsWorkerMain");
856 23 : snprintf(bgw.bgw_name, BGW_MAXLEN, "datachecksums worker");
857 23 : snprintf(bgw.bgw_type, BGW_MAXLEN, "datachecksums worker");
858 23 : bgw.bgw_restart_time = BGW_NEVER_RESTART;
859 23 : bgw.bgw_notify_pid = MyProcPid;
860 : /* pass the invocation number to the worker process */
861 23 : bgw.bgw_main_arg = UInt64GetDatum(invocation);
862 :
863 : /*
864 : * If there are no worker slots available, there is little we can do. If
865 : * we retry in a bit it's still unlikely that the user has managed to
866 : * reconfigure in the meantime and we'd be run through retries fast.
867 : */
868 23 : if (!RegisterDynamicBackgroundWorker(&bgw, &bgw_handle))
869 : {
870 0 : ereport(WARNING,
871 : errmsg("could not start background worker for enabling data checksums in database \"%s\"",
872 : db->dbname),
873 : errhint("The \"%s\" setting might be too low.", "max_worker_processes"));
874 0 : return DATACHECKSUMSWORKER_FAILED;
875 : }
876 :
877 23 : status = WaitForBackgroundWorkerStartup(bgw_handle, &pid);
878 23 : if (status == BGWH_STOPPED)
879 : {
880 : /*
881 : * If the worker managed to start, and stop, before we got to waiting
882 : * for it we can see a STOPPED status here without it being a failure.
883 : */
884 0 : LWLockAcquire(DataChecksumsWorkerLock, LW_SHARED);
885 : Assert(DataChecksumState->worker_invocation == invocation);
886 0 : if (DataChecksumState->worker_result == DATACHECKSUMSWORKER_SUCCESSFUL)
887 : {
888 0 : LWLockRelease(DataChecksumsWorkerLock);
889 0 : pgstat_report_activity(STATE_IDLE, NULL);
890 0 : return DATACHECKSUMSWORKER_SUCCESSFUL;
891 : }
892 0 : LWLockRelease(DataChecksumsWorkerLock);
893 :
894 0 : ereport(WARNING,
895 : errmsg("could not start background worker for enabling data checksums in database \"%s\"",
896 : db->dbname),
897 : errhint("More details on the error might be found in the server log."));
898 :
899 : /*
900 : * Heuristic to see if the database was dropped, and if it was we can
901 : * treat it as not an error, else treat as fatal and error out.
902 : */
903 0 : if (DatabaseExists(db->dboid))
904 0 : return DATACHECKSUMSWORKER_FAILED;
905 : else
906 0 : return DATACHECKSUMSWORKER_DROPDB;
907 : }
908 :
909 : /*
910 : * If the postmaster crashed we cannot end up with a processed database so
911 : * we have no alternative other than exiting. When enabling checksums we
912 : * won't at this time have changed the data checksums state in pg_control
913 : * to enabled so when the cluster comes back up processing will have to be
914 : * restarted.
915 : */
916 23 : if (status == BGWH_POSTMASTER_DIED)
917 0 : ereport(FATAL,
918 : errcode(ERRCODE_ADMIN_SHUTDOWN),
919 : errmsg("cannot enable data checksums without the postmaster process"),
920 : errhint("Restart the database and restart data checksum processing by calling pg_enable_data_checksums()."));
921 :
922 : Assert(status == BGWH_STARTED);
923 23 : ereport(LOG,
924 : errmsg("initiating data checksum processing in database \"%s\"",
925 : db->dbname));
926 :
927 : /* Save the pid of the worker so we can signal it later */
928 23 : LWLockAcquire(DataChecksumsWorkerLock, LW_EXCLUSIVE);
929 : Assert(DataChecksumState->worker_invocation == invocation);
930 23 : DataChecksumState->worker_pid = pid;
931 23 : LWLockRelease(DataChecksumsWorkerLock);
932 :
933 23 : snprintf(activity, sizeof(activity) - 1,
934 : "Waiting for worker in database %s (pid %ld)", db->dbname, (long) pid);
935 23 : pgstat_report_activity(STATE_RUNNING, activity);
936 :
937 23 : status = WaitForBackgroundWorkerShutdown(bgw_handle);
938 22 : if (status == BGWH_POSTMASTER_DIED)
939 0 : ereport(FATAL,
940 : errcode(ERRCODE_ADMIN_SHUTDOWN),
941 : errmsg("postmaster exited during data checksum processing in \"%s\"",
942 : db->dbname),
943 : errhint("Restart the database and restart data checksum processing by calling pg_enable_data_checksums()."));
944 :
945 22 : LWLockAcquire(DataChecksumsWorkerLock, LW_EXCLUSIVE);
946 : Assert(DataChecksumState->worker_invocation == invocation);
947 22 : result = DataChecksumState->worker_result;
948 22 : DataChecksumState->worker_pid = InvalidPid;
949 22 : LWLockRelease(DataChecksumsWorkerLock);
950 :
951 22 : if (result == DATACHECKSUMSWORKER_ABORTED)
952 0 : ereport(LOG,
953 : errmsg("data checksums processing was aborted in database \"%s\"",
954 : db->dbname));
955 22 : pgstat_report_activity(STATE_IDLE, NULL);
956 22 : return result;
957 : }
958 :
959 : /*
960 : * launcher_exit
961 : *
962 : * Internal routine for cleaning up state when a launcher process which has
963 : * performed checksum operations exits. A launcher process which is exiting due
964 : * to a duplicate started launcher does not need to perform any cleanup and
965 : * this function should not be called. Otherwise, we need to clean up the abort
966 : * flag to ensure that processing can be started again if it was previously
967 : * aborted (note: started again, *not* restarted from where it left off).
968 : */
969 : static void
970 14 : launcher_exit(int code, Datum arg)
971 : {
972 14 : abort_requested = false;
973 :
974 14 : if (launcher_running)
975 : {
976 2 : LWLockAcquire(DataChecksumsWorkerLock, LW_EXCLUSIVE);
977 2 : if (DataChecksumState->worker_pid != InvalidPid)
978 : {
979 1 : ereport(LOG,
980 : errmsg("data checksums launcher exiting while worker is still running, signalling worker"));
981 1 : kill(DataChecksumState->worker_pid, SIGTERM);
982 1 : DataChecksumState->worker_pid = InvalidPid;
983 : }
984 2 : LWLockRelease(DataChecksumsWorkerLock);
985 : }
986 :
987 : /*
988 : * If the launcher is exiting before data checksums are enabled then set
989 : * the state to off since processing cannot be resumed.
990 : */
991 14 : if (DataChecksumsInProgressOn())
992 1 : SetDataChecksumsOff();
993 :
994 14 : LWLockAcquire(DataChecksumsWorkerLock, LW_EXCLUSIVE);
995 14 : launcher_running = false;
996 14 : DataChecksumState->launcher_running = false;
997 14 : LWLockRelease(DataChecksumsWorkerLock);
998 14 : }
999 :
1000 : /*
1001 : * launcher_cancel_handler
1002 : *
1003 : * Internal routine for reacting to SIGINT and flagging the worker to abort.
1004 : * The worker won't be interrupted immediately but will check for abort flag
1005 : * between each block in a relation.
1006 : */
1007 : static void
1008 0 : launcher_cancel_handler(SIGNAL_ARGS)
1009 : {
1010 0 : int save_errno = errno;
1011 :
1012 0 : abort_requested = true;
1013 :
1014 : /*
1015 : * There is no sleeping in the main loop, the flag will be checked
1016 : * periodically in ProcessSingleRelationFork. The worker does however
1017 : * sleep when waiting for concurrent transactions to end so we still need
1018 : * to set the latch.
1019 : */
1020 0 : SetLatch(MyLatch);
1021 :
1022 0 : errno = save_errno;
1023 0 : }
1024 :
1025 : /*
1026 : * WaitForAllTransactionsToFinish
1027 : * Blocks awaiting all current transactions to finish
1028 : *
1029 : * Returns when all transactions which are active at the call of the function
1030 : * have ended.
1031 : *
1032 : * NB: this will return early, if aborted by SIGINT or if the target state
1033 : * is changed while we're running.
1034 : */
1035 : static void
1036 9 : WaitForAllTransactionsToFinish(void)
1037 : {
1038 : TransactionId waitforxid;
1039 :
1040 9 : LWLockAcquire(XidGenLock, LW_SHARED);
1041 9 : waitforxid = XidFromFullTransactionId(TransamVariables->nextXid);
1042 9 : LWLockRelease(XidGenLock);
1043 :
1044 9 : while (TransactionIdPrecedes(GetOldestActiveTransactionId(false, true), waitforxid))
1045 : {
1046 : char activity[64];
1047 : int rc;
1048 :
1049 : /* Oldest running xid is older than us, so wait */
1050 0 : snprintf(activity,
1051 : sizeof(activity),
1052 : "Waiting for transactions older than %u to end",
1053 : waitforxid);
1054 0 : pgstat_report_activity(STATE_RUNNING, activity);
1055 :
1056 : /* Retry every 3 seconds */
1057 0 : ResetLatch(MyLatch);
1058 0 : rc = WaitLatch(MyLatch,
1059 : WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
1060 : 3000,
1061 : WAIT_EVENT_CHECKSUM_ENABLE_STARTCONDITION);
1062 :
1063 : /*
1064 : * If the postmaster died, bail out. But first print a log message to
1065 : * note that the checksumming didn't complete.
1066 : */
1067 0 : if (rc & WL_POSTMASTER_DEATH)
1068 0 : ereport(FATAL,
1069 : errcode(ERRCODE_ADMIN_SHUTDOWN),
1070 : errmsg("postmaster exited during data checksums processing"),
1071 : errhint("Data checksums processing must be restarted manually after cluster restart."));
1072 :
1073 0 : CHECK_FOR_INTERRUPTS();
1074 0 : CHECK_FOR_LAUNCHER_ABORT_REQUEST();
1075 :
1076 0 : if (abort_requested)
1077 0 : break;
1078 : }
1079 :
1080 9 : pgstat_report_activity(STATE_IDLE, NULL);
1081 9 : return;
1082 : }
1083 :
1084 : /*
1085 : * DataChecksumsWorkerLauncherMain
1086 : *
1087 : * Main function for launching dynamic background workers for processing data
1088 : * checksums in databases. This function has the bgworker management, with
1089 : * ProcessAllDatabases being responsible for looping over the databases and
1090 : * initiating processing.
1091 : */
1092 : void
1093 14 : DataChecksumsWorkerLauncherMain(Datum arg)
1094 : {
1095 :
1096 14 : ereport(DEBUG1,
1097 : errmsg("background worker \"datachecksums launcher\" started"));
1098 :
1099 14 : pqsignal(SIGTERM, die);
1100 14 : pqsignal(SIGINT, launcher_cancel_handler);
1101 14 : pqsignal(SIGUSR1, procsignal_sigusr1_handler);
1102 14 : pqsignal(SIGUSR2, PG_SIG_IGN);
1103 :
1104 14 : BackgroundWorkerUnblockSignals();
1105 :
1106 14 : MyBackendType = B_DATACHECKSUMSWORKER_LAUNCHER;
1107 14 : init_ps_display(NULL);
1108 :
1109 14 : INJECTION_POINT("datachecksumsworker-launcher-delay", NULL);
1110 :
1111 14 : LWLockAcquire(DataChecksumsWorkerLock, LW_EXCLUSIVE);
1112 :
1113 14 : if (DataChecksumState->launcher_running)
1114 : {
1115 0 : ereport(LOG,
1116 : errmsg("background worker \"datachecksums launcher\" already running, exiting"));
1117 : /* Launcher was already running, let it finish */
1118 0 : LWLockRelease(DataChecksumsWorkerLock);
1119 0 : return;
1120 : }
1121 :
1122 14 : on_shmem_exit(launcher_exit, 0);
1123 14 : launcher_running = true;
1124 :
1125 : /* Initialize a connection to shared catalogs only */
1126 14 : BackgroundWorkerInitializeConnectionByOid(InvalidOid, InvalidOid, 0);
1127 :
1128 14 : operation = DataChecksumState->launch_operation;
1129 14 : DataChecksumState->launcher_running = true;
1130 14 : DataChecksumState->operation = operation;
1131 14 : DataChecksumState->cost_delay = DataChecksumState->launch_cost_delay;
1132 14 : DataChecksumState->cost_limit = DataChecksumState->launch_cost_limit;
1133 14 : LWLockRelease(DataChecksumsWorkerLock);
1134 :
1135 : /*
1136 : * The target state can change while we are busy enabling/disabling
1137 : * checksums, if the user calls pg_disable/enable_data_checksums() before
1138 : * we are finished with the previous request. In that case, we will loop
1139 : * back here, to process the new request.
1140 : */
1141 14 : again:
1142 :
1143 14 : pgstat_progress_start_command(PROGRESS_COMMAND_DATACHECKSUMS,
1144 : InvalidOid);
1145 :
1146 14 : if (operation == ENABLE_DATACHECKSUMS)
1147 : {
1148 : /*
1149 : * If we are asked to enable checksums in a cluster which already has
1150 : * checksums enabled, exit immediately as there is nothing more to do.
1151 : */
1152 9 : if (DataChecksumsNeedVerify())
1153 0 : goto done;
1154 :
1155 9 : ereport(LOG,
1156 : errmsg("enabling data checksums requested, starting data checksum calculation"));
1157 :
1158 : /*
1159 : * Set the state to inprogress-on and wait on the procsignal barrier.
1160 : */
1161 9 : pgstat_progress_update_param(PROGRESS_DATACHECKSUMS_PHASE,
1162 : PROGRESS_DATACHECKSUMS_PHASE_ENABLING);
1163 9 : SetDataChecksumsOnInProgress();
1164 :
1165 : /*
1166 : * All backends are now in inprogress-on state and are writing data
1167 : * checksums. Start processing all data at rest.
1168 : */
1169 9 : if (!ProcessAllDatabases())
1170 : {
1171 : /*
1172 : * If the target state changed during processing then it's not a
1173 : * failure, so restart processing instead.
1174 : */
1175 0 : CHECK_FOR_LAUNCHER_ABORT_REQUEST();
1176 0 : if (abort_requested)
1177 0 : goto done;
1178 0 : ereport(ERROR,
1179 : errcode(ERRCODE_INSUFFICIENT_RESOURCES),
1180 : errmsg("unable to enable data checksums in cluster"));
1181 : }
1182 :
1183 : /*
1184 : * Data checksums have been set on all pages, set the state to on in
1185 : * order to instruct backends to validate checksums on reading.
1186 : */
1187 7 : SetDataChecksumsOn();
1188 :
1189 7 : ereport(LOG,
1190 : errmsg("data checksums are now enabled"));
1191 : }
1192 5 : else if (operation == DISABLE_DATACHECKSUMS)
1193 : {
1194 5 : ereport(LOG,
1195 : errmsg("disabling data checksums requested"));
1196 :
1197 5 : pgstat_progress_update_param(PROGRESS_DATACHECKSUMS_PHASE,
1198 : PROGRESS_DATACHECKSUMS_PHASE_DISABLING);
1199 5 : SetDataChecksumsOff();
1200 5 : ereport(LOG,
1201 : errmsg("data checksums are now disabled"));
1202 : }
1203 : else
1204 : Assert(false);
1205 :
1206 0 : done:
1207 :
1208 : /*
1209 : * This state will only be displayed for a fleeting moment, but for the
1210 : * sake of correctness it is still added before ending the command.
1211 : */
1212 12 : pgstat_progress_update_param(PROGRESS_DATACHECKSUMS_PHASE,
1213 : PROGRESS_DATACHECKSUMS_PHASE_DONE);
1214 :
1215 : /*
1216 : * All done. But before we exit, check if the target state was changed
1217 : * while we were running. In that case we will have to start all over
1218 : * again.
1219 : */
1220 12 : LWLockAcquire(DataChecksumsWorkerLock, LW_EXCLUSIVE);
1221 12 : if (DataChecksumState->launch_operation != operation)
1222 : {
1223 0 : DataChecksumState->operation = DataChecksumState->launch_operation;
1224 0 : operation = DataChecksumState->launch_operation;
1225 0 : DataChecksumState->cost_delay = DataChecksumState->launch_cost_delay;
1226 0 : DataChecksumState->cost_limit = DataChecksumState->launch_cost_limit;
1227 0 : LWLockRelease(DataChecksumsWorkerLock);
1228 0 : goto again;
1229 : }
1230 :
1231 : /* Shut down progress reporting as we are done */
1232 12 : pgstat_progress_end_command();
1233 :
1234 12 : launcher_running = false;
1235 12 : DataChecksumState->launcher_running = false;
1236 12 : LWLockRelease(DataChecksumsWorkerLock);
1237 : }
1238 :
1239 : /*
1240 : * ProcessAllDatabases
1241 : * Compute the list of all databases and process checksums in each
1242 : *
1243 : * This will generate a list of databases to process for enabling checksums.
1244 : * If a database encounters a failure then processing will end immediately and
1245 : * return an error.
1246 : */
1247 : static bool
1248 9 : ProcessAllDatabases(void)
1249 : {
1250 : List *DatabaseList;
1251 9 : int cumulative_total = 0;
1252 :
1253 : /* Set up so first run processes shared catalogs, not once in every db */
1254 9 : LWLockAcquire(DataChecksumsWorkerLock, LW_EXCLUSIVE);
1255 9 : DataChecksumState->process_shared_catalogs = true;
1256 9 : LWLockRelease(DataChecksumsWorkerLock);
1257 :
1258 : /* Get a list of all databases to process */
1259 9 : WaitForAllTransactionsToFinish();
1260 9 : DatabaseList = BuildDatabaseList();
1261 :
1262 : /*
1263 : * Update progress reporting with the total number of databases we need to
1264 : * process. This number should not be changed during processing, the
1265 : * columns for processed databases is instead increased such that it can
1266 : * be compared against the total.
1267 : */
1268 : {
1269 9 : const int index[] = {
1270 : PROGRESS_DATACHECKSUMS_DBS_TOTAL,
1271 : PROGRESS_DATACHECKSUMS_DBS_DONE,
1272 : PROGRESS_DATACHECKSUMS_RELS_TOTAL,
1273 : PROGRESS_DATACHECKSUMS_RELS_DONE,
1274 : PROGRESS_DATACHECKSUMS_BLOCKS_TOTAL,
1275 : PROGRESS_DATACHECKSUMS_BLOCKS_DONE,
1276 : };
1277 :
1278 : int64 vals[6];
1279 :
1280 9 : vals[0] = list_length(DatabaseList);
1281 9 : vals[1] = 0;
1282 : /* translated to NULL */
1283 9 : vals[2] = -1;
1284 9 : vals[3] = -1;
1285 9 : vals[4] = -1;
1286 9 : vals[5] = -1;
1287 :
1288 9 : pgstat_progress_update_multi_param(6, index, vals);
1289 : }
1290 :
1291 37 : foreach_ptr(DataChecksumsWorkerDatabase, db, DatabaseList)
1292 : {
1293 : DataChecksumsWorkerResult result;
1294 :
1295 23 : result = ProcessDatabase(db);
1296 :
1297 : #ifdef USE_INJECTION_POINTS
1298 : /* Allow a test process to alter the result of the operation */
1299 22 : if (IS_INJECTION_POINT_ATTACHED("datachecksumsworker-fail-db-result"))
1300 : {
1301 1 : result = DATACHECKSUMSWORKER_FAILED;
1302 1 : INJECTION_POINT_CACHED("datachecksumsworker-fail-db-result",
1303 : db->dbname);
1304 : }
1305 : #endif
1306 :
1307 22 : pgstat_progress_update_param(PROGRESS_DATACHECKSUMS_DBS_DONE,
1308 : ++cumulative_total);
1309 :
1310 22 : if (result == DATACHECKSUMSWORKER_FAILED)
1311 : {
1312 : /*
1313 : * Disable checksums on cluster, because we failed one of the
1314 : * databases and this is an all or nothing process.
1315 : */
1316 1 : SetDataChecksumsOff();
1317 1 : ereport(ERROR,
1318 : errcode(ERRCODE_INSUFFICIENT_RESOURCES),
1319 : errmsg("data checksums failed to get enabled in all databases, aborting"),
1320 : errhint("The server log might have more information on the cause of the error."));
1321 : }
1322 21 : else if (result == DATACHECKSUMSWORKER_ABORTED || abort_requested)
1323 : {
1324 : /* Abort flag set, so exit the whole process */
1325 0 : return false;
1326 : }
1327 :
1328 : /*
1329 : * When one database has completed, it will have done shared catalogs
1330 : * so we don't have to process them again.
1331 : */
1332 21 : LWLockAcquire(DataChecksumsWorkerLock, LW_EXCLUSIVE);
1333 21 : DataChecksumState->process_shared_catalogs = false;
1334 21 : LWLockRelease(DataChecksumsWorkerLock);
1335 : }
1336 :
1337 7 : FreeDatabaseList(DatabaseList);
1338 :
1339 7 : pgstat_progress_update_param(PROGRESS_DATACHECKSUMS_PHASE,
1340 : PROGRESS_DATACHECKSUMS_PHASE_WAITING_BARRIER);
1341 7 : return true;
1342 : }
1343 :
1344 : /*
1345 : * DataChecksumsShmemRequest
1346 : * Request datachecksumsworker-related shared memory
1347 : */
1348 : static void
1349 1247 : DataChecksumsShmemRequest(void *arg)
1350 : {
1351 1247 : ShmemRequestStruct(.name = "DataChecksumsWorker Data",
1352 : .size = sizeof(DataChecksumsStateStruct),
1353 : .ptr = (void **) &DataChecksumState,
1354 : );
1355 1247 : }
1356 :
1357 : /*
1358 : * DatabaseExists
1359 : *
1360 : * Scans the system catalog to check if a database with the given Oid exists
1361 : * and returns true if it is found and valid, else false. Note, we cannot use
1362 : * database_is_invalid_oid here as it will ERROR out, and we want to gracefully
1363 : * handle errors.
1364 : */
1365 : static bool
1366 0 : DatabaseExists(Oid dboid)
1367 : {
1368 : Relation rel;
1369 : ScanKeyData skey;
1370 : SysScanDesc scan;
1371 : bool found;
1372 : HeapTuple tuple;
1373 : Form_pg_database pg_database_tuple;
1374 :
1375 0 : StartTransactionCommand();
1376 :
1377 0 : rel = table_open(DatabaseRelationId, AccessShareLock);
1378 0 : ScanKeyInit(&skey,
1379 : Anum_pg_database_oid,
1380 : BTEqualStrategyNumber, F_OIDEQ,
1381 : ObjectIdGetDatum(dboid));
1382 0 : scan = systable_beginscan(rel, DatabaseOidIndexId, true, SnapshotSelf,
1383 : 1, &skey);
1384 0 : tuple = systable_getnext(scan);
1385 0 : found = HeapTupleIsValid(tuple);
1386 :
1387 : /* If the Oid exists, ensure that it's not partially dropped */
1388 0 : if (found)
1389 : {
1390 0 : pg_database_tuple = (Form_pg_database) GETSTRUCT(tuple);
1391 0 : if (database_is_invalid_form(pg_database_tuple))
1392 0 : found = false;
1393 : }
1394 :
1395 0 : systable_endscan(scan);
1396 0 : table_close(rel, AccessShareLock);
1397 :
1398 0 : CommitTransactionCommand();
1399 :
1400 0 : return found;
1401 : }
1402 :
1403 : /*
1404 : * BuildDatabaseList
1405 : * Compile a list of all currently available databases in the cluster
1406 : *
1407 : * This creates the list of databases for the datachecksumsworker workers to
1408 : * add checksums to. If the caller wants to ensure that no concurrently
1409 : * running CREATE DATABASE calls exist, this needs to be preceded by a call
1410 : * to WaitForAllTransactionsToFinish().
1411 : */
1412 : static List *
1413 9 : BuildDatabaseList(void)
1414 : {
1415 9 : List *DatabaseList = NIL;
1416 : Relation rel;
1417 : TableScanDesc scan;
1418 : HeapTuple tup;
1419 9 : MemoryContext ctx = CurrentMemoryContext;
1420 : MemoryContext oldctx;
1421 :
1422 9 : StartTransactionCommand();
1423 :
1424 9 : rel = table_open(DatabaseRelationId, AccessShareLock);
1425 9 : scan = table_beginscan_catalog(rel, 0, NULL);
1426 :
1427 36 : while (HeapTupleIsValid(tup = heap_getnext(scan, ForwardScanDirection)))
1428 : {
1429 27 : Form_pg_database pgdb = (Form_pg_database) GETSTRUCT(tup);
1430 : DataChecksumsWorkerDatabase *db;
1431 :
1432 27 : oldctx = MemoryContextSwitchTo(ctx);
1433 :
1434 27 : db = (DataChecksumsWorkerDatabase *) palloc0(sizeof(DataChecksumsWorkerDatabase));
1435 :
1436 27 : db->dboid = pgdb->oid;
1437 27 : db->dbname = pstrdup(NameStr(pgdb->datname));
1438 :
1439 27 : DatabaseList = lappend(DatabaseList, db);
1440 :
1441 27 : MemoryContextSwitchTo(oldctx);
1442 : }
1443 :
1444 9 : table_endscan(scan);
1445 9 : table_close(rel, AccessShareLock);
1446 :
1447 9 : CommitTransactionCommand();
1448 :
1449 9 : return DatabaseList;
1450 : }
1451 :
1452 : static void
1453 7 : FreeDatabaseList(List *dblist)
1454 : {
1455 7 : if (!dblist)
1456 0 : return;
1457 :
1458 35 : foreach_ptr(DataChecksumsWorkerDatabase, db, dblist)
1459 : {
1460 21 : if (db->dbname != NULL)
1461 21 : pfree(db->dbname);
1462 : }
1463 :
1464 7 : list_free_deep(dblist);
1465 : }
1466 :
1467 : /*
1468 : * BuildRelationList
1469 : * Compile a list of relations in the database
1470 : *
1471 : * Returns a list of OIDs for the requested relation types. If temp_relations
1472 : * is True then only temporary relations are returned. If temp_relations is
1473 : * False then non-temporary relations which have data checksums are returned.
1474 : * If include_shared is True then shared relations are included as well in a
1475 : * non-temporary list. include_shared has no relevance when building a list of
1476 : * temporary relations.
1477 : */
1478 : static List *
1479 68 : BuildRelationList(bool temp_relations, bool include_shared)
1480 : {
1481 68 : List *RelationList = NIL;
1482 : Relation rel;
1483 : TableScanDesc scan;
1484 : HeapTuple tup;
1485 68 : MemoryContext ctx = CurrentMemoryContext;
1486 : MemoryContext oldctx;
1487 :
1488 68 : StartTransactionCommand();
1489 :
1490 68 : rel = table_open(RelationRelationId, AccessShareLock);
1491 68 : scan = table_beginscan_catalog(rel, 0, NULL);
1492 :
1493 30847 : while (HeapTupleIsValid(tup = heap_getnext(scan, ForwardScanDirection)))
1494 : {
1495 30779 : Form_pg_class pgc = (Form_pg_class) GETSTRUCT(tup);
1496 :
1497 : /* Only include temporary relations when explicitly asked to */
1498 30779 : if (pgc->relpersistence == RELPERSISTENCE_TEMP)
1499 : {
1500 2 : if (!temp_relations)
1501 1 : continue;
1502 : }
1503 : else
1504 : {
1505 : /*
1506 : * If we are only interested in temp relations then continue
1507 : * immediately as the current relation isn't a temp relation.
1508 : */
1509 30777 : if (temp_relations)
1510 20367 : continue;
1511 :
1512 10410 : if (!RELKIND_HAS_STORAGE(pgc->relkind))
1513 3726 : continue;
1514 :
1515 6684 : if (pgc->relisshared && !include_shared)
1516 644 : continue;
1517 : }
1518 :
1519 6041 : oldctx = MemoryContextSwitchTo(ctx);
1520 6041 : RelationList = lappend_oid(RelationList, pgc->oid);
1521 6041 : MemoryContextSwitchTo(oldctx);
1522 : }
1523 :
1524 68 : table_endscan(scan);
1525 68 : table_close(rel, AccessShareLock);
1526 :
1527 68 : CommitTransactionCommand();
1528 :
1529 68 : return RelationList;
1530 : }
1531 :
1532 : /*
1533 : * DataChecksumsWorkerMain
1534 : *
1535 : * Main function for enabling checksums in a single database. This is the
1536 : * function set as the bgw_function_name in the dynamic background worker
1537 : * process initiated for each database by the worker launcher. After enabling
1538 : * data checksums in each applicable relation in the database, it will wait for
1539 : * all temporary relations that were present when the function started to
1540 : * disappear before returning. This is required since we cannot rewrite
1541 : * existing temporary relations with data checksums.
1542 : */
1543 : void
1544 23 : DataChecksumsWorkerMain(Datum arg)
1545 : {
1546 : Oid dboid;
1547 23 : List *RelationList = NIL;
1548 23 : List *InitialTempTableList = NIL;
1549 : BufferAccessStrategy strategy;
1550 23 : bool aborted = false;
1551 : int64 rels_done;
1552 : bool process_shared;
1553 : #ifdef USE_INJECTION_POINTS
1554 23 : bool retried = false;
1555 : #endif
1556 :
1557 23 : worker_invocation = DatumGetUInt64(arg);
1558 :
1559 23 : operation = ENABLE_DATACHECKSUMS;
1560 :
1561 23 : pqsignal(SIGTERM, die);
1562 23 : pqsignal(SIGUSR1, procsignal_sigusr1_handler);
1563 :
1564 23 : BackgroundWorkerUnblockSignals();
1565 :
1566 23 : MyBackendType = B_DATACHECKSUMSWORKER_WORKER;
1567 23 : init_ps_display(NULL);
1568 :
1569 23 : LWLockAcquire(DataChecksumsWorkerLock, LW_SHARED);
1570 23 : if (DataChecksumState->worker_invocation != worker_invocation)
1571 : {
1572 0 : LWLockRelease(DataChecksumsWorkerLock);
1573 0 : return;
1574 : }
1575 23 : dboid = DataChecksumState->database_oid;
1576 23 : LWLockRelease(DataChecksumsWorkerLock);
1577 :
1578 23 : BackgroundWorkerInitializeConnectionByOid(dboid, InvalidOid,
1579 : BGWORKER_BYPASS_ALLOWCONN);
1580 :
1581 : /* worker will have a separate entry in pg_stat_progress_data_checksums */
1582 23 : pgstat_progress_start_command(PROGRESS_COMMAND_DATACHECKSUMS,
1583 : InvalidOid);
1584 :
1585 : /*
1586 : * Get a list of all temp tables present as we start in this database. We
1587 : * need to wait until they are all gone before we exit. For the list of
1588 : * relations to enable checksums in, check if shared catalogs have been
1589 : * processed already.
1590 : */
1591 23 : InitialTempTableList = BuildRelationList(true, false);
1592 23 : LWLockAcquire(DataChecksumsWorkerLock, LW_EXCLUSIVE);
1593 23 : if (DataChecksumState->worker_invocation != worker_invocation)
1594 : {
1595 0 : LWLockRelease(DataChecksumsWorkerLock);
1596 0 : return;
1597 : }
1598 23 : process_shared = DataChecksumState->process_shared_catalogs;
1599 :
1600 : /*
1601 : * Enable vacuum cost delay, if any. While this process isn't doing any
1602 : * vacuuming, we are re-using the infrastructure that vacuum cost delay
1603 : * provides rather than inventing something bespoke. This is an internal
1604 : * implementation detail and care should be taken to avoid it bleeding
1605 : * through to the user to avoid confusion.
1606 : *
1607 : * VacuumUpdateCosts() propagates the values to the variables actually
1608 : * read by vacuum_delay_point().
1609 : */
1610 23 : VacuumCostDelay = DataChecksumState->cost_delay;
1611 23 : VacuumCostLimit = DataChecksumState->cost_limit;
1612 23 : LWLockRelease(DataChecksumsWorkerLock);
1613 23 : VacuumUpdateCosts();
1614 23 : VacuumCostBalance = 0;
1615 :
1616 : /*
1617 : * Create and set the vacuum strategy as our buffer strategy.
1618 : */
1619 23 : strategy = GetAccessStrategy(BAS_VACUUM);
1620 :
1621 23 : RelationList = BuildRelationList(false, process_shared);
1622 :
1623 : /* Update the total number of relations to be processed in this DB. */
1624 : {
1625 23 : const int index[] = {
1626 : PROGRESS_DATACHECKSUMS_RELS_TOTAL,
1627 : PROGRESS_DATACHECKSUMS_RELS_DONE
1628 : };
1629 :
1630 : int64 vals[2];
1631 :
1632 23 : vals[0] = list_length(RelationList);
1633 23 : vals[1] = 0;
1634 :
1635 23 : pgstat_progress_update_multi_param(2, index, vals);
1636 : }
1637 :
1638 : /* Process the relations */
1639 23 : rels_done = 0;
1640 6012 : foreach_oid(reloid, RelationList)
1641 : {
1642 5968 : bool costs_updated = false;
1643 :
1644 5968 : if (!ProcessSingleRelationByOid(reloid, strategy))
1645 : {
1646 0 : aborted = true;
1647 0 : break;
1648 : }
1649 :
1650 5967 : pgstat_progress_update_param(PROGRESS_DATACHECKSUMS_RELS_DONE,
1651 : ++rels_done);
1652 5967 : CHECK_FOR_INTERRUPTS();
1653 5967 : CHECK_FOR_WORKER_ABORT_REQUEST();
1654 :
1655 5967 : if (abort_requested)
1656 0 : break;
1657 :
1658 : /*
1659 : * Check if the cost settings changed during runtime and if so, update
1660 : * to reflect the new values and signal that the access strategy needs
1661 : * to be refreshed.
1662 : */
1663 5967 : LWLockAcquire(DataChecksumsWorkerLock, LW_EXCLUSIVE);
1664 5967 : if (DataChecksumState->worker_invocation != worker_invocation)
1665 : {
1666 0 : LWLockRelease(DataChecksumsWorkerLock);
1667 0 : break;
1668 : }
1669 5967 : if ((DataChecksumState->launch_cost_delay != DataChecksumState->cost_delay)
1670 5967 : || (DataChecksumState->launch_cost_limit != DataChecksumState->cost_limit))
1671 : {
1672 0 : costs_updated = true;
1673 0 : VacuumCostDelay = DataChecksumState->launch_cost_delay;
1674 0 : VacuumCostLimit = DataChecksumState->launch_cost_limit;
1675 0 : VacuumUpdateCosts();
1676 :
1677 0 : DataChecksumState->cost_delay = DataChecksumState->launch_cost_delay;
1678 0 : DataChecksumState->cost_limit = DataChecksumState->launch_cost_limit;
1679 : }
1680 : else
1681 5967 : costs_updated = false;
1682 5967 : LWLockRelease(DataChecksumsWorkerLock);
1683 :
1684 5967 : if (costs_updated)
1685 : {
1686 0 : FreeAccessStrategy(strategy);
1687 0 : strategy = GetAccessStrategy(BAS_VACUUM);
1688 : }
1689 : }
1690 :
1691 22 : list_free(RelationList);
1692 22 : FreeAccessStrategy(strategy);
1693 :
1694 22 : if (aborted || abort_requested)
1695 : {
1696 0 : LWLockAcquire(DataChecksumsWorkerLock, LW_EXCLUSIVE);
1697 0 : if (DataChecksumState->worker_invocation == worker_invocation)
1698 0 : DataChecksumState->worker_result = DATACHECKSUMSWORKER_ABORTED;
1699 0 : LWLockRelease(DataChecksumsWorkerLock);
1700 0 : ereport(DEBUG1,
1701 : errmsg("data checksum processing aborted in database OID %u",
1702 : dboid));
1703 0 : return;
1704 : }
1705 :
1706 : /* The worker is about to wait for temporary tables to go away. */
1707 22 : pgstat_progress_update_param(PROGRESS_DATACHECKSUMS_PHASE,
1708 : PROGRESS_DATACHECKSUMS_PHASE_WAITING_TEMPREL);
1709 :
1710 : /*
1711 : * Wait for all temp tables that existed when we started to go away. This
1712 : * is necessary since we cannot "reach" them to enable checksums. Any temp
1713 : * tables created after we started will already have checksums in them
1714 : * (due to the "inprogress-on" state), so no need to wait for those.
1715 : */
1716 : for (;;)
1717 0 : {
1718 : List *CurrentTempTables;
1719 : int numleft;
1720 : char activity[64];
1721 :
1722 22 : CurrentTempTables = BuildRelationList(true, false);
1723 22 : numleft = 0;
1724 44 : foreach_oid(tmptbloid, InitialTempTableList)
1725 : {
1726 0 : if (list_member_oid(CurrentTempTables, tmptbloid))
1727 0 : numleft++;
1728 : }
1729 22 : list_free(CurrentTempTables);
1730 :
1731 : #ifdef USE_INJECTION_POINTS
1732 22 : if (IS_INJECTION_POINT_ATTACHED("datachecksumsworker-fake-temptable-wait"))
1733 : {
1734 : /* Make sure to just cause one retry */
1735 0 : if (!retried && numleft == 0)
1736 : {
1737 0 : numleft = 1;
1738 0 : retried = true;
1739 :
1740 0 : INJECTION_POINT_CACHED("datachecksumsworker-fake-temptable-wait", NULL);
1741 : }
1742 : }
1743 : #endif
1744 :
1745 22 : if (numleft == 0)
1746 22 : break;
1747 :
1748 : /*
1749 : * At least one temp table is left to wait for, indicate in pgstat
1750 : * activity and progress reporting.
1751 : */
1752 0 : snprintf(activity,
1753 : sizeof(activity),
1754 : "Waiting for %d temp tables to be removed", numleft);
1755 0 : pgstat_report_activity(STATE_RUNNING, activity);
1756 :
1757 : /* Retry every 3 seconds */
1758 0 : ResetLatch(MyLatch);
1759 0 : (void) WaitLatch(MyLatch,
1760 : WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
1761 : 3000,
1762 : WAIT_EVENT_CHECKSUM_ENABLE_TEMPTABLE_WAIT);
1763 :
1764 0 : CHECK_FOR_INTERRUPTS();
1765 0 : CHECK_FOR_WORKER_ABORT_REQUEST();
1766 :
1767 0 : if (aborted || abort_requested)
1768 : {
1769 0 : LWLockAcquire(DataChecksumsWorkerLock, LW_EXCLUSIVE);
1770 0 : if (DataChecksumState->worker_invocation == worker_invocation)
1771 0 : DataChecksumState->worker_result = DATACHECKSUMSWORKER_ABORTED;
1772 0 : LWLockRelease(DataChecksumsWorkerLock);
1773 0 : ereport(LOG,
1774 : errmsg("data checksum processing aborted in database OID %u",
1775 : dboid));
1776 0 : return;
1777 : }
1778 : }
1779 :
1780 22 : list_free(InitialTempTableList);
1781 :
1782 : /* worker done */
1783 22 : pgstat_progress_end_command();
1784 :
1785 22 : LWLockAcquire(DataChecksumsWorkerLock, LW_EXCLUSIVE);
1786 22 : if (DataChecksumState->worker_invocation == worker_invocation)
1787 22 : DataChecksumState->worker_result = DATACHECKSUMSWORKER_SUCCESSFUL;
1788 22 : LWLockRelease(DataChecksumsWorkerLock);
1789 : }
|