Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AWS Amplify Storage HttpErrorCode(SOCKET_TIMEOUT) #2745

Closed
1 task done
xaluxs opened this issue Mar 27, 2024 · 15 comments
Closed
1 task done

AWS Amplify Storage HttpErrorCode(SOCKET_TIMEOUT) #2745

xaluxs opened this issue Mar 27, 2024 · 15 comments
Labels
bug Something isn't working

Comments

@xaluxs
Copy link

xaluxs commented Mar 27, 2024

Before opening, please confirm:

Language and Async Model

Kotlin - Coroutines

Amplify Categories

Storage

Gradle script dependencies

// Put output below this line
    implementation ("com.amplifyframework:core-kotlin:2.14.12")
    implementation ("com.amplifyframework:aws-auth-cognito:2.14.12")
    implementation ("com.amplifyframework:aws-storage-s3:2.14.12")

Environment information

# Put output below this line
------------------------------------------------------------
Gradle 8.2
------------------------------------------------------------

Build time:   2023-06-30 18:02:30 UTC
Revision:     5f4a070a62a31a17438ac998c2b849f4f6892877

Kotlin:       1.8.20
Groovy:       3.0.17
Ant:          Apache Ant(TM) version 1.10.13 compiled on January 4 2023
JVM:          17.0.8 (Oracle Corporation 17.0.8+9-LTS-211)
OS:           Mac OS X 13.5.1 x86_64


Please include any relevant guides or documentation you're referencing

No response

Describe the bug

This happens on phones, it is not a duplicate of the one that already exists since the other thing only happened on Wear Os

I have an application in which I load image-type files, with an approximate weight of 2 to 8 MB, the problem is that when the internet speed is very low, such as 3G or 4G or low-speed Wi-Fi, it generates HttpErrorCode(SOCKET_TIMEOUT) and close the application, I can't catch the error with try/catch

Reproduction steps (if applicable)

No response

Code Snippet

// Put your code below this line.
 
    if (!file.canRead()) {
        onImageNotFound(photo)
        return
    }

    val upload = Amplify.Storage.uploadFile(buildKey(file.name), file, buildOptions())

    multiPhotoDao.setTransferId(photo.photoId,  upload.transferId)

    val progressJob = scope.launch {
        upload.progress().collect(onProgressChange(photo))
    }
    jobs[photo.transferId] = progressJob


    val s3Key = upload.result().key

    onUploadFinished(photo, s3Key)

    jobs[photo.photoId]?.cancelAndJoin()
    jobs.remove(photo.photoId)
    jobs[photo.transferId]?.cancelAndJoin()
    jobs.remove(photo.transferId)

Log output

// Put your logs below this line
PartUploadTransferWorker failed with exception: aws.smithy.kotlin.runtime.http.HttpException: java.net.SocketTimeoutException: timeout; HttpErrorCode(SOCKET_TIMEOUT)
	at aws.smithy.kotlin.runtime.http.engine.okhttp.OkHttpEngine.roundTrip(OkHttpEngine.kt:158)
	at aws.smithy.kotlin.runtime.http.engine.okhttp.OkHttpEngine$roundTrip$1.invokeSuspend(Unknown Source:15)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
	at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:584)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:793)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:697)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:684)
Caused by: java.net.SocketTimeoutException: timeout
	at okio.SocketAsyncTimeout.newTimeoutException(JvmOkio.kt:146)
	at okio.AsyncTimeout.access$newTimeoutException(AsyncTimeout.kt:186)
	at okio.AsyncTimeout$source$1.read(AsyncTimeout.kt:390)
	at okio.RealBufferedSource.indexOf(RealBufferedSource.kt:430)
	at okio.RealBufferedSource.readUtf8LineStrict(RealBufferedSource.kt:323)
	at okhttp3.internal.http1.HeadersReader.readLine(HeadersReader.kt:29)
	at okhttp3.internal.http1.Http1ExchangeCodec.readResponseHeaders(Http1ExchangeCodec.kt:179)
	at okhttp3.internal.connection.Exchange.readResponseHeaders(Exchange.kt:111)
	at okhttp3.internal.http.CallServerInterceptor.intercept(CallServerInterceptor.kt:95)
	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
	at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.kt:34)
	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
	at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.kt:95)
	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
	at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.kt:84)
	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
	at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.kt:65)
	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
	at aws.smithy.kotlin.runtime.http.engine.okhttp.MetricsInterceptor.intercept(MetricsInterceptor.kt:30)
	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
	at okhttp3.internal.connection.RealCall.getResponseWithInterceptorChain$okhttp(RealCall.kt:205)
	at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:537)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644)
	at java.lang.Thread.run(Thread.java:1012)
Caused by: java.net.SocketException: Socket closed
	at java.net.SocketInputStream.read(SocketInputStream.java:188)
	at java.net.SocketInputStream.read(SocketInputStream.java:143)
	at com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.readFromSocket(ConscryptEngineSocket.java:983)
	at com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.processDataFromSocket(ConscryptEngineSocket.java:947)
	at com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.readUntilDataAvailable(ConscryptEngineSocket.java:862)
	at com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.read(ConscryptEngineSocket.java:835)
	at okio.InputStreamSource.read(JvmOkio.kt:93)
	at okio.AsyncTimeout$source$1.read(AsyncTimeout.kt:153)
	at okio.RealBufferedSource.indexOf(RealBufferedSource.kt:430) 
	at okio.RealBufferedSource.readUtf8LineStrict(RealBufferedSource.kt:323) 
	at okhttp3.internal.http1.HeadersReader.readLine(HeadersReader.kt:29) 
	at okhttp3.internal.http1.Http1ExchangeCodec.readResponseHeaders(Http1ExchangeCodec.kt:179) 
	at okhttp3.internal.connection.Exchange.readResponseHeaders(Exchange.kt:111) 
	at okhttp3.internal.http.CallServerInterceptor.intercept(CallServerInterceptor.kt:95) 
	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109) 
	at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.kt:34) 
	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109) 
	at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.kt:95) 
	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109) 
	at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.kt:84) 
	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109) 
	at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.kt:65) 
	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109) 
	at aws.smithy.kotlin.runtime.http.engine.okhttp.MetricsInterceptor.intercept(MetricsInterceptor.kt:30) 
	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109) 
	at okhttp3.internal.connection.RealCall.getResponseWithInterceptorChain$okhttp(RealCall.kt:205) 
	at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:537) 
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) 
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644) 
	at java.lang.Thread.run(Thread.java:1012) 


amplifyconfiguration.json

No response

GraphQL Schema

// Put your schema below this line

Additional information and screenshots

To replicate this you could use Charles or Proxyman

@github-actions github-actions bot added the pending-triage Issue is pending triage label Mar 27, 2024
@tylerjroach
Copy link
Member

Hi @xaluxs Just to confirm, this issue is causing a fatal crash that is force closing the application? I'll do my best to replicate. The PartUploadTransferWorker has a try/catch around the worker executing the request. Its possible this crash is occurring at a lower level than Amplify and we are unable to catch it.

I'll look to replicate and notify our Kotlin SDK team if we are unable to prevent the crash on our end.

@tylerjroach tylerjroach added bug Something isn't working and removed pending-triage Issue is pending triage labels Mar 28, 2024
@xaluxs xaluxs closed this as not planned Won't fix, can't repro, duplicate, stale Mar 28, 2024
Copy link
Contributor

This issue is now closed. Comments on closed issues are hard for our team to see.
If you need more assistance, please open a new issue that references this one.

@tylerjroach
Copy link
Member

@xaluxs I saw a response come in and then it appears to have been deleted and the issue closed. Was this intentional? I want to make sure that we are properly able to track this potential issue.

@xaluxs
Copy link
Author

xaluxs commented Mar 28, 2024

Hi @tylerjroach

Sorry I closed this by mistake but it's still happening,

And in answer to your question, yes, it is causing a fatal crash that forces you to close the application.

As an additional addition, I have noticed that it happens more frequently when, for example, in my tests there are more than 10 requests in queue and the connection is very slow, I simulate it with Proxyman, but it also happens using data or a slow connection. WiFi connection.

@xaluxs xaluxs reopened this Mar 28, 2024
@tylerjroach
Copy link
Member

Thanks for the additional info. I'll try and simulate this as well

@tylerjroach
Copy link
Member

@xaluxs I was able to replicate the stacktrace you provided exactly, but it is not crashing the application. I see logs after the error is thrown in logs where the result is provided to me as failed.

Are you sure this isn't just an error log? Do you have any logs after what you have provided that may indicacate something else causing the crash?

@xaluxs
Copy link
Author

xaluxs commented Mar 29, 2024

@tylerjroach I replicated it again, I have 10 photos queued with a slow network, I do several forced reloads, wait a couple of minutes and then it starts throwing errors.
Note that every retry I make, I restart it with the following

value transfer = Amplify.Storage.getTransfer(image.transferId)
transfer.resume()

in case it exists.

I leave a video replicating, as well as a log where the crash begins, it seems that this time it was with:

StorageException{message=Something went wrong with the AWS S3 Storage file upload operation, cause=java.lang.Exception: aws.smithy.kotlin.runtime.http.HttpException: java.net.SocketTimeoutException: timeout; HttpErrorCode(SOCKET_TIMEOUT), recoverySuggestion=See attached exception for more information and suggestions}
                    at com.amplifyframework.storage.s3.operation.AWSS3StorageUploadFileOperation$UploadTransferListener.onError(AWSS3StorageUploadFileOperation.kt:228)
Ampliy.Crash.mp4

amplfy_as_log.txt

net config:

Captura de pantalla 2024-03-29 a la(s) 1 26 21 p m

@tylerjroach
Copy link
Member

Looking at the stacktrace, I believe the crash comes from upload.result() which needs to be wrapped in a try/catch for StorageException. That call will throw if the upload failed.

@xaluxs
Copy link
Author

xaluxs commented Mar 29, 2024

@tylerjroach It is already wrapped in a try/catch

@tylerjroach
Copy link
Member

@xaluxs Can you show me a larger code snippet that shows this?

@tylerjroach
Copy link
Member

tylerjroach commented Mar 29, 2024

From the snippet that you provided above, I can't see what scope you are currently in for executing the suspending function:

 val s3Key = upload.result().key

However, I believe the issue you are running into is not having proper try/catches inside the scope.

For example, the below code cannot catch the exception, as you cannot try/catch an exception from within a coroutine outside of its scope.

try {
    scope.launch { 
        upload.result() // This will not catch and will crash the app
    }
} catch (e: Exception) {
    //handle error
}

The necessary implementation is to try/catch an exception from a suspending function inside of its own scope. This example below will catch the exception you are experiencing.

scope.launch { 
    try {
        upload.result() 
    } catch (e: Exception) {
    //handle error
    }
} 

@xaluxs
Copy link
Author

xaluxs commented Mar 29, 2024

@tylerjroach Broadly speaking, it is in the implementation to start uploading the images.
Or it could be that I am required to package starNewUpload with try/catch, since I include it in the main uploadPhoto method, or just mark it with @\Throws

