shapely: GEOS aborting with "Assertion failed" on OS X, discussion and work around

I’ve spent a bit of free time looking more deeply into the reports of GEOS aborting in the AbstractSTRtree.cpp module (either in query() or itemsTree()) on OS X when fiona is imported before shapely in a Python script.

Here are the conditions for a program that will unexpectedly abort:

  1. Any version of Python on OS X.
  2. Fiona and Shapely binary wheels for macosx downloaded from PyPI. These wheels include libgeos_c.dylib and libgeos-3.6.2.dylib files.
  3. A script that imports fiona before shapely and which triggers the creation of a GEOS STR R-tree. Calling ops.unary_union with a number of lines or polygons will suffice.

Here’s an example program:

import fiona

from shapely.geometry import Point
from shapely.geos import geos_version
from shapely.ops import unary_union

print(geos_version)

SHAPES = [Point(i, i).buffer(1.5) for i in range(20)]
for i, s in enumerate(SHAPES):
    print(i, s.is_valid)
    print(i, s.wkt)
    print(i, s.area, s.length)
    print(i, s.intersects(Point(10, 10).buffer(8.0)))

union = unary_union(SHAPES)
print(union)

I’ve run the script under lldb with a modified version of GEOS 3.6.2 that prints some debugging output before calling assert().

...
19 7.057234103728362 9.420993470864257
19 False
Assertion failed: (!static_cast<bool>("should never be reached")), function itemsTree, file AbstractSTRtree.cpp, line 379.
dynamic_cast<AbstractNode*>: 0x0dynamic_cast<ItemBoundable*>: 0x0childBoundable: 0x100498510Process 17968 stopped
* thread #1: tid = 0x606ba9, 0x00007fff8d415f06 libsystem_kernel.dylib`__pthread_kill + 10, queue = 'com.apple.main-thread', stop reason = signal SIGABRT
    frame #0: 0x00007fff8d415f06 libsystem_kernel.dylib`__pthread_kill + 10
libsystem_kernel.dylib`__pthread_kill:
->  0x7fff8d415f06 <+10>: jae    0x7fff8d415f10            ; <+20>
    0x7fff8d415f08 <+12>: movq   %rax, %rdi
    0x7fff8d415f0b <+15>: jmp    0x7fff8d4107cd            ; cerror_nocancel
    0x7fff8d415f10 <+20>: retq

I’ve failed to get access to variables in the frame where assert() is called. I am frankly over my head in trying to debug a dlopen’ed C++ library.

Changing the intersects() call in the script to intersection() leads to another failed assertion and an aborted program the first time there is a non-empty intersection.

...
4 7.057234103728363 9.42099347086426
Assertion failed: (0), function query, file AbstractSTRtree.cpp, line 288.
Process 18002 stopped
* thread #1: tid = 0x6074fe, 0x00007fff8d415f06 libsystem_kernel.dylib`__pthread_kill + 10, queue = 'com.apple.main-thread', stop reason = signal SIGABRT
    frame #0: 0x00007fff8d415f06 libsystem_kernel.dylib`__pthread_kill + 10
