postgresqlgoblockingcgo

Code written in golang, output as a C dynamic library, is called by posgres17, but Golang-generated dynamic library functions is blocked


everyone: I wrote a demo, but it doesn't work properly, can you help me, thank you very much.

Code written in golang, output as C dynamic library "libtest1.so", is called by the "load_so_test.so" registered background process written in posgres17(version 12 also has the problem),The "Test1()" function in "libtest1.so" blocks when "load_so_test.so" is configured in shared_preload_libraries in postgresql.conf.

But the "Test1()" function in "libtest1.so" executes normally when "load_so_test.so" registers a function with create extension and uses this function to start a background process.

The overall call logic is: pg -> load_so_test.so(register a background process and call libtest1.so) -> libtest1.so (export a Test1() function for load_so_test.so to call)

Test environment: OS:CentOS9 (CentOS7 Ubuntu22.04 also tested, same problem) golang: go1.23.3 linux/amd64 posgres:posgres17

Here is all the code used for the test.

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()
}

Execute a command:

go build -buildmode=c-shared -o libtest1.so main.go

Generate the libtest1.h and libtest1.so files.

pg code:

Create a load_so_test directory in contrib with the following files:

├── libtest1.h  ---go code generation file
├── libtest1.so ---go code generation file
├── 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(There are two ways to create a background process):


#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

After you run make install, you now need to manually copy libtest1.so to the lib/ directory of the pg installation directory.

Run this command when shared_preload_libraries = 'load_so_test' in the postgresql.conf file is setted.

./bin/pg_ctl -D ./data start -l logfile

Test1() is executed in the load_so_main function. Blocking occurs.

If you do not set shared_preload_libraries. Instead, to create extension and create background processes dynamically through functions, Test1() is executed in the load_so_main function; It can be executed normally.

create extension load_so_test;
select load_so_launch();

Now the result I want is that when shared_preload_libraries = 'load_so_test' is configured, the background process will also execute properly instead of blocking.

Thanks a lot.

Now the result I want is that when shared_preload_libraries = 'load_so_test' is configured, the background process will also execute properly instead of blocking.


Solution

  • I don't know why, but I've solved it now. go compiled dynamic library “libtest1.so”, from compile time with -ltest1 command, to run in load_so_main() function through dlopen() to open libtest1.so, and then through dlsym() to find Test1() function, it worked properly.