Azure Java SDK v12 is not downloading a file asynchronously
See original GitHub issueDescribe the bug I am writing a quick proof-of-concept for downloading images from Azure Blob Storage using the Java 12 Azure Storage SDK. The following code works properly when I convert it to synchronous. However, despite the subscribe() at the bottom of the code, I only see the subscription message when I run this reactive method. The success and error handlers are not firing. I’ve debugged and reviewed this code a lot, and cannot move forward, so I suspect that there may be a bug.
Thank you for your time and help.
Exception or Stack Trace No exceptions are thrown.
To Reproduce Insert proper values for the variables at the top of the method and run it. You will the see subscription handler fire and print a message on your console, but the other handlers never fire nor provide any errors or feedback… Just silence.
Code Snippet
private fun azureReactorDownload() {
var startTime = 0L
var accountName = "abcd"
var key = "09sd0908sd08f0s&&6^%"
var endpoint = "https://${accountName}.blob.core.windows.net/$accountName
var containerName = "mycontainer"
var blobName = "animage.jpg"
// Get the Blob Service client, so we can use it to access blobs, containers, etc.
BlobServiceClientBuilder()
// Container URL
.endpoint(endpoint)
.credential(
SharedKeyCredential(
accountName,
key
)
)
.buildAsyncClient()
// Get the container client so we can work with our container and its blobs.
.getContainerAsyncClient(containerName)
// Get the block blob client so we can access individual blobs and include the path
// within the container as part of the filename.
.getBlockBlobAsyncClient(blobName)
// Initiate the download of the desired blob.
.download()
.map { response ->
// Drill down to the ByteBuffer.
response.value()
}
.doOnSubscribe {
println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> Subscription arrived.")
startTime = System.currentTimeMillis()
}
.doOnSuccess { data ->
data.map { byteBuffer ->
println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> READY TO WRITE TO THE FILE")
byteBuffer.writeToFile("/tmp/azrxblobdownload.jpg")
val elapsedTime = System.currentTimeMillis() - startTime
println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> Finished downloading blob in $elapsedTime ms.")
}
}
.doOnError {
println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> Failed to download blob: ${it.localizedMessage}")
}
.subscribe()
}
fun ByteBuffer.writeToFile(path: String) {
val fc = FileOutputStream(path).channel
fc.write(this)
fc.close()
}
Expected behavior I expected to download a blob that exists in our Azure Blob Storage container. Originally, I wrote a synchronous method that is the mirror image of this one, and it worked properly. I am using the same values for the variables, so I don’t know what is going on.
Screenshots None required; No error messages, exceptions, or feedback, other than a successful subscription.
Setup (please complete the following information):
- OS: Ubuntu 18.10, 64 bit
- IDE : IntelliJ Ultimate 2019.2
- Version of the Library used: com.azure:azure-storage-blob:12.0.0-preview.2
Additional context
Add any other context about the problem here.
I’ve attached my build.gradle.kts file. I can provide a sample project, if necessary.
Information Checklist Kindly make sure that you have added all the following information above and checkoff the required fields otherwise we will treat the issuer as an incomplete report
- Bug Description Added
- Repro Steps Added
- Setup information Added
Issue Analytics
- State:
- Created 4 years ago
- Comments:9 (4 by maintainers)
Awesome explanation, Andy! I think that I need to spend some time stepping into the azure sdk code so I can learn more about some of these nuances. Have a great weekend, and thank you for your help.
@mgingios thanks for explaining your use case.
The behavior you’re seeing is due to the “asynchronous” nature of the Single<?> returned by the
download()
method. This is expected actually.The
subscribe()
call subscribe to the Single<?> in “Main Thread” and the lambda we provided in thedoOnSubscribe
gets called from the same “Main thread”.The lambda we registered via
doOnNext
to receive ByteBuf chunks will get invoked from “IO Thread”.What is happening here is - the main thread is exiting before Flux<ByteBuf> complete the emission in different "IO Thread"s or even before it get a chance to emit.
As a test - If we put Thread.sleep(5000) after
subscribe()
[to delay the main thread termination by 5 sec] and print the thread id fromdoOnSubscribe
anddoOnNext
, you can see result like:When we call
blockingLast()
, it does subscription and wait for Flux<ByteBuf> emission to complete.At first glance these kind of blocking call can be confusing and give us a feeling that things are not asynchronous.
The real power reactive programming is - the flexibility it provide that enable us to combine multiple reactive instances (Flux/Single instances) using operators like merge. Such a merge result in a new Flux instance which when blocked result in distributing work associated with all reactive instances into different threads. The framework takes care of scheduling, load balancing and ensure there is no race condition, in a way save us from challenges associated with manual multi-threading.