Closed sedovalx closed 3 years ago
Hi @sedovalx, thanks for the report. Could you check it with 1.3.0-beta-2
?
Hi @sedovalx,
I tried to reproduce it using simplified code (1.2.5
and master
), but I could not.
I used the following code for server:
fun main() {
embeddedServer(Jetty, port = 7654) {
routing {
post("/handler") {
val multipart = call.receiveMultipart()
val out = arrayListOf<String>()
multipart.forEachPart { part ->
out += when (part) {
is PartData.FormItem -> {
"FormItem(${part.name},${part.value})"
}
is PartData.FileItem -> {
val bytes = part.streamProvider().readBytes()
"FileItem(${part.name},${part.originalFileName},${hex(bytes)})"
}
is PartData.BinaryItem -> {
"BinaryItem(${part.name},${hex(part.provider().readBytes())})"
}
}
part.dispose()
}
call.respondText(out.joinToString("; "))
}
}
}.start(true)
}
And the following for the client:
suspend fun main() {
HttpClient(Apache).use { client ->
val bytes = ByteArray(1024 * 1024)
val data = formData {
append("jar", "user.jar") {
writeFully(bytes, 0, bytes.size)
}
}
client.submitFormWithBinaryData<HttpResponse>("http://localhost:7654/handler", data).use { response ->
if (!response.status.isSuccess()) {
error("Failed to save jar, server responded with ${response.status}: ${response.readText()}")
}
}
}
}
Could you provide a reproducer that doesn't require additional modules/code and reproduce the problem?
@dmitrievanthony I would love to but doubt I can. The error happened from time to time, seems to be flaky. I don't have ideas what may impact the correct behavior. Recently we updated to ktor 1.3.1. I will post an update here if/when I face this issue again.
Thanks, @sedovalx, we appreciate it a lot.
The problem reproduced on 1.3.2
java.io.IOException: Failed to skip delimiter: actual bytes differ from delimiter bytes
at io.ktor.utils.io.DelimitedKt.tryEnsureDelimiter(Delimited.kt:144)
at io.ktor.utils.io.DelimitedKt.access$tryEnsureDelimiter(Delimited.kt:1)
at io.ktor.utils.io.DelimitedKt$skipDelimiter$2.invoke(Delimited.kt:47)
at io.ktor.utils.io.DelimitedKt$skipDelimiter$2.invoke(Delimited.kt)
at io.ktor.utils.io.ByteBufferChannel.lookAhead(ByteBufferChannel.kt:1859)
at io.ktor.utils.io.DelimitedKt.skipDelimiter(Delimited.kt:46)
at io.ktor.http.cio.MultipartKt.skipBoundary(Multipart.kt:201)
at io.ktor.http.cio.MultipartKt$parseMultipart$1.invokeSuspend(Multipart.kt:325)
at (Coroutine boundary. ( )
at circlet.server.application.OrgRoutesKt$orgKeyInterceptor$1.invokeSuspend(OrgRoutes.kt:83)
at io.ktor.features.ContentNegotiation$Feature$install$1.invokeSuspend(ContentNegotiation.kt:107)
at libraries.coroutines.extra.CoroutineBuildersCommonKt$withContext$2.invokeSuspend(CoroutineBuildersCommon.kt:44)
at io.ktor.routing.Routing.executeResult(Routing.kt:147)
at io.ktor.routing.Routing.interceptor(Routing.kt:34)
at io.ktor.routing.Routing$Feature$install$1.invokeSuspend(Routing.kt:99)
Caused by: java.io.IOException: Failed to skip delimiter: actual bytes differ from delimiter bytes
at io.ktor.utils.io.DelimitedKt.tryEnsureDelimiter(Delimited.kt:144)
at io.ktor.utils.io.DelimitedKt.access$tryEnsureDelimiter(Delimited.kt:1)
at io.ktor.utils.io.DelimitedKt$skipDelimiter$2.invoke(Delimited.kt:47)
at io.ktor.utils.io.DelimitedKt$skipDelimiter$2.invoke(Delimited.kt)
at io.ktor.utils.io.ByteBufferChannel.lookAhead(ByteBufferChannel.kt:1859)
at io.ktor.utils.io.DelimitedKt.skipDelimiter(Delimited.kt:46)
at io.ktor.http.cio.MultipartKt.skipBoundary(Multipart.kt:201)
at io.ktor.http.cio.MultipartKt$parseMultipart$1.invokeSuspend(Multipart.kt:325)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedContinuationKt.resumeCancellableWith(DispatchedContinuation.kt:314)
at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:26)
at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:109)
at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:158)
at kotlinx.coroutines.channels.ProduceKt.produce(Produce.kt:98)
at kotlinx.coroutines.channels.ProduceKt.produce$default(Produce.kt:92)
at io.ktor.http.cio.MultipartKt.parseMultipart(Multipart.kt:311)
at io.ktor.http.cio.MultipartKt.parseMultipart(Multipart.kt:287)
at io.ktor.http.cio.CIOMultipartDataBase.<init>(CIOMultipartData.kt:30)
at io.ktor.http.cio.CIOMultipartDataBase.<init>(CIOMultipartData.kt:28)
at io.ktor.server.engine.DefaultTransformKt.multiPartData(DefaultTransform.kt:99)
at io.ktor.server.engine.DefaultTransformKt.access$multiPartData(DefaultTransform.kt:1)
at io.ktor.server.engine.DefaultTransformKt$installDefaultTransformations$2.invokeSuspend(DefaultTransform.kt:50)
at io.ktor.server.engine.DefaultTransformKt$installDefaultTransformations$2.invoke(DefaultTransform.kt)
at io.ktor.util.pipeline.SuspendFunctionGun.loop(PipelineContext.kt:318)
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(PipelineContext.kt:163)
at io.ktor.util.pipeline.SuspendFunctionGun.execute(PipelineContext.kt:183)
at io.ktor.util.pipeline.Pipeline.execute(Pipeline.kt:27)
at io.ktor.request.ApplicationReceiveFunctionsKt.receive(ApplicationReceiveFunctions.kt:110)
at circlet.pipelines.PipelinesRoutesKt.handleUserScriptSaveRequest(PipelinesRoutes.kt:296)
at circlet.pipelines.PipelinesRoutesKt$pipelines$1$3$1$1.invokeSuspend(PipelinesRoutes.kt:56)
at circlet.pipelines.PipelinesRoutesKt$pipelines$1$3$1$1.invoke(PipelinesRoutes.kt)
at io.ktor.util.pipeline.SuspendFunctionGun.loop(PipelineContext.kt:318)
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(PipelineContext.kt:163)
at circlet.server.application.OrgRoutesKt$orgKeyInterceptor$1$invokeSuspend$$inlined$with$1.invokeSuspend(MDC.kt:98)
at circlet.server.application.OrgRoutesKt$orgKeyInterceptor$1$invokeSuspend$$inlined$with$1.invoke(MDC.kt)
at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:91)
at kotlinx.coroutines.BuildersKt__Builders_commonKt.withContext(Builders.common.kt:154)
at kotlinx.coroutines.BuildersKt.withContext(Unknown Source)
at circlet.server.application.OrgRoutesKt$orgKeyInterceptor$1.invokeSuspend(OrgRoutes.kt:83)
at circlet.server.application.OrgRoutesKt$orgKeyInterceptor$1.invoke(OrgRoutes.kt)
at io.ktor.util.pipeline.SuspendFunctionGun.loop(PipelineContext.kt:318)
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(PipelineContext.kt:163)
at io.ktor.features.ContentNegotiation$Feature$install$1.invokeSuspend(ContentNegotiation.kt:107)
at io.ktor.features.ContentNegotiation$Feature$install$1.invoke(ContentNegotiation.kt)
at io.ktor.util.pipeline.SuspendFunctionGun.loop(PipelineContext.kt:318)
at io.ktor.util.pipeline.SuspendFunctionGun.proceed(PipelineContext.kt:163)
at circlet.platform.server.ServerKt$platformMain$$inlined$maybeMeasure$lambda$1$2$1.invokeSuspend(Server.kt:138)
at circlet.platform.server.ServerKt$platformMain$$inlined$maybeMeasure$lambda$1$2$1.invoke(Server.kt)
at libraries.coroutines.extra.CoroutineBuildersCommonKt$withContext$2.invokeSuspend(CoroutineBuildersCommon.kt:44)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:834)
Server code where exception happens
suspend fun handleUserScriptSaveRequest(call: ApplicationCall) {
val scriptHash = call.parameters["scriptHash"]
if (scriptHash == null) {
call.respond(HttpStatusCode.BadRequest)
return
}
val saver = call.orgKey.component<CompilationDataSaver>()
call.receiveMultipart().apply {
while (true) {
val part = readPart() ?: break
try {
when (part) {
is PartData.FileItem -> {
try {
when (part.name) {
"jar" -> {
val jarContent = part.streamProvider().use { it.readBytes() }
saver.saveJar(scriptHash, jarContent)
}
"metadata" -> {
val metadataContent = part.streamProvider().use { it.readBytes() }
saver.saveMetadata(scriptHash, metadataContent)
}
else -> {
call.respond(HttpStatusCode.BadRequest)
return
}
}
}
catch (ex: Throwable) {
call.respond(HttpStatusCode.BadRequest)
return
}
}
else -> {
call.respond(HttpStatusCode.BadRequest)
return
}
}
}
finally {
part.dispose()
}
}
}
call.respond(HttpStatusCode.OK)
}
Can't say for sure where exactly it happens as line numbers in the stack trace don't match.
And the client code:
private fun uploadExample(url: String, accessToken: String, content: String) {
runBlocking {
HttpClient(Apache.config {
customizeClient {
setThreadFactory(NamedThreadFactory("Ktor-client-apache-Automation-RemoteClient-", daemon = true))
setDefaultIOReactorConfig(IOReactorConfig.custom().setIoThreadCount(4).build())
}
}).use { client ->
val data =
formData {
append("metadata", "script.metadata", bodyBuilder = { writeStringUtf8(content) })
}
client.submitFormWithBinaryData<HttpResponse>(url, data) {
header(HttpHeaders.Authorization, "Bearer $accessToken")
}.also { response ->
if (!response.status.isSuccess()) {
error("Failed to save jar url $url responded with ${response.status}: ${response.readText()}")
}
}
}
}
}
Thanks, @sedovalx,
Could you please say if this issue reproduces every time or it's fluky?
I tried to write a simpler reproducer, but I still can't get any results. Could you please check the following code in your environment?
import io.ktor.application.*
import io.ktor.http.*
import io.ktor.http.content.*
import io.ktor.request.*
import io.ktor.response.*
import io.ktor.routing.*
import io.ktor.server.engine.*
import io.ktor.server.jetty.*
/*
* Copyright 2014-2020 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/
fun main() {
embeddedServer(Jetty, port = 9876) {
routing {
post("/") {
call.receiveMultipart().apply {
while (true) {
val part = readPart() ?: break
try {
when (part) {
is PartData.FileItem -> {
try {
when (part.name) {
"jar" -> {
part.streamProvider().use { it.readBytes() }
}
"metadata" -> {
part.streamProvider().use { it.readBytes() }
}
else -> {
call.respond(HttpStatusCode.BadRequest)
}
}
}
catch (ex: Throwable) {
call.respond(HttpStatusCode.BadRequest)
}
}
else -> {
call.respond(HttpStatusCode.BadRequest)
}
}
}
finally {
part.dispose()
}
}
}
call.respond(HttpStatusCode.OK)
}
}
}.start(true)
}
import io.ktor.client.*
import io.ktor.client.engine.*
import io.ktor.client.engine.apache.*
import io.ktor.client.request.*
import io.ktor.client.request.forms.*
import io.ktor.client.statement.*
import io.ktor.http.*
import org.apache.http.impl.nio.reactor.*
import java.util.concurrent.ThreadFactory
/*
* Copyright 2014-2020 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/
suspend fun main() {
HttpClient(Apache.config {
customizeClient {
setThreadFactory(ThreadFactory { Thread(it) })
setDefaultIOReactorConfig(IOReactorConfig.custom().setIoThreadCount(4).build())
}
}).use { client ->
val data =
formData {
append("metadata", "script.metadata", bodyBuilder = { writeStringUtf8(String(ByteArray(10 * 1024 * 1024))) })
}
repeat(1000) {
client.submitFormWithBinaryData<HttpResponse>("http://localhost:9876/", data) {
header(HttpHeaders.Authorization, "Bearer MY_TOKEN")
}.also { response ->
if (!response.status.isSuccess()) {
error("Failed to save jar url responded with ${response.status}: ${response.readText()}")
}
}
}
}
}
The error is flaky and rarely reproduces. We can add diagnostic logging or something in our code if you will come with a suggestion.
Please check the following ticket on YouTrack for follow-ups to this issue. GitHub issues will be closed in the coming weeks.
Please reopen if the problem is still active
Ktor Version and Engine Used (client or server and name)
Describe the bug The exception happens when a client code tries to upload a
ByteArray
to the server. The client code:Server error stacktrace: