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