Skip to content

SPOC-502: Replace opaque "unavailable" message in exception_log with informative discard context#424

Open
danolivo wants to merge 1 commit intomainfrom
spoc-502
Open

SPOC-502: Replace opaque "unavailable" message in exception_log with informative discard context#424
danolivo wants to merge 1 commit intomainfrom
spoc-502

Conversation

@danolivo
Copy link
Copy Markdown
Contributor

The message names the active spock.exception_behaviour GUC value and the failed_action counter from the exception log slot, allowing an operator to cross-reference the log entry that holds the actual error.

Key design decisions:

  • Message construction happens in log_insert_exception (covering INSERT/UPDATE/DELETE/TRUNCATE) and inline in handle_sql_or_exception (covering DDL/SQL queued messages), where all required context is naturally available — keeping add_entry_to_exception_log a thin persistence layer with no knowledge of exception modes.
  • A small static helper get_exception_behaviour_name() maps the enum to its GUC string, avoiding duplication between the two call sites.
  • add_entry_to_exception_log now asserts error_message != NULL, making the contract explicit.

Additional fix

handle_insert incremented xact_action_counter and set errcallback_arg.action_name after spock_read_insert, whereas handle_update and handle_delete both do so before the read. This asymmetry meant that a "can't find relation" failure in INSERT was not counted as an action. Both are moved before the read to match the established pattern.

@danolivo danolivo self-assigned this Apr 20, 2026
@danolivo danolivo added the enhancement New feature or request label Apr 20, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 20, 2026

📝 Walkthrough

Walkthrough

This PR changes internal exception logging and error-message handling across three C modules: synthesizing messages that include exception-behaviour names and action counters, asserting non-null error messages in the exception log path, and adding a conditional header include.

Changes

Cohort / File(s) Summary
Apply / Logging
src/spock_apply.c
Added get_exception_behaviour_name(); synthesize errmsg when NULL to include behaviour name and failed_action counter; moved errcallback_arg.action_name = "INSERT" and xact_action_counter++ to right after begin_replication_step(); refined selection of which error message to log.
Exception Handler Assertion
src/spock_exception_handler.c
Enforced non-null error_message with Assert(error_message != NULL) in add_entry_to_exception_log, removing previous nullable-conditional assignment while retaining the fallback expression.
Injection Header Include
src/spock_injection.c
Added #include "spock_injection.h" under the SPOCK_RANDOM_DELAYS guard to ensure the header is available when that compile flag is enabled.

Poem

🐇 I nibble at logs in the moonlit code,
Naming behaviours where errors once strode,
If NULL tried to hide, I stitch it with care,
Counting actions and messages fair,
Hopping through headers with a cheerful nod.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and specifically summarizes the main change: replacing an opaque 'unavailable' message with informative exception context that includes the exception behaviour and failed action counter.
Description check ✅ Passed The description comprehensively explains the changes, design decisions, and the additional fix to handle_insert consistency, with clear context about where message construction occurs and why.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch spoc-502

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codacy-production
Copy link
Copy Markdown

codacy-production bot commented Apr 20, 2026

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 0 duplication

Metric Results
Duplication 0

View in Codacy

TIP This summary will be updated as you push new changes. Give us feedback

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/spock_apply.c (1)

2413-2435: ⚠️ Potential issue | 🔴 Critical

Critical: fallback_msg is used outside its scope — dangling stack pointer.

fallback_msg is declared inside the inner else { ... } block (Line 2419), assigned to error_msg at Line 2425, and then the block closes at Line 2426. error_msg is subsequently dereferenced at Line 2435 inside add_entry_to_exception_log(...), at which point the storage duration of fallback_msg has ended per C99 §6.2.4 — this is undefined behavior. Today it may appear to work because compilers typically don't reuse the stack slot before the function returns, but a future codegen / stack-protector / ASan build can turn this into garbage text (or worse) written to spock.exception_log.

Hoist the buffer to the scope where error_msg lives, matching the pattern already used in log_insert_exception (Line 1230).

