Skip to content

Commit f4ec0ce

Browse files
eeeebbbbrrrrEric B. Ridge
andauthored
introduce #[pg_guard(unsafe_entry_thread)] (pgcentralfoundation#2242)
initial commit of rejiggering how pgrx tracks the "ACTIVE_THREAD". This is to address issue pgcentralfoundation#2228. Functions annotated with `#[pg_guard(unsafe_entry_thread)]` do some trickery to reset pgrx' understanding of the "ACTIVE_THREAD" so that whatever thread happens to be calling that function then becomes our understanding of the "ACTIVE_THREAD", thereby allowing pgrx to do Postgres FFI on a thread that isn't the main thread. The situation behind pgcentralfoundation#2228 is that pg_duckdb, an extension written in C++, uses threads (presumably "safely") within Postgres and if one of those threads does something like SPI, which then causes planner/executor hooks to be called, and one of those hooks is implemented with pgrx, this new `unsafe_entry_thread` tag will let that callback run. It's basically making an assumption that the caller is properly managing the thread it's on. There's very little code changes here -- the bulk of the PR is a new example crate/extension with its own little c-shim so that I could manually test Postgres itself kicking off a thread that does SPI that ends up calling into a planner hook setup by the rust/pgrx extension. I have a need to push this through for $DayJob, so I'm probably going to do that later this week. --------- Co-authored-by: Eric B. Ridge <[email protected]>
1 parent 3696382 commit f4ec0ce

18 files changed

Lines changed: 531 additions & 124 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ exclude = [
3636
"pgrx-examples/wal_decoder",
3737
"pgrx-examples/errors",
3838
"pgrx-examples/hooks",
39+
"pgrx-examples/pgthread",
3940
"pgrx-examples/nostd",
4041
"pgrx-examples/numeric",
4142
"pgrx-examples/pgtrybuilder",

pgrx-bindgen/src/build.rs

Lines changed: 4 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,11 @@
77
//LICENSE All rights reserved.
88
//LICENSE
99
//LICENSE Use of this source code is governed by the MIT license that can be found in the LICENSE file.
10+
use crate::{detect_pg_config, env_tracked, is_for_release};
1011
use bindgen::NonCopyUnionStyle;
1112
use bindgen::callbacks::{DeriveTrait, EnumVariantValue, ImplementsTrait, MacroParsingBehavior};
1213
use eyre::{WrapErr, eyre};
13-
use pgrx_pg_config::{
14-
PgConfig, PgConfigSelector, PgMinorVersion, PgVersion, Pgrx, SUPPORTED_VERSIONS,
15-
is_supported_major_version,
16-
};
14+
use pgrx_pg_config::{PgConfig, PgMinorVersion, PgVersion, Pgrx};
1715
use quote::{ToTokens, quote};
1816
use std::cell::RefCell;
1917
use std::cmp::Ordering;
@@ -161,81 +159,13 @@ pub fn main() -> eyre::Result<()> {
161159
}
162160

163161
let compile_cshim = env_tracked("CARGO_FEATURE_CSHIM").as_deref() == Some("1");
164-
let is_for_release =
165-
env_tracked("PGRX_PG_SYS_GENERATE_BINDINGS_FOR_RELEASE").as_deref() == Some("1");
166-
167162
let build_paths = BuildPaths::from_env();
168163

169164
eprintln!("build_paths={build_paths:?}");
170165

171166
emit_rerun_if_changed();
172167

173-
let pg_configs: Vec<(u16, PgConfig)> = if is_for_release {
174-
// This does not cross-check config.toml and Cargo.toml versions, as it is release infra.
175-
Pgrx::from_config()?.iter(PgConfigSelector::All)
176-
.map(|r| r.expect("invalid pg_config"))
177-
.map(|c| (c.major_version().expect("invalid major version"), c))
178-
.filter_map(|t| {
179-
if is_supported_major_version(t.0) {
180-
Some(t)
181-
} else {
182-
println!(
183-
"cargo:warning={} contains a configuration for pg{}, which pgrx does not support.",
184-
Pgrx::config_toml()
185-
.expect("Could not get PGRX configuration TOML")
186-
.to_string_lossy(),
187-
t.0
188-
);
189-
None
190-
}
191-
})
192-
.collect()
193-
} else {
194-
let mut found = Vec::new();
195-
for pgver in SUPPORTED_VERSIONS() {
196-
if env_tracked(&format!("CARGO_FEATURE_PG{}", pgver.major)).is_some() {
197-
found.push(pgver);
198-
}
199-
}
200-
let found_ver = match &found[..] {
201-
[ver] => ver,
202-
[] => {
203-
return Err(eyre!(
204-
"Did not find `pg$VERSION` feature. `pgrx-pg-sys` requires one of {} to be set",
205-
SUPPORTED_VERSIONS()
206-
.iter()
207-
.map(|pgver| format!("`pg{}`", pgver.major))
208-
.collect::<Vec<_>>()
209-
.join(", ")
210-
));
211-
}
212-
versions => {
213-
return Err(eyre!(
214-
"Multiple `pg$VERSION` features found.\n`--no-default-features` may be required.\nFound: {}",
215-
versions
216-
.iter()
217-
.map(|version| format!("pg{}", version.major))
218-
.collect::<Vec<String>>()
219-
.join(", ")
220-
));
221-
}
222-
};
223-
224-
let found_major = found_ver.major;
225-
if let Ok(pg_config) = PgConfig::from_env() {
226-
let major_version = pg_config.major_version()?;
227-
228-
if major_version != found_major {
229-
panic!(
230-
"Feature flag `pg{found_major}` does not match version from the environment-described PgConfig (`{major_version}`)"
231-
)
232-
}
233-
vec![(major_version, pg_config)]
234-
} else {
235-
let specific = Pgrx::from_config()?.get(&format!("pg{}", found_ver.major))?;
236-
vec![(found_ver.major, specific)]
237-
}
238-
};
168+
let pg_configs = detect_pg_config()?;
239169

240170
// make sure we're not trying to build any of the yanked postgres versions
241171
for (_, pg_config) in &pg_configs {
@@ -262,7 +192,7 @@ pub fn main() -> eyre::Result<()> {
262192
*pg_major_ver,
263193
pg_config,
264194
&build_paths,
265-
is_for_release,
195+
is_for_release(),
266196
compile_cshim,
267197
)
268198
})
@@ -903,37 +833,6 @@ fn add_derives(bind: bindgen::Builder) -> bindgen::Builder {
903833
.derive_partialord(false)
904834
}
905835

906-
fn env_tracked(s: &str) -> Option<String> {
907-
// a **sorted** list of environment variable keys that cargo might set that we don't need to track
908-
// these were picked out, by hand, from: https://doc.rust-lang.org/cargo/reference/environment-variables.html
909-
const CARGO_KEYS: &[&str] = &[
910-
"BROWSER",
911-
"DEBUG",
912-
"DOCS_RS",
913-
"HOST",
914-
"HTTP_PROXY",
915-
"HTTP_TIMEOUT",
916-
"NUM_JOBS",
917-
"OPT_LEVEL",
918-
"OUT_DIR",
919-
"PATH",
920-
"PROFILE",
921-
"TARGET",
922-
"TERM",
923-
];
924-
925-
let is_cargo_key =
926-
s.starts_with("CARGO") || s.starts_with("RUST") || CARGO_KEYS.binary_search(&s).is_ok();
927-
928-
if !is_cargo_key {
929-
// if it's an envar that cargo gives us, we don't want to ask it to rerun build.rs if it changes
930-
// we'll let cargo figure that out for itself, and doing so, depending on the key, seems to
931-
// cause cargo to rerun build.rs every time, which is terrible
932-
println!("cargo:rerun-if-env-changed={s}");
933-
}
934-
std::env::var(s).ok()
935-
}
936-
937836
fn target_env_tracked(s: &str) -> Option<String> {
938837
let target = env_tracked("TARGET").unwrap();
939838
env_tracked(&format!("{s}_{target}")).or_else(|| env_tracked(s))

pgrx-bindgen/src/lib.rs

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,122 @@
1+
//LICENSE Portions Copyright 2019-2021 ZomboDB, LLC.
2+
//LICENSE
3+
//LICENSE Portions Copyright 2021-2023 Technology Concepts & Design, Inc.
4+
//LICENSE
5+
//LICENSE Portions Copyright 2023-2023 PgCentral Foundation, Inc. <[email protected]>
6+
//LICENSE
7+
//LICENSE All rights reserved.
8+
//LICENSE
9+
//LICENSE Use of this source code is governed by the MIT license that can be found in the LICENSE file.
10+
use eyre::eyre;
11+
use pgrx_pg_config::{
12+
PgConfig, PgConfigSelector, Pgrx, SUPPORTED_VERSIONS, is_supported_major_version,
13+
};
14+
115
pub mod build;
16+
17+
fn is_for_release() -> bool {
18+
env_tracked("PGRX_PG_SYS_GENERATE_BINDINGS_FOR_RELEASE").as_deref() == Some("1")
19+
}
20+
21+
/// Determines which `pg_config` is to be used, based on a combination of pgrx' internal knowledge
22+
/// of supported Postgres versions and `cargo` options/feature flags.
23+
pub fn detect_pg_config() -> eyre::Result<Vec<(u16, PgConfig)>> {
24+
let pg_configs: Vec<(u16, PgConfig)> = if is_for_release() {
25+
// This does not cross-check config.toml and Cargo.toml versions, as it is release infra.
26+
Pgrx::from_config()?.iter(PgConfigSelector::All)
27+
.map(|r| r.expect("invalid pg_config"))
28+
.map(|c| (c.major_version().expect("invalid major version"), c))
29+
.filter_map(|t| {
30+
if is_supported_major_version(t.0) {
31+
Some(t)
32+
} else {
33+
println!(
34+
"cargo:warning={} contains a configuration for pg{}, which pgrx does not support.",
35+
Pgrx::config_toml()
36+
.expect("Could not get PGRX configuration TOML")
37+
.to_string_lossy(),
38+
t.0
39+
);
40+
None
41+
}
42+
})
43+
.collect()
44+
} else {
45+
let mut found = Vec::new();
46+
for pgver in SUPPORTED_VERSIONS() {
47+
if env_tracked(&format!("CARGO_FEATURE_PG{}", pgver.major)).is_some() {
48+
found.push(pgver);
49+
}
50+
}
51+
let found_ver = match &found[..] {
52+
[ver] => ver,
53+
[] => {
54+
return Err(eyre!(
55+
"Did not find `pg$VERSION` feature. `pgrx-pg-sys` requires one of {} to be set",
56+
SUPPORTED_VERSIONS()
57+
.iter()
58+
.map(|pgver| format!("`pg{}`", pgver.major))
59+
.collect::<Vec<_>>()
60+
.join(", ")
61+
));
62+
}
63+
versions => {
64+
return Err(eyre!(
65+
"Multiple `pg$VERSION` features found.\n`--no-default-features` may be required.\nFound: {}",
66+
versions
67+
.iter()
68+
.map(|version| format!("pg{}", version.major))
69+
.collect::<Vec<String>>()
70+
.join(", ")
71+
));
72+
}
73+
};
74+
75+
let found_major = found_ver.major;
76+
if let Ok(pg_config) = PgConfig::from_env() {
77+
let major_version = pg_config.major_version()?;
78+
79+
if major_version != found_major {
80+
panic!(
81+
"Feature flag `pg{found_major}` does not match version from the environment-described PgConfig (`{major_version}`)"
82+
)
83+
}
84+
vec![(major_version, pg_config)]
85+
} else {
86+
let specific = Pgrx::from_config()?.get(&format!("pg{}", found_ver.major))?;
87+
vec![(found_ver.major, specific)]
88+
}
89+
};
90+
Ok(pg_configs)
91+
}
92+
93+
fn env_tracked(s: &str) -> Option<String> {
94+
// a **sorted** list of environment variable keys that cargo might set that we don't need to track
95+
// these were picked out, by hand, from: https://doc.rust-lang.org/cargo/reference/environment-variables.html
96+
const CARGO_KEYS: &[&str] = &[
97+
"BROWSER",
98+
"DEBUG",
99+
"DOCS_RS",
100+
"HOST",
101+
"HTTP_PROXY",
102+
"HTTP_TIMEOUT",
103+
"NUM_JOBS",
104+
"OPT_LEVEL",
105+
"OUT_DIR",
106+
"PATH",
107+
"PROFILE",
108+
"TARGET",
109+
"TERM",
110+
];
111+
112+
let is_cargo_key =
113+
s.starts_with("CARGO") || s.starts_with("RUST") || CARGO_KEYS.binary_search(&s).is_ok();
114+
115+
if !is_cargo_key {
116+
// if it's an envar that cargo gives us, we don't want to ask it to rerun build.rs if it changes
117+
// we'll let cargo figure that out for itself, and doing so, depending on the key, seems to
118+
// cause cargo to rerun build.rs every time, which is terrible
119+
println!("cargo:rerun-if-env-changed={s}");
120+
}
121+
std::env::var(s).ok()
122+
}

pgrx-examples/pgthread/.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
.DS_Store
2+
.idea/
3+
/target
4+
*.iml
5+
**/*.rs.bk
6+
Cargo.lock
7+
sql/*
8+
c_ext/*.o
9+
c_ext/*.dylib
10+
c_ext/*.so

pgrx-examples/pgthread/Cargo.toml

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#LICENSE Portions Copyright 2019-2021 ZomboDB, LLC.
2+
#LICENSE
3+
#LICENSE Portions Copyright 2021-2023 Technology Concepts & Design, Inc.
4+
#LICENSE
5+
#LICENSE Portions Copyright 2023-2023 PgCentral Foundation, Inc. <[email protected]>
6+
#LICENSE
7+
#LICENSE All rights reserved.
8+
#LICENSE
9+
#LICENSE Use of this source code is governed by the MIT license that can be found in the LICENSE file.
10+
11+
[package]
12+
name = "pgthread"
13+
version = "0.0.0"
14+
edition = "2021"
15+
publish = false
16+
17+
[lib]
18+
crate-type = ["cdylib", "lib"]
19+
20+
[[bin]]
21+
name = "pgrx_embed_pgthread"
22+
path = "./src/bin/pgrx_embed.rs"
23+
24+
[features]
25+
default = ["pg13"]
26+
pg13 = ["pgrx/pg13", "pgrx-tests/pg13" ]
27+
pg14 = ["pgrx/pg14", "pgrx-tests/pg14" ]
28+
pg15 = ["pgrx/pg15", "pgrx-tests/pg15" ]
29+
pg16 = ["pgrx/pg16", "pgrx-tests/pg16" ]
30+
pg17 = ["pgrx/pg17", "pgrx-tests/pg17" ]
31+
pg18 = ["pgrx/pg18", "pgrx-tests/pg18" ]
32+
pg_test = []
33+
34+
[dependencies]
35+
pgrx = { path = "../../pgrx", default-features = false }
36+
37+
[dev-dependencies]
38+
pgrx-tests = { path = "../../pgrx-tests" }
39+
40+
[build-dependencies]
41+
pgrx-bindgen = { path = "../../pgrx-bindgen" }
42+
43+
# uncomment these if compiling outside of 'pgrx'
44+
# [profile.dev]
45+
# panic = "unwind"
46+
47+
# [profile.release]
48+
# panic = "unwind"
49+
# opt-level = 3
50+
# lto = "fat"
51+
# codegen-units = 1

pgrx-examples/pgthread/build.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
use std::process::{Command, ExitCode};
2+
3+
use std::fs;
4+
use std::path::PathBuf;
5+
6+
fn main() -> Result<ExitCode, Box<dyn std::error::Error>> {
7+
println!("cargo:rerun-if-changed=c_ext/c_ext.c");
8+
println!("cargo:rerun-if-changed=c_ext/Makefile");
9+
10+
let (_, pg_config) =
11+
pgrx_bindgen::detect_pg_config()?.pop().unwrap_or_else(|| panic!("no pg_config detected"));
12+
13+
let child = Command::new("make")
14+
.current_dir("c_ext")
15+
.env("USE_PGXS", "1")
16+
.env("PROFILE", "")
17+
.env("PG_CONFIG", pg_config.path().expect("pg_config must have a path"))
18+
.spawn()?;
19+
let output = child.wait_with_output()?;
20+
if !output.status.success() {
21+
eprintln!("{}", std::str::from_utf8(&output.stderr)?);
22+
return Ok(ExitCode::from(output.status.code().unwrap() as u8));
23+
}
24+
25+
let output = std::env::current_dir()?.join("c_ext");
26+
println!("cargo:rustc-link-arg-cdylib={}/c_ext.o", output.display());
27+
28+
let out_dir = PathBuf::from(std::env::var("OUT_DIR")?);
29+
30+
// Force-export these symbols from the final .so
31+
let vers = out_dir.join("exports.map");
32+
fs::write(
33+
&vers,
34+
r#"
35+
{
36+
global:
37+
start_thread;
38+
pg_finfo_start_thread;
39+
local:
40+
*;
41+
};
42+
"#,
43+
)?;
44+
45+
let target = std::env::var("TARGET")?;
46+
if target.contains("apple-darwin") {
47+
println!("cargo:rustc-link-arg-cdylib=-Wl,-u,_start_thread");
48+
println!("cargo:rustc-link-arg-cdylib=-Wl,-exported_symbol,_start_thread");
49+
println!("cargo:rustc-link-arg-cdylib=-Wl,-exported_symbol,_pg_finfo_start_thread");
50+
} else if target.contains("unknown-linux-gnu") {
51+
println!("cargo:rustc-link-arg-cdylib=-Wl,--undefined=start_thread");
52+
println!("cargo:rustc-link-arg-cdylib=-Wl,--undefined=pg_finfo_start_thread");
53+
println!("cargo:rustc-link-arg-cdylib=-Wl,--version-script={}", vers.display());
54+
}
55+
56+
Ok(ExitCode::SUCCESS)
57+
}

0 commit comments

Comments
 (0)