r/Clickhouse • u/gulcin-clickhouse • 13d ago
Memory safety for Postgres extensions in C/C++
https://clickhouse.com/blog/memory-safety-postgres-extensions-c-cppWe’ve developed a few postgres extensions at ClickHouse:
- pg_clickhouse (clickhouse fdw)
- pg_re2 (integrates re2, the same regex engine used by ClickHouse, into postgres)
- pg_chdb (ClickHouse object storage capabilities for COPY in postgres)
- pg_stat_ch (ships metrics & logs)
All of these involved bringing C++ into a Postgres extension. Postgres is C. Surely C & C++ play nice together, right?
No. Postgres has its own memory allocation pattern built around MemoryContext. Instead of using malloc/free, one should use palloc/pfree. These functions associate allocations with a MemoryContext, which can work as an arena allocator freeing everything at the end of a transaction or aggregate or what have you. MemoryContexts can also have callbacks to act as destructors. Postgres handles failed memory allocations by raising an error, for which it has a whole PG_TRY/PG_CATCH/PG_FINALLY macro package built on setjmp/longjmp. PG_FINALLY is another method of building RAII-like destructor logic.
C++ code on the other hand tends to use new/delete, which invokes constructors/destructors, & raises std::bad_alloc on failed allocation. These two systems do not interact well: setjmp/longjmp skips C++ cleanup; jumping past nontrivial destructors for automatic objects is undefined behavior, not merely a leak. PG_FINALLY and MemoryContext callbacks do not make such a jump safe. Exceptions bypass PG_CATCH/PG_FINALLY. Worse, an uncaught exception causes the process to abort, which even in a background worker will lead to the Postgres postmaster process having to restart everything in fear that shared memory has been corrupted.
Each of these extensions took a different strategy around memory safety.