You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Golang生成C动态库在PostgreSQL预加载时阻塞问题求助

Go C-Shared Library Blocks in PostgreSQL Preloaded Background Worker

我用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

复现步骤

  1. 编译Go代码生成libtest1.so和libtest1.h,复制到PostgreSQL扩展目录contrib/load_so_test下
  2. 执行make install编译安装PostgreSQL扩展
  3. 将libtest1.so手动复制到PostgreSQL安装目录的lib/下
  4. 阻塞场景:修改postgresql.conf,设置shared_preload_libraries='load_so_test',启动PostgreSQL,后台进程调用的Test1()阻塞
  5. 正常场景:不配置预加载,启动PostgreSQL后执行CREATE EXTENSION load_so_test;,调用select load_so_launch();动态创建后台进程,Test1()正常执行

需求

需要实现当配置shared_preload_libraries='load_so_test'时,后台进程能正常执行Test1()而不阻塞。

内容的提问来源于stack exchange,提问作者wgf098

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 16:41:03