libsystem_kernel.dylib`__pthread_kill:
->  0x7fff8d415f06 <+10>: jae    0x7fff8d415f10            ; <+20>
    0x7fff8d415f08 <+12>: movq   %rax, %rdi
    0x7fff8d415f0b <+15>: jmp    0x7fff8d4107cd            ; cerror_nocancel
    0x7fff8d415f10 <+20>: retq

The problem in query() appears to be the same as in itemsTree(): the dynamic casts return null pointers and the library fails by calling assert(0).

https://github.com/OSGeo/geos/blob/master/src/index/strtree/AbstractSTRtree.cpp#L277-L287

If fiona is imported after shapely, the programs (either with intersects() or intersection()) run without problems.

My hypothesis is that there is a race condition in initialization of the libgeos-3.6.2 library that leaves some objects in the AbstractSTRtree.cpp module in a partly or improperly initialized state when we dlopen the GEOS lib after it has already been loaded in the process via import of fiona. Use of GEOS as both a dependent library of fiona modules and as a library we dynamically load during import of shapely is a little weird, I grant, but doesn’t necessarily have to trigger crashing bugs. There’s no problem on Linux. I see some hints that the difference between ELF and Mach-O might be important.

Workaround 1

Install the GEOS library to one standard location on your macosx system using homebrew, fink, macports, or from source, do the same for GDAL, and then install fiona and shapely like so:

pip install --no-binary shapely,fiona shapely fiona

You can import fiona and shapely in any order if you do this.

Workaround 2

On OS X, if you’re using fiona and shapely wheels together, import shapely first. It’s annoying to have to do this, I know. Abstracting away the C/C++ details of GEOS and GDAL has been one of my main design principles for fiona and shapely, and I’m falling short.

Solution

I think there’s a race condition bug to be fixed in the GEOS library. You can see some indication of that at https://lists.osgeo.org/pipermail/geos-devel/2017-November/008157.html. Because this doesn’t affect Linux and PostGIS, it’s a very low-flying bug.

Fixing it will require more knowledge and experience with C++ and dynamic library building and loading on Mac OS X than I currently have, and so the fix isn’t going to be coming soon unless we can get some help.

I’m somewhat content to at least have a master ticket that I can point users at. It’s a start.

/cc @snorfalorpagus @perrygeo

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 17
  • Comments: 15 (5 by maintainers)

Commits related to this issue

Most upvoted comments

Update: in my experience, shapely 1.7a2 (released yesterday) solves this on OS X. The example program runs with no errors and no crashes. If you’d like to confirm, try pip install -U --pre shapely and let me know how it goes.

Just to add to this thread, I’m seeing the same behavior in a virtualenv that meets the above descriptions.

Re-installing from source for Fiona and Shapely did not fix the problem.

I did some digging in my virtualenv and found the geos binary installed by rasterio:

.../lib/python3.5/site-packages/rasterio/.dylibs/libgeos-3.6.2.dylib
.../lib/python3.5/site-packages/rasterio/.dylibs/libgeos_c.1.dylib

This appears to have fixed it:

pip uninstall -y rasterio && pip install rasterio==1.0.11 --no-binary rasterio

On OS X, if you’re using fiona and shapely wheels together, import shapely first.

Noting that import shapely before Fiona is not enough - you need to import any one of the submodules that loads the GEOS dll (import shapely.geometry for instance)

@jcusick13 I’m closing this issue because it has been resolved. The Shell is not a LinearRing message has been reported a few times: https://github.com/Toblerity/Shapely/search?q=“shell+is+not+a+linearring”&type=Issues. It’s a symptom of a shared library incompatibility. Using conda is a good choice because, if your environment is in good shape, you only have one GEOS shared library between all of the geopandas dependencies.

Upgrading to shapely 1.7a2 solved the GEOS assertion error for me, but then I quickly ran into issues when reading polygon data from a file geodatabase. Any polygon files would be read in without geometry and I’d get a Shell is not a LinearRing error when attempting any operation on them.

I read up on solutions for the second issue and they all seemed to point to making sure that conda is being used correctly. I have everything installed into an independent environment using only pip and haven’t ran into problems like this before.

Is there a stable pathway for installing shapely, fiona, and geopandas through pip or do we have to move to using conda for installs only?

My current environment file (before upgrading to shapely 1.7a2) is below

requirements.txt

affine==2.2.1
appnope==0.1.0
area==1.1.0
asn1crypto==0.24.0
astroid==2.2.5
attrs==18.2.0
azure-common==1.1.16
azure-nspkg==2.0.0
azure-storage==0.36.0
backcall==0.1.0
bleach==3.0.2
boto3==1.7.4
botocore==1.10.84
certifi==2018.11.29
cffi==1.11.5
chardet==3.0.4
Click==7.0
click-plugins==1.0.4
cligj==0.5.0
cryptography==2.4.2
cycler==0.10.0
Cython==0.29.5
decorator==4.3.0
defusedxml==0.5.0
descartes==1.1.0
docutils==0.14
entrypoints==0.3
Fiona==1.8.3
flake8==3.7.5
future==0.17.1
geojson==2.3.0
geopandas==0.4.0
greenlet==0.4.15
idna==2.7
ijson==2.3
ipykernel==5.1.0
ipython==7.2.0
ipython-genutils==0.2.0
ipywidgets==7.4.2
isort==4.3.21
jedi==0.13.1
Jinja2==2.10
jmespath==0.9.3
joblib==0.13.1
jsonschema==2.6.0
jupyter==1.0.0
jupyter-client==5.2.3
jupyter-console==6.0.0
jupyter-core==4.4.0
jupyterlab==0.35.4
jupyterlab-server==0.2.0
keyring==17.0.0
kiwisolver==1.0.1
lazy-object-proxy==1.4.2
MarkupSafe==1.1.0
matplotlib==3.0.2
mccabe==0.6.1
mercantile==1.0.4
mgrs==1.3.5
mistune==0.8.4
msgpack==0.6.1
munch==2.3.2
nbconvert==5.4.0
nbformat==4.4.0
neovim==0.3.1
notebook==5.7.2
numpy==1.15.4
pandas==0.23.4
pandocfilters==1.4.2
parso==0.3.1
pdfkit==0.6.1
PDPbox==0.2.0
pexpect==4.6.0
pickleshare==0.7.5
Pillow==5.4.1
plotly==3.4.2
prometheus-client==0.4.2
prompt-toolkit==2.0.7
psutil==5.5.0
ptyprocess==0.6.0
pyarrow==0.8.0
pyasn1==0.4.4
pyasn1-modules==0.2.2
pycodestyle==2.5.0
pycparser==2.19
pycryptodomex==3.7.2
pyflakes==2.1.0
pyGeoTile==1.0.6
Pygments==2.3.0
PyJWT==1.7.0
pylint==2.3.1
pynvim==0.3.2
pyOpenSSL==18.0.0
pyparsing==2.3.0
pyproj==1.9.5.1
pyshp==2.0.1
python-dateutil==2.7.5
python-dotenv==0.10.1
pytz==2018.4
PyYAML==3.12
pyzmq==17.1.2
qtconsole==4.4.3
rasterio==1.0.12
requests==2.20.1
retrying==1.3.3
Rtree==0.8.3
s3transfer==0.1.13
scikit-learn==0.20.2
scipy==1.1.0
seaborn==0.9.0
Send2Trash==1.5.0
sentinelhub==2.5.0
Shapely==1.6.2.post1
six==1.11.0
sklearn==0.0
snowflake-connector-python==1.6.10
snuggs==1.4.2
SQLAlchemy==1.2.15
supermercado==0.0.5
terminado==0.8.1
testpath==0.4.2
tifffile==2019.1.30
tornado==5.1.1
traitlets==4.3.2
typed-ast==1.4.0
urllib3==1.24.1
utm==0.4.2
wcwidth==0.1.7
webencodings==0.5.1
widgetsnbextension==3.4.2
wrapt==1.11.2
xmltodict==0.11.0

shapely 1.7a2 solved this issue for me. I had to reinstall fiona as well. Now I can run the initial example regardless of the import order (fiona vs shapely). Thank you @sgillies