@@ -53,10 +53,28 @@ extern "C" {
5353#include " pgduckdb/pgduckdb_background_worker.hpp"
5454#include " pgduckdb/pgduckdb_metadata_cache.hpp"
5555
56- static bool is_background_worker = false ;
5756static std::unordered_map<std::string, std::string> last_known_motherduck_catalog_versions;
5857static uint64 initial_cache_version = 0 ;
5958
59+ namespace pgduckdb {
60+
61+ bool is_background_worker = false ;
62+
63+ static void SyncMotherDuckCatalogsWithPg (bool drop_with_cascade, duckdb::ClientContext &context);
64+ static void SyncMotherDuckCatalogsWithPg_Cpp (bool drop_with_cascade, duckdb::ClientContext *context);
65+
66+ typedef struct BackgoundWorkerShmemStruct {
67+ Latch *bgw_latch; /* The latch of the background worker */
68+
69+ slock_t lock; /* protects all the fields below */
70+
71+ int64 activity_count; /* the number of times activity was triggered by other backends */
72+ } BackgoundWorkerShmemStruct;
73+
74+ static BackgoundWorkerShmemStruct *BgwShmemStruct;
75+
76+ } // namespace pgduckdb
77+
6078extern " C" {
6179PGDLLEXPORT void
6280pgduckdb_background_worker_main (Datum /* main_arg */ ) {
@@ -66,9 +84,15 @@ pgduckdb_background_worker_main(Datum /* main_arg */) {
6684 BackgroundWorkerUnblockSignals ();
6785
6886 BackgroundWorkerInitializeConnection (duckdb_motherduck_postgres_database, NULL , 0 );
87+ SpinLockAcquire (&pgduckdb::BgwShmemStruct->lock );
88+ pgduckdb::BgwShmemStruct->bgw_latch = MyLatch;
89+ int64 last_activity_count = pgduckdb::BgwShmemStruct->activity_count ;
90+ SpinLockRelease (&pgduckdb::BgwShmemStruct->lock );
6991
7092 pgduckdb::doing_motherduck_sync = true ;
71- is_background_worker = true ;
93+ pgduckdb::is_background_worker = true ;
94+
95+ duckdb::unique_ptr<duckdb::Connection> connection;
7296
7397 while (true ) {
7498 // Initialize SPI (Server Programming Interface) and connect to the database
@@ -78,12 +102,23 @@ pgduckdb_background_worker_main(Datum /* main_arg */) {
78102 PushActiveSnapshot (GetTransactionSnapshot ());
79103
80104 if (pgduckdb::IsExtensionRegistered ()) {
105+ if (!connection) {
106+ connection = pgduckdb::DuckDBManager::Get ().CreateConnection ();
107+ }
108+ SpinLockAcquire (&pgduckdb::BgwShmemStruct->lock );
109+ int64 new_activity_count = pgduckdb::BgwShmemStruct->activity_count ;
110+ SpinLockRelease (&pgduckdb::BgwShmemStruct->lock );
111+ if (last_activity_count != new_activity_count) {
112+ last_activity_count = new_activity_count;
113+ /* Trigger some activity to restart the syncing */
114+ pgduckdb::DuckDBQueryOrThrow (*connection, " FROM duckdb_tables() limit 0" );
115+ }
81116 /*
82117 * If the extension is not registerid this loop is a no-op, which
83118 * means we essentially keep polling until the extension is
84119 * installed
85120 */
86- pgduckdb::SyncMotherDuckCatalogsWithPg (false );
121+ pgduckdb::SyncMotherDuckCatalogsWithPg (false , *connection-> context );
87122 }
88123
89124 // Commit the transaction
@@ -108,24 +143,79 @@ force_motherduck_sync(PG_FUNCTION_ARGS) {
108143 Datum drop_with_cascade = PG_GETARG_BOOL (0 );
109144 /* clear the cache of known catalog versions to force a full sync */
110145 last_known_motherduck_catalog_versions.clear ();
146+
147+ /*
148+ * We don't use GetConnection, because we want to be able to precisely
149+ * control the transaction lifecycle. We commit Postgres connections
150+ * throughout this function, and the GetConnect its cached connection its
151+ * lifecycle would be linked to those postgres transactions, which we
152+ * don't want.
153+ */
154+ auto connection = pgduckdb::DuckDBManager::Get ().CreateConnection ();
111155 SPI_connect_ext (SPI_OPT_NONATOMIC);
112156 PG_TRY ();
113157 {
114158 pgduckdb::doing_motherduck_sync = true ;
115- pgduckdb::SyncMotherDuckCatalogsWithPg (drop_with_cascade);
159+ pgduckdb::SyncMotherDuckCatalogsWithPg (drop_with_cascade, *connection-> context );
116160 }
117161 PG_FINALLY ();
118- {
119- pgduckdb::doing_motherduck_sync = false ;
120- }
162+ { pgduckdb::doing_motherduck_sync = false ; }
121163 PG_END_TRY ();
122164 SPI_finish ();
123165 PG_RETURN_VOID ();
124166}
125167}
126168
169+ namespace pgduckdb {
170+ static shmem_request_hook_type prev_shmem_request_hook = NULL ;
171+ static shmem_startup_hook_type prev_shmem_startup_hook = NULL ;
172+
173+ /*
174+ * shmem_request hook: request additional shared resources. We'll allocate or
175+ * attach to the shared resources in pgss_shmem_startup().
176+ */
177+ static void
178+ ShmemRequest (void ) {
179+ if (prev_shmem_request_hook)
180+ prev_shmem_request_hook ();
181+
182+ RequestAddinShmemSpace (sizeof (BackgoundWorkerShmemStruct));
183+ }
184+
185+ /*
186+ * CheckpointerShmemInit
187+ * Allocate and initialize checkpointer-related shared memory
188+ */
189+ static void
190+ ShmemStartup (void ) {
191+ if (prev_shmem_startup_hook)
192+ prev_shmem_startup_hook ();
193+
194+ Size size = sizeof (BackgoundWorkerShmemStruct);
195+ bool found;
196+
197+ /*
198+ * Create or attach to the shared memory state, including hash table
199+ */
200+ LWLockAcquire (AddinShmemInitLock, LW_EXCLUSIVE);
201+
202+ BgwShmemStruct = (BackgoundWorkerShmemStruct *)ShmemInitStruct (" DuckdbBackgroundWorker Data" , size, &found);
203+
204+ if (!found) {
205+ /*
206+ * First time through, so initialize. Note that we zero the whole
207+ * requests array; this is so that CompactCheckpointerRequestQueue can
208+ * assume that any pad bytes in the request structs are zeroes.
209+ */
210+ MemSet (BgwShmemStruct, 0 , size);
211+ SpinLockInit (&BgwShmemStruct->lock );
212+ }
213+
214+ LWLockRelease (AddinShmemInitLock);
215+ }
216+
127217void
128- DuckdbInitBackgroundWorker (void ) {
218+ InitBackgroundWorker (void ) {
129219 if (!pgduckdb::IsMotherDuckEnabledAnywhere ()) {
130220 return ;
131221 }
@@ -143,9 +233,27 @@ DuckdbInitBackgroundWorker(void) {
143233
144234 // Register the worker
145235 RegisterBackgroundWorker (&worker);
236+
237+ /* Set up the shared memory hooks */
238+ prev_shmem_request_hook = shmem_request_hook;
239+ shmem_request_hook = ShmemRequest;
240+ prev_shmem_startup_hook = shmem_startup_hook;
241+ shmem_startup_hook = ShmemStartup;
242+ }
243+
244+ void
245+ TriggerActivity (void ) {
246+ if (!IsMotherDuckEnabled ()) {
247+ return ;
248+ }
249+
250+ SpinLockAcquire (&BgwShmemStruct->lock );
251+ BgwShmemStruct->activity_count ++;
252+ /* Force wake up the background worker */
253+ SetLatch (BgwShmemStruct->bgw_latch );
254+ SpinLockRelease (&BgwShmemStruct->lock );
146255}
147256
148- namespace pgduckdb {
149257/* Global variables that are used to communicate with our event triggers so
150258 * they handle DDL of syncing differently than user-initiated DDL */
151259bool doing_motherduck_sync;
@@ -279,9 +387,7 @@ SPI_run_utility_command(const char *query) {
279387 */
280388 BeginInternalSubTransaction (NULL );
281389 PG_TRY ();
282- {
283- ret = SPI_exec (query, 0 );
284- }
390+ { ret = SPI_exec (query, 0 ); }
285391 PG_CATCH ();
286392 {
287393 MemoryContextSwitchTo (old_context);
@@ -546,30 +652,25 @@ CreateSchemaIfNotExists(const char *postgres_schema_name, bool is_default_db) {
546652 return true ;
547653}
548654
549- void SyncMotherDuckCatalogsWithPg_Cpp (bool drop_with_cascade);
550-
551- void
552- SyncMotherDuckCatalogsWithPg (bool drop_with_cascade) {
553- InvokeCPPFunc (SyncMotherDuckCatalogsWithPg_Cpp, drop_with_cascade);
655+ static void
656+ SyncMotherDuckCatalogsWithPg (bool drop_with_cascade, duckdb::ClientContext &context) {
657+ /*
658+ * TODO: Passing a reference through InvokeCPPFunc doesn't work here
659+ * for some reason. So to work around that we use a pointer instead.
660+ * We should fix the underlying problem instead.
661+ */
662+ InvokeCPPFunc (SyncMotherDuckCatalogsWithPg_Cpp, drop_with_cascade, &context);
554663}
555664
556- void
557- SyncMotherDuckCatalogsWithPg_Cpp (bool drop_with_cascade) {
665+ static void
666+ SyncMotherDuckCatalogsWithPg_Cpp (bool drop_with_cascade, duckdb::ClientContext *_context ) {
558667 if (!pgduckdb::IsMotherDuckEnabled ()) {
559668 throw std::runtime_error (" MotherDuck support is not enabled" );
560669 }
561670
562- initial_cache_version = pgduckdb::CacheVersion () ;
671+ auto &context = *_context ;
563672
564- /*
565- * We don't use GetConnection, because we want to be able to precisely
566- * control the transaction lifecycle. We commit Postgres connections
567- * throughout this function, and the GetConnect its cached connection its
568- * lifecycle would be linked to those postgres transactions, which we
569- * don't want.
570- */
571- auto connection = pgduckdb::DuckDBManager::Get ().CreateConnection ();
572- auto &context = *connection->context ;
673+ initial_cache_version = pgduckdb::CacheVersion ();
573674
574675 auto &db_manager = duckdb::DatabaseManager::Get (context);
575676 const auto &default_db = db_manager.GetDefaultDatabase (context);
0 commit comments