Skip to content

Commit d175b54

Browse files
committed
fix: continue-on-close-failure behavior needs to work for successful writes that also fail on closing the file. It's a bit more involved, since we need to flush the write operation, but the logic is the same as for read
Signed-off-by: Caleb Hulbert <cmhulbert@gmail.com>
1 parent fdf451e commit d175b54

2 files changed

Lines changed: 38 additions & 17 deletions

File tree

src/main/java/org/janelia/saalfeldlab/n5/FsIoPolicy.java

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -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
}

src/main/java/org/janelia/saalfeldlab/n5/LockedFileChannel.java

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@
1212
/**
1313
* LockedFileChannel implementation for both read and write operations.
1414
*/
15-
// TODO: This only has to be public because of a test in another package. Fix that
16-
public class LockedFileChannel implements LockedChannel {
15+
class LockedFileChannel implements LockedChannel {
1716

1817
private final FileChannel channel;
1918
private ReleaseLock releaseLock;
@@ -54,12 +53,6 @@ public OutputStream newOutputStream() throws N5Exception.N5IOException {
5453
return Channels.newOutputStream(channel);
5554
}
5655

57-
// TODO: This only has to be public because of a test in another package. Fix that
58-
public FileChannel getFileChannel() {
59-
60-
return channel;
61-
}
62-
6356
@Override
6457
public void close() throws IOException {
6558

0 commit comments

Comments
 (0)