Skip to content

Commit fa26ffc

Browse files
Nio byte channel (Azure#18836)
* Some experiments with seekable byte channel * Updated to use ClosedFileSystemException instead of IOException * Wrote all tests except for seek and read and write * Added all tests * Fixed all tests * Recordings and cleanup * ci fixes * Reverted test file size * Updated recordings * fixed a test and responded to feedback * Fixed warnings * typo fix * Update sdk/storage/azure-storage-blob-nio/src/main/java/com/azure/storage/blob/nio/AzureFileSystemProvider.java Co-authored-by: Alan Zimmer <48699787+alzimmermsft@users.noreply.github.com> * Pr feedback * PR feedback * small javadoc fix for ci * Unused imports * Fixed buffer position issue Co-authored-by: Alan Zimmer <48699787+alzimmermsft@users.noreply.github.com>
1 parent 636296d commit fa26ffc

File tree

64 files changed

+13031
-336
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+13031
-336
lines changed

sdk/storage/azure-storage-blob-nio/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
## 12.0.0-beta.3 (Unreleased)
44
- Added support for FileSystemProvider.checkAccess method
55
- Added support for file key on AzureBasicFileAttributes and AzureBlobFileAttributes
6+
- Added support for SeekableByteChannel
7+
- When an operation is performed on a closed FileSystem, a ClosedFileSystemException is thrown instead of an IOException
68
- Adjusted the required flags for opening an outputstream
79

810
## 12.0.0-beta.2 (2020-08-13)

sdk/storage/azure-storage-blob-nio/src/main/java/com/azure/storage/blob/nio/AzureDirectoryStream.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -100,11 +100,8 @@ private static class AzureDirectoryIterator implements Iterator<Path> {
100100

101101
@Override
102102
public boolean hasNext() {
103-
try {
104-
AzurePath.ensureFileSystemOpen(path);
105-
} catch (IOException e) {
106-
throw LoggingUtility.logError(logger, new DirectoryIteratorException(e));
107-
}
103+
AzurePath.ensureFileSystemOpen(path);
104+
108105
// Closing the parent stream halts iteration.
109106
if (parentStream.closed) {
110107
return false;

sdk/storage/azure-storage-blob-nio/src/main/java/com/azure/storage/blob/nio/AzureFileSystemProvider.java

Lines changed: 62 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import java.nio.file.FileSystem;
3232
import java.nio.file.FileSystemAlreadyExistsException;
3333
import java.nio.file.FileSystemNotFoundException;
34+
import java.nio.file.Files;
3435
import java.nio.file.LinkOption;
3536
import java.nio.file.NoSuchFileException;
3637
import java.nio.file.NotDirectoryException;
@@ -46,9 +47,12 @@
4647
import java.time.Duration;
4748
import java.util.ArrayList;
4849
import java.util.Arrays;
50+
import java.util.Collections;
4951
import java.util.HashMap;
52+
import java.util.HashSet;
5053
import java.util.List;
5154
import java.util.Map;
55+
import java.util.Objects;
5256
import java.util.Set;
5357
import java.util.concurrent.ConcurrentHashMap;
5458
import java.util.concurrent.ConcurrentMap;
@@ -158,6 +162,17 @@ public final class AzureFileSystemProvider extends FileSystemProvider {
158162

159163
private static final String ACCOUNT_QUERY_KEY = "account";
160164
private static final int COPY_TIMEOUT_SECONDS = 30;
165+
private static final Set<OpenOption> OUTPUT_STREAM_DEFAULT_OPTIONS =
166+
Collections.unmodifiableSet(new HashSet<>(Arrays.asList(StandardOpenOption.CREATE,
167+
StandardOpenOption.WRITE,
168+
StandardOpenOption.TRUNCATE_EXISTING)));
169+
private static final Set<OpenOption> OUTPUT_STREAM_SUPPORTED_OPTIONS =
170+
Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
171+
StandardOpenOption.CREATE_NEW,
172+
StandardOpenOption.CREATE,
173+
StandardOpenOption.WRITE,
174+
// Though we don't actually truncate, the same result is achieved by overwriting the destination.
175+
StandardOpenOption.TRUNCATE_EXISTING)));
161176

162177
private final ConcurrentMap<String, FileSystem> openFileSystems;
163178

@@ -252,12 +267,20 @@ public Path getPath(URI uri) {
252267
}
253268

254269
/**
255-
* Unsupported. Use {@link #newInputStream(Path, OpenOption...)} or {@link #newOutputStream(Path, OpenOption...)}
256-
* instead.
270+
* Opens or creates a file, returning a seekable byte channel to access the file.
271+
* <p>
272+
* This method is primarily offered to support some jdk convenience methods such as
273+
* {@link Files#createFile(Path, FileAttribute[])} which requires opening a channel and closing it. A channel may
274+
* only be opened in read mode OR write mode. It may not be opened in read/write mode. Seeking is supported for
275+
* reads, but not for writes. Modifications to existing files is not permitted--only creating new files or
276+
* overwriting existing files.
277+
* <p>
278+
* This type is not threadsafe to prevent having to hold locks across network calls.
279+
* <p>
257280
*
258-
* @param path the Path
259-
* @param set open options
260-
* @param fileAttributes attributes
281+
* @param path the path of the file to open
282+
* @param set options specifying how the file should be opened
283+
* @param fileAttributes an optional list of file attributes to set atomically when creating the directory
261284
* @return a new seekable byte channel
262285
* @throws UnsupportedOperationException Operation is not supported.
263286
* @throws IllegalArgumentException if the set contains an invalid combination of options
@@ -269,7 +292,17 @@ public Path getPath(URI uri) {
269292
@Override
270293
public SeekableByteChannel newByteChannel(Path path, Set<? extends OpenOption> set,
271294
FileAttribute<?>... fileAttributes) throws IOException {
272-
throw LoggingUtility.logError(logger, new UnsupportedOperationException());
295+
if (Objects.isNull(set)) {
296+
set = Collections.emptySet();
297+
}
298+
299+
if (set.contains(StandardOpenOption.WRITE)) {
300+
return new AzureSeekableByteChannel(
301+
(NioBlobOutputStream) this.newOutputStreamInternal(path, set, fileAttributes), path);
302+
} else {
303+
return new AzureSeekableByteChannel(
304+
(NioBlobInputStream) this.newInputStream(path, set.toArray(new OpenOption[0])), path);
305+
}
273306
}
274307

275308
/**
@@ -349,24 +382,19 @@ public InputStream newInputStream(Path path, OpenOption... options) throws IOExc
349382
*/
350383
@Override
351384
public OutputStream newOutputStream(Path path, OpenOption... options) throws IOException {
385+
return newOutputStreamInternal(path, new HashSet<>(Arrays.asList(options)));
386+
}
387+
388+
OutputStream newOutputStreamInternal(Path path, Set<? extends OpenOption> optionsSet,
389+
FileAttribute<?>... fileAttributes) throws IOException {
352390
// If options are empty, add Create, Write, TruncateExisting as defaults per nio docs.
353-
if (options == null || options.length == 0) {
354-
options = new OpenOption[] {
355-
StandardOpenOption.CREATE,
356-
StandardOpenOption.WRITE,
357-
StandardOpenOption.TRUNCATE_EXISTING };
391+
if (optionsSet == null || optionsSet.size() == 0) {
392+
optionsSet = OUTPUT_STREAM_DEFAULT_OPTIONS;
358393
}
359-
List<OpenOption> optionsList = Arrays.asList(options);
360394

361395
// Check for unsupported options.
362-
List<OpenOption> supportedOptions = Arrays.asList(
363-
StandardOpenOption.CREATE_NEW,
364-
StandardOpenOption.CREATE,
365-
StandardOpenOption.WRITE,
366-
// Though we don't actually truncate, the same result is achieved by overwriting the destination.
367-
StandardOpenOption.TRUNCATE_EXISTING);
368-
for (OpenOption option : optionsList) {
369-
if (!supportedOptions.contains(option)) {
396+
for (OpenOption option : optionsSet) {
397+
if (!OUTPUT_STREAM_SUPPORTED_OPTIONS.contains(option)) {
370398
throw LoggingUtility.logError(logger, new UnsupportedOperationException("Unsupported option: "
371399
+ option.toString()));
372400
}
@@ -376,9 +404,9 @@ public OutputStream newOutputStream(Path path, OpenOption... options) throws IOE
376404
Write must be specified. Either create_new or truncate must be specified. This is to ensure that no edits or
377405
appends are allowed.
378406
*/
379-
if (!optionsList.contains(StandardOpenOption.WRITE)
380-
|| !(optionsList.contains(StandardOpenOption.TRUNCATE_EXISTING)
381-
|| optionsList.contains(StandardOpenOption.CREATE_NEW))) {
407+
if (!optionsSet.contains(StandardOpenOption.WRITE)
408+
|| !(optionsSet.contains(StandardOpenOption.TRUNCATE_EXISTING)
409+
|| optionsSet.contains(StandardOpenOption.CREATE_NEW))) {
382410
throw LoggingUtility.logError(logger,
383411
new IllegalArgumentException("Write and either CreateNew or TruncateExisting must be specified to open "
384412
+ "an OutputStream"));
@@ -396,14 +424,14 @@ public OutputStream newOutputStream(Path path, OpenOption... options) throws IOE
396424

397425
// Writing to an empty location requires a create option.
398426
if (status.equals(DirectoryStatus.DOES_NOT_EXIST)
399-
&& !(optionsList.contains(StandardOpenOption.CREATE)
400-
|| optionsList.contains(StandardOpenOption.CREATE_NEW))) {
427+
&& !(optionsSet.contains(StandardOpenOption.CREATE)
428+
|| optionsSet.contains(StandardOpenOption.CREATE_NEW))) {
401429
throw LoggingUtility.logError(logger, new IOException("Writing to an empty location requires a create "
402430
+ "option. Path: " + path.toString()));
403431
}
404432

405433
// Cannot write to an existing file if create new was specified.
406-
if (status.equals(DirectoryStatus.NOT_A_DIRECTORY) && optionsList.contains(StandardOpenOption.CREATE_NEW)) {
434+
if (status.equals(DirectoryStatus.NOT_A_DIRECTORY) && optionsSet.contains(StandardOpenOption.CREATE_NEW)) {
407435
throw LoggingUtility.logError(logger, new IOException("A file already exists at this location and "
408436
+ "CREATE_NEW was specified. Path: " + path.toString()));
409437
}
@@ -417,12 +445,17 @@ public OutputStream newOutputStream(Path path, OpenOption... options) throws IOE
417445

418446
// Add an extra etag check for create new
419447
BlobRequestConditions rq = null;
420-
if (optionsList.contains(StandardOpenOption.CREATE_NEW)) {
448+
if (optionsSet.contains(StandardOpenOption.CREATE_NEW)) {
421449
rq = new BlobRequestConditions().setIfNoneMatch("*");
422450
}
423451

424-
return new NioBlobOutputStream(resource.getBlobClient().getBlockBlobClient().getBlobOutputStream(pto, null,
425-
null, null, rq), resource.getPath());
452+
// For parsing properties and metadata
453+
if (fileAttributes == null) {
454+
fileAttributes = new FileAttribute<?>[0];
455+
}
456+
resource.setFileAttributes(Arrays.asList(fileAttributes));
457+
458+
return new NioBlobOutputStream(resource.getBlobOutputStream(pto, rq), resource.getPath());
426459
}
427460

428461
/**
@@ -992,7 +1025,6 @@ public Map<String, Object> readAttributes(Path path, String attributes, LinkOpti
9921025

9931026
// If "*" is specified, add all of the attributes from the specified set.
9941027
if (attributeName.equals("*")) {
995-
Set<String> attributesToAdd;
9961028
if (viewType.equals(AzureBasicFileAttributeView.NAME)) {
9971029
for (String attr : AzureBasicFileAttributes.ATTRIBUTE_STRINGS) {
9981030
results.put(attr, attributeSuppliers.get(attr).get());

sdk/storage/azure-storage-blob-nio/src/main/java/com/azure/storage/blob/nio/AzurePath.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import java.io.IOException;
1212
import java.net.URI;
1313
import java.net.URISyntaxException;
14+
import java.nio.file.ClosedFileSystemException;
1415
import java.nio.file.FileSystem;
1516
import java.nio.file.InvalidPathException;
1617
import java.nio.file.LinkOption;
@@ -774,10 +775,10 @@ private String rootToFileStore(String root) {
774775
return root.substring(0, root.length() - 1); // Remove the ROOT_DIR_SUFFIX
775776
}
776777

777-
static void ensureFileSystemOpen(Path p) throws IOException {
778+
static void ensureFileSystemOpen(Path p) {
778779
if (!p.getFileSystem().isOpen()) {
779780
throw LoggingUtility.logError(((AzurePath) p).logger,
780-
new IOException("FileSystem for path has been closed. Path: " + p.toString()));
781+
new ClosedFileSystemException());
781782
}
782783
}
783784
}

sdk/storage/azure-storage-blob-nio/src/main/java/com/azure/storage/blob/nio/AzureResource.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
import com.azure.storage.blob.models.BlobRequestConditions;
1414
import com.azure.storage.blob.models.BlobStorageException;
1515
import com.azure.storage.blob.models.ListBlobsOptions;
16+
import com.azure.storage.blob.models.ParallelTransferOptions;
17+
import com.azure.storage.blob.options.BlockBlobOutputStreamOptions;
18+
import com.azure.storage.blob.specialized.BlobOutputStream;
1619
import com.azure.storage.common.implementation.Constants;
1720

1821
import java.io.IOException;
@@ -240,6 +243,15 @@ BlobClient getBlobClient() {
240243
return this.blobClient;
241244
}
242245

246+
BlobOutputStream getBlobOutputStream(ParallelTransferOptions pto, BlobRequestConditions rq) {
247+
BlockBlobOutputStreamOptions options = new BlockBlobOutputStreamOptions()
248+
.setHeaders(this.blobHeaders)
249+
.setMetadata(this.blobMetadata)
250+
.setParallelTransferOptions(pto)
251+
.setRequestConditions(rq);
252+
return this.blobClient.getBlockBlobClient().getBlobOutputStream(options);
253+
}
254+
243255
private Map<String, String> prepareMetadataForDirectory() {
244256
if (this.blobMetadata == null) {
245257
this.blobMetadata = new HashMap<>();

0 commit comments

Comments
 (0)