Code

Ticket #10923: geos_threadsafe_v1.diff

File geos_threadsafe_v1.diff, 36.2 KB (added by jbronn, 4 years ago)
Line 
1Index: django/contrib/gis/geos/geometry.py
2===================================================================
3--- django/contrib/gis/geos/geometry.py (revision 12205)
4+++ django/contrib/gis/geos/geometry.py (working copy)
5@@ -21,6 +21,9 @@
6 # the underlying GEOS library.
7 from django.contrib.gis.geos import prototypes as capi
8 
9+# These are thread-local instance of their corresponding GEOS I/O class.
10+from django.contrib.gis.geos.prototypes.io import wkt_r, wkt_w, wkb_r, wkb_w, ewkb_w, ewkb_w3d
11+
12 # Regular expression for recognizing HEXEWKB and WKT.  A prophylactic measure
13 # to prevent potentially malicious input from reaching the underlying C
14 # library.  Not a substitute for good web security programming practices.
15@@ -655,9 +658,6 @@
16                 7 : GeometryCollection,
17                 }
18 
19-# Similarly, import the GEOS I/O instances here to avoid conflicts.
20-from django.contrib.gis.geos.io import wkt_r, wkt_w, wkb_r, wkb_w, ewkb_w, ewkb_w3d
21-
22 # If supported, import the PreparedGeometry class.
23 if GEOS_PREPARE:
24     from django.contrib.gis.geos.prepared import PreparedGeometry
25Index: django/contrib/gis/geos/prototypes/misc.py
26===================================================================
27--- django/contrib/gis/geos/prototypes/misc.py  (revision 12205)
28+++ django/contrib/gis/geos/prototypes/misc.py  (working copy)
29@@ -3,8 +3,9 @@
30  ones that return the area, distance, and length.
31 """
32 from ctypes import c_int, c_double, POINTER
33-from django.contrib.gis.geos.libgeos import lgeos, GEOM_PTR
34+from django.contrib.gis.geos.libgeos import GEOM_PTR
35 from django.contrib.gis.geos.prototypes.errcheck import check_dbl
36+from django.contrib.gis.geos.prototypes.threadsafe import GEOSFunc
37 
38 ### ctypes generator function ###
39 def dbl_from_geom(func, num_geom=1):
40@@ -22,6 +23,6 @@
41 ### ctypes prototypes ###
42 
43 # Area, distance, and length prototypes.
44-geos_area = dbl_from_geom(lgeos.GEOSArea)
45-geos_distance = dbl_from_geom(lgeos.GEOSDistance, num_geom=2)
46-geos_length = dbl_from_geom(lgeos.GEOSLength)
47+geos_area = dbl_from_geom(GEOSFunc('GEOSArea'))
48+geos_distance = dbl_from_geom(GEOSFunc('GEOSDistance'), num_geom=2)
49+geos_length = dbl_from_geom(GEOSFunc('GEOSLength'))
50Index: django/contrib/gis/geos/prototypes/topology.py
51===================================================================
52--- django/contrib/gis/geos/prototypes/topology.py      (revision 12205)
53+++ django/contrib/gis/geos/prototypes/topology.py      (working copy)
54@@ -8,9 +8,10 @@
55            'geos_simplify', 'geos_symdifference', 'geos_union', 'geos_relate']
56 
57 from ctypes import c_char_p, c_double, c_int
58-from django.contrib.gis.geos.libgeos import lgeos, GEOM_PTR, GEOS_PREPARE
59+from django.contrib.gis.geos.libgeos import GEOM_PTR, GEOS_PREPARE
60 from django.contrib.gis.geos.prototypes.errcheck import check_geom, check_string
61 from django.contrib.gis.geos.prototypes.geom import geos_char_p
62+from django.contrib.gis.geos.prototypes.threadsafe import GEOSFunc
63 
64 def topology(func, *args):
65     "For GEOS unary topology functions."
66@@ -22,29 +23,29 @@
67     return func
68 
69 ### Topology Routines ###
70-geos_boundary = topology(lgeos.GEOSBoundary)
71-geos_buffer = topology(lgeos.GEOSBuffer, c_double, c_int)
72-geos_centroid = topology(lgeos.GEOSGetCentroid)
73-geos_convexhull = topology(lgeos.GEOSConvexHull)
74-geos_difference = topology(lgeos.GEOSDifference, GEOM_PTR)
75-geos_envelope = topology(lgeos.GEOSEnvelope)
76-geos_intersection = topology(lgeos.GEOSIntersection, GEOM_PTR)
77-geos_linemerge = topology(lgeos.GEOSLineMerge)
78-geos_pointonsurface = topology(lgeos.GEOSPointOnSurface)
79-geos_preservesimplify = topology(lgeos.GEOSTopologyPreserveSimplify, c_double)
80-geos_simplify = topology(lgeos.GEOSSimplify, c_double)
81-geos_symdifference = topology(lgeos.GEOSSymDifference, GEOM_PTR)
82-geos_union = topology(lgeos.GEOSUnion, GEOM_PTR)
83+geos_boundary = topology(GEOSFunc('GEOSBoundary'))
84+geos_buffer = topology(GEOSFunc('GEOSBuffer'), c_double, c_int)
85+geos_centroid = topology(GEOSFunc('GEOSGetCentroid'))
86+geos_convexhull = topology(GEOSFunc('GEOSConvexHull'))
87+geos_difference = topology(GEOSFunc('GEOSDifference'), GEOM_PTR)
88+geos_envelope = topology(GEOSFunc('GEOSEnvelope'))
89+geos_intersection = topology(GEOSFunc('GEOSIntersection'), GEOM_PTR)
90+geos_linemerge = topology(GEOSFunc('GEOSLineMerge'))
91+geos_pointonsurface = topology(GEOSFunc('GEOSPointOnSurface'))
92+geos_preservesimplify = topology(GEOSFunc('GEOSTopologyPreserveSimplify'), c_double)
93+geos_simplify = topology(GEOSFunc('GEOSSimplify'), c_double)
94+geos_symdifference = topology(GEOSFunc('GEOSSymDifference'), GEOM_PTR)
95+geos_union = topology(GEOSFunc('GEOSUnion'), GEOM_PTR)
96 
97 # GEOSRelate returns a string, not a geometry.
98-geos_relate = lgeos.GEOSRelate
99+geos_relate = GEOSFunc('GEOSRelate')
100 geos_relate.argtypes = [GEOM_PTR, GEOM_PTR]
101 geos_relate.restype = geos_char_p
102 geos_relate.errcheck = check_string
103 
104 # Routines only in GEOS 3.1+
105 if GEOS_PREPARE:
106-    geos_cascaded_union = lgeos.GEOSUnionCascaded
107+    geos_cascaded_union = GEOSFunc('GEOSUnionCascaded')
108     geos_cascaded_union.argtypes = [GEOM_PTR]
109     geos_cascaded_union.restype = GEOM_PTR
110     __all__.append('geos_cascaded_union')
111Index: django/contrib/gis/geos/prototypes/coordseq.py
112===================================================================
113--- django/contrib/gis/geos/prototypes/coordseq.py      (revision 12205)
114+++ django/contrib/gis/geos/prototypes/coordseq.py      (working copy)
115@@ -1,6 +1,7 @@
116 from ctypes import c_double, c_int, c_uint, POINTER
117-from django.contrib.gis.geos.libgeos import lgeos, GEOM_PTR, CS_PTR
118+from django.contrib.gis.geos.libgeos import GEOM_PTR, CS_PTR
119 from django.contrib.gis.geos.prototypes.errcheck import last_arg_byref, GEOSException
120+from django.contrib.gis.geos.prototypes.threadsafe import GEOSFunc
121 
122 ## Error-checking routines specific to coordinate sequences. ##
123 def check_cs_ptr(result, func, cargs):
124@@ -59,24 +60,24 @@
125 ## Coordinate Sequence ctypes prototypes ##
126 
127 # Coordinate Sequence constructors & cloning.
128-cs_clone = cs_output(lgeos.GEOSCoordSeq_clone, [CS_PTR])
129-create_cs = cs_output(lgeos.GEOSCoordSeq_create, [c_uint, c_uint])
130-get_cs = cs_output(lgeos.GEOSGeom_getCoordSeq, [GEOM_PTR])
131+cs_clone = cs_output(GEOSFunc('GEOSCoordSeq_clone'), [CS_PTR])
132+create_cs = cs_output(GEOSFunc('GEOSCoordSeq_create'), [c_uint, c_uint])
133+get_cs = cs_output(GEOSFunc('GEOSGeom_getCoordSeq'), [GEOM_PTR])
134 
135 # Getting, setting ordinate
136-cs_getordinate = cs_operation(lgeos.GEOSCoordSeq_getOrdinate, ordinate=True, get=True)
137-cs_setordinate = cs_operation(lgeos.GEOSCoordSeq_setOrdinate, ordinate=True)
138+cs_getordinate = cs_operation(GEOSFunc('GEOSCoordSeq_getOrdinate'), ordinate=True, get=True)
139+cs_setordinate = cs_operation(GEOSFunc('GEOSCoordSeq_setOrdinate'), ordinate=True)
140 
141 # For getting, x, y, z
142-cs_getx = cs_operation(lgeos.GEOSCoordSeq_getX, get=True)
143-cs_gety = cs_operation(lgeos.GEOSCoordSeq_getY, get=True)
144-cs_getz = cs_operation(lgeos.GEOSCoordSeq_getZ, get=True)
145+cs_getx = cs_operation(GEOSFunc('GEOSCoordSeq_getX'), get=True)
146+cs_gety = cs_operation(GEOSFunc('GEOSCoordSeq_getY'), get=True)
147+cs_getz = cs_operation(GEOSFunc('GEOSCoordSeq_getZ'), get=True)
148 
149 # For setting, x, y, z
150-cs_setx = cs_operation(lgeos.GEOSCoordSeq_setX)
151-cs_sety = cs_operation(lgeos.GEOSCoordSeq_setY)
152-cs_setz = cs_operation(lgeos.GEOSCoordSeq_setZ)
153+cs_setx = cs_operation(GEOSFunc('GEOSCoordSeq_setX'))
154+cs_sety = cs_operation(GEOSFunc('GEOSCoordSeq_setY'))
155+cs_setz = cs_operation(GEOSFunc('GEOSCoordSeq_setZ'))
156 
157 # These routines return size & dimensions.
158-cs_getsize = cs_int(lgeos.GEOSCoordSeq_getSize)
159-cs_getdims = cs_int(lgeos.GEOSCoordSeq_getDimensions)
160+cs_getsize = cs_int(GEOSFunc('GEOSCoordSeq_getSize'))
161+cs_getdims = cs_int(GEOSFunc('GEOSCoordSeq_getDimensions'))
162Index: django/contrib/gis/geos/prototypes/prepared.py
163===================================================================
164--- django/contrib/gis/geos/prototypes/prepared.py      (revision 12205)
165+++ django/contrib/gis/geos/prototypes/prepared.py      (working copy)
166@@ -1,13 +1,14 @@
167 from ctypes import c_char
168-from django.contrib.gis.geos.libgeos import lgeos, GEOM_PTR, PREPGEOM_PTR
169+from django.contrib.gis.geos.libgeos import GEOM_PTR, PREPGEOM_PTR
170 from django.contrib.gis.geos.prototypes.errcheck import check_predicate
171+from django.contrib.gis.geos.prototypes.threadsafe import GEOSFunc
172 
173 # Prepared geometry constructor and destructors.
174-geos_prepare = lgeos.GEOSPrepare
175+geos_prepare = GEOSFunc('GEOSPrepare')
176 geos_prepare.argtypes = [GEOM_PTR]
177 geos_prepare.restype = PREPGEOM_PTR
178 
179-prepared_destroy = lgeos.GEOSPreparedGeom_destroy
180+prepared_destroy = GEOSFunc('GEOSPreparedGeom_destroy')
181 prepared_destroy.argtpes = [PREPGEOM_PTR]
182 prepared_destroy.restype = None
183 
184@@ -18,7 +19,7 @@
185     func.errcheck = check_predicate
186     return func
187 
188-prepared_contains = prepared_predicate(lgeos.GEOSPreparedContains)
189-prepared_contains_properly = prepared_predicate(lgeos.GEOSPreparedContainsProperly)
190-prepared_covers = prepared_predicate(lgeos.GEOSPreparedCovers)
191-prepared_intersects = prepared_predicate(lgeos.GEOSPreparedIntersects)
192+prepared_contains = prepared_predicate(GEOSFunc('GEOSPreparedContains'))
193+prepared_contains_properly = prepared_predicate(GEOSFunc('GEOSPreparedContainsProperly'))
194+prepared_covers = prepared_predicate(GEOSFunc('GEOSPreparedCovers'))
195+prepared_intersects = prepared_predicate(GEOSFunc('GEOSPreparedIntersects'))
196Index: django/contrib/gis/geos/prototypes/predicates.py
197===================================================================
198--- django/contrib/gis/geos/prototypes/predicates.py    (revision 12205)
199+++ django/contrib/gis/geos/prototypes/predicates.py    (working copy)
200@@ -3,8 +3,9 @@
201  unary and binary predicate operations on geometries.
202 """
203 from ctypes import c_char, c_char_p, c_double
204-from django.contrib.gis.geos.libgeos import lgeos, GEOM_PTR
205+from django.contrib.gis.geos.libgeos import GEOM_PTR
206 from django.contrib.gis.geos.prototypes.errcheck import check_predicate
207+from django.contrib.gis.geos.prototypes.threadsafe import GEOSFunc
208 
209 ## Binary & unary predicate functions ##
210 def binary_predicate(func, *args):
211@@ -24,20 +25,20 @@
212     return func
213 
214 ## Unary Predicates ##
215-geos_hasz = unary_predicate(lgeos.GEOSHasZ)
216-geos_isempty = unary_predicate(lgeos.GEOSisEmpty)
217-geos_isring = unary_predicate(lgeos.GEOSisRing)
218-geos_issimple = unary_predicate(lgeos.GEOSisSimple)
219-geos_isvalid = unary_predicate(lgeos.GEOSisValid)
220+geos_hasz = unary_predicate(GEOSFunc('GEOSHasZ'))
221+geos_isempty = unary_predicate(GEOSFunc('GEOSisEmpty'))
222+geos_isring = unary_predicate(GEOSFunc('GEOSisRing'))
223+geos_issimple = unary_predicate(GEOSFunc('GEOSisSimple'))
224+geos_isvalid = unary_predicate(GEOSFunc('GEOSisValid'))
225 
226 ## Binary Predicates ##
227-geos_contains = binary_predicate(lgeos.GEOSContains)
228-geos_crosses = binary_predicate(lgeos.GEOSCrosses)
229-geos_disjoint = binary_predicate(lgeos.GEOSDisjoint)
230-geos_equals = binary_predicate(lgeos.GEOSEquals)
231-geos_equalsexact = binary_predicate(lgeos.GEOSEqualsExact, c_double)
232-geos_intersects = binary_predicate(lgeos.GEOSIntersects)
233-geos_overlaps = binary_predicate(lgeos.GEOSOverlaps)
234-geos_relatepattern = binary_predicate(lgeos.GEOSRelatePattern, c_char_p)
235-geos_touches = binary_predicate(lgeos.GEOSTouches)
236-geos_within = binary_predicate(lgeos.GEOSWithin)
237+geos_contains = binary_predicate(GEOSFunc('GEOSContains'))
238+geos_crosses = binary_predicate(GEOSFunc('GEOSCrosses'))
239+geos_disjoint = binary_predicate(GEOSFunc('GEOSDisjoint'))
240+geos_equals = binary_predicate(GEOSFunc('GEOSEquals'))
241+geos_equalsexact = binary_predicate(GEOSFunc('GEOSEqualsExact'), c_double)
242+geos_intersects = binary_predicate(GEOSFunc('GEOSIntersects'))
243+geos_overlaps = binary_predicate(GEOSFunc('GEOSOverlaps'))
244+geos_relatepattern = binary_predicate(GEOSFunc('GEOSRelatePattern'), c_char_p)
245+geos_touches = binary_predicate(GEOSFunc('GEOSTouches'))
246+geos_within = binary_predicate(GEOSFunc('GEOSWithin'))
247Index: django/contrib/gis/geos/prototypes/errcheck.py
248===================================================================
249--- django/contrib/gis/geos/prototypes/errcheck.py      (revision 12205)
250+++ django/contrib/gis/geos/prototypes/errcheck.py      (working copy)
251@@ -3,15 +3,17 @@
252 """
253 import os
254 from ctypes import c_void_p, string_at, CDLL
255+from ctypes.util import find_library
256 from django.contrib.gis.geos.error import GEOSException
257 from django.contrib.gis.geos.libgeos import lgeos, GEOS_VERSION
258+from django.contrib.gis.geos.prototypes.threadsafe import GEOSFunc
259 
260 # Getting the `free` routine used to free the memory allocated for
261 # string pointers returned by GEOS.
262 if GEOS_VERSION >= (3, 1, 1):
263     # In versions 3.1.1 and above, `GEOSFree` was added to the C API
264     # because `free` isn't always available on all platforms.
265-    free = lgeos.GEOSFree
266+    free = GEOSFunc('GEOSFree')
267     free.argtypes = [c_void_p]
268     free.restype = None
269 else:
270Index: django/contrib/gis/geos/prototypes/threadsafe.py
271===================================================================
272--- django/contrib/gis/geos/prototypes/threadsafe.py    (revision 0)
273+++ django/contrib/gis/geos/prototypes/threadsafe.py    (revision 0)
274@@ -0,0 +1,80 @@
275+import threading
276+from django.contrib.gis.geos.libgeos import lgeos, notice_h, error_h, CONTEXT_PTR
277+
278+class GEOSContextHandle(object):
279+    """
280+    Python object representing a GEOS context handle.
281+    """
282+    def __init__(self):
283+        self.ptr = lgeos.initGEOS_r(notice_h, error_h)
284+
285+    def __del__(self):
286+        lgeos.finishGEOS_r(self.ptr)
287+
288+# Defining a thread-local object and attaching a GEOSContextHandle
289+# instance (as `handle` attribute) for this thread.
290+thread_context = threading.local()
291+thread_context.handle = GEOSContextHandle()
292+
293+def call_geos_threaded(cfunc, args):
294+    """
295+    This module-level routine calls the specified GEOS C thread-safe
296+    function with the context for this current thread.
297+    """
298+    return cfunc(thread_context.handle.ptr, *args)
299+
300+class GEOSFunc(object):
301+    """
302+    Class that serves as a wrapper for GEOS C Functions, and will
303+    use thread-safe function variants when available.
304+    """
305+    def __init__(self, func_name):
306+        try:
307+            # GEOS thread-safe function signatures end with '_r'.
308+            self.cfunc = getattr(lgeos, func_name + '_r')
309+            self.threaded = True
310+        except AttributeError:
311+            # Otherwise, use usual function.
312+            self.cfunc = getattr(lgeos, func_name)
313+            self.threaded = False
314+
315+    def __call__(self, *args):
316+        if self.threaded:
317+            return call_geos_threaded(self.cfunc, args)
318+        else:
319+            return self.cfunc(*args)
320+
321+    def __str__(self):
322+        return self.cfunc.__name__
323+
324+    # argtypes property
325+    def _get_argtypes(self):
326+        return self.cfunc.argtypes
327+
328+    def _set_argtypes(self, argtypes):
329+        if self.threaded:
330+            new_argtypes = [CONTEXT_PTR]
331+            new_argtypes.extend(argtypes)
332+            self.cfunc.argtypes = new_argtypes
333+        else:
334+            self.cfunc.argtypes = argtypes
335+
336+    argtypes = property(_get_argtypes, _set_argtypes)
337+
338+    # restype property
339+    def _get_restype(self):
340+        return self.cfunc.restype
341+
342+    def _set_restype(self, restype):
343+        self.cfunc.restype = restype
344+
345+    restype = property(_get_restype, _set_restype)
346+
347+    # errcheck property
348+    def _get_errcheck(self):
349+        return self.cfunc.errcheck
350+
351+    def _set_errcheck(self, errcheck):
352+        self.cfunc.errcheck = errcheck
353+
354+    errcheck = property(_get_errcheck, _set_errcheck)
355Index: django/contrib/gis/geos/prototypes/geom.py
356===================================================================
357--- django/contrib/gis/geos/prototypes/geom.py  (revision 12205)
358+++ django/contrib/gis/geos/prototypes/geom.py  (working copy)
359@@ -1,7 +1,8 @@
360 from ctypes import c_char_p, c_int, c_size_t, c_ubyte, c_uint, POINTER
361-from django.contrib.gis.geos.libgeos import lgeos, CS_PTR, GEOM_PTR, PREPGEOM_PTR, GEOS_PREPARE
362+from django.contrib.gis.geos.libgeos import CS_PTR, GEOM_PTR, PREPGEOM_PTR, GEOS_PREPARE
363 from django.contrib.gis.geos.prototypes.errcheck import \
364     check_geom, check_minus_one, check_sized_string, check_string, check_zero
365+from django.contrib.gis.geos.prototypes.threadsafe import GEOSFunc
366 
367 # This is the return type used by binary output (WKB, HEX) routines.
368 c_uchar_p = POINTER(c_ubyte)
369@@ -62,56 +63,57 @@
370 
371 ### ctypes prototypes ###
372 
373-# Deprecated creation and output routines from WKB, HEX, WKT
374-from_hex = bin_constructor(lgeos.GEOSGeomFromHEX_buf)
375-from_wkb = bin_constructor(lgeos.GEOSGeomFromWKB_buf)
376-from_wkt = geom_output(lgeos.GEOSGeomFromWKT, [c_char_p])
377+# Deprecated creation routines from WKB, HEX, WKT
378+from_hex = bin_constructor(GEOSFunc('GEOSGeomFromHEX_buf'))
379+from_wkb = bin_constructor(GEOSFunc('GEOSGeomFromWKB_buf'))
380+from_wkt = geom_output(GEOSFunc('GEOSGeomFromWKT'), [c_char_p])
381 
382-to_hex = bin_output(lgeos.GEOSGeomToHEX_buf)
383-to_wkb = bin_output(lgeos.GEOSGeomToWKB_buf)
384-to_wkt = string_from_geom(lgeos.GEOSGeomToWKT)
385+# Deprecated output routines
386+to_hex = bin_output(GEOSFunc('GEOSGeomToHEX_buf'))
387+to_wkb = bin_output(GEOSFunc('GEOSGeomToWKB_buf'))
388+to_wkt = string_from_geom(GEOSFunc('GEOSGeomToWKT'))
389 
390-# The GEOS geometry type, typeid, num_coordinates and number of geometries
391-geos_normalize = int_from_geom(lgeos.GEOSNormalize)
392-geos_type = string_from_geom(lgeos.GEOSGeomType)
393-geos_typeid = int_from_geom(lgeos.GEOSGeomTypeId)
394-get_dims = int_from_geom(lgeos.GEOSGeom_getDimensions, zero=True)
395-get_num_coords = int_from_geom(lgeos.GEOSGetNumCoordinates)
396-get_num_geoms = int_from_geom(lgeos.GEOSGetNumGeometries)
397+# The GEOS geometry type, typeid, num_coordites and number of geometries
398+geos_normalize = int_from_geom(GEOSFunc('GEOSNormalize'))
399+geos_type = string_from_geom(GEOSFunc('GEOSGeomType'))
400+geos_typeid = int_from_geom(GEOSFunc('GEOSGeomTypeId'))
401+get_dims = int_from_geom(GEOSFunc('GEOSGeom_getDimensions'), zero=True)
402+get_num_coords = int_from_geom(GEOSFunc('GEOSGetNumCoordinates'))
403+get_num_geoms = int_from_geom(GEOSFunc('GEOSGetNumGeometries'))
404 
405 # Geometry creation factories
406-create_point = geom_output(lgeos.GEOSGeom_createPoint, [CS_PTR])
407-create_linestring = geom_output(lgeos.GEOSGeom_createLineString, [CS_PTR])
408-create_linearring = geom_output(lgeos.GEOSGeom_createLinearRing, [CS_PTR])
409+create_point = geom_output(GEOSFunc('GEOSGeom_createPoint'), [CS_PTR])
410+create_linestring = geom_output(GEOSFunc('GEOSGeom_createLineString'), [CS_PTR])
411+create_linearring = geom_output(GEOSFunc('GEOSGeom_createLinearRing'), [CS_PTR])
412 
413 # Polygon and collection creation routines are special and will not
414 # have their argument types defined.
415-create_polygon = geom_output(lgeos.GEOSGeom_createPolygon, None)
416-create_collection = geom_output(lgeos.GEOSGeom_createCollection, None)
417+create_polygon = geom_output(GEOSFunc('GEOSGeom_createPolygon'), None)
418+create_collection = geom_output(GEOSFunc('GEOSGeom_createCollection'), None)
419 
420 # Ring routines
421-get_extring = geom_output(lgeos.GEOSGetExteriorRing, [GEOM_PTR])
422-get_intring = geom_index(lgeos.GEOSGetInteriorRingN)
423-get_nrings = int_from_geom(lgeos.GEOSGetNumInteriorRings)
424+get_extring = geom_output(GEOSFunc('GEOSGetExteriorRing'), [GEOM_PTR])
425+get_intring = geom_index(GEOSFunc('GEOSGetInteriorRingN'))
426+get_nrings = int_from_geom(GEOSFunc('GEOSGetNumInteriorRings'))
427 
428 # Collection Routines
429-get_geomn = geom_index(lgeos.GEOSGetGeometryN)
430+get_geomn = geom_index(GEOSFunc('GEOSGetGeometryN'))
431 
432 # Cloning
433-geom_clone = lgeos.GEOSGeom_clone
434+geom_clone = GEOSFunc('GEOSGeom_clone')
435 geom_clone.argtypes = [GEOM_PTR]
436 geom_clone.restype = GEOM_PTR
437 
438 # Destruction routine.
439-destroy_geom = lgeos.GEOSGeom_destroy
440+destroy_geom = GEOSFunc('GEOSGeom_destroy')
441 destroy_geom.argtypes = [GEOM_PTR]
442 destroy_geom.restype = None
443 
444 # SRID routines
445-geos_get_srid = lgeos.GEOSGetSRID
446+geos_get_srid = GEOSFunc('GEOSGetSRID')
447 geos_get_srid.argtypes = [GEOM_PTR]
448 geos_get_srid.restype = c_int
449 
450-geos_set_srid = lgeos.GEOSSetSRID
451+geos_set_srid = GEOSFunc('GEOSSetSRID')
452 geos_set_srid.argtypes = [GEOM_PTR, c_int]
453 geos_set_srid.restype = None
454Index: django/contrib/gis/geos/prototypes/io.py
455===================================================================
456--- django/contrib/gis/geos/prototypes/io.py    (revision 12205)
457+++ django/contrib/gis/geos/prototypes/io.py    (working copy)
458@@ -1,7 +1,10 @@
459-from ctypes import c_char_p, c_int, c_char, c_size_t, Structure, POINTER
460-from django.contrib.gis.geos.libgeos import lgeos, GEOM_PTR
461+import threading
462+from ctypes import byref, c_char_p, c_int, c_char, c_size_t, Structure, POINTER
463+from django.contrib.gis.geos.base import GEOSBase
464+from django.contrib.gis.geos.libgeos import GEOM_PTR
465 from django.contrib.gis.geos.prototypes.errcheck import check_geom, check_string, check_sized_string
466 from django.contrib.gis.geos.prototypes.geom import c_uchar_p, geos_char_p
467+from django.contrib.gis.geos.prototypes.threadsafe import GEOSFunc
468 
469 ### The WKB/WKT Reader/Writer structures and pointers ###
470 class WKTReader_st(Structure): pass
471@@ -15,34 +18,34 @@
472 WKB_WRITE_PTR = POINTER(WKBReader_st)
473 
474 ### WKTReader routines ###
475-wkt_reader_create = lgeos.GEOSWKTReader_create
476+wkt_reader_create = GEOSFunc('GEOSWKTReader_create')
477 wkt_reader_create.restype = WKT_READ_PTR
478 
479-wkt_reader_destroy = lgeos.GEOSWKTReader_destroy
480+wkt_reader_destroy = GEOSFunc('GEOSWKTReader_destroy')
481 wkt_reader_destroy.argtypes = [WKT_READ_PTR]
482 
483-wkt_reader_read = lgeos.GEOSWKTReader_read
484+wkt_reader_read = GEOSFunc('GEOSWKTReader_read')
485 wkt_reader_read.argtypes = [WKT_READ_PTR, c_char_p]
486 wkt_reader_read.restype = GEOM_PTR
487 wkt_reader_read.errcheck = check_geom
488 
489 ### WKTWriter routines ###
490-wkt_writer_create = lgeos.GEOSWKTWriter_create
491+wkt_writer_create = GEOSFunc('GEOSWKTWriter_create')
492 wkt_writer_create.restype = WKT_WRITE_PTR
493 
494-wkt_writer_destroy = lgeos.GEOSWKTWriter_destroy
495+wkt_writer_destroy = GEOSFunc('GEOSWKTWriter_destroy')
496 wkt_writer_destroy.argtypes = [WKT_WRITE_PTR]
497 
498-wkt_writer_write = lgeos.GEOSWKTWriter_write
499+wkt_writer_write = GEOSFunc('GEOSWKTWriter_write')
500 wkt_writer_write.argtypes = [WKT_WRITE_PTR, GEOM_PTR]
501 wkt_writer_write.restype = geos_char_p
502 wkt_writer_write.errcheck = check_string
503 
504 ### WKBReader routines ###
505-wkb_reader_create = lgeos.GEOSWKBReader_create
506+wkb_reader_create = GEOSFunc('GEOSWKBReader_create')
507 wkb_reader_create.restype = WKB_READ_PTR
508 
509-wkb_reader_destroy = lgeos.GEOSWKBReader_destroy
510+wkb_reader_destroy = GEOSFunc('GEOSWKBReader_destroy')
511 wkb_reader_destroy.argtypes = [WKB_READ_PTR]
512 
513 def wkb_read_func(func):
514@@ -56,14 +59,14 @@
515     func.errcheck = check_geom
516     return func
517 
518-wkb_reader_read = wkb_read_func(lgeos.GEOSWKBReader_read)
519-wkb_reader_read_hex = wkb_read_func(lgeos.GEOSWKBReader_readHEX)
520+wkb_reader_read = wkb_read_func(GEOSFunc('GEOSWKBReader_read'))
521+wkb_reader_read_hex = wkb_read_func(GEOSFunc('GEOSWKBReader_readHEX'))
522 
523 ### WKBWriter routines ###
524-wkb_writer_create = lgeos.GEOSWKBWriter_create
525+wkb_writer_create = GEOSFunc('GEOSWKBWriter_create')
526 wkb_writer_create.restype = WKB_WRITE_PTR
527 
528-wkb_writer_destroy = lgeos.GEOSWKBWriter_destroy
529+wkb_writer_destroy = GEOSFunc('GEOSWKBWriter_destroy')
530 wkb_writer_destroy.argtypes = [WKB_WRITE_PTR]
531 
532 # WKB Writing prototypes.
533@@ -73,8 +76,8 @@
534     func.errcheck = check_sized_string
535     return func
536 
537-wkb_writer_write = wkb_write_func(lgeos.GEOSWKBWriter_write)
538-wkb_writer_write_hex = wkb_write_func(lgeos.GEOSWKBWriter_writeHEX)
539+wkb_writer_write = wkb_write_func(GEOSFunc('GEOSWKBWriter_write'))
540+wkb_writer_write_hex = wkb_write_func(GEOSFunc('GEOSWKBWriter_writeHEX'))
541 
542 # WKBWriter property getter/setter prototypes.
543 def wkb_writer_get(func, restype=c_int):
544@@ -86,9 +89,124 @@
545     func.argtypes = [WKB_WRITE_PTR, argtype]
546     return func
547 
548-wkb_writer_get_byteorder = wkb_writer_get(lgeos.GEOSWKBWriter_getByteOrder)
549-wkb_writer_set_byteorder = wkb_writer_set(lgeos.GEOSWKBWriter_setByteOrder)
550-wkb_writer_get_outdim    = wkb_writer_get(lgeos.GEOSWKBWriter_getOutputDimension)
551-wkb_writer_set_outdim    = wkb_writer_set(lgeos.GEOSWKBWriter_setOutputDimension)
552-wkb_writer_get_include_srid = wkb_writer_get(lgeos.GEOSWKBWriter_getIncludeSRID, restype=c_char)
553-wkb_writer_set_include_srid = wkb_writer_set(lgeos.GEOSWKBWriter_setIncludeSRID, argtype=c_char)
554+wkb_writer_get_byteorder = wkb_writer_get(GEOSFunc('GEOSWKBWriter_getByteOrder'))
555+wkb_writer_set_byteorder = wkb_writer_set(GEOSFunc('GEOSWKBWriter_setByteOrder'))
556+wkb_writer_get_outdim    = wkb_writer_get(GEOSFunc('GEOSWKBWriter_getOutputDimension'))
557+wkb_writer_set_outdim    = wkb_writer_set(GEOSFunc('GEOSWKBWriter_setOutputDimension'))
558+wkb_writer_get_include_srid = wkb_writer_get(GEOSFunc('GEOSWKBWriter_getIncludeSRID'), restype=c_char)
559+wkb_writer_set_include_srid = wkb_writer_set(GEOSFunc('GEOSWKBWriter_setIncludeSRID'), argtype=c_char)
560+
561+### Base I/O Class ###
562+class IOBase(GEOSBase):
563+    "Base class for GEOS I/O objects."
564+    def __init__(self):
565+        # Getting the pointer with the constructor.
566+        self.ptr = self._constructor()
567+
568+    def __del__(self):
569+        # Cleaning up with the appropriate destructor.
570+        if self._ptr: self._destructor(self._ptr)
571+
572+### WKB/WKT Reading and Writing objects ###
573+
574+# Non-public WKB/WKT reader classes for internal use because
575+# their `read` methods return _pointers_ instead of GEOSGeometry
576+# objects.
577+class _WKTReader(IOBase):
578+    _constructor = wkt_reader_create
579+    _destructor = wkt_reader_destroy
580+    ptr_type = WKT_READ_PTR
581+
582+    def read(self, wkt):
583+        if not isinstance(wkt, basestring): raise TypeError
584+        return wkt_reader_read(self.ptr, wkt)
585+
586+class _WKBReader(IOBase):
587+    _constructor = wkb_reader_create
588+    _destructor = wkb_reader_destroy
589+    ptr_type = WKB_READ_PTR
590+
591+    def read(self, wkb):
592+        "Returns a _pointer_ to C GEOS Geometry object from the given WKB."
593+        if isinstance(wkb, buffer):
594+            wkb_s = str(wkb)
595+            return wkb_reader_read(self.ptr, wkb_s, len(wkb_s))
596+        elif isinstance(wkb, basestring):
597+            return wkb_reader_read_hex(self.ptr, wkb, len(wkb))
598+        else:
599+            raise TypeError
600+
601+### WKB/WKT Writer Classes ###
602+class WKTWriter(IOBase):
603+    _constructor = wkt_writer_create
604+    _destructor = wkt_writer_destroy
605+    ptr_type = WKT_WRITE_PTR
606+
607+    def write(self, geom):
608+        "Returns the WKT representation of the given geometry."
609+        return wkt_writer_write(self.ptr, geom.ptr)
610+
611+class WKBWriter(IOBase):
612+    _constructor = wkb_writer_create
613+    _destructor = wkb_writer_destroy
614+    ptr_type = WKB_WRITE_PTR
615+
616+    def write(self, geom):
617+        "Returns the WKB representation of the given geometry."
618+        return buffer(wkb_writer_write(self.ptr, geom.ptr, byref(c_size_t())))
619+
620+    def write_hex(self, geom):
621+        "Returns the HEXEWKB representation of the given geometry."
622+        return wkb_writer_write_hex(self.ptr, geom.ptr, byref(c_size_t()))
623+
624+    ### WKBWriter Properties ###
625+
626+    # Property for getting/setting the byteorder.
627+    def _get_byteorder(self):
628+        return wkb_writer_get_byteorder(self.ptr)
629+
630+    def _set_byteorder(self, order):
631+        if not order in (0, 1): raise ValueError('Byte order parameter must be 0 (Big Endian) or 1 (Little Endian).')
632+        wkb_writer_set_byteorder(self.ptr, order)
633+
634+    byteorder = property(_get_byteorder, _set_byteorder)
635+
636+    # Property for getting/setting the output dimension.
637+    def _get_outdim(self):
638+        return wkb_writer_get_outdim(self.ptr)
639+
640+    def _set_outdim(self, new_dim):
641+        if not new_dim in (2, 3): raise ValueError('WKB output dimension must be 2 or 3')
642+        wkb_writer_set_outdim(self.ptr, new_dim)
643+
644+    outdim = property(_get_outdim, _set_outdim)
645+
646+    # Property for getting/setting the include srid flag.
647+    def _get_include_srid(self):
648+        return bool(ord(wkb_writer_get_include_srid(self.ptr)))
649+
650+    def _set_include_srid(self, include):
651+        if bool(include): flag = chr(1)
652+        else: flag = chr(0)
653+        wkb_writer_set_include_srid(self.ptr, flag)
654+
655+    srid = property(_get_include_srid, _set_include_srid)
656+
657+# Thread-local instances of the WKT and WKB reader/writer objects.
658+thread_context = threading.local()
659+thread_context.wkt_r = _WKTReader()
660+thread_context.wkt_w = WKTWriter()
661+thread_context.wkb_r = _WKBReader()
662+thread_context.wkb_w = WKBWriter()
663+thread_context.ewkb_w = WKBWriter()
664+thread_context.ewkb_w.srid = True
665+thread_context.ewkb_w3d = WKBWriter()
666+thread_context.ewkb_w3d.srid = True
667+thread_context.ewkb_w3d.outdim = 3
668+
669+wkt_r = thread_context.wkt_r
670+wkt_w = thread_context.wkt_w
671+wkb_r = thread_context.wkb_r
672+wkb_w = thread_context.wkb_w
673+ewkb_w = thread_context.ewkb_w
674+ewkb_w3d = thread_context.ewkb_w3d
675Index: django/contrib/gis/geos/io.py
676===================================================================
677--- django/contrib/gis/geos/io.py       (revision 12205)
678+++ django/contrib/gis/geos/io.py       (working copy)
679@@ -3,128 +3,18 @@
680 objects.  Specifically, this has Python implementations of WKB/WKT
681 reader and writer classes.
682 """
683-from ctypes import byref, c_size_t
684-from django.contrib.gis.geos.base import GEOSBase
685-from django.contrib.gis.geos.error import GEOSException
686 from django.contrib.gis.geos.geometry import GEOSGeometry
687-from django.contrib.gis.geos.libgeos import GEOM_PTR
688-from django.contrib.gis.geos.prototypes import io as capi
689+from django.contrib.gis.geos.prototypes.io import _WKTReader, _WKBReader, WKBWriter, WKTWriter
690 
691-class IOBase(GEOSBase):
692-    "Base class for GEOS I/O objects."
693-    def __init__(self):
694-        # Getting the pointer with the constructor.
695-        self.ptr = self._constructor()
696+# Public classes for (WKB|WKT)Reader, which return GEOSGeometry
697+class WKBReader(_WKBReader):
698+    def read(self, wkb):
699+        "Returns a GEOSGeometry for the given WKB buffer."
700+        return GEOSGeometry(super(WKBReader, self).read(wkb))
701 
702-    def __del__(self):
703-        # Cleaning up with the appropriate destructor.
704-        if self._ptr: self._destructor(self._ptr)
705-
706-### WKT Reading and Writing objects ###
707-
708-# Non-public class for internal use because its `read` method returns
709-# _pointers_ instead of a GEOSGeometry object.
710-class _WKTReader(IOBase):
711-    _constructor = capi.wkt_reader_create
712-    _destructor = capi.wkt_reader_destroy
713-    ptr_type = capi.WKT_READ_PTR
714-
715-    def read(self, wkt):
716-        if not isinstance(wkt, basestring): raise TypeError
717-        return capi.wkt_reader_read(self.ptr, wkt)
718-
719 class WKTReader(_WKTReader):
720     def read(self, wkt):
721         "Returns a GEOSGeometry for the given WKT string."
722         return GEOSGeometry(super(WKTReader, self).read(wkt))
723 
724-class WKTWriter(IOBase):
725-    _constructor = capi.wkt_writer_create
726-    _destructor = capi.wkt_writer_destroy
727-    ptr_type = capi.WKT_WRITE_PTR
728 
729-    def write(self, geom):
730-        "Returns the WKT representation of the given geometry."
731-        return capi.wkt_writer_write(self.ptr, geom.ptr)
732-
733-### WKB Reading and Writing objects ###
734-
735-# Non-public class for the same reason as _WKTReader above.
736-class _WKBReader(IOBase):
737-    _constructor = capi.wkb_reader_create
738-    _destructor = capi.wkb_reader_destroy
739-    ptr_type = capi.WKB_READ_PTR
740-
741-    def read(self, wkb):
742-        "Returns a _pointer_ to C GEOS Geometry object from the given WKB."
743-        if isinstance(wkb, buffer):
744-            wkb_s = str(wkb)
745-            return capi.wkb_reader_read(self.ptr, wkb_s, len(wkb_s))
746-        elif isinstance(wkb, basestring):
747-            return capi.wkb_reader_read_hex(self.ptr, wkb, len(wkb))
748-        else:
749-            raise TypeError
750-
751-class WKBReader(_WKBReader):
752-    def read(self, wkb):
753-        "Returns a GEOSGeometry for the given WKB buffer."
754-        return GEOSGeometry(super(WKBReader, self).read(wkb))
755-
756-class WKBWriter(IOBase):
757-    _constructor = capi.wkb_writer_create
758-    _destructor = capi.wkb_writer_destroy
759-    ptr_type = capi.WKB_WRITE_PTR
760-
761-    def write(self, geom):
762-        "Returns the WKB representation of the given geometry."
763-        return buffer(capi.wkb_writer_write(self.ptr, geom.ptr, byref(c_size_t())))
764-
765-    def write_hex(self, geom):
766-        "Returns the HEXEWKB representation of the given geometry."
767-        return capi.wkb_writer_write_hex(self.ptr, geom.ptr, byref(c_size_t()))
768-
769-    ### WKBWriter Properties ###
770-
771-    # Property for getting/setting the byteorder.
772-    def _get_byteorder(self):
773-        return capi.wkb_writer_get_byteorder(self.ptr)
774-
775-    def _set_byteorder(self, order):
776-        if not order in (0, 1): raise ValueError('Byte order parameter must be 0 (Big Endian) or 1 (Little Endian).')
777-        capi.wkb_writer_set_byteorder(self.ptr, order)
778-
779-    byteorder = property(_get_byteorder, _set_byteorder)
780-
781-    # Property for getting/setting the output dimension.
782-    def _get_outdim(self):
783-        return capi.wkb_writer_get_outdim(self.ptr)
784-
785-    def _set_outdim(self, new_dim):
786-        if not new_dim in (2, 3): raise ValueError('WKB output dimension must be 2 or 3')
787-        capi.wkb_writer_set_outdim(self.ptr, new_dim)
788-
789-    outdim = property(_get_outdim, _set_outdim)
790-
791-    # Property for getting/setting the include srid flag.
792-    def _get_include_srid(self):
793-        return bool(ord(capi.wkb_writer_get_include_srid(self.ptr)))
794-
795-    def _set_include_srid(self, include):
796-        if bool(include): flag = chr(1)
797-        else: flag = chr(0)
798-        capi.wkb_writer_set_include_srid(self.ptr, flag)
799-
800-    srid = property(_get_include_srid, _set_include_srid)
801-
802-# Instances of the WKT and WKB reader/writer objects.
803-wkt_r = _WKTReader()
804-wkt_w = WKTWriter()
805-wkb_r = _WKBReader()
806-wkb_w = WKBWriter()
807-
808-# These instances are for writing EWKB in 2D and 3D.
809-ewkb_w = WKBWriter()
810-ewkb_w.srid = True
811-ewkb_w3d = WKBWriter()
812-ewkb_w3d.srid = True
813-ewkb_w3d.outdim = 3
814Index: django/contrib/gis/geos/libgeos.py
815===================================================================
816--- django/contrib/gis/geos/libgeos.py  (revision 12205)
817+++ django/contrib/gis/geos/libgeos.py  (working copy)
818@@ -45,14 +45,14 @@
819                         '", "'.join(lib_names))
820 
821 # Getting the GEOS C library.  The C interface (CDLL) is used for
822-#  both *NIX and Windows.
823+# both *NIX and Windows.
824 # See the GEOS C API source code for more details on the library function calls:
825 #  http://geos.refractions.net/ro/doxygen_docs/html/geos__c_8h-source.html
826 lgeos = CDLL(lib_path)
827 
828 # The notice and error handler C function callback definitions.
829-#  Supposed to mimic the GEOS message handler (C below):
830-#  "typedef void (*GEOSMessageHandler)(const char *fmt, ...);"
831+# Supposed to mimic the GEOS message handler (C below):
832+#  typedef void (*GEOSMessageHandler)(const char *fmt, ...);
833 NOTICEFUNC = CFUNCTYPE(None, c_char_p, c_char_p)
834 def notice_h(fmt, lst, output_h=sys.stdout):
835     try:
836@@ -71,23 +71,19 @@
837     output_h.write('GEOS_ERROR: %s\n' % err_msg)
838 error_h = ERRORFUNC(error_h)
839 
840-# The initGEOS routine should be called first, however, that routine takes
841-#  the notice and error functions as parameters.  Here is the C code that
842-#  is wrapped:
843-#  "extern void GEOS_DLL initGEOS(GEOSMessageHandler notice_function, GEOSMessageHandler error_function);"
844-lgeos.initGEOS(notice_h, error_h)
845-
846 #### GEOS Geometry C data structures, and utility functions. ####
847 
848 # Opaque GEOS geometry structures, used for GEOM_PTR and CS_PTR
849 class GEOSGeom_t(Structure): pass
850 class GEOSPrepGeom_t(Structure): pass
851 class GEOSCoordSeq_t(Structure): pass
852+class GEOSContextHandle_t(Structure): pass
853 
854 # Pointers to opaque GEOS geometry structures.
855 GEOM_PTR = POINTER(GEOSGeom_t)
856 PREPGEOM_PTR = POINTER(GEOSPrepGeom_t)
857 CS_PTR = POINTER(GEOSCoordSeq_t)
858+CONTEXT_PTR  = POINTER(GEOSContextHandle_t)
859 
860 # Used specifically by the GEOSGeom_createPolygon and GEOSGeom_createCollection
861 #  GEOS routines
862@@ -126,5 +122,18 @@
863 GEOS_VERSION = (GEOS_MAJOR_VERSION, GEOS_MINOR_VERSION, GEOS_SUBMINOR_VERSION)
864 GEOS_PREPARE = GEOS_VERSION >= (3, 1, 0)
865 
866-# Calling the finishGEOS() upon exit of the interpreter.
867-atexit.register(lgeos.finishGEOS)
868+if GEOS_PREPARE:
869+    # Here we set up the prototypes for the initGEOS_r and finishGEOS_r
870+    # routines.  These functions aren't actually called until they are
871+    # attached to a GEOS context handle -- this actually occurs in
872+    # geos/prototypes/threadsafe.py.
873+    lgeos.initGEOS_r.restype = CONTEXT_PTR
874+    lgeos.finishGEOS_r.argtypes = [CONTEXT_PTR]
875+else:
876+    # When thread-safety isn't available, the initGEOS routine must be called
877+    # first.  This function takes the notice and error functions, defined
878+    # as Python callbacks above, as parameters. Here is the C code that is
879+    # wrapped:
880+    #  extern void GEOS_DLL initGEOS(GEOSMessageHandler notice_function, GEOSMessageHandler error_function);
881+    lgeos.initGEOS(notice_h, error_h)
882+    atexit.register(lgeos.finishGEOS)