@@ -216,8 +216,6 @@ fn area_set_bbox(parea: *mut proj_sys::PJ_AREA, new_area: Option<Area>) {
216216/// which the thread-local and any surviving `Proj` instances are dropped at thread exit.
217217enum Context {
218218 Owned ( * mut PJ_CONTEXT ) ,
219- // Wired into Proj::new/new_known_crs in a follow-up commit.
220- #[ allow( dead_code) ]
221219 Shared ( Rc < SharedContext > ) ,
222220}
223221
@@ -264,7 +262,6 @@ thread_local! {
264262}
265263
266264/// Return a reference-counted handle to the calling thread's shared PROJ context.
267- #[ allow( dead_code) ] // wired into Proj::new/new_known_crs in a follow-up commit
268265fn thread_local_context ( ) -> Context {
269266 Context :: Shared ( SHARED_CONTEXT . with ( Rc :: clone) )
270267}
@@ -717,8 +714,7 @@ impl Proj {
717714 // PJ_LP signals projection of geodetic coordinates, with output being PJ_XY
718715 // and vice versa, or using PJ_XY for conversion operations
719716 pub fn new ( definition : & str ) -> Result < Proj , ProjCreateError > {
720- let ctx = unsafe { proj_context_create ( ) } ;
721- transform_string ( Context :: Owned ( ctx) , definition)
717+ transform_string ( thread_local_context ( ) , definition)
722718 }
723719
724720 /// Try to create a new transformation object that is a pipeline between two known coordinate reference systems.
@@ -773,8 +769,7 @@ impl Proj {
773769 to : & str ,
774770 area : Option < Area > ,
775771 ) -> Result < Proj , ProjCreateError > {
776- let ctx = unsafe { proj_context_create ( ) } ;
777- transform_epsg ( Context :: Owned ( ctx) , from, to, area)
772+ transform_epsg ( thread_local_context ( ) , from, to, area)
778773 }
779774
780775 /// Create a transformation object that is a pipeline _between_ two known coordinate reference systems.
@@ -1674,6 +1669,81 @@ mod test {
16741669 let proj = Proj :: new ( wgs84) . unwrap ( ) ;
16751670 let np = proj. coordinate_metadata_create ( epoch) . unwrap ( ) ;
16761671 assert_eq ! ( np. coordinate_metadata_get_epoch( ) , 2021.3 ) ;
1672+ // The metadata object clones the context, so it owns a distinct one rather than
1673+ // borrowing the shared per-thread context.
1674+ assert_ne ! ( np. ctx( ) as usize , proj. ctx( ) as usize ) ;
1675+ }
1676+
1677+ #[ test]
1678+ fn test_new_reuses_thread_context ( ) {
1679+ // Every Proj::new on a thread borrows the same per-thread context.
1680+ let a = Proj :: new ( "EPSG:4326" ) . unwrap ( ) ;
1681+ let b = Proj :: new_known_crs ( "EPSG:4326" , "EPSG:3857" , None ) . unwrap ( ) ;
1682+ assert_eq ! ( a. ctx( ) , b. ctx( ) ) ;
1683+ }
1684+
1685+ #[ test]
1686+ fn test_each_thread_gets_its_own_context ( ) {
1687+ // The shared context is per-thread: each thread must get a distinct context. A barrier
1688+ // keeps every context alive simultaneously while addresses are captured, so a freed
1689+ // context's address cannot be reused by another thread and falsely collide.
1690+ use std:: sync:: { Arc , Barrier } ;
1691+
1692+ let threads = 4 ;
1693+ let barrier = Arc :: new ( Barrier :: new ( threads + 1 ) ) ;
1694+ let main = Proj :: new ( "EPSG:4326" ) . unwrap ( ) ;
1695+
1696+ let handles: Vec < _ > = ( 0 ..threads)
1697+ . map ( |_| {
1698+ let barrier = Arc :: clone ( & barrier) ;
1699+ // Send the context address (usize), not the !Send Proj itself.
1700+ std:: thread:: spawn ( move || {
1701+ let proj = Proj :: new ( "EPSG:4326" ) . unwrap ( ) ;
1702+ let addr = proj. ctx ( ) as usize ;
1703+ barrier. wait ( ) ;
1704+ drop ( proj) ;
1705+ addr
1706+ } )
1707+ } )
1708+ . collect ( ) ;
1709+
1710+ barrier. wait ( ) ;
1711+ let mut seen = vec ! [ main. ctx( ) as usize ] ;
1712+ for handle in handles {
1713+ seen. push ( handle. join ( ) . unwrap ( ) ) ;
1714+ }
1715+
1716+ let total = seen. len ( ) ;
1717+ seen. sort_unstable ( ) ;
1718+ seen. dedup ( ) ;
1719+ assert_eq ! (
1720+ seen. len( ) ,
1721+ total,
1722+ "each thread should get a distinct context"
1723+ ) ;
1724+ }
1725+
1726+ #[ test]
1727+ fn test_builder_context_is_not_shared ( ) {
1728+ // ProjBuilder owns its context (it mutates context state), so it must not borrow the
1729+ // shared per-thread context.
1730+ let shared = Proj :: new ( "EPSG:4326" ) . unwrap ( ) . ctx ( ) as usize ;
1731+ let built = ProjBuilder :: new ( ) . proj ( "EPSG:4326" ) . unwrap ( ) ;
1732+ assert_ne ! ( built. ctx( ) as usize , shared) ;
1733+ }
1734+
1735+ #[ test]
1736+ fn test_drop_does_not_free_shared_context ( ) {
1737+ // Dropping a Proj must not destroy the shared context other Proj instances still use.
1738+ // Under AddressSanitizer this would surface as a use-after-free if the invariant broke.
1739+ let first = Proj :: new ( "EPSG:4326" ) . unwrap ( ) . ctx ( ) as usize ;
1740+ for _ in 0 ..1000 {
1741+ let _ = Proj :: new ( "EPSG:4326" ) . unwrap ( ) ;
1742+ }
1743+ let transformer = Proj :: new_known_crs ( "EPSG:4326" , "EPSG:3857" , None ) . unwrap ( ) ;
1744+ assert_eq ! ( transformer. ctx( ) as usize , first) ;
1745+ // The shared context is still valid and usable.
1746+ transformer. convert ( ( 2.0 , 49.0 ) ) . unwrap ( ) ;
16771747 }
16781748
16791749 #[ test]
@@ -1688,6 +1758,8 @@ mod test {
16881758
16891759 assert_relative_eq ! ( result. x( ) , 1450880.2910605022 , epsilon = 1.0e-8 ) ;
16901760 assert_relative_eq ! ( result. y( ) , 1141263.0111604782 , epsilon = 1.0e-8 ) ;
1761+ // The transformer clones the source context, so it owns a distinct one.
1762+ assert_ne ! ( transformer. ctx( ) as usize , from. ctx( ) as usize ) ;
16911763 }
16921764
16931765 #[ test]
0 commit comments