1 - Check if it is already in progress or start a new upload

   override suspend fun uploadPhoto(photo: S3Photo) {

        try {

            /** TODO Check if the photo is already uploaded or in progress. **/

            val image =  multiPhotoDao.fetchPhotoByPhotoId(photo.photoId) ?: return

            val imageState = TransferState.getState(image.status)

            val transfer = try { Amplify.Storage.getTransfer(image.transferId) } catch (e: Exception) {
               printe("onProgress_X Error to get transfer imageState -> $imageState ")
                C.NULL
            }

            printd("onProgress_X= 00000  ${transfer?.transferState}  - ${jobs.containsKey(image.photoId)}   status ${jobs.size} ${transfer?.error} --- ")

            if (transfer != null) {

                val transferState = transfer.transferState

                if (transferState.isFailed()) {
                    removeJobAndStartNewUpload(image)
                } else if (TransferState.isStarted(transferState)) {
                    
                    transfer.resume()

                } else {

                    if (jobs.containsKey(image.photoId)) {
                        transfer.start()
                    } else {
                        removeJobAndStartNewUpload(image)
                    }
                }

            } else {

                if (imageState == TransferState.COMPLETED) {
                    if (photo is MultiPhotoEntity) {
                        onFinish.emit(Pair(photo.captureId,  photo.subFormId))
                    }
                } else {
                    removeJobAndStartNewUpload(image)
                }

            }


        } catch (e: Exception) {
            printd("onProgress_X= Exception ${photo.photoId}  ${e.printStackTrace()}")
            /** Try uploading again. **/
            if (e.message?.contains(C.TRANSFER_ID_NOT_FOUND) == C.TRUE) {
                val image =  multiPhotoDao.fetchPhotoByPhotoId(photo.photoId) ?: return
                if (image.status != TransferState.COMPLETED.name) {
                    removeJobAndStartNewUpload(photo)
                }
            }

            onUploadFailed(photo, e.stackTraceToString())
        }
    }

2 - removeJobAndStartNewUpload

 protected open suspend fun removeJobAndStartNewUpload(photo: S3Photo) {

        val uploadJob = jobs[photo.photoId]
        uploadJob?.cancelAndJoin()

        val progressJob = jobs[photo.transferId]
        progressJob?.cancelAndJoin()

        jobs.remove(photo.photoId)
        jobs.remove(photo.transferId)

        val job = scope.launch { starNewUpload(photo) }
        jobs[photo.photoId] = job

    }

3 - starNewUpload

    @OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class)
    override suspend fun starNewUpload(photo: S3Photo) {

        Uri.parse(photo.localURL).path?.let { File(it) }?.let { file ->

            if (!file.canRead()) {
                onImageNotFound(photo)
                return
            }


            val upload = Amplify.Storage.uploadFile(buildKey(file.name), file, buildOptions())

            val transferId = upload.transferId

            multiPhotoDao.setTransferId(photo.photoId,  transferId)

            val progressJob = scope.launch {
                upload.progress().collect { uploadProgressChange(photo.photoId, it) }
            }

            jobs[transferId] = progressJob

            val s3Key = upload.result().key

            uploadFinish(photo.photoId, s3Key)

            jobs[photo.photoId]?.cancelAndJoin()
            jobs.remove(photo.photoId)
            progressJob.cancel()
            jobs.remove(transferId)


        } ?: run {
            onImageNotFound(photo)
        }


    }

@xaluxs
Copy link
Author

xaluxs commented Mar 29, 2024

@tylerjroach Ok, I think the problem may be that I am not doing it within the scope of the coroutine or I don't know when or why I remove it 👾. As soon as I can I make the change and confirm that I am leaving home.

@xaluxs
Copy link
Author

xaluxs commented Apr 1, 2024

@tylerjroach Hi, I was barely able to confirm and if the error was due to what was mentioned, it already works correctly for me, I don't know why I missed it.

try {
    upload.result()
} catch (e: exception) {
       //handle error
}

Just one more thing, there is more detailed documentation in which scenarios resume(), start() of Amplify.Storage.getTransfer(image.transferId) would be used.

For example, if a load remains in the IN_PROGRESS state
and the device turns off halfway, when could the device then use the resume() or satart() method to continue charging or would it have to restart charging from scratch?

@xaluxs xaluxs closed this as completed Apr 1, 2024
Copy link
Contributor

github-actions bot commented Apr 1, 2024

This issue is now closed. Comments on closed issues are hard for our team to see.
If you need more assistance, please open a new issue that references this one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants