Branch data 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 ErrorOnInvalidDatabases(void);
387 : : static bool ProcessSingleRelationFork(Relation reln, ForkNumber forkNum, BufferAccessStrategy strategy);
388 : : static void ResetDataChecksumsProgressCounters(void);
389 : : static bool ProcessSingleRelationByOid(Oid relationId, BufferAccessStrategy strategy);
390 : : static BgwHandleStatus WaitForDataChecksumsWorkerState(BackgroundWorkerHandle *handle,
391 : : bool wait_for_startup,
392 : : pid_t *pidp,
393 : : uint32 wait_event);
394 : : static DataChecksumsWorkerResult ProcessDatabase(DataChecksumsWorkerDatabase *db);
395 : : static void launcher_exit(int code, Datum arg);
396 : : static void launcher_cancel_handler(SIGNAL_ARGS);
397 : : static void WaitForAllTransactionsToFinish(void);
398 : : static bool ProcessAllDatabases(void);
399 : : static void DataChecksumsShmemRequest(void *arg);
400 : : static bool DatabaseExists(Oid dboid);
401 : : static List *BuildDatabaseList(void);
402 : : static void FreeDatabaseList(List *dblist);
403 : : static List *BuildRelationList(bool temp_relations, bool include_shared);
404 : :
405 : : const ShmemCallbacks DataChecksumsShmemCallbacks = {
406 : : .request_fn = DataChecksumsShmemRequest,
407 : : };
408 : :
409 : : #define CHECK_FOR_LAUNCHER_ABORT_REQUEST() \
410 : : do { \
411 : : Assert(MyBackendType == B_DATACHECKSUMSWORKER_LAUNCHER); \
412 : : LWLockAcquire(DataChecksumsWorkerLock, LW_SHARED); \
413 : : if (DataChecksumState->launch_operation != operation) \
414 : : abort_requested = true; \
415 : : LWLockRelease(DataChecksumsWorkerLock); \
416 : : } while (0)
417 : :
418 : : #define CHECK_FOR_WORKER_ABORT_REQUEST() \
419 : : do { \
420 : : Assert(MyBackendType == B_DATACHECKSUMSWORKER_WORKER); \
421 : : LWLockAcquire(DataChecksumsWorkerLock, LW_SHARED); \
422 : : if (DataChecksumState->worker_invocation != worker_invocation || \
423 : : DataChecksumState->launch_operation != operation) \
424 : : abort_requested = true; \
425 : : LWLockRelease(DataChecksumsWorkerLock); \
426 : : } while (0)
427 : :
428 : :
429 : : /*****************************************************************************
430 : : * Functionality for manipulating the data checksum state in the cluster
431 : : */
432 : :
433 : : void
434 : 8 : EmitAndWaitDataChecksumsBarrier(uint32 state)
435 : : {
436 : : uint64 barrier;
437 : :
438 [ + + + + : 8 : switch (state)
- ]
439 : : {
440 : 3 : case PG_DATA_CHECKSUM_INPROGRESS_ON:
441 : 3 : barrier = EmitProcSignalBarrier(PROCSIGNAL_BARRIER_CHECKSUM_INPROGRESS_ON);
442 : 3 : WaitForProcSignalBarrier(barrier);
443 : 3 : break;
444 : :
445 : 1 : case PG_DATA_CHECKSUM_INPROGRESS_OFF:
446 : 1 : barrier = EmitProcSignalBarrier(PROCSIGNAL_BARRIER_CHECKSUM_INPROGRESS_OFF);
447 : 1 : WaitForProcSignalBarrier(barrier);
448 : 1 : break;
449 : :
450 : 2 : case PG_DATA_CHECKSUM_VERSION:
451 : 2 : barrier = EmitProcSignalBarrier(PROCSIGNAL_BARRIER_CHECKSUM_ON);
452 : 2 : WaitForProcSignalBarrier(barrier);
453 : 2 : break;
454 : :
455 : 2 : case PG_DATA_CHECKSUM_OFF:
456 : 2 : barrier = EmitProcSignalBarrier(PROCSIGNAL_BARRIER_CHECKSUM_OFF);
457 : 2 : WaitForProcSignalBarrier(barrier);
458 : 2 : break;
459 : :
460 : 8 : default:
461 : : Assert(false);
462 : : }
463 : 8 : }
464 : :
465 : : /*
466 : : * AbsorbDataChecksumsBarrier
467 : : * Generic function for absorbing data checksum state changes
468 : : *
469 : : * All procsignalbarriers regarding data checksum state changes are absorbed
470 : : * with this function. The set of conditions required for the state change to
471 : : * be accepted are listed in the checksum_barriers struct, target_state is
472 : : * used to look up the relevant entry.
473 : : */
474 : : bool
475 : 443 : AbsorbDataChecksumsBarrier(ProcSignalBarrierType barrier)
476 : : {
477 : : uint32 target_state;
478 : 443 : int current = data_checksums;
479 : 443 : bool found = false;
480 : :
481 : : /*
482 : : * Translate the barrier condition to the target state, doing it here
483 : : * instead of in the procsignal code saves the latter from knowing about
484 : : * checksum states.
485 : : */
486 [ + + + + : 443 : switch (barrier)
- ]
487 : : {
488 : 151 : case PROCSIGNAL_BARRIER_CHECKSUM_INPROGRESS_ON:
489 : 151 : target_state = PG_DATA_CHECKSUM_INPROGRESS_ON;
490 : 151 : break;
491 : 102 : case PROCSIGNAL_BARRIER_CHECKSUM_ON:
492 : 102 : target_state = PG_DATA_CHECKSUM_VERSION;
493 : 102 : break;
494 : 93 : case PROCSIGNAL_BARRIER_CHECKSUM_INPROGRESS_OFF:
495 : 93 : target_state = PG_DATA_CHECKSUM_INPROGRESS_OFF;
496 : 93 : break;
497 : 97 : case PROCSIGNAL_BARRIER_CHECKSUM_OFF:
498 : 97 : target_state = PG_DATA_CHECKSUM_OFF;
499 : 97 : break;
500 : 0 : default:
501 [ # # ]: 0 : elog(ERROR, "incorrect barrier \"%d\" received", barrier);
502 : : }
503 : :
504 : : /*
505 : : * If the target state matches the current state then the barrier has been
506 : : * repeated.
507 : : */
508 [ + + ]: 443 : if (current == target_state)
509 : 1 : return true;
510 : :
511 : : /*
512 : : * If the cluster is in recovery we skip the validation of current state
513 : : * since the replay is trusted.
514 : : */
515 [ + + ]: 442 : if (RecoveryInProgress())
516 : : {
517 : 48 : SetLocalDataChecksumState(target_state);
518 : 48 : return true;
519 : : }
520 : :
521 : : /*
522 : : * Find the barrier condition definition for the target state. Not finding
523 : : * a condition would be a grave programmer error as the states are a
524 : : * discrete set.
525 : : */
526 [ + - + + ]: 1803 : for (size_t i = 0; i < lengthof(checksum_barriers) && !found; i++)
527 : : {
528 [ + + + + ]: 1409 : if (checksum_barriers[i].from == current && checksum_barriers[i].to == target_state)
529 : 394 : found = true;
530 : : }
531 : :
532 : : /*
533 : : * If the relevant state criteria aren't satisfied, throw an error which
534 : : * will be caught by the procsignal machinery for a later retry.
535 : : */
536 [ - + ]: 394 : if (!found)
537 [ # # ]: 0 : ereport(ERROR,
538 : : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
539 : : errmsg("incorrect data checksum state %d for target state %d",
540 : : current, target_state));
541 : :
542 : 394 : SetLocalDataChecksumState(target_state);
543 : 394 : return true;
544 : : }
545 : :
546 : :
547 : : /*
548 : : * Disables data checksums for the cluster, if applicable. Starts a background
549 : : * worker which turns off the data checksums.
550 : : */
551 : : Datum
552 : 12 : disable_data_checksums(PG_FUNCTION_ARGS)
553 : : {
554 : 12 : PreventCommandDuringRecovery("pg_disable_data_checksums()");
555 : :
556 [ - + ]: 12 : if (!superuser())
557 [ # # ]: 0 : ereport(ERROR,
558 : : errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
559 : : errmsg("must be superuser to change data checksum state"));
560 : :
561 : 12 : StartDataChecksumsWorkerLauncher(DISABLE_DATACHECKSUMS, 0, 0);
562 : 12 : PG_RETURN_VOID();
563 : : }
564 : :
565 : : /*
566 : : * Enables data checksums for the cluster, if applicable. Supports vacuum-
567 : : * like cost based throttling to limit system load. Starts a background worker
568 : : * which updates data checksums on existing data.
569 : : */
570 : : Datum
571 : 18 : enable_data_checksums(PG_FUNCTION_ARGS)
572 : : {
573 : 18 : int cost_delay = PG_GETARG_INT32(0);
574 : 18 : int cost_limit = PG_GETARG_INT32(1);
575 : :
576 : 18 : PreventCommandDuringRecovery("pg_enable_data_checksums()");
577 : :
578 [ - + ]: 18 : if (!superuser())
579 [ # # ]: 0 : ereport(ERROR,
580 : : errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
581 : : errmsg("must be superuser to change data checksum state"));
582 : :
583 [ - + ]: 18 : if (cost_delay < 0)
584 [ # # ]: 0 : ereport(ERROR,
585 : : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
586 : : errmsg("cost delay cannot be a negative value"));
587 : :
588 [ - + ]: 18 : if (cost_limit <= 0)
589 [ # # ]: 0 : ereport(ERROR,
590 : : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
591 : : errmsg("cost limit must be greater than zero"));
592 : :
593 : : /*
594 : : * An invalid database cannot be connected to, so the worker would fail to
595 : : * process it, and unlike a dropped database its files stay around. Error
596 : : * out early with a hint rather than failing halfway through processing. A
597 : : * database which turns invalid after this check, for example from an
598 : : * interrupted DROP DATABASE, instead makes its worker fail; the launcher
599 : : * then aborts and leaves checksums disabled, since the invalid database's
600 : : * files would otherwise be left without valid checksums.
601 : : */
602 : 18 : ErrorOnInvalidDatabases();
603 : :
604 : 17 : StartDataChecksumsWorkerLauncher(ENABLE_DATACHECKSUMS, cost_delay, cost_limit);
605 : :
606 : 17 : PG_RETURN_VOID();
607 : : }
608 : :
609 : :
610 : : /*****************************************************************************
611 : : * Functionality for running the datachecksumsworker and associated launcher
612 : : */
613 : :
614 : : /*
615 : : * StartDataChecksumsWorkerLauncher
616 : : * Start the datachecksumsworker launcher process, if not running yet
617 : : *
618 : : * This is called to start data checksums processing for enabling as well as
619 : : * disabling.
620 : : */
621 : : static void
622 : 29 : StartDataChecksumsWorkerLauncher(DataChecksumsWorkerOperation op,
623 : : int cost_delay,
624 : : int cost_limit)
625 : : {
626 : : BackgroundWorker bgw;
627 : : BackgroundWorkerHandle *bgw_handle;
628 : : bool running;
629 : :
630 : : #ifdef USE_ASSERT_CHECKING
631 : : /* The cost delay settings have no effect when disabling */
632 : : if (op == DISABLE_DATACHECKSUMS)
633 : : Assert(cost_delay == 0 && cost_limit == 0);
634 : : #endif
635 : :
636 : 29 : INJECTION_POINT("datachecksumsworker-startup-delay", NULL);
637 : :
638 : : /* Store the desired state in shared memory */
639 : 29 : LWLockAcquire(DataChecksumsWorkerLock, LW_EXCLUSIVE);
640 : :
641 : 29 : DataChecksumState->launch_operation = op;
642 : 29 : DataChecksumState->launch_cost_delay = cost_delay;
643 : 29 : DataChecksumState->launch_cost_limit = cost_limit;
644 : :
645 : : /* Is the launcher already running? If so, what is it doing? */
646 : 29 : running = DataChecksumState->launcher_running;
647 : :
648 : 29 : LWLockRelease(DataChecksumsWorkerLock);
649 : :
650 : : /*
651 : : * Launch a new launcher process, if it's not running already.
652 : : *
653 : : * If the launcher is currently busy enabling the checksums, and we want
654 : : * them disabled (or vice versa), the launcher will notice that at latest
655 : : * when it's about to exit, and will loop back to process the new request.
656 : : * So if the launcher is already running, we don't need to do anything
657 : : * more here to abort it.
658 : : *
659 : : * If you call pg_enable/disable_data_checksums() twice in a row, before
660 : : * the launcher has had a chance to start up, we still end up launching it
661 : : * twice. That's OK, the second invocation will see that a launcher is
662 : : * already running and exit quickly.
663 : : */
664 [ + - ]: 29 : if (!running)
665 : : {
666 [ + + + + : 29 : if ((op == ENABLE_DATACHECKSUMS && DataChecksumsOn()) ||
+ + ]
667 [ + + ]: 12 : (op == DISABLE_DATACHECKSUMS && DataChecksumsOff()))
668 : : {
669 [ + - ]: 5 : ereport(LOG,
670 : : errmsg("data checksums already in desired state, exiting"));
671 : 5 : return;
672 : : }
673 : :
674 : : /*
675 : : * Prepare the BackgroundWorker and launch it.
676 : : */
677 : 24 : memset(&bgw, 0, sizeof(bgw));
678 : 24 : bgw.bgw_flags = BGWORKER_SHMEM_ACCESS | BGWORKER_BACKEND_DATABASE_CONNECTION;
679 : 24 : bgw.bgw_start_time = BgWorkerStart_RecoveryFinished;
680 : 24 : snprintf(bgw.bgw_library_name, BGW_MAXLEN, "postgres");
681 : 24 : snprintf(bgw.bgw_function_name, BGW_MAXLEN, "DataChecksumsWorkerLauncherMain");
682 : 24 : snprintf(bgw.bgw_name, BGW_MAXLEN, "datachecksums launcher");
683 : 24 : snprintf(bgw.bgw_type, BGW_MAXLEN, "datachecksums launcher");
684 : 24 : bgw.bgw_restart_time = BGW_NEVER_RESTART;
685 : 24 : bgw.bgw_notify_pid = MyProcPid;
686 : 24 : bgw.bgw_main_arg = (Datum) 0;
687 : :
688 [ - + ]: 24 : if (!RegisterDynamicBackgroundWorker(&bgw, &bgw_handle))
689 [ # # ]: 0 : ereport(ERROR,
690 : : errcode(ERRCODE_INSUFFICIENT_RESOURCES),
691 : : errmsg("failed to start background worker to process data checksums"));
692 : : }
693 : : else
694 : : {
695 [ # # ]: 0 : ereport(LOG,
696 : : errmsg("data checksum processing already running"));
697 : : }
698 : : }
699 : :
700 : : /*
701 : : * ProcessSingleRelationFork
702 : : * Enable data checksums in a single relation/fork.
703 : : *
704 : : * Returns true if successful, and false if *aborted*. On error, an actual
705 : : * error is raised in the lower levels.
706 : : */
707 : : static bool
708 : 12965 : ProcessSingleRelationFork(Relation reln, ForkNumber forkNum, BufferAccessStrategy strategy)
709 : : {
710 : 12965 : BlockNumber numblocks = RelationGetNumberOfBlocksInFork(reln, forkNum);
711 : : char activity[NAMEDATALEN * 2 + 128];
712 : : char *relns;
713 : :
714 : 12965 : relns = get_namespace_name(RelationGetNamespace(reln));
715 : :
716 : : /* Report the current relation to pg_stat_activity */
717 : 12965 : snprintf(activity, sizeof(activity) - 1, "processing: %s.%s (%s, %u blocks)",
718 [ + - ]: 12965 : (relns ? relns : ""), RelationGetRelationName(reln), forkNames[forkNum], numblocks);
719 : 12965 : pgstat_report_activity(STATE_RUNNING, activity);
720 : : {
721 : 12965 : const int index[] = {
722 : : PROGRESS_DATACHECKSUMS_BLOCKS_TOTAL,
723 : : PROGRESS_DATACHECKSUMS_BLOCKS_DONE
724 : : };
725 : :
726 : : int64 vals[2];
727 : :
728 : 12965 : vals[0] = numblocks;
729 : 12965 : vals[1] = 0;
730 : :
731 : 12965 : pgstat_progress_update_multi_param(2, index, vals);
732 : : }
733 [ + - ]: 12965 : if (relns)
734 : 12965 : pfree(relns);
735 : :
736 : : /*
737 : : * We are looping over the blocks which existed at the time of process
738 : : * start, which is safe since new blocks are created with checksums set
739 : : * already due to the state being "inprogress-on".
740 : : */
741 [ + + ]: 69602 : for (BlockNumber blknum = 0; blknum < numblocks; blknum++)
742 : : {
743 : 56637 : Buffer buf = ReadBufferExtended(reln, forkNum, blknum, RBM_NORMAL, strategy);
744 : :
745 : : /* Need to get an exclusive lock to mark the buffer as dirty */
746 : 56637 : LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
747 : :
748 : : /*
749 : : * Mark the buffer as dirty and force a full page write. We have to
750 : : * re-write the page to WAL even if the checksum hasn't changed,
751 : : * because if there is a replica it might have a slightly different
752 : : * version of the page with an invalid checksum, caused by unlogged
753 : : * changes (e.g. hint bits) on the primary happening while checksums
754 : : * were off. This can happen if there was a valid checksum on the page
755 : : * at one point in the past, so only when checksums are first on, then
756 : : * off, and then turned on again. TODO: investigate if this could be
757 : : * avoided if the checksum is calculated to be correct and wal_level
758 : : * is set to "minimal".
759 : : *
760 : : * Unlogged relations don't need WAL since they are reset to their
761 : : * init fork on recovery. We still dirty the buffer so that the
762 : : * checksum is written to disk at the next checkpoint.
763 : : *
764 : : * The init fork is an exception: it is WAL-logged so the standby can
765 : : * materialize the relation after promotion (see
766 : : * ResetUnloggedRelations()). Skipping it here would leave the
767 : : * standby with a stale init fork that, once copied to the main fork
768 : : * on promotion, would fail checksum verification on every read.
769 : : */
770 : 56637 : START_CRIT_SECTION();
771 : 56637 : MarkBufferDirty(buf);
772 [ + + + + : 56637 : if (RelationNeedsWAL(reln) || forkNum == INIT_FORKNUM)
+ - - + +
+ ]
773 : 56603 : log_newpage_buffer(buf, false);
774 : 56637 : END_CRIT_SECTION();
775 : :
776 : 56637 : UnlockReleaseBuffer(buf);
777 : :
778 : : /* Check if we are asked to abort, the abortion will bubble up. */
779 : : Assert(operation == ENABLE_DATACHECKSUMS);
780 [ + - - + ]: 56637 : CHECK_FOR_WORKER_ABORT_REQUEST();
781 [ - + ]: 56637 : if (abort_requested)
782 : 0 : return false;
783 : :
784 : : /* update the block counter */
785 : 56637 : pgstat_progress_update_param(PROGRESS_DATACHECKSUMS_BLOCKS_DONE,
786 : 56637 : (blknum + 1));
787 : :
788 : : /*
789 : : * Processing is re-using the vacuum cost delay for process
790 : : * throttling, hence why we call vacuum APIs here.
791 : : */
792 : 56637 : vacuum_delay_point(false);
793 : : }
794 : :
795 : 12965 : return true;
796 : : }
797 : :
798 : : /*
799 : : * Initialize all data checksum progress counters to be displayed as NULL.
800 : : */
801 : : static void
802 : 61 : ResetDataChecksumsProgressCounters(void)
803 : : {
804 : 61 : const int index[] = {
805 : : PROGRESS_DATACHECKSUMS_DBS_TOTAL,
806 : : PROGRESS_DATACHECKSUMS_DBS_DONE,
807 : : PROGRESS_DATACHECKSUMS_RELS_TOTAL,
808 : : PROGRESS_DATACHECKSUMS_RELS_DONE,
809 : : PROGRESS_DATACHECKSUMS_BLOCKS_TOTAL,
810 : : PROGRESS_DATACHECKSUMS_BLOCKS_DONE,
811 : : };
812 : :
813 : : int64 vals[lengthof(index)];
814 : :
815 [ + + ]: 427 : for (size_t i = 0; i < lengthof(index); i++)
816 : 366 : vals[i] = -1;
817 : :
818 : 61 : pgstat_progress_update_multi_param(lengthof(index), index, vals);
819 : 61 : }
820 : :
821 : : /*
822 : : * ProcessSingleRelationByOid
823 : : * Process a single relation based on oid.
824 : : *
825 : : * Returns true if successful, and false if *aborted*. On error, an actual
826 : : * error is raised in the lower levels.
827 : : */
828 : : static bool
829 : 9982 : ProcessSingleRelationByOid(Oid relationId, BufferAccessStrategy strategy)
830 : : {
831 : : Relation rel;
832 : 9982 : bool aborted = false;
833 : :
834 : 9982 : StartTransactionCommand();
835 : :
836 : 9982 : rel = try_relation_open(relationId, AccessShareLock);
837 [ - + ]: 9982 : if (rel == NULL)
838 : : {
839 : : /*
840 : : * Relation no longer exists. We don't consider this an error since
841 : : * there are no pages in it that need data checksums, and thus return
842 : : * true. The worker operates off a list of relations generated at the
843 : : * start of processing, so relations being dropped in the meantime is
844 : : * to be expected.
845 : : */
846 : 0 : CommitTransactionCommand();
847 : 0 : pgstat_report_activity(STATE_IDLE, NULL);
848 : 0 : return true;
849 : : }
850 : :
851 [ + + ]: 49910 : for (ForkNumber fnum = 0; fnum <= MAX_FORKNUM; fnum++)
852 : : {
853 [ + + ]: 39928 : if (smgrexists(RelationGetSmgr(rel), fnum))
854 : : {
855 [ - + ]: 12965 : if (!ProcessSingleRelationFork(rel, fnum, strategy))
856 : : {
857 : 0 : aborted = true;
858 : 0 : break;
859 : : }
860 : : }
861 : : }
862 : 9982 : relation_close(rel, AccessShareLock);
863 : :
864 : 9982 : CommitTransactionCommand();
865 : 9982 : pgstat_report_activity(STATE_IDLE, NULL);
866 : :
867 : 9982 : return !aborted;
868 : : }
869 : :
870 : : /*
871 : : * WaitForDataChecksumsWorkerState
872 : : * Wait for a data checksums worker to start or stop.
873 : : *
874 : : * This is like WaitForBackgroundWorkerStartup() and
875 : : * WaitForBackgroundWorkerShutdown(), except that it also reacts to SIGINT
876 : : * received by the launcher. The launcher owns the overall checksum
877 : : * operation, so canceling it should stop the worker it has registered or is
878 : : * currently running.
879 : : *
880 : : * If wait_for_startup is true, wait until the worker is no longer in
881 : : * BGWH_NOT_YET_STARTED state, like WaitForBackgroundWorkerStartup(). If it
882 : : * is false, wait until the worker reaches BGWH_STOPPED state, like
883 : : * WaitForBackgroundWorkerShutdown().
884 : : *
885 : : * pidp is set to the worker's PID when startup succeeds, if it is not NULL.
886 : : */
887 : : static BgwHandleStatus
888 : 80 : WaitForDataChecksumsWorkerState(BackgroundWorkerHandle *handle,
889 : : bool wait_for_startup,
890 : : pid_t *pidp,
891 : : uint32 wait_event)
892 : : {
893 : : BgwHandleStatus status;
894 : 80 : bool termination_requested = false;
895 : :
896 : : for (;;)
897 : 83 : {
898 : : int rc;
899 : : pid_t pid;
900 : :
901 [ + + ]: 163 : CHECK_FOR_INTERRUPTS();
902 : :
903 : 162 : status = GetBackgroundWorkerPid(handle, &pid);
904 [ + + + + ]: 162 : if (status == BGWH_STARTED && pidp)
905 : 40 : *pidp = pid;
906 : :
907 [ + + + + ]: 162 : if (abort_requested && !termination_requested)
908 : : {
909 : 1 : TerminateBackgroundWorker(handle);
910 : 1 : termination_requested = true;
911 : : }
912 : :
913 : : /*
914 : : * Startup waits for the worker to leave BGWH_NOT_YET_STARTED, while
915 : : * shutdown waits for it to reach BGWH_STOPPED.
916 : : */
917 [ + + + + ]: 162 : if (status == BGWH_STOPPED ||
918 [ + + ]: 72 : (wait_for_startup && status == BGWH_STARTED))
919 : : break;
920 : :
921 : 83 : rc = WaitLatch(MyLatch,
922 : : WL_LATCH_SET | WL_POSTMASTER_DEATH, 0,
923 : : wait_event);
924 : :
925 [ - + ]: 83 : if (rc & WL_POSTMASTER_DEATH)
926 : : {
927 : 0 : status = BGWH_POSTMASTER_DIED;
928 : 0 : break;
929 : : }
930 : :
931 : 83 : ResetLatch(MyLatch);
932 : : }
933 : :
934 : 79 : return status;
935 : : }
936 : :
937 : : /*
938 : : * ProcessDatabase
939 : : * Enable data checksums in a single database.
940 : : *
941 : : * We do this by launching a dynamic background worker into this database, and
942 : : * waiting for it to finish. We have to do this in a separate worker, since
943 : : * each process can only be connected to one database during its lifetime.
944 : : */
945 : : static DataChecksumsWorkerResult
946 : 40 : ProcessDatabase(DataChecksumsWorkerDatabase *db)
947 : : {
948 : : BackgroundWorker bgw;
949 : : BackgroundWorkerHandle *bgw_handle;
950 : : BgwHandleStatus status;
951 : : pid_t pid;
952 : : uint64 invocation;
953 : : char activity[NAMEDATALEN + 64];
954 : : DataChecksumsWorkerResult result;
955 : :
956 : 40 : LWLockAcquire(DataChecksumsWorkerLock, LW_EXCLUSIVE);
957 : :
958 : : /*
959 : : * Initialize result to FAILED. The worker will change it to SUCCESSFUL
960 : : * if it completes successfully.
961 : : */
962 : 40 : DataChecksumState->worker_result = DATACHECKSUMSWORKER_FAILED;
963 : 40 : DataChecksumState->worker_pid = InvalidPid;
964 : :
965 : 40 : invocation = ++DataChecksumState->worker_invocation_counter;
966 : 40 : DataChecksumState->worker_invocation = invocation;
967 : 40 : DataChecksumState->database_oid = db->dboid;
968 : :
969 : 40 : LWLockRelease(DataChecksumsWorkerLock);
970 : :
971 : 40 : memset(&bgw, 0, sizeof(bgw));
972 : 40 : bgw.bgw_flags = BGWORKER_SHMEM_ACCESS | BGWORKER_BACKEND_DATABASE_CONNECTION;
973 : 40 : bgw.bgw_start_time = BgWorkerStart_RecoveryFinished;
974 : 40 : snprintf(bgw.bgw_library_name, BGW_MAXLEN, "postgres");
975 : 40 : snprintf(bgw.bgw_function_name, BGW_MAXLEN, "%s", "DataChecksumsWorkerMain");
976 : 40 : snprintf(bgw.bgw_name, BGW_MAXLEN, "datachecksums worker");
977 : 40 : snprintf(bgw.bgw_type, BGW_MAXLEN, "datachecksums worker");
978 : 40 : bgw.bgw_restart_time = BGW_NEVER_RESTART;
979 : 40 : bgw.bgw_notify_pid = MyProcPid;
980 : : /* pass the invocation number to the worker process */
981 : 40 : bgw.bgw_main_arg = UInt64GetDatum(invocation);
982 : :
983 : : /*
984 : : * If there are no worker slots available, there is little we can do. If
985 : : * we retry in a bit it's still unlikely that the user has managed to
986 : : * reconfigure in the meantime and we'd be run through retries fast.
987 : : */
988 [ - + ]: 40 : if (!RegisterDynamicBackgroundWorker(&bgw, &bgw_handle))
989 : : {
990 [ # # ]: 0 : ereport(WARNING,
991 : : errmsg("could not start background worker for enabling data checksums in database \"%s\"",
992 : : db->dbname),
993 : : errhint("The \"%s\" setting might be too low.", "max_worker_processes"));
994 : 0 : return DATACHECKSUMSWORKER_FAILED;
995 : : }
996 : :
997 : : /*
998 : : * While this expects to wait for BGWORKER_STARTUP it may return _STOPPED
999 : : * if the worker was terminated in the meantime so we must check status.
1000 : : */
1001 : 40 : status = WaitForDataChecksumsWorkerState(bgw_handle, true, &pid,
1002 : : WAIT_EVENT_BGWORKER_STARTUP);
1003 [ - + ]: 40 : if (status == BGWH_STOPPED)
1004 : : {
1005 [ # # ]: 0 : if (abort_requested)
1006 : : {
1007 : 0 : result = DATACHECKSUMSWORKER_ABORTED;
1008 : 0 : goto done;
1009 : : }
1010 : :
1011 : : /*
1012 : : * If the worker managed to start, and stop, before we got to waiting
1013 : : * for it we can see a STOPPED status here without it being a failure.
1014 : : */
1015 : 0 : LWLockAcquire(DataChecksumsWorkerLock, LW_SHARED);
1016 : : Assert(DataChecksumState->worker_invocation == invocation);
1017 [ # # ]: 0 : if (DataChecksumState->worker_result == DATACHECKSUMSWORKER_SUCCESSFUL)
1018 : : {
1019 : 0 : LWLockRelease(DataChecksumsWorkerLock);
1020 : 0 : pgstat_report_activity(STATE_IDLE, NULL);
1021 : 0 : return DATACHECKSUMSWORKER_SUCCESSFUL;
1022 : : }
1023 : 0 : LWLockRelease(DataChecksumsWorkerLock);
1024 : :
1025 [ # # ]: 0 : ereport(WARNING,
1026 : : errmsg("could not start background worker for enabling data checksums in database \"%s\"",
1027 : : db->dbname),
1028 : : errhint("More details on the error might be found in the server log."));
1029 : :
1030 : : /*
1031 : : * Heuristic to see if the database was dropped, and if it was we can
1032 : : * treat it as not an error, else treat as fatal and error out.
1033 : : */
1034 [ # # ]: 0 : if (DatabaseExists(db->dboid))
1035 : 0 : return DATACHECKSUMSWORKER_FAILED;
1036 : : else
1037 : 0 : return DATACHECKSUMSWORKER_DROPDB;
1038 : : }
1039 : :
1040 : : /*
1041 : : * If the postmaster crashed we cannot end up with a processed database so
1042 : : * we have no alternative other than exiting. When enabling checksums we
1043 : : * won't at this time have changed the data checksums state in pg_control
1044 : : * to enabled so when the cluster comes back up processing will have to be
1045 : : * restarted.
1046 : : */
1047 [ - + ]: 40 : if (status == BGWH_POSTMASTER_DIED)
1048 [ # # ]: 0 : ereport(FATAL,
1049 : : errcode(ERRCODE_ADMIN_SHUTDOWN),
1050 : : errmsg("cannot enable data checksums without the postmaster process"),
1051 : : errhint("Restart the database and restart data checksum processing by calling pg_enable_data_checksums()."));
1052 : :
1053 : : Assert(status == BGWH_STARTED);
1054 [ + - ]: 40 : ereport(LOG,
1055 : : errmsg("initiating data checksum processing in database \"%s\"",
1056 : : db->dbname));
1057 : :
1058 : : /* Save the pid of the worker so we can signal it later */
1059 : 40 : LWLockAcquire(DataChecksumsWorkerLock, LW_EXCLUSIVE);
1060 : : Assert(DataChecksumState->worker_invocation == invocation);
1061 : 40 : DataChecksumState->worker_pid = pid;
1062 : 40 : LWLockRelease(DataChecksumsWorkerLock);
1063 : :
1064 : 40 : snprintf(activity, sizeof(activity) - 1,
1065 : : "Waiting for worker in database %s (pid %ld)", db->dbname, (long) pid);
1066 : 40 : pgstat_report_activity(STATE_RUNNING, activity);
1067 : :
1068 : 40 : status = WaitForDataChecksumsWorkerState(bgw_handle, false, NULL,
1069 : : WAIT_EVENT_BGWORKER_SHUTDOWN);
1070 [ - + ]: 39 : if (status == BGWH_POSTMASTER_DIED)
1071 [ # # ]: 0 : ereport(FATAL,
1072 : : errcode(ERRCODE_ADMIN_SHUTDOWN),
1073 : : errmsg("postmaster exited during data checksum processing in \"%s\"",
1074 : : db->dbname),
1075 : : errhint("Restart the database and restart data checksum processing by calling pg_enable_data_checksums()."));
1076 : :
1077 : 39 : LWLockAcquire(DataChecksumsWorkerLock, LW_EXCLUSIVE);
1078 : : Assert(DataChecksumState->worker_invocation == invocation);
1079 : 39 : result = DataChecksumState->worker_result;
1080 : 39 : DataChecksumState->worker_pid = InvalidPid;
1081 : 39 : LWLockRelease(DataChecksumsWorkerLock);
1082 : :
1083 : : /*
1084 : : * A worker which started but failed before reporting a result has most
1085 : : * likely FATALed in InitPostgres. If the database was dropped after we
1086 : : * built the database list then that is the expected outcome and not an
1087 : : * error, so apply the same heuristic as when the worker failed to start.
1088 : : */
1089 [ + + + + ]: 39 : if (result == DATACHECKSUMSWORKER_FAILED && !DatabaseExists(db->dboid))
1090 : 2 : result = DATACHECKSUMSWORKER_DROPDB;
1091 : :
1092 [ - + ]: 39 : CHECK_FOR_LAUNCHER_ABORT_REQUEST();
1093 [ + + ]: 39 : if (abort_requested)
1094 : 1 : result = DATACHECKSUMSWORKER_ABORTED;
1095 : :
1096 : 38 : done:
1097 [ + + ]: 39 : if (result == DATACHECKSUMSWORKER_ABORTED)
1098 [ + - ]: 1 : ereport(LOG,
1099 : : errmsg("data checksums processing was aborted in database \"%s\"",
1100 : : db->dbname));
1101 : 39 : pgstat_report_activity(STATE_IDLE, NULL);
1102 : 39 : return result;
1103 : : }
1104 : :
1105 : : /*
1106 : : * launcher_exit
1107 : : *
1108 : : * Internal routine for cleaning up state when a launcher process which has
1109 : : * performed checksum operations exits. A launcher process which is exiting due
1110 : : * to a duplicate started launcher does not need to perform any cleanup and
1111 : : * this function should not be called. Otherwise, we need to clean up the abort
1112 : : * flag to ensure that processing can be started again if it was previously
1113 : : * aborted (note: started again, *not* restarted from where it left off).
1114 : : */
1115 : : static void
1116 : 23 : launcher_exit(int code, Datum arg)
1117 : : {
1118 : 23 : abort_requested = false;
1119 : :
1120 [ + + ]: 23 : if (launcher_running)
1121 : : {
1122 : 3 : LWLockAcquire(DataChecksumsWorkerLock, LW_EXCLUSIVE);
1123 [ + + ]: 3 : if (DataChecksumState->worker_pid != InvalidPid)
1124 : : {
1125 [ + - ]: 1 : ereport(LOG,
1126 : : errmsg("data checksums launcher exiting while worker is still running, signalling worker"));
1127 : 1 : kill(DataChecksumState->worker_pid, SIGTERM);
1128 : 1 : DataChecksumState->worker_pid = InvalidPid;
1129 : : }
1130 : 3 : LWLockRelease(DataChecksumsWorkerLock);
1131 : : }
1132 : :
1133 : : /*
1134 : : * If the launcher is exiting before data checksums are enabled then set
1135 : : * the state to off since processing cannot be resumed.
1136 : : */
1137 [ + + ]: 23 : if (DataChecksumsInProgressOn())
1138 : 2 : SetDataChecksumsOff();
1139 : :
1140 : 23 : LWLockAcquire(DataChecksumsWorkerLock, LW_EXCLUSIVE);
1141 : 23 : launcher_running = false;
1142 : 23 : DataChecksumState->launcher_running = false;
1143 : 23 : LWLockRelease(DataChecksumsWorkerLock);
1144 : 23 : }
1145 : :
1146 : : /*
1147 : : * launcher_cancel_handler
1148 : : *
1149 : : * Internal routine for reacting to SIGINT and flagging the launcher to abort.
1150 : : * If a worker is registered or running, the launcher will request worker
1151 : : * termination from its normal control flow.
1152 : : */
1153 : : static void
1154 : 1 : launcher_cancel_handler(SIGNAL_ARGS)
1155 : : {
1156 : 1 : int save_errno = errno;
1157 : :
1158 : 1 : abort_requested = true;
1159 : :
1160 : : /*
1161 : : * Wake the launcher if it is waiting for transactions to finish or for a
1162 : : * worker to start up or shut down.
1163 : : */
1164 : 1 : SetLatch(MyLatch);
1165 : :
1166 : 1 : errno = save_errno;
1167 : 1 : }
1168 : :
1169 : : /*
1170 : : * WaitForAllTransactionsToFinish
1171 : : * Blocks awaiting all current transactions to finish
1172 : : *
1173 : : * Returns when all transactions which are active at the call of the function
1174 : : * have ended.
1175 : : *
1176 : : * NB: this will return early, if aborted by SIGINT or if the target state
1177 : : * is changed while we're running.
1178 : : */
1179 : : static void
1180 : 15 : WaitForAllTransactionsToFinish(void)
1181 : : {
1182 : : TransactionId waitforxid;
1183 : :
1184 : 15 : LWLockAcquire(XidGenLock, LW_SHARED);
1185 : 15 : waitforxid = XidFromFullTransactionId(TransamVariables->nextXid);
1186 : 15 : LWLockRelease(XidGenLock);
1187 : :
1188 [ - + ]: 15 : while (TransactionIdPrecedes(GetOldestActiveTransactionId(false, true), waitforxid))
1189 : : {
1190 : : char activity[64];
1191 : : int rc;
1192 : :
1193 : : /* Oldest running xid is older than us, so wait */
1194 : 0 : snprintf(activity,
1195 : : sizeof(activity),
1196 : : "Waiting for transactions older than %u to end",
1197 : : waitforxid);
1198 : 0 : pgstat_report_activity(STATE_RUNNING, activity);
1199 : :
1200 : : /* Retry every 3 seconds */
1201 : 0 : ResetLatch(MyLatch);
1202 : 0 : rc = WaitLatch(MyLatch,
1203 : : WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
1204 : : 3000,
1205 : : WAIT_EVENT_CHECKSUM_ENABLE_STARTCONDITION);
1206 : :
1207 : : /*
1208 : : * If the postmaster died, bail out. But first print a log message to
1209 : : * note that the checksumming didn't complete.
1210 : : */
1211 [ # # ]: 0 : if (rc & WL_POSTMASTER_DEATH)
1212 [ # # ]: 0 : ereport(FATAL,
1213 : : errcode(ERRCODE_ADMIN_SHUTDOWN),
1214 : : errmsg("postmaster exited during data checksums processing"),
1215 : : errhint("Data checksums processing must be restarted manually after cluster restart."));
1216 : :
1217 [ # # ]: 0 : CHECK_FOR_INTERRUPTS();
1218 [ # # ]: 0 : CHECK_FOR_LAUNCHER_ABORT_REQUEST();
1219 : :
1220 [ # # ]: 0 : if (abort_requested)
1221 : 0 : break;
1222 : : }
1223 : :
1224 : 15 : pgstat_report_activity(STATE_IDLE, NULL);
1225 : 15 : return;
1226 : : }
1227 : :
1228 : : /*
1229 : : * DataChecksumsWorkerLauncherMain
1230 : : *
1231 : : * Main function for launching dynamic background workers for processing data
1232 : : * checksums in databases. This function has the bgworker management, with
1233 : : * ProcessAllDatabases being responsible for looping over the databases and
1234 : : * initiating processing.
1235 : : */
1236 : : void
1237 : 23 : DataChecksumsWorkerLauncherMain(Datum arg)
1238 : : {
1239 : :
1240 [ - + ]: 23 : ereport(DEBUG1,
1241 : : errmsg("background worker \"datachecksums launcher\" started"));
1242 : :
1243 : 23 : pqsignal(SIGTERM, die);
1244 : 23 : pqsignal(SIGINT, launcher_cancel_handler);
1245 : 23 : pqsignal(SIGUSR1, procsignal_sigusr1_handler);
1246 : 23 : pqsignal(SIGUSR2, PG_SIG_IGN);
1247 : :
1248 : 23 : BackgroundWorkerUnblockSignals();
1249 : :
1250 : 23 : MyBackendType = B_DATACHECKSUMSWORKER_LAUNCHER;
1251 : 23 : init_ps_display(NULL);
1252 : :
1253 : 23 : INJECTION_POINT("datachecksumsworker-launcher-delay", NULL);
1254 : :
1255 : 23 : LWLockAcquire(DataChecksumsWorkerLock, LW_EXCLUSIVE);
1256 : :
1257 [ - + ]: 23 : if (DataChecksumState->launcher_running)
1258 : : {
1259 [ # # ]: 0 : ereport(LOG,
1260 : : errmsg("background worker \"datachecksums launcher\" already running, exiting"));
1261 : : /* Launcher was already running, let it finish */
1262 : 0 : LWLockRelease(DataChecksumsWorkerLock);
1263 : 0 : return;
1264 : : }
1265 : :
1266 : 23 : on_shmem_exit(launcher_exit, 0);
1267 : 23 : launcher_running = true;
1268 : :
1269 : : /* Initialize a connection to shared catalogs only */
1270 : 23 : BackgroundWorkerInitializeConnectionByOid(InvalidOid, InvalidOid, 0);
1271 : :
1272 : 23 : operation = DataChecksumState->launch_operation;
1273 : 23 : DataChecksumState->launcher_running = true;
1274 : 23 : DataChecksumState->operation = operation;
1275 : 23 : DataChecksumState->cost_delay = DataChecksumState->launch_cost_delay;
1276 : 23 : DataChecksumState->cost_limit = DataChecksumState->launch_cost_limit;
1277 : 23 : LWLockRelease(DataChecksumsWorkerLock);
1278 : :
1279 : : /*
1280 : : * The target state can change while we are busy enabling/disabling
1281 : : * checksums, if the user calls pg_disable/enable_data_checksums() before
1282 : : * we are finished with the previous request. In that case, we will loop
1283 : : * back here, to process the new request.
1284 : : */
1285 : 23 : again:
1286 : :
1287 : 23 : pgstat_progress_start_command(PROGRESS_COMMAND_DATACHECKSUMS,
1288 : : InvalidOid);
1289 : 23 : ResetDataChecksumsProgressCounters();
1290 : :
1291 [ + + ]: 23 : if (operation == ENABLE_DATACHECKSUMS)
1292 : : {
1293 : : /*
1294 : : * If we are asked to enable checksums in a cluster which already has
1295 : : * checksums enabled, exit immediately as there is nothing more to do.
1296 : : */
1297 [ - + ]: 15 : if (DataChecksumsNeedVerify())
1298 : 0 : goto done;
1299 : :
1300 [ + - ]: 15 : ereport(LOG,
1301 : : errmsg("enabling data checksums requested, starting data checksum calculation"));
1302 : :
1303 : : /*
1304 : : * Set the state to inprogress-on and wait on the procsignal barrier.
1305 : : */
1306 : 15 : pgstat_progress_update_param(PROGRESS_DATACHECKSUMS_PHASE,
1307 : : PROGRESS_DATACHECKSUMS_PHASE_ENABLING);
1308 : 15 : SetDataChecksumsOnInProgress();
1309 : :
1310 : : /*
1311 : : * All backends are now in inprogress-on state and are writing data
1312 : : * checksums. Start processing all data at rest.
1313 : : */
1314 [ + + ]: 15 : if (!ProcessAllDatabases())
1315 : : {
1316 : : /*
1317 : : * If processing was canceled, or the target state changed during
1318 : : * processing, then it's not a failure. In the latter case, the
1319 : : * launcher will restart processing with the new target state.
1320 : : */
1321 [ - + ]: 1 : CHECK_FOR_LAUNCHER_ABORT_REQUEST();
1322 [ + - ]: 1 : if (abort_requested)
1323 : 1 : goto done;
1324 [ # # ]: 0 : ereport(ERROR,
1325 : : errcode(ERRCODE_INSUFFICIENT_RESOURCES),
1326 : : errmsg("unable to enable data checksums in cluster"));
1327 : : }
1328 : :
1329 : : /*
1330 : : * Data checksums have been set on all pages, set the state to on in
1331 : : * order to instruct backends to validate checksums on reading.
1332 : : */
1333 : 11 : SetDataChecksumsOn();
1334 : :
1335 [ + - ]: 11 : ereport(LOG,
1336 : : errmsg("data checksums are now enabled"));
1337 : : }
1338 [ - + ]: 8 : else if (operation == DISABLE_DATACHECKSUMS)
1339 : : {
1340 [ + - ]: 8 : ereport(LOG,
1341 : : errmsg("disabling data checksums requested"));
1342 : :
1343 : 8 : pgstat_progress_update_param(PROGRESS_DATACHECKSUMS_PHASE,
1344 : : PROGRESS_DATACHECKSUMS_PHASE_DISABLING);
1345 : 8 : SetDataChecksumsOff();
1346 [ + - ]: 8 : ereport(LOG,
1347 : : errmsg("data checksums are now disabled"));
1348 : : }
1349 : : else
1350 : : Assert(false);
1351 : :
1352 : 0 : done:
1353 : :
1354 : : /*
1355 : : * This state will only be displayed for a fleeting moment, but for the
1356 : : * sake of correctness it is still added before ending the command.
1357 : : */
1358 : 20 : pgstat_progress_update_param(PROGRESS_DATACHECKSUMS_PHASE,
1359 : : PROGRESS_DATACHECKSUMS_PHASE_DONE);
1360 : :
1361 : : /*
1362 : : * All done. But before we exit, check if the target state was changed
1363 : : * while we were running. In that case we will have to start all over
1364 : : * again.
1365 : : */
1366 : 20 : LWLockAcquire(DataChecksumsWorkerLock, LW_EXCLUSIVE);
1367 [ - + ]: 20 : if (DataChecksumState->launch_operation != operation)
1368 : : {
1369 : 0 : DataChecksumState->operation = DataChecksumState->launch_operation;
1370 : 0 : operation = DataChecksumState->launch_operation;
1371 : 0 : DataChecksumState->cost_delay = DataChecksumState->launch_cost_delay;
1372 : 0 : DataChecksumState->cost_limit = DataChecksumState->launch_cost_limit;
1373 : 0 : LWLockRelease(DataChecksumsWorkerLock);
1374 : 0 : goto again;
1375 : : }
1376 : :
1377 : : /* Shut down progress reporting as we are done */
1378 : 20 : pgstat_progress_end_command();
1379 : :
1380 : 20 : launcher_running = false;
1381 : 20 : DataChecksumState->launcher_running = false;
1382 : 20 : LWLockRelease(DataChecksumsWorkerLock);
1383 : : }
1384 : :
1385 : : /*
1386 : : * ProcessAllDatabases
1387 : : * Compute the list of all databases and process checksums in each
1388 : : *
1389 : : * This will generate a list of databases to process for enabling checksums.
1390 : : * If a database encounters a failure then processing will end immediately and
1391 : : * return an error.
1392 : : */
1393 : : static bool
1394 : 15 : ProcessAllDatabases(void)
1395 : : {
1396 : : List *DatabaseList;
1397 : 15 : int cumulative_total = 0;
1398 : :
1399 : : /* Set up so first run processes shared catalogs, not once in every db */
1400 : 15 : LWLockAcquire(DataChecksumsWorkerLock, LW_EXCLUSIVE);
1401 : 15 : DataChecksumState->process_shared_catalogs = true;
1402 : 15 : LWLockRelease(DataChecksumsWorkerLock);
1403 : :
1404 : : /* Get a list of all databases to process */
1405 : 15 : WaitForAllTransactionsToFinish();
1406 [ - + ]: 15 : if (abort_requested)
1407 : 0 : return false;
1408 : 15 : DatabaseList = BuildDatabaseList();
1409 : :
1410 : : /*
1411 : : * Update progress reporting with the total number of databases we need to
1412 : : * process. This number should not be changed during processing, the
1413 : : * columns for processed databases is instead increased such that it can
1414 : : * be compared against the total.
1415 : : */
1416 : : {
1417 : 15 : const int index[] = {
1418 : : PROGRESS_DATACHECKSUMS_DBS_TOTAL,
1419 : : PROGRESS_DATACHECKSUMS_DBS_DONE,
1420 : : };
1421 : :
1422 : : int64 vals[2];
1423 : :
1424 : 15 : vals[0] = list_length(DatabaseList);
1425 : 15 : vals[1] = 0;
1426 : :
1427 : 15 : pgstat_progress_update_multi_param(2, index, vals);
1428 : : }
1429 : :
1430 [ + - + + : 62 : foreach_ptr(DataChecksumsWorkerDatabase, db, DatabaseList)
+ + ]
1431 : : {
1432 : : DataChecksumsWorkerResult result;
1433 : :
1434 : 40 : result = ProcessDatabase(db);
1435 : :
1436 : : #ifdef USE_INJECTION_POINTS
1437 : : /* Allow a test process to alter the result of the operation */
1438 [ + + ]: 39 : if (IS_INJECTION_POINT_ATTACHED("datachecksumsworker-fail-db-result"))
1439 : : {
1440 : 1 : result = DATACHECKSUMSWORKER_FAILED;
1441 : 1 : INJECTION_POINT_CACHED("datachecksumsworker-fail-db-result",
1442 : : db->dbname);
1443 : : }
1444 : : #endif
1445 : :
1446 : 39 : pgstat_progress_update_param(PROGRESS_DATACHECKSUMS_DBS_DONE,
1447 : : ++cumulative_total);
1448 : :
1449 [ + + ]: 39 : if (result == DATACHECKSUMSWORKER_FAILED)
1450 : : {
1451 : : /*
1452 : : * Disable checksums on cluster, because we failed one of the
1453 : : * databases and this is an all or nothing process.
1454 : : */
1455 : 2 : SetDataChecksumsOff();
1456 [ + - ]: 2 : ereport(ERROR,
1457 : : errcode(ERRCODE_INSUFFICIENT_RESOURCES),
1458 : : errmsg("data checksums failed to get enabled in all databases, aborting"),
1459 : : errhint("The server log might have more information on the cause of the error."));
1460 : : }
1461 [ + + - + ]: 37 : else if (result == DATACHECKSUMSWORKER_ABORTED || abort_requested)
1462 : : {
1463 : : /* Abort flag set, so exit the whole process */
1464 : 1 : FreeDatabaseList(DatabaseList);
1465 : 1 : return false;
1466 : : }
1467 [ + + ]: 36 : else if (result == DATACHECKSUMSWORKER_DROPDB)
1468 : : {
1469 : : /*
1470 : : * Ignore databases that were dropped before their worker could
1471 : : * process them, and continue with the remaining databases.
1472 : : */
1473 : 2 : continue;
1474 : : }
1475 : :
1476 : : /*
1477 : : * When one database has completed, it will have done shared catalogs
1478 : : * so we don't have to process them again.
1479 : : */
1480 : 34 : LWLockAcquire(DataChecksumsWorkerLock, LW_EXCLUSIVE);
1481 : 34 : DataChecksumState->process_shared_catalogs = false;
1482 : 34 : LWLockRelease(DataChecksumsWorkerLock);
1483 : : }
1484 : :
1485 : 11 : FreeDatabaseList(DatabaseList);
1486 : :
1487 : 11 : pgstat_progress_update_param(PROGRESS_DATACHECKSUMS_PHASE,
1488 : : PROGRESS_DATACHECKSUMS_PHASE_WAITING_BARRIER);
1489 : 11 : return true;
1490 : : }
1491 : :
1492 : : /*
1493 : : * DataChecksumsShmemRequest
1494 : : * Request datachecksumsworker-related shared memory
1495 : : */
1496 : : static void
1497 : 1268 : DataChecksumsShmemRequest(void *arg)
1498 : : {
1499 : 1268 : ShmemRequestStruct(.name = "DataChecksumsWorker Data",
1500 : : .size = sizeof(DataChecksumsStateStruct),
1501 : : .ptr = (void **) &DataChecksumState,
1502 : : );
1503 : 1268 : }
1504 : :
1505 : : /*
1506 : : * DatabaseExists
1507 : : *
1508 : : * Scans the system catalog to check if a database with the given Oid exists
1509 : : * and returns true if it is found, even if it is marked invalid. An invalid
1510 : : * database still has files that need checksums, so only a missing catalog row
1511 : : * proves that a concurrent DROP DATABASE completed.
1512 : : */
1513 : : static bool
1514 : 4 : DatabaseExists(Oid dboid)
1515 : : {
1516 : : Relation rel;
1517 : : ScanKeyData skey;
1518 : : SysScanDesc scan;
1519 : : bool found;
1520 : : HeapTuple tuple;
1521 : :
1522 : 4 : StartTransactionCommand();
1523 : :
1524 : : /*
1525 : : * DROP DATABASE holds an exclusive lock on the database from before it
1526 : : * terminates the connections to it until it commits, so take a lock which
1527 : : * conflicts with it to wait out a drop which is in flight. Without this
1528 : : * we can see a database whose worker was just killed by DROP DATABASE ...
1529 : : * WITH (FORCE) as still existing, and report a spurious failure.
1530 : : */
1531 : 4 : LockSharedObject(DatabaseRelationId, dboid, 0, AccessShareLock);
1532 : :
1533 : 4 : rel = table_open(DatabaseRelationId, AccessShareLock);
1534 : 4 : ScanKeyInit(&skey,
1535 : : Anum_pg_database_oid,
1536 : : BTEqualStrategyNumber, F_OIDEQ,
1537 : : ObjectIdGetDatum(dboid));
1538 : 4 : scan = systable_beginscan(rel, DatabaseOidIndexId, true, SnapshotSelf,
1539 : : 1, &skey);
1540 : 4 : tuple = systable_getnext(scan);
1541 : 4 : found = HeapTupleIsValid(tuple);
1542 : :
1543 : 4 : systable_endscan(scan);
1544 : 4 : table_close(rel, AccessShareLock);
1545 : :
1546 : 4 : CommitTransactionCommand();
1547 : :
1548 : 4 : return found;
1549 : : }
1550 : :
1551 : : /*
1552 : : * ErrorOnInvalidDatabases
1553 : : * Error out if the cluster contains an invalid database
1554 : : *
1555 : : * A database left invalid by an interrupted DROP DATABASE cannot be connected
1556 : : * to, so data checksums can never be enabled in it, while its files remain on
1557 : : * disk where checksum verification will find them. Report it to the caller
1558 : : * so the user can drop it before retrying. Called from a normal backend, so
1559 : : * unlike DatabaseExists we are already in a transaction.
1560 : : *
1561 : : * A cluster can contain more than one invalid database, but only the first one
1562 : : * found is reported; collecting them all is not worth the complexity here. A
1563 : : * user with several of them gets the error again for the next one after
1564 : : * dropping the reported database, which the hint accounts for.
1565 : : */
1566 : : static void
1567 : 18 : ErrorOnInvalidDatabases(void)
1568 : : {
1569 : : Relation rel;
1570 : : TableScanDesc scan;
1571 : : HeapTuple tup;
1572 : :
1573 : 18 : rel = table_open(DatabaseRelationId, AccessShareLock);
1574 : 18 : scan = table_beginscan_catalog(rel, 0, NULL);
1575 : :
1576 [ + + ]: 75 : while (HeapTupleIsValid(tup = heap_getnext(scan, ForwardScanDirection)))
1577 : : {
1578 : 58 : Form_pg_database pgdb = (Form_pg_database) GETSTRUCT(tup);
1579 : :
1580 [ + + ]: 58 : if (database_is_invalid_form(pgdb))
1581 [ + - ]: 1 : ereport(ERROR,
1582 : : errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
1583 : : errmsg("cannot enable data checksums in a cluster with invalid database \"%s\"",
1584 : : NameStr(pgdb->datname)),
1585 : : errhint("Use DROP DATABASE to drop invalid databases."));
1586 : : }
1587 : :
1588 : 17 : table_endscan(scan);
1589 : 17 : table_close(rel, AccessShareLock);
1590 : 17 : }
1591 : :
1592 : : /*
1593 : : * BuildDatabaseList
1594 : : * Compile a list of all currently available databases in the cluster
1595 : : *
1596 : : * This creates the list of databases for the datachecksumsworker workers to
1597 : : * add checksums to. If the caller wants to ensure that no concurrently
1598 : : * running CREATE DATABASE calls exist, this needs to be preceded by a call
1599 : : * to WaitForAllTransactionsToFinish().
1600 : : */
1601 : : static List *
1602 : 15 : BuildDatabaseList(void)
1603 : : {
1604 : 15 : List *DatabaseList = NIL;
1605 : : Relation rel;
1606 : : TableScanDesc scan;
1607 : : HeapTuple tup;
1608 : 15 : MemoryContext ctx = CurrentMemoryContext;
1609 : : MemoryContext oldctx;
1610 : :
1611 : 15 : StartTransactionCommand();
1612 : :
1613 : 15 : rel = table_open(DatabaseRelationId, AccessShareLock);
1614 : 15 : scan = table_beginscan_catalog(rel, 0, NULL);
1615 : :
1616 [ + + ]: 63 : while (HeapTupleIsValid(tup = heap_getnext(scan, ForwardScanDirection)))
1617 : : {
1618 : 48 : Form_pg_database pgdb = (Form_pg_database) GETSTRUCT(tup);
1619 : : DataChecksumsWorkerDatabase *db;
1620 : :
1621 : 48 : oldctx = MemoryContextSwitchTo(ctx);
1622 : :
1623 : 48 : db = palloc0_object(DataChecksumsWorkerDatabase);
1624 : :
1625 : 48 : db->dboid = pgdb->oid;
1626 : 48 : db->dbname = pstrdup(NameStr(pgdb->datname));
1627 : :
1628 : 48 : DatabaseList = lappend(DatabaseList, db);
1629 : :
1630 : 48 : MemoryContextSwitchTo(oldctx);
1631 : : }
1632 : :
1633 : 15 : table_endscan(scan);
1634 : 15 : table_close(rel, AccessShareLock);
1635 : :
1636 : 15 : CommitTransactionCommand();
1637 : :
1638 : 15 : return DatabaseList;
1639 : : }
1640 : :
1641 : : static void
1642 : 12 : FreeDatabaseList(List *dblist)
1643 : : {
1644 [ - + ]: 12 : if (!dblist)
1645 : 0 : return;
1646 : :
1647 [ + - + + : 62 : foreach_ptr(DataChecksumsWorkerDatabase, db, dblist)
+ + ]
1648 : : {
1649 [ + - ]: 38 : if (db->dbname != NULL)
1650 : 38 : pfree(db->dbname);
1651 : : }
1652 : :
1653 : 12 : list_free_deep(dblist);
1654 : : }
1655 : :
1656 : : /*
1657 : : * BuildRelationList
1658 : : * Compile a list of relations in the database
1659 : : *
1660 : : * Returns a list of OIDs for the requested relation types. If temp_relations
1661 : : * is True then only temporary relations with storage are returned. If
1662 : : * temp_relations is False then non-temporary relations with storage are
1663 : : * returned. If include_shared is True then shared relations are included as
1664 : : * well in a non-temporary list. include_shared has no relevance when building
1665 : : * a list of temporary relations.
1666 : : */
1667 : : static List *
1668 : 118 : BuildRelationList(bool temp_relations, bool include_shared)
1669 : : {
1670 : 118 : List *RelationList = NIL;
1671 : : Relation rel;
1672 : : TableScanDesc scan;
1673 : : HeapTuple tup;
1674 : 118 : MemoryContext ctx = CurrentMemoryContext;
1675 : : MemoryContext oldctx;
1676 : :
1677 : 118 : StartTransactionCommand();
1678 : :
1679 : 118 : rel = table_open(RelationRelationId, AccessShareLock);
1680 : 118 : scan = table_beginscan_catalog(rel, 0, NULL);
1681 : :
1682 [ + + ]: 53651 : while (HeapTupleIsValid(tup = heap_getnext(scan, ForwardScanDirection)))
1683 : : {
1684 : 53533 : Form_pg_class pgc = (Form_pg_class) GETSTRUCT(tup);
1685 : :
1686 [ + + + + : 53533 : if (!RELKIND_HAS_STORAGE(pgc->relkind))
+ - + + +
- ]
1687 : 19234 : continue;
1688 : :
1689 : : /* Only include temporary relations when explicitly asked to */
1690 [ + + ]: 34299 : if (pgc->relpersistence == RELPERSISTENCE_TEMP)
1691 : : {
1692 [ + + ]: 16 : if (!temp_relations)
1693 : 5 : continue;
1694 : : }
1695 : : else
1696 : : {
1697 : : /*
1698 : : * If we are only interested in temp relations then continue
1699 : : * immediately as the current relation isn't a temp relation.
1700 : : */
1701 [ + + ]: 34283 : if (temp_relations)
1702 : 23243 : continue;
1703 : :
1704 [ + + + + ]: 11040 : if (pgc->relisshared && !include_shared)
1705 : 1058 : continue;
1706 : : }
1707 : :
1708 : 9993 : oldctx = MemoryContextSwitchTo(ctx);
1709 : 9993 : RelationList = lappend_oid(RelationList, pgc->oid);
1710 : 9993 : MemoryContextSwitchTo(oldctx);
1711 : : }
1712 : :
1713 : 118 : table_endscan(scan);
1714 : 118 : table_close(rel, AccessShareLock);
1715 : :
1716 : 118 : CommitTransactionCommand();
1717 : :
1718 : 118 : return RelationList;
1719 : : }
1720 : :
1721 : : /*
1722 : : * DataChecksumsWorkerMain
1723 : : *
1724 : : * Main function for enabling checksums in a single database. This is the
1725 : : * function set as the bgw_function_name in the dynamic background worker
1726 : : * process initiated for each database by the worker launcher. After enabling
1727 : : * data checksums in each applicable relation in the database, it will wait for
1728 : : * all temporary relations that were present when the function started to
1729 : : * disappear before returning. This is required since we cannot rewrite
1730 : : * existing temporary relations with data checksums.
1731 : : */
1732 : : void
1733 : 40 : DataChecksumsWorkerMain(Datum arg)
1734 : : {
1735 : : Oid dboid;
1736 : 40 : List *RelationList = NIL;
1737 : 40 : List *InitialTempTableList = NIL;
1738 : : BufferAccessStrategy strategy;
1739 : 40 : bool aborted = false;
1740 : : int64 rels_done;
1741 : : bool process_shared;
1742 : : #ifdef USE_INJECTION_POINTS
1743 : 40 : bool retried = false;
1744 : : #endif
1745 : :
1746 : 40 : worker_invocation = DatumGetUInt64(arg);
1747 : :
1748 : 40 : operation = ENABLE_DATACHECKSUMS;
1749 : :
1750 : 40 : pqsignal(SIGTERM, die);
1751 : 40 : pqsignal(SIGUSR1, procsignal_sigusr1_handler);
1752 : :
1753 : 40 : BackgroundWorkerUnblockSignals();
1754 : :
1755 : 40 : MyBackendType = B_DATACHECKSUMSWORKER_WORKER;
1756 : 40 : init_ps_display(NULL);
1757 : :
1758 : 40 : LWLockAcquire(DataChecksumsWorkerLock, LW_SHARED);
1759 [ - + ]: 40 : if (DataChecksumState->worker_invocation != worker_invocation)
1760 : : {
1761 : 0 : LWLockRelease(DataChecksumsWorkerLock);
1762 : 0 : return;
1763 : : }
1764 : 40 : dboid = DataChecksumState->database_oid;
1765 : 40 : LWLockRelease(DataChecksumsWorkerLock);
1766 : :
1767 : 40 : BackgroundWorkerInitializeConnectionByOid(dboid, InvalidOid,
1768 : : BGWORKER_BYPASS_ALLOWCONN);
1769 : :
1770 : : /* worker will have a separate entry in pg_stat_progress_data_checksums */
1771 : 38 : pgstat_progress_start_command(PROGRESS_COMMAND_DATACHECKSUMS,
1772 : : InvalidOid);
1773 : 38 : ResetDataChecksumsProgressCounters();
1774 : :
1775 : : /*
1776 : : * Get a list of all temp tables present as we start in this database. We
1777 : : * need to wait until they are all gone before we exit. For the list of
1778 : : * relations to enable checksums in, check if shared catalogs have been
1779 : : * processed already.
1780 : : */
1781 : 38 : InitialTempTableList = BuildRelationList(true, false);
1782 : 38 : LWLockAcquire(DataChecksumsWorkerLock, LW_EXCLUSIVE);
1783 [ - + ]: 38 : if (DataChecksumState->worker_invocation != worker_invocation)
1784 : : {
1785 : 0 : LWLockRelease(DataChecksumsWorkerLock);
1786 : 0 : return;
1787 : : }
1788 : 38 : process_shared = DataChecksumState->process_shared_catalogs;
1789 : :
1790 : : /*
1791 : : * Enable vacuum cost delay, if any. While this process isn't doing any
1792 : : * vacuuming, we are re-using the infrastructure that vacuum cost delay
1793 : : * provides rather than inventing something bespoke. This is an internal
1794 : : * implementation detail and care should be taken to avoid it bleeding
1795 : : * through to the user to avoid confusion.
1796 : : *
1797 : : * VacuumUpdateCosts() propagates the values to the variables actually
1798 : : * read by vacuum_delay_point().
1799 : : */
1800 : 38 : VacuumCostDelay = DataChecksumState->cost_delay;
1801 : 38 : VacuumCostLimit = DataChecksumState->cost_limit;
1802 : 38 : LWLockRelease(DataChecksumsWorkerLock);
1803 : 38 : VacuumUpdateCosts();
1804 : 38 : VacuumCostBalance = 0;
1805 : :
1806 : : /*
1807 : : * Create and set the vacuum strategy as our buffer strategy.
1808 : : */
1809 : 38 : strategy = GetAccessStrategy(BAS_VACUUM);
1810 : :
1811 : 38 : RelationList = BuildRelationList(false, process_shared);
1812 : :
1813 : : /* Update the total number of relations to be processed in this DB. */
1814 : : {
1815 : 38 : const int index[] = {
1816 : : PROGRESS_DATACHECKSUMS_RELS_TOTAL,
1817 : : PROGRESS_DATACHECKSUMS_RELS_DONE
1818 : : };
1819 : :
1820 : : int64 vals[2];
1821 : :
1822 : 38 : vals[0] = list_length(RelationList);
1823 : 38 : vals[1] = 0;
1824 : :
1825 : 38 : pgstat_progress_update_multi_param(2, index, vals);
1826 : : }
1827 : :
1828 : : /* Process the relations */
1829 : 38 : rels_done = 0;
1830 [ + - + + : 10058 : foreach_oid(reloid, RelationList)
+ + ]
1831 : : {
1832 : 9982 : bool costs_updated = false;
1833 : :
1834 [ - + ]: 9982 : if (!ProcessSingleRelationByOid(reloid, strategy))
1835 : : {
1836 : 0 : aborted = true;
1837 : 0 : break;
1838 : : }
1839 : :
1840 : 9982 : pgstat_progress_update_param(PROGRESS_DATACHECKSUMS_RELS_DONE,
1841 : : ++rels_done);
1842 [ - + ]: 9982 : CHECK_FOR_INTERRUPTS();
1843 [ + - - + ]: 9982 : CHECK_FOR_WORKER_ABORT_REQUEST();
1844 : :
1845 [ - + ]: 9982 : if (abort_requested)
1846 : 0 : break;
1847 : :
1848 : : /*
1849 : : * Check if the cost settings changed during runtime and if so, update
1850 : : * to reflect the new values and signal that the access strategy needs
1851 : : * to be refreshed.
1852 : : */
1853 : 9982 : LWLockAcquire(DataChecksumsWorkerLock, LW_EXCLUSIVE);
1854 [ - + ]: 9982 : if (DataChecksumState->worker_invocation != worker_invocation)
1855 : : {
1856 : 0 : LWLockRelease(DataChecksumsWorkerLock);
1857 : 0 : break;
1858 : : }
1859 [ + - ]: 9982 : if ((DataChecksumState->launch_cost_delay != DataChecksumState->cost_delay)
1860 [ - + ]: 9982 : || (DataChecksumState->launch_cost_limit != DataChecksumState->cost_limit))
1861 : : {
1862 : 0 : costs_updated = true;
1863 : 0 : VacuumCostDelay = DataChecksumState->launch_cost_delay;
1864 : 0 : VacuumCostLimit = DataChecksumState->launch_cost_limit;
1865 : 0 : VacuumUpdateCosts();
1866 : :
1867 : 0 : DataChecksumState->cost_delay = DataChecksumState->launch_cost_delay;
1868 : 0 : DataChecksumState->cost_limit = DataChecksumState->launch_cost_limit;
1869 : : }
1870 : : else
1871 : 9982 : costs_updated = false;
1872 : 9982 : LWLockRelease(DataChecksumsWorkerLock);
1873 : :
1874 [ - + ]: 9982 : if (costs_updated)
1875 : : {
1876 : 0 : FreeAccessStrategy(strategy);
1877 : 0 : strategy = GetAccessStrategy(BAS_VACUUM);
1878 : : }
1879 : : }
1880 : :
1881 : 38 : list_free(RelationList);
1882 : 38 : FreeAccessStrategy(strategy);
1883 : :
1884 [ + - - + ]: 38 : if (aborted || abort_requested)
1885 : : {
1886 : 0 : LWLockAcquire(DataChecksumsWorkerLock, LW_EXCLUSIVE);
1887 [ # # ]: 0 : if (DataChecksumState->worker_invocation == worker_invocation)
1888 : 0 : DataChecksumState->worker_result = DATACHECKSUMSWORKER_ABORTED;
1889 : 0 : LWLockRelease(DataChecksumsWorkerLock);
1890 [ # # ]: 0 : ereport(DEBUG1,
1891 : : errmsg("data checksum processing aborted in database OID %u",
1892 : : dboid));
1893 : 0 : return;
1894 : : }
1895 : :
1896 : : /* The worker is about to wait for temporary tables to go away. */
1897 : 38 : pgstat_progress_update_param(PROGRESS_DATACHECKSUMS_PHASE,
1898 : : PROGRESS_DATACHECKSUMS_PHASE_WAITING_TEMPREL);
1899 : :
1900 : : /*
1901 : : * Wait for all temp tables that existed when we started to go away. This
1902 : : * is necessary since we cannot "reach" them to enable checksums. Any temp
1903 : : * tables created after we started will already have checksums in them
1904 : : * (due to the "inprogress-on" state), so no need to wait for those.
1905 : : */
1906 : : for (;;)
1907 : 4 : {
1908 : : List *CurrentTempTables;
1909 : : int numleft;
1910 : : char activity[64];
1911 : :
1912 : 42 : CurrentTempTables = BuildRelationList(true, false);
1913 : 42 : numleft = 0;
1914 [ + + + + : 92 : foreach_oid(tmptbloid, InitialTempTableList)
+ + ]
1915 : : {
1916 [ + + ]: 8 : if (list_member_oid(CurrentTempTables, tmptbloid))
1917 : 6 : numleft++;
1918 : : }
1919 : 42 : list_free(CurrentTempTables);
1920 : :
1921 : : #ifdef USE_INJECTION_POINTS
1922 [ + + ]: 42 : if (IS_INJECTION_POINT_ATTACHED("datachecksumsworker-fake-temptable-wait"))
1923 : : {
1924 : : /* Make sure to just cause one retry */
1925 [ + - + - ]: 1 : if (!retried && numleft == 0)
1926 : : {
1927 : 1 : numleft = 1;
1928 : 1 : retried = true;
1929 : :
1930 : 1 : INJECTION_POINT_CACHED("datachecksumsworker-fake-temptable-wait", NULL);
1931 : : }
1932 : : }
1933 : : #endif
1934 : :
1935 [ + + ]: 42 : if (numleft == 0)
1936 : 35 : break;
1937 : :
1938 : : /*
1939 : : * At least one temp table is left to wait for, indicate in pgstat
1940 : : * activity and progress reporting.
1941 : : */
1942 : 7 : snprintf(activity,
1943 : : sizeof(activity),
1944 : : "Waiting for %d temp tables to be removed", numleft);
1945 : 7 : pgstat_report_activity(STATE_RUNNING, activity);
1946 : :
1947 : : /* Retry every 3 seconds */
1948 : 7 : ResetLatch(MyLatch);
1949 : 7 : (void) WaitLatch(MyLatch,
1950 : : WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
1951 : : 3000,
1952 : : WAIT_EVENT_CHECKSUM_ENABLE_TEMPTABLE_WAIT);
1953 : :
1954 [ + + ]: 7 : CHECK_FOR_INTERRUPTS();
1955 [ + - - + ]: 4 : CHECK_FOR_WORKER_ABORT_REQUEST();
1956 : :
1957 [ + - - + ]: 4 : if (aborted || abort_requested)
1958 : : {
1959 : 0 : LWLockAcquire(DataChecksumsWorkerLock, LW_EXCLUSIVE);
1960 [ # # ]: 0 : if (DataChecksumState->worker_invocation == worker_invocation)
1961 : 0 : DataChecksumState->worker_result = DATACHECKSUMSWORKER_ABORTED;
1962 : 0 : LWLockRelease(DataChecksumsWorkerLock);
1963 [ # # ]: 0 : ereport(LOG,
1964 : : errmsg("data checksum processing aborted in database OID %u",
1965 : : dboid));
1966 : 0 : return;
1967 : : }
1968 : : }
1969 : :
1970 : 35 : list_free(InitialTempTableList);
1971 : :
1972 : : /* worker done */
1973 : 35 : pgstat_progress_end_command();
1974 : :
1975 : 35 : LWLockAcquire(DataChecksumsWorkerLock, LW_EXCLUSIVE);
1976 [ + - ]: 35 : if (DataChecksumState->worker_invocation == worker_invocation)
1977 : 35 : DataChecksumState->worker_result = DATACHECKSUMSWORKER_SUCCESSFUL;
1978 : 35 : LWLockRelease(DataChecksumsWorkerLock);
1979 : : }
|