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.
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.