Contexts and functions taking multiple geometries

Daniel Baston dbaston at gmail.com
Thu Jan 9 05:40:58 PST 2025


Hi Tom,

With the caveat that I did not develop the "_r" API, here's my
understanding. The implementation of GEOS contexts is quite short and can
be found near the top of geos_ts_c.cpp. From looking at the implementation
we can see that a context has pretty limited functionality:

- registering error and notice handlers
- passing error messages to these handlers using a context-local buffer
- storing properties of a global WKB writer
- storing a single point that is mutated by a small set of functions such
as GEOSPreparedContainsXY that want to avoid instantiating a point on each
call

The "_r" functions are a C API construct only and do not indicate anything
about the thread-safety of the underlying algorithms. The parts of GEOS
that actually _do_ things are implemented in C++ and have no notion of a
"context."

With regard to the situation you describe in your email:

GEOSUnion_r(g.context.handle, g.geom, other.geom)

GEOSUnion_r only accesses a context for the purpose of error reporting, and
the only context it will access is the first argument. It does not matter
whether "g" or "other" were created using this context, or whether "g" and
"other" have the same context. If you were to call GEOSUnion_r
simultaneously from multiple threads using the same first argument, AFAIK
the worst thing that could happen is that simultaneously-emitted error
messages generated would be garbled. But you can prevent that by locking
the context provided in the first argument.

If the context of "g" and "other" is irrelevant, can you use them safely
from multiple threads? Not necessarily. The programming style used in GEOS
lends itself to thread-safety (objects are generally immutable, etc.)
although the use of lazy initialization sometimes gets in the way and the
thread-safety of various operations is not well-documented or tested. The
pattern you describe of locking the contexts of both "g" and "other" will
protect against thread-safety issues but is unnecessarily broad; the same
benefit could be achieved with locks scoped to the geometries themselves.
Even this more limited locking is unnecessary for many operations, but
unfortunately the GEOS docs don't give any guidance about which ones.
Adding more tests and documentation around GEOS thread-safety would be a
big improvement for the library.

Dan

PS

There has been some discussion about giving contexts additional
responsibility, such as managing interrupts:
https://github.com/libgeos/geos/pull/803

You might also find some useful discussion within the georust project, e.g.
https://github.com/georust/geos/pull/164

On Wed, Jan 8, 2025 at 9:36 PM Tom Payne <twpayne at gmail.com> wrote:

> Gentle ping on this. I would really like to understand the requirements
> for combining geometries from different GEOS contexts.
>
> If my question is unclear, missing information, stupid, answered
> elsewhere, or there is any other problem with it then please tell me. I
> would really like to understand this.
>
> Regards,
> Tom
>
> On Fri, 22 Nov 2024 at 10:50, Tom Payne <twpayne at gmail.com> wrote:
>
>> Hi,
>>
>> tl;dr when calling a function which takes multiple geometries, like
>> GEOSUnion_r
>> <https://libgeos.org/doxygen/geos__c_8h.html#afd3d82a6d039ab5637e0a8a066694b7d>, where
>> the two geometries are associated with different contexts, do I have to
>> ensure that both geometries' contexts are used exclusively?
>>
>> Background:
>>
>> I maintain the Go bindings for GEOS <https://github.com/twpayne/go-geos>,
>> which exclusively use the thread-safe *_r functions. Every created geometry
>> is associated with a context. Every context has a mutex to ensure that it
>> is only accessed from a single thread at time.
>>
>> For functions that take multiple geometries I check if the geometries are
>> from different contexts, and if so, lock both mutexes. Here
>> <https://github.com/twpayne/go-geos/blob/c9ed31526fa2ee3599ffe0fdf4556a6cf9c0b204/geommethods.go#L865-L875>
>> is an example:
>>
>>     // Union returns the union of g and other.
>>     func (g *Geom) Union(other *Geom) *Geom {
>>         g.mustNotBeDestroyed()
>>         g.context.Lock()
>>         defer g.context.Unlock()
>>         if other.context != g.context {
>>             other.context.Lock()
>>             defer other.context.Unlock()
>>         }
>>         return g.context.newGeom(C.GEOSUnion_r(g.context.handle, g.geom,
>> other.geom), nil)
>>     }
>>
>> However, there is a potential deadlock if there are two geometries A and
>> B owned by different contexts and A.Union(B) and B.Union(A) are called
>> simultaneously from different threads. In practice this pattern is unlikely
>> to occur, but I would like to guard against it.
>>
>> I checked the documentation on GEOS's C API
>> <https://libgeos.org/usage/c_api/>, the GEOS developer notes
>> <https://github.com/libgeos/geos/blob/main/DEVELOPER-NOTES.md>, did a
>> superficial search of the GitHub issues
>> <https://github.com/search?q=repo%3Alibgeos%2Fgeos+context&type=issues>,
>> and a superficial search of the geos-devel
>> <https://www.google.com/search?q=site%3Alists.osgeo.org+%22%5Bgeos-devel%5D%22+context>
>> archives, and could not find an answer to this question.
>>
>> Many thanks for any insight,
>> Tom
>>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.osgeo.org/pipermail/geos-devel/attachments/20250109/5c9fa730/attachment.htm>


More information about the geos-devel mailing list