Skip to content

Commit 516e799

Browse files
authored
Fix a bug when passing data to GMT in Session.open_virtual_file() (#490)
External programs like PyGMT can pass dataset/momory to GMT. By default, GMT can read, modify and free the momery, which sometimes can cause crashes. Issue #406 reports an example in which PyGMT crashes. The issue was reported to the upstream (see GenericMappingTools/gmt#3515 and GenericMappingTools/gmt#3528). It turns out to be a API user error (i.e., a PyGMT bug). As per the explanation of Paul, external programs like PyGMT should always use `GMT_IN|GMT_IS_REFERENCE` to tell GMT that the data is read-only, so that GMT won't try to change and free the memory. This PR makes the change from `GMT_IN` to `GMT_IN|GMT_IS_REFERENCE` in the `Session.open_virtual_file()` function, updates a few docstrings, and also adds the script in #406 as a test.
1 parent eaf81bb commit 516e799

File tree

4 files changed

+34
-5
lines changed

4 files changed

+34
-5
lines changed

pygmt/clib/session.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -922,6 +922,9 @@ def open_virtual_file(self, family, geometry, direction, data):
922922
direction : str
923923
Either ``'GMT_IN'`` or ``'GMT_OUT'`` to indicate if passing data to
924924
GMT or getting it out of GMT, respectively.
925+
By default, GMT can modify the data you pass in. Add modifier
926+
``'GMT_IS_REFERENCE'`` to tell GMT the data are read-only, or
927+
``'GMT_IS_DUPLICATE'' to tell GMT to duplicate the data.
925928
data : int
926929
The ctypes void pointer to your GMT data structure.
927930
@@ -950,7 +953,7 @@ def open_virtual_file(self, family, geometry, direction, data):
950953
... lib.put_vector(dataset, column=0, vector=x)
951954
... lib.put_vector(dataset, column=1, vector=y)
952955
... # Add the dataset to a virtual file
953-
... vfargs = (family, geometry, 'GMT_IN', dataset)
956+
... vfargs = (family, geometry, 'GMT_IN|GMT_IS_REFERENCE', dataset)
954957
... with lib.open_virtual_file(*vfargs) as vfile:
955958
... # Send the output to a temp file so that we can read it
956959
... with GMTTempFile() as ofile:
@@ -1086,7 +1089,9 @@ def virtualfile_from_vectors(self, *vectors):
10861089
for col, array in enumerate(arrays):
10871090
self.put_vector(dataset, column=col, vector=array)
10881091

1089-
with self.open_virtual_file(family, geometry, "GMT_IN", dataset) as vfile:
1092+
with self.open_virtual_file(
1093+
family, geometry, "GMT_IN|GMT_IS_REFERENCE", dataset
1094+
) as vfile:
10901095
yield vfile
10911096

10921097
@contextmanager
@@ -1167,7 +1172,9 @@ def virtualfile_from_matrix(self, matrix):
11671172

11681173
self.put_matrix(dataset, matrix)
11691174

1170-
with self.open_virtual_file(family, geometry, "GMT_IN", dataset) as vfile:
1175+
with self.open_virtual_file(
1176+
family, geometry, "GMT_IN|GMT_IS_REFERENCE", dataset
1177+
) as vfile:
11711178
yield vfile
11721179

11731180
@contextmanager
Loading

pygmt/tests/test_clib.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -474,7 +474,7 @@ def test_virtual_file():
474474
data = np.arange(shape[0] * shape[1], dtype=dtype).reshape(shape)
475475
lib.put_matrix(dataset, matrix=data)
476476
# Add the dataset to a virtual file and pass it along to gmt info
477-
vfargs = (family, geometry, "GMT_IN", dataset)
477+
vfargs = (family, geometry, "GMT_IN|GMT_IS_REFERENCE", dataset)
478478
with lib.open_virtual_file(*vfargs) as vfile:
479479
with GMTTempFile() as outfile:
480480
lib.call_module("info", "{} ->{}".format(vfile, outfile.name))
@@ -491,7 +491,12 @@ def test_virtual_file_fails():
491491
Check that opening and closing virtual files raises an exception for
492492
non-zero return codes
493493
"""
494-
vfargs = ("GMT_IS_DATASET|GMT_VIA_MATRIX", "GMT_IS_POINT", "GMT_IN", None)
494+
vfargs = (
495+
"GMT_IS_DATASET|GMT_VIA_MATRIX",
496+
"GMT_IS_POINT",
497+
"GMT_IN|GMT_IS_REFERENCE",
498+
None,
499+
)
495500

496501
# Mock Open_VirtualFile to test the status check when entering the context.
497502
# If the exception is raised, the code won't get to the closing of the

pygmt/tests/test_plot.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,23 @@ def test_plot_vectors():
258258
return fig
259259

260260

261+
@pytest.mark.mpl_image_compare
262+
def test_plot_lines_with_arrows():
263+
"""Plot lines with arrows.
264+
265+
The test is slightly different from test_plot_vectors().
266+
Here the vectors are plotted as lines, with arrows at the end.
267+
268+
The test also check if the API crashes.
269+
See https://github.com/GenericMappingTools/pygmt/issues/406.
270+
"""
271+
fig = Figure()
272+
fig.basemap(region=[-2, 2, -2, 2], frame=True)
273+
fig.plot(x=[-1.0, -1.0], y=[-1.0, 1.0], pen="1p,black+ve0.2c")
274+
fig.plot(x=[1.0, 1.0], y=[-1.0, 1.0], pen="1p,black+ve0.2c")
275+
return fig
276+
277+
261278
@pytest.mark.mpl_image_compare
262279
def test_plot_scalar_xy():
263280
"Plot symbols given scalar x, y coordinates"

0 commit comments

Comments
 (0)