🐛 Proposed fix
 		if (should_log_exception(failed))
 		{
 			char	   *error_msg;
+			char		fallback_msg[128];

 			/* Just to be paranoid */
 			Assert(my_exception_log_index >= 0);

 			if (failed)
 				error_msg = edata->message;
 			else if (xact_action_counter ==
 					 exception_log_ptr[my_exception_log_index].failed_action &&
 					 exception_log_ptr[my_exception_log_index].initial_error_message[0] != '\0')
 				error_msg = exception_log_ptr[my_exception_log_index].initial_error_message;
 			else
 			{
-				char		fallback_msg[128];
-
 				snprintf(fallback_msg, sizeof(fallback_msg),
 						 "%s: tuple discarded due to exception at command_counter %u",
 						 get_exception_behaviour_name(),
 						 exception_log_ptr[my_exception_log_index].failed_action);
 				error_msg = fallback_msg;
 			}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/spock_apply.c` around lines 2413 - 2435, The code assigns error_msg to
point at a locally scoped buffer fallback_msg inside the inner else, then passes
error_msg to add_entry_to_exception_log after that buffer is out of scope; hoist
the fallback_msg buffer to the same scope as error_msg (i.e., declare a char
fallback_msg[128] alongside error_msg before the if/else), then use snprintf
into that buffer in the else branch and set error_msg = fallback_msg so the
pointer remains valid when calling add_entry_to_exception_log (follow the same
scoping pattern used by log_insert_exception).
🧹 Nitpick comments (1)
src/spock_exception_handler.c (1)

191-194: Drop the now-dead "unavailable" fallback to make the contract unambiguous.

With Assert(error_message != NULL) the ternary error_message != NULL ? error_message : "unavailable" is dead in debug builds and silently reintroduces the exact opaque message this PR is trying to eliminate in release builds. Per the PR objective, add_entry_to_exception_log is now a thin persistence layer with a non-NULL contract — honor that here too.

♻️ Proposed simplification
 	Assert(error_message != NULL);

-	values[Anum_exception_log_error_message - 1] =
-		CStringGetTextDatum(error_message != NULL ? error_message : "unavailable");
+	values[Anum_exception_log_error_message - 1] = CStringGetTextDatum(error_message);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/spock_exception_handler.c` around lines 191 - 194, The code currently
asserts error_message != NULL but then uses a ternary fallback to "unavailable"
when populating values[Anum_exception_log_error_message - 1]; remove the dead
fallback and pass error_message directly to CStringGetTextDatum (i.e., replace
CStringGetTextDatum(error_message != NULL ? error_message : "unavailable") with
CStringGetTextDatum(error_message)), keeping the Assert(error_message != NULL)
and leaving add_entry_to_exception_log's non-NULL contract intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/spock_apply.c`:
- Around line 2413-2435: The code assigns error_msg to point at a locally scoped
buffer fallback_msg inside the inner else, then passes error_msg to
add_entry_to_exception_log after that buffer is out of scope; hoist the
fallback_msg buffer to the same scope as error_msg (i.e., declare a char
fallback_msg[128] alongside error_msg before the if/else), then use snprintf
into that buffer in the else branch and set error_msg = fallback_msg so the
pointer remains valid when calling add_entry_to_exception_log (follow the same
scoping pattern used by log_insert_exception).

---

Nitpick comments:
In `@src/spock_exception_handler.c`:
- Around line 191-194: The code currently asserts error_message != NULL but then
uses a ternary fallback to "unavailable" when populating
values[Anum_exception_log_error_message - 1]; remove the dead fallback and pass
error_message directly to CStringGetTextDatum (i.e., replace
CStringGetTextDatum(error_message != NULL ? error_message : "unavailable") with
CStringGetTextDatum(error_message)), keeping the Assert(error_message != NULL)
and leaving add_entry_to_exception_log's non-NULL contract intact.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5784fba1-8591-4732-85c5-3cd3c0562b9e

📥 Commits

Reviewing files that changed from the base of the PR and between a4b93e8 and 86f2d96.

⛔ Files ignored due to path filters (1)
  • tests/regress/expected/replication_set.out is excluded by !**/*.out
📒 Files selected for processing (3)
  • src/spock_apply.c
  • src/spock_exception_handler.c
  • src/spock_injection.c

When a tuple is skipped during TRANSDISCARD/SUB_DISABLE dry-run replay
(i.e. it is not the command that originally failed), the error_message
column in spock.exception_log previously stored the opaque string
"unavailable".  Replace it with a message of the form:

  "<mode>: tuple discarded due to exception at command_counter N"

Also fix a pre-existing asymmetry in handle_insert: xact_action_counter
was incremented after spock_read_insert, whereas handle_update and
handle_delete both increment it before the read.  Move the increment
(and errcallback_arg.action_name assignment) to match.
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/spock_exception_handler.c`:
- Around line 191-194: The code currently uses a ternary fallback
CStringGetTextDatum(error_message != NULL ? error_message : "unavailable") which
reintroduces the stale "unavailable" value; since Assert(error_message != NULL)
guarantees non-NULL by contract, replace the ternary with a direct use of
error_message (i.e., call CStringGetTextDatum(error_message)) for
values[Anum_exception_log_error_message - 1], leaving the existing Assert in
place to catch violations in debug builds.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4929aad9-0435-46e0-9fdf-96d250683143

📥 Commits

Reviewing files that changed from the base of the PR and between 86f2d96 and e32dddd.

⛔ Files ignored due to path filters (1)
  • tests/regress/expected/replication_set.out is excluded by !**/*.out
📒 Files selected for processing (3)
  • src/spock_apply.c
  • src/spock_exception_handler.c
  • src/spock_injection.c
✅ Files skipped from review due to trivial changes (1)
  • src/spock_injection.c
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/spock_apply.c

Comment thread src/spock_exception_handler.c
@danolivo danolivo requested a review from mason-sharp April 20, 2026 10:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant