@@ -23,7 +23,7 @@ use std::{
2323 str,
2424} ;
2525
26- use crate :: context:: { Context , ProjContext } ;
26+ use crate :: context:: { Context , ProjContext , thread_local_context } ;
2727use crate :: cstring_array:: CStringArray ;
2828
2929#[ cfg( feature = "network" ) ]
@@ -673,7 +673,7 @@ impl Proj {
673673 // PJ_LP signals projection of geodetic coordinates, with output being PJ_XY
674674 // and vice versa, or using PJ_XY for conversion operations
675675 pub fn new ( definition : & str ) -> Result < Proj , ProjCreateError > {
676- ProjContext :: Owned ( Context :: new ( ) ) . transform_string ( definition)
676+ thread_local_context ( ) . transform_string ( definition)
677677 }
678678
679679 /// Try to create a new transformation object that is a pipeline between two known coordinate reference systems.
@@ -728,7 +728,7 @@ impl Proj {
728728 to : & str ,
729729 area : Option < Area > ,
730730 ) -> Result < Proj , ProjCreateError > {
731- ProjContext :: Owned ( Context :: new ( ) ) . transform_epsg ( from, to, area)
731+ thread_local_context ( ) . transform_epsg ( from, to, area)
732732 }
733733
734734 /// Create a transformation object that is a pipeline _between_ two known coordinate reference systems.
@@ -1655,6 +1655,81 @@ mod test {
16551655 let proj = Proj :: new ( wgs84) . unwrap ( ) ;
16561656 let np = proj. coordinate_metadata_create ( epoch) . unwrap ( ) ;
16571657 assert_eq ! ( np. coordinate_metadata_get_epoch( ) , 2021.3 ) ;
1658+ // The metadata object clones the context, so it owns a distinct one rather than
1659+ // borrowing the shared per-thread context.
1660+ assert_ne ! ( np. ctx( ) as usize , proj. ctx( ) as usize ) ;
1661+ }
1662+
1663+ #[ test]
1664+ fn test_new_reuses_thread_context ( ) {
1665+ // Every Proj::new on a thread borrows the same per-thread context.
1666+ let a = Proj :: new ( "EPSG:4326" ) . unwrap ( ) ;
1667+ let b = Proj :: new_known_crs ( "EPSG:4326" , "EPSG:3857" , None ) . unwrap ( ) ;
1668+ assert_eq ! ( a. ctx( ) , b. ctx( ) ) ;
1669+ }
1670+
1671+ #[ test]
1672+ fn test_each_thread_gets_its_own_context ( ) {
1673+ // The shared context is per-thread: each thread must get a distinct context. A barrier
1674+ // keeps every context alive simultaneously while addresses are captured, so a freed
1675+ // context's address cannot be reused by another thread and falsely collide.
1676+ use std:: sync:: { Arc , Barrier } ;
1677+
1678+ let threads = 4 ;
1679+ let barrier = Arc :: new ( Barrier :: new ( threads + 1 ) ) ;
1680+ let main = Proj :: new ( "EPSG:4326" ) . unwrap ( ) ;
1681+
1682+ let handles: Vec < _ > = ( 0 ..threads)
1683+ . map ( |_| {
1684+ let barrier = Arc :: clone ( & barrier) ;
1685+ // Send the context address (usize), not the !Send Proj itself.
1686+ std:: thread:: spawn ( move || {
1687+ let proj = Proj :: new ( "EPSG:4326" ) . unwrap ( ) ;
1688+ let addr = proj. ctx ( ) as usize ;
1689+ barrier. wait ( ) ;
1690+ drop ( proj) ;
1691+ addr
1692+ } )
1693+ } )
1694+ . collect ( ) ;
1695+
1696+ barrier. wait ( ) ;
1697+ let mut seen = vec ! [ main. ctx( ) as usize ] ;
1698+ for handle in handles {
1699+ seen. push ( handle. join ( ) . unwrap ( ) ) ;
1700+ }
1701+
1702+ let total = seen. len ( ) ;
1703+ seen. sort_unstable ( ) ;
1704+ seen. dedup ( ) ;
1705+ assert_eq ! (
1706+ seen. len( ) ,
1707+ total,
1708+ "each thread should get a distinct context"
1709+ ) ;
1710+ }
1711+
1712+ #[ test]
1713+ fn test_builder_context_is_not_shared ( ) {
1714+ // ProjBuilder owns its context (it mutates context state), so it must not borrow the
1715+ // shared per-thread context.
1716+ let shared = Proj :: new ( "EPSG:4326" ) . unwrap ( ) . ctx ( ) as usize ;
1717+ let built = ProjBuilder :: new ( ) . proj ( "EPSG:4326" ) . unwrap ( ) ;
1718+ assert_ne ! ( built. ctx( ) as usize , shared) ;
1719+ }
1720+
1721+ #[ test]
1722+ fn test_drop_does_not_free_shared_context ( ) {
1723+ // Dropping a Proj must not destroy the shared context other Proj instances still use.
1724+ // Under AddressSanitizer this would surface as a use-after-free if the invariant broke.
1725+ let first = Proj :: new ( "EPSG:4326" ) . unwrap ( ) . ctx ( ) as usize ;
1726+ for _ in 0 ..1000 {
1727+ let _ = Proj :: new ( "EPSG:4326" ) . unwrap ( ) ;
1728+ }
1729+ let transformer = Proj :: new_known_crs ( "EPSG:4326" , "EPSG:3857" , None ) . unwrap ( ) ;
1730+ assert_eq ! ( transformer. ctx( ) as usize , first) ;
1731+ // The shared context is still valid and usable.
1732+ transformer. convert ( ( 2.0 , 49.0 ) ) . unwrap ( ) ;
16581733 }
16591734
16601735 #[ test]
@@ -1669,6 +1744,8 @@ mod test {
16691744
16701745 assert_relative_eq ! ( result. x( ) , 1450880.2910605022 , epsilon = 1.0e-8 ) ;
16711746 assert_relative_eq ! ( result. y( ) , 1141263.0111604782 , epsilon = 1.0e-8 ) ;
1747+ // The transformer clones the source context, so it owns a distinct one.
1748+ assert_ne ! ( transformer. ctx( ) as usize , from. ctx( ) as usize ) ;
16721749 }
16731750
16741751 #[ test]
0 commit comments