Golang生成C动态库在PostgreSQL预加载时阻塞问题求助
我用Golang编写了导出函数Test1(),编译为C动态库libtest1.so,由PostgreSQL后台进程load_so_test.so调用。当通过postgresql.conf配置shared_preload_libraries='load_so_test'启动PostgreSQL时,Test1()会阻塞;但通过CREATE EXTENSION注册后调用load_so_launch()动态创建后台进程时,Test1()可以正常执行。
调用链路:PostgreSQL → load_so_test.so(注册后台进程并调用libtest1.so)→ libtest1.so(导出Test1())
测试环境
- OS: CentOS9(CentOS7、Ubuntu22.04均复现)
- Golang: go1.23.3 linux/amd64
- PostgreSQL: 17(v12同样存在问题)
代码实现
Go代码
package main import "C" import ( "fmt" ) //export Test1 func Test1() { ch := make(chan int) go func() { ch <- 10 fmt.Println("in go routine") }() val := <-ch fmt.Printf("get val is %d\n", val) } func main() { Test1() }
编译命令:
go build -buildmode=c-shared -o libtest1.so main.go
编译后生成libtest1.h和libtest1.so文件。
PostgreSQL扩展代码
在PostgreSQL的contrib目录下创建load_so_test目录,包含以下文件:
├── libtest1.h # Go编译生成的头文件 ├── libtest1.so # Go编译生成的动态库 ├── load_so_test--1.0.sql ├── load_so_test.c ├── load_so_test.control └── Makefile
load_so_test--1.0.sql
\echo Use "CREATE EXTENSION load_so_test" to load this file. \quit CREATE FUNCTION load_so_launch() RETURNS pg_catalog.int4 STRICT AS 'MODULE_PATHNAME' LANGUAGE C;
load_so_test.c
#include "postgres.h" /* These are always necessary for a bgworker */ #include "miscadmin.h" #include "postmaster/bgworker.h" #include "postmaster/interrupt.h" #include "storage/ipc.h" #include "storage/latch.h" #include "storage/lwlock.h" #include "storage/proc.h" #include "storage/shmem.h" /* these headers are used by this particular worker's code */ #include "access/xact.h" #include "commands/dbcommands.h" #include "executor/spi.h" #include "fmgr.h" #include "lib/stringinfo.h" #include "pgstat.h" #include "tcop/utility.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/snapmgr.h" #include "libtest1.h" PG_MODULE_MAGIC; PG_FUNCTION_INFO_V1(load_so_launch); PGDLLEXPORT void load_so_main(Datum main_arg) pg_attribute_noreturn(); static uint32 worker_spi_wait_event_main = 0; void load_so_main(Datum main_arg) { /* Establish signal handlers before unblocking signals. */ pqsignal(SIGHUP, SignalHandlerForConfigReload); pqsignal(SIGTERM, die); /* We're now ready to receive signals */ BackgroundWorkerUnblockSignals(); //golang exported function Test1(); for (;;) { if (worker_spi_wait_event_main == 0) worker_spi_wait_event_main = WaitEventExtensionNew("LoadSoTest"); (void)WaitLatch(MyLatch, WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, 3000L, worker_spi_wait_event_main); ResetLatch(MyLatch); ereport(LOG, (errmsg("load_so_main is running"))); } } void _PG_init(void) { BackgroundWorker worker; if (!process_shared_preload_libraries_in_progress) return; memset(&worker, 0, sizeof(worker)); worker.bgw_flags = BGWORKER_SHMEM_ACCESS | BGWORKER_BACKEND_DATABASE_CONNECTION; worker.bgw_start_time = BgWorkerStart_RecoveryFinished; worker.bgw_restart_time = BGW_NEVER_RESTART; sprintf(worker.bgw_library_name, "load_so_test"); sprintf(worker.bgw_function_name, "load_so_main"); snprintf(worker.bgw_name, BGW_MAXLEN, "load_so_test worker"); snprintf(worker.bgw_type, BGW_MAXLEN, "load_so_test"); worker.bgw_notify_pid = 0; RegisterBackgroundWorker(&worker); } Datum load_so_launch(PG_FUNCTION_ARGS) { BackgroundWorker worker; BackgroundWorkerHandle *handle; BgwHandleStatus status; pid_t pid; memset(&worker, 0, sizeof(worker)); worker.bgw_flags = BGWORKER_SHMEM_ACCESS | BGWORKER_BACKEND_DATABASE_CONNECTION; worker.bgw_start_time = BgWorkerStart_RecoveryFinished; worker.bgw_restart_time = BGW_NEVER_RESTART; sprintf(worker.bgw_library_name, "load_so_test"); sprintf(worker.bgw_function_name, "load_so_main"); snprintf(worker.bgw_name, BGW_MAXLEN, "load_so_test dynamic worker"); snprintf(worker.bgw_type, BGW_MAXLEN, "load_so_test dynamic"); worker.bgw_main_arg = Int32GetDatum(0); /* set bgw_notify_pid so that we can use WaitForBackgroundWorkerStartup */ worker.bgw_notify_pid = MyProcPid; if (!RegisterDynamicBackgroundWorker(&worker, &handle)) PG_RETURN_NULL(); status = WaitForBackgroundWorkerStartup(handle, &pid); if (status == BGWH_STOPPED) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_RESOURCES), errmsg("could not start background process"), errhint("More details may be available in the server log."))); if (status == BGWH_POSTMASTER_DIED) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_RESOURCES), errmsg("cannot start background processes without postmaster"), errhint("Kill all remaining database processes and restart the database."))); Assert(status == BGWH_STARTED); PG_RETURN_INT32(pid); }
load_so_test.control
# load_so_test extension comment = 'load_so_test test' default_version = '1.0' module_pathname = '$libdir/load_so_test' relocatable = true
Makefile
# contrib/load_so_test/Makefile MODULES = load_so_test EXTENSION = load_so_test DATA = load_so_test--1.0.sql PG_CFLAGS = -L. -ltest1 ifdef USE_PGXS PG_CONFIG = pg_config PGXS := $(shell $(PG_CONFIG) --pgxs) include $(PGXS) else subdir = contrib/load_so_test top_builddir = ../.. include $(top_builddir)/src/Makefile.global include $(top_srcdir)/contrib/contrib-global.mk endif
复现步骤
- 编译Go代码生成
libtest1.so和libtest1.h,复制到PostgreSQL扩展目录contrib/load_so_test下 - 执行
make install编译安装PostgreSQL扩展 - 将
libtest1.so手动复制到PostgreSQL安装目录的lib/下 - 阻塞场景:修改
postgresql.conf,设置shared_preload_libraries='load_so_test',启动PostgreSQL,后台进程调用的Test1()阻塞 - 正常场景:不配置预加载,启动PostgreSQL后执行
CREATE EXTENSION load_so_test;,调用select load_so_launch();动态创建后台进程,Test1()正常执行
需求
需要实现当配置shared_preload_libraries='load_so_test'时,后台进程能正常执行Test1()而不阻塞。
内容的提问来源于stack exchange,提问作者wgf098
相关产品推荐
相关产品推荐

