@@ -52,16 +52,44 @@ static FileChannel openFileChannel(final Path path, final boolean forWriting) th
5252 }
5353 }
5454
55+
56+ /**
57+ * This method is necessary to handle the situtation where writing is successful, but `close` fails on the file channel.
58+ * This has been observed to happen fairly consistently on MacOS when writing to a file mounted over SMB.
59+ *
60+ * @param readData to write to the {@code Path}
61+ * @param path to write to
62+ * @throws IOException if writing failed.
63+ */
64+ private static void writeToPathIgnoreCloseException (ReadData readData , Path path ) throws IOException {
65+
66+ FileChannel channel = openFileChannel (path , true );
67+ OutputStream os = Channels .newOutputStream (channel );
68+
69+ try {
70+ readData .writeTo (os );
71+ os .flush ();
72+ channel .force (true );
73+ } catch (Throwable e ) {
74+ os .close ();
75+ channel .close ();
76+ throw e ;
77+ }
78+
79+ /* if we get here, the write succeeded, and the os/channel may not be closed yet */
80+ try {
81+ os .close ();
82+ channel .close ();
83+ } catch (IOException | UncheckedIOException ignore ) {
84+ /* Ignore; we know the data was written already. */
85+ }
86+ }
87+
5588 public static class Unsafe implements IoPolicy {
5689 @ Override
5790 public void write (String key , ReadData readData ) throws IOException {
5891 final Path path = Paths .get (key );
59- try (
60- FileChannel channel = openFileChannel (path , true );
61- OutputStream os = Channels .newOutputStream (channel );
62- ) {
63- readData .writeTo (os );
64- }
92+ writeToPathIgnoreCloseException (readData , path );
6593 }
6694
6795 @ Override
@@ -164,9 +192,9 @@ public ReadData materialize(final long offset, final long length) {
164192 throw new N5Exception .N5NoSuchKeyException ("No such file" , e );
165193 } catch (IOException | UncheckedIOException e ) {
166194 /* Occasionally (frequently for some source remote mounted file systems) this can throw exceptions during
167- * `channel.close()` which is called automatically in the try-with-resources block. In this case, we have
168- * successfully read the data, and we can return it, and ignore the exception.
169- * */
195+ * `channel.close()` which is called automatically in the try-with-resources block. In this case, we have
196+ * successfully read the data, and we can return it, and ignore the exception.
197+ * */
170198 if (readData == null )
171199 throw new N5Exception .N5IOException (e );
172200 }
0 commit comments