0

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.

1
  • Please clarify your specific problem or provide additional details to highlight exactly what you need. As it's currently written, it's hard to tell exactly what you're asking. Commented Dec 11, 2024 at 1:42

1 Answer 1

0

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.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.