Skip to content

Commit 7cc8978

Browse files
PXB-3747 : Addressing review comments
https://perconadev.atlassian.net/browse/PXB-3747 Follow-up to the two commits on this branch that introduced per-datasink metrics reporting and uncompressed-backup-size tracking. Will be squashed before merge. Problem: -------------- Review raised five issues on the initial split: 1. fs_support_punch_hole was left uninitialized for every non-local datasink. ds_local_init() probes the filesystem and sets it, but every other init() allocated ds_ctxt_t with my_malloc() without MY_ZEROFILL, so the field held whichever bytes my_malloc returned. ds_create() previously masked this by unconditionally clobbering the field to false after init, which also discarded the local datasink's probe result (the regression we already fixed). That left xbstream - which natively encodes sparse chunks in its wire format and therefore does support punch-hole - advertising whatever garbage happened to be there. 2. local_close() writes one trailing zero byte to stamp out the final page of a sparse file, but it did not add that byte to ds_local_ctxt_t::bytes_written. Backups with sparse files therefore under-reported backup_size by 1 byte per sparse file. 3. backup_copy.cc had a "Finsh" typo and an awkwardly reflowed doc comment above report_backup_size(). 4. test/inc/common.sh::sum_file_bytes() ran find without a -d guard, so callers probing a directory that might not exist got a stray "No such file or directory" on stderr. Fix/Implementation: ------------------- 1. Framework-field initialization: - ds_ctxt_t::pipe_ctxt gets an in-class default of nullptr (matches the existing fs_support_punch_hole = false default), so every new ds_ctxt_t site starts with a sane pipe_ctxt. - The sinks that allocate ds_ctxt_t via my_malloc (local, stdout, xbstream, buffer, tmpfile) now pass MY_FAE | MY_ZEROFILL, so those framework fields start at zero/false too. - xbstream_init() then sets fs_support_punch_hole = true because the xbstream wire format carries sparse chunks natively and can faithfully round-trip holes regardless of the extraction filesystem. - ds_create()'s normalization block is replaced with a short comment explaining the new contract: every init() is responsible for advertising its own capabilities; the framework no longer overrides them. 2. local_close() adds 1 to ds_local_ctxt_t::bytes_written right after the successful 1-byte trailing write, so backup_size stays authoritative for sparse files under --target-dir. 3. "Finsh" -> "Finish"; the report_backup_size() doc comment is reflowed so the closing parenthesis lands on the same line as the example it belongs to. 4. sum_file_bytes() early-returns 0 when its argument is not a directory, eliminating the stray find(1) error and matching the "0 if the directory is empty or does not exist" contract in the helper's own comment. Test: ----- - bld/ (Debug) rebuild clean. - t/backup_size_basic.sh: passed. - t/backup_size_compress.sh: passed.
1 parent 7b99d0a commit 7cc8978

9 files changed

Lines changed: 60 additions & 24 deletions

File tree

