Hi All,
PostgreSQL code uses Assert() as a way to
1. document the assumption or conditions which must be true at a given
place in code
2. make sure that some bug elsewhere does not break these assumptions or rules
3. conditions which can not be (easily) induced by user actions

E.g. following conditions in adjust_appendrel_attrs()
/* If there's nothing to adjust, don't call this function. */
Assert(nappinfos >= 1 && appinfos != NULL);

/* Should never be translating a Query tree. */
Assert(node == NULL || !IsA(node, Query));

These conditions do not make it to non-assert builds and thus do not
make it to the production binary. That saves some CPU cycles.

When an Assert fails, and it fails quite a lot for developers, the
Postgres backend that caused the Assert is Aborted, restarting the
server. So a developer testing code that caused the Assert has to
start a psql again, run any session setup before running the faulting
query, gdb needs to be reattached to the new backend process. That's
some waste of time and frustration, esp. when the Assert does not
damage the backend itself e.g. by corrupting memory.

Most of the Asserts are recoverable by rolling back the transaction
without crashing the backend. So an elog(ERROR, ) is enough. But just
by themselves elogs are compiled into non-debug binary and the
condition check can waste CPU cycles esp. conditions are mostly true
like the ones we use in Assert.

Attached patch combines Assert and elog(ERROR, ) so that when an
Assert is triggered in assert-enabled binary, it will throw an error
while keeping the backend intact. Thus it does not affect gdb session
or psql session. These elogs do not make their way to non-assert
binary so do not make it to production like Assert.

I have been using AssertLog for my work for some time. It is quite
convenient. With AssertLog I get
```
#explain (summary on) select * from a, b, c where a.c1 = b.c1 and b.c1
= c.c1 and a.c2 < b.c2 and a.c3 + b.c3 < c.c3;
ERROR:  failed Assert("equal(child_rinfo, adjust_appendrel_attrs(root,
(Node *) rinfo, nappinfos, appinfos))"), File: "relnode.c", Line: 997,
PID: 568088
```
instead of
```
#explain (summary on) select * from a, b, c where a.c1 = b.c1 and b.c1
= c.c1 and a.c2 < b.c2 and a.c3 + b.c3 < c.c3;
server closed the connection unexpectedly
        This probably means the server terminated abnormally
        before or while processing the request.
The connection to the server was lost. Attempting reset: Failed.
The connection to the server was lost. Attempting reset: Failed.
@!#\q
```

If there is an interest in accepting the patch, I will add it to the
commitfest and work on it further.

--
Best Wishes,
Ashutosh Bapat
From 2464ac9de8a58846521d93db5c3c43a24d73f6b8 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <ashutosh.ba...@enterprisedb.com>
Date: Tue, 8 Aug 2023 14:37:59 +0530
Subject: [PATCH 1/6] Add AssertLog to report an error instead of Abort

Assert is used in the code to document assumptions, check conditions which
ought to be correct at given places in the code and so on. When an Assert
fails, the process Aborts, restarting the whole server. This is too disruptive
for development. Many of the Assert check conditions which are quite local in
nature, many a times very specific to the encapsulating transaction. In such
cases the backend can be brought out of the bad state just by aborting the
transaction. elog(ERROR) serves that purpose well. But when just elog is used
it get compiled in non-assert and non-debug binary. The condition around it
eats some CPU cycles in production.

This commit introduces AssertLog which brings together the benefits of both
Assert() and elog(). AssertLog is compiled only in assert enabled binaries. It
throws an ERROR when the condition fails instead of Aborting the process. Thus
the backend recovers simply by aborting the transaction and developers can
continue to debug, investigate without restarting their psql and reattaching
gdb.

Ashutosh Bapat
---
 src/include/c.h | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/src/include/c.h b/src/include/c.h
index f69d739be5..052b9441bc 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -66,6 +66,7 @@
 #endif
 #include <stdint.h>
 #include <sys/types.h>
+#include <unistd.h>
 #include <errno.h>
 #if defined(WIN32) || defined(__CYGWIN__)
 #include <fcntl.h>				/* ensure O_BINARY is available */
@@ -840,12 +841,14 @@ typedef NameData *Name;
 #ifndef USE_ASSERT_CHECKING
 
 #define Assert(condition)	((void)true)
+#define AssertLog(condition)	((void)true)
 #define AssertMacro(condition)	((void)true)
 
 #elif defined(FRONTEND)
 
 #include <assert.h>
 #define Assert(p) assert(p)
+#define AssertLog(p) assert(p)
 #define AssertMacro(p)	((void) assert(p))
 
 #else							/* USE_ASSERT_CHECKING && !FRONTEND */
@@ -860,6 +863,13 @@ typedef NameData *Name;
 			ExceptionalCondition(#condition, __FILE__, __LINE__); \
 	} while (0)
 
+#define AssertLog(condition) \
+	do { \
+		if (!(condition)) \
+			elog(ERROR, "failed Assert(\"%s\"), File: \"%s\", Line: %d, PID: %d", \
+						#condition , __FILE__, __LINE__, (int) getpid()); \
+	} while (0)
+
 /*
  * AssertMacro is the same as Assert but it's suitable for use in
  * expression-like macros, for example:
-- 
2.25.1

Reply via email to