Also, I happened to ran into this error as well, d...
# ktor
h
Also, I happened to ran into this error as well, does anyone know what the part where it said
Concurrent read attempts
mean? I'm trying to read a file in this function where this error happened.
Copy code
2025-11-02 07:39:59.407 [eventLoopGroupProxy-4-3] ERROR Application - Unhandled exception caught for CoroutineName(call-handler)
Nov 02 07:39:59 backend-server java[675297]: io.ktor.utils.io.ConcurrentIOException: Concurrent read attempts
Nov 02 07:39:59 backend-server java[675297]:         at io.ktor.utils.io.ByteChannel.awaitContent(ByteChannel.kt:272)
Nov 02 07:39:59 backend-server java[675297]:         at io.ktor.utils.io.ByteReadChannel$DefaultImpls.awaitContent$default(ByteReadChannel.kt:30)
Nov 02 07:39:59 backend-server java[675297]:         at io.ktor.utils.io.ByteReadChannelOperationsKt.discard(ByteReadChannelOperations.kt:349)
Nov 02 07:39:59 backend-server java[675297]:         at io.ktor.utils.io.ByteReadChannelOperationsKt.discard$default(ByteReadChannelOperations.kt:344)
Nov 02 07:39:59 backend-server java[675297]:         at io.ktor.server.engine.DefaultEnginePipelineKt$defaultEnginePipeline$1.invokeSuspend(DefaultEnginePipeline.kt:42)
Nov 02 07:39:59 backend-server java[675297]:         at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
Nov 02 07:39:59 backend-server java[675297]:         at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:99)
Nov 02 07:39:59 backend-server java[675297]:         at kotlinx.coroutines.EventLoop.processUnconfinedEvent(EventLoop.common.kt:65)
Nov 02 07:39:59 backend-server java[675297]:         at kotlinx.coroutines.internal.DispatchedContinuation.resumeWith(DispatchedContinuation.kt:327)
Nov 02 07:39:59 backend-server java[675297]:         at io.ktor.util.pipeline.SuspendFunctionGun.resumeRootWith(SuspendFunctionGun.kt:149)
Nov 02 07:39:59 backend-server java[675297]:         at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:134)
Nov 02 07:39:59 backend-server java[675297]:         at io.ktor.util.pipeline.SuspendFunctionGun.proceed(SuspendFunctionGun.kt:89)
Nov 02 07:39:59 backend-server java[675297]:         at io.ktor.util.pipeline.SuspendFunctionGun.execute$ktor_utils(SuspendFunctionGun.kt:109)
Nov 02 07:39:59 backend-server java[675297]:         at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:86)
Nov 02 07:39:59 backend-server java[675297]:         at
a
It means you're trying to read the byte channel multiple times simultaneously. Can you please share the code snippet where you read a file?
h
Copy code
multiPart.forEachPart { part ->
            when (part) {
                is PartData.FileItem -> {
                    val fileName = UUID.randomUUID().toString() + "_" + part.originalFileName
                    val partStream = part.provider().toInputStream()

                    val filePath = "uploads/$fileName"
                    val file = File(filePath)

                    // Use input stream to save the file
                    saveImageToDisk(file, part)

                    saveImageToS3(partStream, fileName, file, s3Client)

                    val outputName = UUID.randomUUID().toString() + ".mp4"
                    val outputPath = "uploads/$outputName"

                    convertImageToVideo(filePath, outputPath)

                    val contentID = UUID.randomUUID().toString()

                    val outputVideoPath = "uploads/$contentID.mp4"
                    val outputManifestPath = "uploads/$contentID-manifest"

                    val encryptedVideoInputStream = FileInputStream(outputVideoPath)

                    encryptedVideoInputStream.use {
                        val replayableStream = ByteStream.fromInputStream(encryptedVideoInputStream)

                        val s3ObjectRequest = PutObjectRequest {
                            key = outputVideoPath
                            body = replayableStream
                            contentType = "application/mp4"
                            acl = ObjectCannedAcl.PublicRead
                        }

                        s3Client.putObject(s3ObjectRequest)
                    }

                    val manifestFile = FileInputStream(outputManifestPath)

                    manifestFile.use { manifest ->

                        val replayableStream = ByteStream.fromInputStream(manifest)

                        val s3ObjectRequest = PutObjectRequest {
                            key = widevineOutputManifestPath
                            body = replayableStream
                            contentType = "text/plain"
                            acl = ObjectCannedAcl.PublicRead
                        }
                        s3Client.putObject(s3ObjectRequest)
                    }
here it is
a
There might be concurrent reads in the
saveImageToDisk
and
saveImageToS3
methods which cause the exception.
h
here are those 2 functions:
Copy code
fun saveImageToDisk(file: File, part: PartData.FileItem) {
    val output = file.outputStream()
    val input = part.provider().toInputStream()

    input.copyTo(output)

    try {
        input.close()
        output.close()
    } catch (e: Exception) {
        println(e)
    }


}
Copy code
suspend fun saveImageToS3(partStream: InputStream, fileName: String, file: File, s3Client: S3Client) {
    partStream.use { stream ->

        val replayableStream = ByteStream.fromInputStream(file.inputStream())

        val s3ObjectRequest = PutObjectRequest({
            key = fileName
            body = replayableStream
            contentType = "image/jpeg"
        })

        s3Client.putObject(s3ObjectRequest)
    }
}
one is reading from the file and the other is writing to it. But I can't see where they concurrently try to read the file
a
Yes, you're right. I was confused by the fact that the
partStream
is passed to the
saveImageToS3
function. I suggest putting a breakpoint inside the
ByteChannel.awaitContent
method to find out the caller stack traces.
h
I understand. Thanks, @Aleksei Tirman [JB] Also I can’t remember why I passed
partStream
into there, must have been some kind of mistake