storage/innobase/xtrabackup/src/backup_copy.cc

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1603,10 +1603,9 @@ and the compression ratio) to the error log. A successful backup run
16031603
must have produced on-disk output, so the leaf counter must be
16041604
non-zero; under --compress at least one top-level ds_open_track_uncomp()
16051605
must have enabled an uncomp_bytes counter, so xb_uncomp_bytes_counter
1606-
must be non-zero too. A
1607-
zero value therefore indicates a silent reporting bug: assert in
1608-
debug, warn and skip in release to avoid emitting misleading numbers
1609-
(e.g. "Compression ratio: inf"). */
1606+
must be non-zero too. A zero value therefore indicates a silent
1607+
reporting bug: assert in debug, warn and skip in release to avoid
1608+
emitting misleading numbers (e.g. "Compression ratio: inf"). */
16101609
static void report_backup_size() {
16111610
const unsigned long long backup_size = get_final_backup_size();
16121611

@@ -1643,7 +1642,7 @@ static void report_backup_size() {
16431642
<< ratio << "x";
16441643
}
16451644

1646-
/* Finsh the backup. Release all locks. Write down backup metadata.
1645+
/* Finish the backup. Release all locks. Write down backup metadata.
16471646
@return true if success. */
16481647
bool backup_finish(Backup_context &context) {
16491648
/* release all locks */

storage/innobase/xtrabackup/src/datasink.cc

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -96,12 +96,13 @@ ds_ctxt_t *ds_create(const char *root, ds_type_t type) {
9696
ctxt = ds->init(root);
9797
if (ctxt != NULL) {
9898
ctxt->datasink = ds;
99-
/* Per-datasink init() allocates ds_ctxt_t with my_malloc(), which
100-
does not zero memory. Normalize framework-level fields the
101-
per-sink init() is not expected to set. Do NOT touch fields that
102-
a leaf's init() is responsible for (e.g. fs_support_punch_hole is
103-
probed by local_init() and must be preserved). */
104-
ctxt->pipe_ctxt = nullptr;
99+
/* Every per-datasink init() is expected to leave the framework-level
100+
fields of ds_ctxt_t in a well-defined state: ds_ctxt_t's in-class
101+
initializers cover the `new ds_ctxt_t` call sites, and the `my_malloc`
102+
call sites use MY_ZEROFILL. pipe_ctxt therefore starts NULL, and
103+
fs_support_punch_hole starts false; each sink then advertises its
104+
own capability (local probes the filesystem, xbstream sets it to
105+
true because its wire format carries sparse chunks natively). */
105106
} else {
106107
msg("Error: failed to initialize datasink.\n");
107108
exit(EXIT_FAILURE);

storage/innobase/xtrabackup/src/datasink.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,12 @@ typedef struct ds_ctxt {
7171
datasink_t *datasink;
7272
char *root;
7373
void *ptr;
74-
struct ds_ctxt *pipe_ctxt;
74+
/* pipe_ctxt chains a datasink to the next stage downstream.
75+
Default to nullptr so ds_leaf() never walks off into garbage when a
76+
datasink init() forgets to set it (e.g. terminal sinks that do not
77+
forward). The my_malloc-based inits use MY_ZEROFILL to match, and
78+
the `new ds_ctxt_t` inits rely on this in-class initializer. */
79+
struct ds_ctxt *pipe_ctxt = nullptr;
7580
bool fs_support_punch_hole = false;
7681
} ds_ctxt_t;
7782

storage/innobase/xtrabackup/src/ds_buffer.cc

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,13 @@ static ds_ctxt_t *buffer_init(const char *root) {
6666
ds_ctxt_t *ctxt;
6767
ds_buffer_ctxt_t *buffer_ctxt;
6868

69-
ctxt = static_cast<ds_ctxt *>(
70-
my_malloc(PSI_NOT_INSTRUMENTED,
71-
sizeof(ds_ctxt_t) + sizeof(ds_buffer_ctxt_t), MYF(MY_FAE)));
69+
/* MY_ZEROFILL so framework-level fields (pipe_ctxt, fs_support_punch_hole)
70+
start at well-defined defaults. buffer is a pass-through wrapper that
71+
forwards to its pipe_ctxt; it owns no filesystem or stream of its own,
72+
so fs_support_punch_hole stays false. */
73+
ctxt = static_cast<ds_ctxt *>(my_malloc(
74+
PSI_NOT_INSTRUMENTED, sizeof(ds_ctxt_t) + sizeof(ds_buffer_ctxt_t),
75+
MYF(MY_FAE | MY_ZEROFILL)));
7276
buffer_ctxt = (ds_buffer_ctxt_t *)(ctxt + 1);
7377
buffer_ctxt->buffer_size = DS_DEFAULT_BUFFER_SIZE;
7478

storage/innobase/xtrabackup/src/ds_local.cc

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,12 @@ static ds_ctxt_t *local_init(const char *root) {
105105
return NULL;
106106
}
107107

108-
ctxt = static_cast<ds_ctxt_t *>(
109-
my_malloc(PSI_NOT_INSTRUMENTED, sizeof(ds_ctxt_t), MYF(MY_FAE)));
108+
/* MY_ZEROFILL so framework-level fields (pipe_ctxt, fs_support_punch_hole)
109+
start at well-defined defaults; is_fallocate_punch_hole_supported() below
110+
then flips fs_support_punch_hole on if the target filesystem supports
111+
FALLOC_FL_PUNCH_HOLE, leaving it false otherwise. */
112+
ctxt = static_cast<ds_ctxt_t *>(my_malloc(
113+
PSI_NOT_INSTRUMENTED, sizeof(ds_ctxt_t), MYF(MY_FAE | MY_ZEROFILL)));
110114

111115
ctxt->ptr = new ds_local_ctxt_t{};
112116
ctxt->root = my_strdup(PSI_NOT_INSTRUMENTED, root, MYF(MY_FAE));
@@ -241,6 +245,12 @@ static int local_close(ds_file_t *file) {
241245
if (rc != 0) {
242246
return 1;
243247
}
248+
/* The trailing-hole fix above emits exactly one byte to the leaf. Keep
249+
bytes_written authoritative for backup_size by accounting for it here;
250+
otherwise sparse files under --target-dir would under-report by 1 byte
251+
per sparse file. */
252+
auto local_ctxt = static_cast<ds_local_ctxt_t *>(file->ctxt->ptr);
253+
local_ctxt->bytes_written.fetch_add(1, std::memory_order_relaxed);
244254
}
245255

246256
my_free(file);

storage/innobase/xtrabackup/src/ds_stdout.cc

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,11 @@ datasink_t datasink_stdout = {
5252
static ds_ctxt_t *stdout_init(const char *root) {
5353
ds_ctxt_t *ctxt;
5454

55-
ctxt = static_cast<ds_ctxt_t *>(
56-
my_malloc(PSI_NOT_INSTRUMENTED, sizeof(ds_ctxt_t), MYF(MY_FAE)));
55+
/* MY_ZEROFILL so framework-level fields (pipe_ctxt, fs_support_punch_hole)
56+
start at their well-defined zero/false default; stdout does not write to a
57+
filesystem, so it leaves fs_support_punch_hole = false. */
58+
ctxt = static_cast<ds_ctxt_t *>(my_malloc(
59+
PSI_NOT_INSTRUMENTED, sizeof(ds_ctxt_t), MYF(MY_FAE | MY_ZEROFILL)));
5760

5861
ctxt->ptr = new ds_stdout_ctxt_t{};
5962
ctxt->root = my_strdup(PSI_NOT_INSTRUMENTED, root, MYF(MY_FAE));

storage/innobase/xtrabackup/src/ds_tmpfile.cc

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,13 @@ static ds_ctxt_t *tmpfile_init(const char *root) {
6262
ds_ctxt_t *ctxt;
6363
ds_tmpfile_ctxt_t *tmpfile_ctxt;
6464

65-
ctxt = static_cast<ds_ctxt_t *>(
66-
my_malloc(PSI_NOT_INSTRUMENTED,
67-
sizeof(ds_ctxt_t) + sizeof(ds_tmpfile_ctxt_t), MYF(MY_FAE)));
65+
/* MY_ZEROFILL so framework-level fields (pipe_ctxt, fs_support_punch_hole)
66+
start at well-defined defaults. tmpfile stages bytes in a scratch file
67+
that is later copied into pipe_ctxt; it does not own the final sink and
68+
leaves fs_support_punch_hole at false. */
69+
ctxt = static_cast<ds_ctxt_t *>(my_malloc(
70+
PSI_NOT_INSTRUMENTED, sizeof(ds_ctxt_t) + sizeof(ds_tmpfile_ctxt_t),
71+
MYF(MY_FAE | MY_ZEROFILL)));
6872
tmpfile_ctxt = (ds_tmpfile_ctxt_t *)(ctxt + 1);
6973
tmpfile_ctxt->file_list = NULL;
7074
ctxt->ptr = tmpfile_ctxt;

storage/innobase/xtrabackup/src/ds_xbstream.cc

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,16 @@ static ds_ctxt_t *xbstream_init(const char *root __attribute__((unused))) {
8686
ds_parallel_stream_ctxt_t *parallel_stream_ctxt =
8787
new ds_parallel_stream_ctxt_t;
8888

89+
/* MY_ZEROFILL so framework-level fields (pipe_ctxt, fs_support_punch_hole)
90+
start at well-defined defaults; we then flip fs_support_punch_hole to true
91+
below because the xbstream wire format encodes sparse chunks natively and
92+
can faithfully round-trip holes regardless of the extraction filesystem. */
8993
ctxt = static_cast<ds_ctxt_t *>(
9094
my_malloc(PSI_NOT_INSTRUMENTED,
9195
sizeof(ds_ctxt_t) + sizeof(ds_parallel_stream_ctxt_t) +
9296
(sizeof(ds_stream_ctxt_t) * (xtrabackup_fifo_streams + 1)),
93-
MYF(MY_FAE)));
97+
MYF(MY_FAE | MY_ZEROFILL)));
98+
ctxt->fs_support_punch_hole = true;
9499

95100
for (uint i = 0; i < xtrabackup_fifo_streams; i++) {
96101
ds_stream_ctxt_t *stream_ctxt = new ds_stream_ctxt_t;

storage/innobase/xtrabackup/test/inc/common.sh

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,12 @@ file_size() { stat -c '%s' "$1"; }
109109
# sum_file_bytes <dir>
110110
# Echo the sum of the sizes of every regular file under <dir>
111111
# (recursive). 0 if the directory is empty or does not exist.
112-
sum_file_bytes() { find "$1" -type f -printf '%s\n' | awk '{s+=$1} END{print s+0}'; }
112+
# The explicit -d guard keeps find from spraying a "No such file"
113+
# message to stderr when callers probe a path that may be absent.
114+
sum_file_bytes() {
115+
[ -d "$1" ] || { echo 0; return; }
116+
find "$1" -type f -printf '%s\n' | awk '{s+=$1} END{print s+0}'
117+
}
113118

114119
# find_info_file <dir>
115120
# Locate xtrabackup_info (or one of its compressed/encrypted variants)

0 commit comments

Comments
 (0)