Comments 8
Выглядит очень заманчиво, но хотелось бы больше деталей о реализации - пока что-то непонятно. Зачем классы-заглушки? Сколько их генериться? Когда? Можно же, наверное, создать руками любой стектрейс, не?
Зачем классы-заглушки?
Классы-заглушки нужны для эмуляции стека вызова. Например, рассмотрим 3 функции, которые по очереди друг друга вызывают:
suspend fun fun1() {
fun2()
delay(10)
}
suspend fun fun2() {
fun3()
delay(10)
}
suspend fun fun3() {
delay(10)
throw Exception()
}
Мы вызваем fun1
, после этого вызываются fun2
и fun3
и корутина "засыпает".
В обычном случае после "пробуждения" будет вызвана только функция fun3
и когда сгенерируется исключение, в его трейсе будет содержаться только этот метод.
C моей библиотекой происходит по-другому: генерируется класс-заглушка, содержащий методы fun1
и fun2
, fun1
вызывает fun2
, а fun2
"будит" корутину. В результате стек вызова содержит все 3 метода. И стектрейс исключения содержит их все.
Сколько их генериться?
Столько же, сколько содержится классов с suspend-функциями.
Когда?
Для класса A генерируется заглушка во время первого "пробуждения" корутины, содержащей в стеке методы класса A. После этого заглушка кешируется и повторные "пробуждения" не приводят к генерации заглушки.
Можно же, наверное, создать руками любой стектрейс, не?
Создать-то можно. Нельзя переписать стектрейс исключения, сгенерированного во время выполнения корутины.
А как вы понимаете, какая функцию какую вызывает в классе А (ведь все уже скомпилировано)? Ведь там может быть сколь угодно жуткая логика, не?
Как это будет работать вот для такого случая (для многократных вызовов fun1). Не будет ли иногда в стектрейсе лишнего fun2?
suspend fun fun1() {
if ((0..1).random() == 0) {
fun2("throw fun2")
} else {
fun3("directly fun3")
}
delay(10)
}
suspend fun fun2(x: String) {
fun3(x)
delay(10)
}
suspend fun fun3(x: String) {
delay(10)
throw Exception(x)
}
Для генерации заглушек мне не нужно знать, в каком порядке они будут вызываться - они могут вызывать себя как угодно, в каком угодно порядке.
Это обеспечивается за счет использования MethodHandle API. Просто, грубо говоря, при вызове метода заглушки я ему передаю массив MethodHandle[] которые нужно вызвать.
Конкретный порядок я определяю именно в момент "пробуждения" корутины. В вашем примере все будет работать
upd:
Если вопрос в том, как я определяю стек вызова во время "пробуждения" - в Kotlin stdlib есть функция для этого.
Я попробую вашу либу, которая для JVM. У меня есть бек полностью на котлине + спринг, правда он сделан на блокирующих библиотеках, но я его потихоньку мигрирую на реактивные с использованием корутин. Подключу и буду юзать, спасибо за хорошую идею
? Баги/вопросы/предложения/замечания можете оставлять в Github issue, Gitter или в личном сообщении мне)
Спасибо за статью. Очень интересная библиотека. Не рассматривали вариант с плагином для компилятора вместо генерации классов в рантайме?
О дебаге Kotlin-корутин