https://kotlinlang.org logo
Title
a

adev_one

05/30/2019, 7:03 AM
Добрый день! Я сейчас разрабатываю кодогенерацию Swagger для kotlin-multiplatform. Под капотом использую ktor, kotlinx-serialization и kotlinx-coroutines. Одна из фич кодогенерации - обработка схемы кастомных ошибок. Сейчас это реализовано через Exceptions. Это накладывает кучу ограничений. Например, мне всегда нужно пользоваться SupervisorScope и при обработке всегда прокидывать CancellationsException. Насколко я понимаю, exceptions имеют другое значение при использовании корутин (механизм управления ресурасами?). Я попробовал использовать Result из Kotlin 1.3, но его нельзя вернуть и нельзя параметризовать типом ошибки. У каждого роута у меня свой набор ошибок. Мой вопрос в том, как правильно организовать обработку ошибок при использовании корутин (и ktor)?
g

gildor

05/30/2019, 7:13 AM
Не кидать ошибку, вернуть какой нибудь Sealed класс результата, в общем работа с ошибками в этом случае ничем у корутин не отличается с любым другим кодом
так же не оченьо понятно чем Result в данном случае бы помог. ведь это фактически тоже самое, это не кастомная ошибка, а просто обертка над эксепшеном
a

altavir

05/30/2019, 7:33 AM
Исключения имеют ровно ту же природу в корутинах. Исключением является CancelationException, которое может быть частью нормального поведения.
1
g

gildor

05/30/2019, 7:39 AM
Может есть какой то пример кода, сейчас звучит как будто можно улучшить то как эта корутина написана, SupervisorScope имеет смысл, но в относительно специфичных кейсах и в большинстве suspend функий смысла в нем нет
r

rrader

05/31/2019, 10:28 AM

https://www.youtube.com/watch?v=GYBhHUGR1ZY

Это для GraphQL но хорошо и для REST
g

gildor

05/31/2019, 10:29 AM
я думаю тут больше проблема в том как конкретно корутины используются чем общий вопрос
a

adev_one

06/03/2019, 4:39 AM
@gildor Спасибо за совет! В итоге, сделал именно sealed классы для каждого варианта. Но, всё же, интересно, почему выбрано именно такое поведение корутин по-умолчанию
val scope = CoroutineScope(<http://Dispatchers.IO|Dispatchers.IO>)

scope.async {
	
	// Я хочу обработать exception здесь 
	// и не пробрасывать его на уровень выше,
	// но он пробрасывается
	val result = try { 
		otherCallAsync(this).await()
	} catch (e: Exception) {
		fallback
	}

	result
}

fun otherCallAsync(scope: CoroutineScope) = scope.async {
	throw RuntimeException("no internet")
}
g

gildor

06/03/2019, 4:42 AM
Попозже скину почему такое поведение выбрано, что бы не отменять скоуп и обработать ошибку руками просто создайте CoroutineScope передав SupervisorJob
a

adev_one

06/08/2019, 9:30 AM
@gildor Добрый день! Можете, пожалуйста, скинуть материалы по поводу этого поведения?
g

gildor

06/08/2019, 9:30 AM
Ох. забыл
прежде всего это описано в официальном гайде по корутинам https://kotlinlang.org/docs/reference/coroutines/exception-handling.html
там же написано как использовать SupervisorJob что бы обрабатывать отмену родительского скопа вручную
Вот еще немного контекста, там описано почему такое поведение по умолчанию, но issue о том что необходимо еще и SupervisorJob https://github.com/Kotlin/kotlinx.coroutines/issues/576
@adev_one Еще раз возвращаясь к вашему примеру выше, хотел бы добавить, что как демонстрация этого поведения он работает, но сам подход не должен быть использоваться для продакшн кода, например функции которые возвращают Deferred должны быть очень редки и по хорошему должно быть переписано:
val scope = CoroutineScope(<http://Dispatchers.IO|Dispatchers.IO>)

scope.async {
    val result = try { 
        otherCallAsync(this)
    } catch (e: Exception) {
        fallback
    }

    result
}

fun otherCallAsync() {
   throw RuntimeException("no internet")
}
Deferred по хорошему нужен только что бы паралелить операции, создавать async и сразу вызывать await всегда значит не верное использование (в данном случае скрытое правда за вызовом функции)
a

adev_one

06/08/2019, 11:19 AM
@gildor Спасибо за материалы! Раньше не видел issue на github. Стало понятнее, почему было принято такое решение. Оно действительно оптимально для бэка, где у нас много клиентов на одной ноде. Нам async нужен именно для parallel исполнения, а не concurrent. За счёт того, что клиентов много и они исполняются concurrently и так, мы эффективно утилизируем ресурсы. Если бы мы использовали async внутри handler’ов - нам бы это ничего не дало так как просто бы выросла очередь. Но на мобилках другой кейс. Мы хотим доставить данные пользователю как можно скорее и если что-то отвалится - всё равно что-то показать. Чаще всего, одновременно исполняется только один Interactor, но он ходит в несколько источников. И все они нестабильны. И интернет плохой. Поэтому хочется использовать что-то для решения этой проблемы, но async не подходит, а альтернатив в корутинах я не знаю. Именно поэтому возник такой вопрос.
g

gildor

06/09/2019, 5:37 AM
@adev_one Не уверен что это как то отличается при использовании на бэкенде или на клиенте, и там и там есть разные юз кейсы, подходящие под разные способы использования лайфсайкла корутин Не понимаю почему вы пишите, что async не подходит, просто используйте SupervisorJob:
val scope = CoroutineScope(SupervisorJob () + <http://Dispatchers.IO|Dispatchers.IO>)
Мои комменты выше были по отношению к стилю того как вы ваш код групируете, что если есть возможность, лучше использовать только suspend функции и паралелить их врапая в async, но только на месте вызова, где можно будет легко обработать ошибку, это просто то как рекомендуется в официальном гайде это делать, опять же там где возможно и это зависит от юз кейса и если у вас есть пример кода, то как вы используете корутины, то можно было бы более предметно поговорить