Skip to content

Commit 0f84929

Browse files
authored
Merge pull request #279 from jonashaag/master
Fix sf_open error checking when used concurrently
2 parents d364d1a + 3577fe9 commit 0f84929

File tree

2 files changed

+36
-4
lines changed

2 files changed

+36
-4
lines changed

soundfile.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1181,8 +1181,10 @@ def _open(self, file, mode_int, closefd):
11811181
mode_int, self._info, _ffi.NULL)
11821182
else:
11831183
raise TypeError("Invalid file: {0!r}".format(self.name))
1184-
_error_check(_snd.sf_error(file_ptr),
1185-
"Error opening {0!r}: ".format(self.name))
1184+
if file_ptr == _ffi.NULL:
1185+
# get the actual error code
1186+
err = _snd.sf_error(file_ptr)
1187+
raise LibsndfileError(err, prefix="Error opening {0!r}: ".format(self.name))
11861188
if mode_int == _snd.SFM_WRITE:
11871189
# Due to a bug in libsndfile version <= 1.0.25, frames != 0
11881190
# when opening a named pipe in SFM_WRITE mode.
@@ -1538,8 +1540,14 @@ def __init__(self, code, prefix=""):
15381540
@property
15391541
def error_string(self):
15401542
"""Raw libsndfile error message."""
1541-
err_str = _snd.sf_error_number(self.code)
1542-
return _ffi.string(err_str).decode('utf-8', 'replace')
1543+
if self.code:
1544+
err_str = _snd.sf_error_number(self.code)
1545+
return _ffi.string(err_str).decode('utf-8', 'replace')
1546+
else:
1547+
# Due to race conditions, if used concurrently, sf_error() may
1548+
# return 0 (= no error) even if an error has happened.
1549+
# See https://github.com/erikd/libsndfile/issues/610 for details.
1550+
return "(Garbled error message from libsndfile)"
15431551

15441552
def __str__(self):
15451553
return self.prefix + self.error_string

tests/test_pysoundfile.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import sys
99
import gc
1010
import weakref
11+
import threading
1112

1213
# floating point data is typically limited to the interval [-1.0, 1.0],
1314
# but smaller/larger values are supported as well
@@ -750,6 +751,29 @@ def test_read_into_out_over_end_with_fill_should_return_full_data_and_write_into
750751
assert np.all(data[2:] == 0)
751752
assert out.shape == (4, sf_stereo_r.channels)
752753

754+
def test_concurren_open_error_reporting(file_inmemory):
755+
# Test that no sf_open errors are missed when pysoundfile is used
756+
# concurrently (there are race conditions in libsndfile's error reporting).
757+
758+
n_threads = 4
759+
n_trials_per_thread = 10
760+
761+
n_reported_errors = [0]
762+
763+
def target():
764+
for _ in range(n_trials_per_thread):
765+
try:
766+
sf.SoundFile(file_inmemory)
767+
except sf.LibsndfileError:
768+
n_reported_errors[0] += 1
769+
770+
threads = [threading.Thread(target=target) for _ in range(n_threads)]
771+
for thread in threads:
772+
thread.start()
773+
for thread in threads:
774+
thread.join()
775+
assert n_reported_errors[0] == n_threads * n_trials_per_thread
776+
753777

754778
# -----------------------------------------------------------------------------
755779
# Test buffer read

0 commit comments

Comments
 (0)