puniverse / quasar

Fibers, Channels and Actors for the JVM
http://docs.paralleluniverse.co/quasar/
Other
4.56k stars 575 forks source link

Some Kotlin code using `fiber` doesn't work properly with AoT instrumentation #140

Closed Jire closed 8 years ago

Jire commented 8 years ago

This is with Kotlin 1.0.0-beta-3595. I am receiving a VerifyError with this code:

package org.abendigo

import co.paralleluniverse.fibers.Fiber
import co.paralleluniverse.kotlin.fiber
import co.paralleluniverse.strands.Strand
import org.jire.kotmem.isKeyDown
import java.util.concurrent.TimeUnit

fun sleep(duration: Int, timeUnit: TimeUnit = TimeUnit.MILLISECONDS) = sleep(duration.toLong(), timeUnit)

fun sleep(duration: Long, timeUnit: TimeUnit = TimeUnit.MILLISECONDS) = Strand.sleep(duration, timeUnit)

inline fun <T> every(duration: Int, timeUnit: TimeUnit = TimeUnit.MILLISECONDS, crossinline action: () -> T):
        Fiber<Unit> = every(duration.toLong(), timeUnit, action)

inline fun <T> every(duration: Long, timeUnit: TimeUnit = TimeUnit.MILLISECONDS, crossinline action: () -> T) =
        fiber {
            action.invoke()
            while (!Strand.interrupted()) {
                sleep(duration, timeUnit)
                action.invoke()
            }
        }

open class UpdateableLazy<T>(private val lazy: () -> T) {

    private var current: T? = null
    private var previous: T? = null

    operator fun invoke(): T {
        if (current == null) unaryPlus()
        return current!!
    }

    operator fun unaryPlus(): T {
        previous = current
        current = lazy.invoke()
        return current!!
    }

    operator fun unaryMinus(): T {
        current = previous ?: return current!!
        return current!!
    }

}

object keys {
    operator fun get(vKey: Int) = isKeyDown(vKey)
}

fun main(args: Array<String>) {
    every(1000) { println("test") }
    Thread.sleep(Long.MAX_VALUE)
}
Exception in thread "main" java.lang.VerifyError: Bad type on operand stack
Exception Details:
  Location:
    org/abendigo/AbendigoKt.main([Ljava/lang/String;)V @38: invokespecial
  Reason:
    Type long_2nd (current frame, stack[3]) is not assignable to 'kotlin/jvm/functions/Function0'
  Current Frame:
    bci: @38
    flags: { }
    locals: { '[Ljava/lang/String;', integer, 'java/util/concurrent/TimeUnit', integer, long, long_2nd }
    stack: { uninitialized 31, uninitialized 31, long, long_2nd, 'java/util/concurrent/TimeUnit' }
  Bytecode:
    0x0000000: 2a12 3fb8 000f 1103 e83c 014d 053e 001d
    0x0000010: 057e 9900 07b2 001c 4d00 1b85 3704 00bb
    0x0000020: 0041 5916 042c b700 44c0 002f b800 3557
    0x0000030: b200 4ab6 004e c000 50b6 0054 b800 59b1
    0x0000040:                                        
  Stackmap Table:
    append_frame(@25,Integer,Object[#25],Integer)

    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Class.java:264)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:122)
Jire commented 8 years ago

Quasar seems extremely glitchy in my use cases. For example the following straightforward code results in hi there being printed twice.

fiber {
    println("hi there")
    Fiber.sleep(3000)
    println("hi there later")
}
circlespainter commented 8 years ago

Shouldn't sleep be @Suspendable?

Jire commented 8 years ago

@circlespainter you're right. This fixes the VerifyError but the application still prints numerous times. It may be a bug in Kotlin.

circlespainter commented 8 years ago

Ok, this is somewhat articulated:

package org.abendigo

import co.paralleluniverse.fibers.Fiber
import co.paralleluniverse.fibers.Suspendable
import co.paralleluniverse.kotlin.fiber
import co.paralleluniverse.strands.Strand
import java.util.*
import java.util.concurrent.TimeUnit

@Suspendable fun sleep(duration: Long, timeUnit: TimeUnit = TimeUnit.MILLISECONDS) = Strand.sleep(duration, timeUnit)

inline fun <T> every(duration: Int, timeUnit: TimeUnit = TimeUnit.MILLISECONDS, crossinline action: () -> T):
    Fiber<Unit> = every(duration.toLong(), timeUnit, action)

inline fun <T> every(duration: Long, timeUnit: TimeUnit = TimeUnit.MILLISECONDS, crossinline action: () -> T) =
    fiber @Suspendable {
        println("\nEvery - start\n")
        action()
        while (true) {
            println("Every - sleeping")
            sleep(duration, timeUnit)
            println("Every - action")
            action()
        }
    }

public fun main(args: Array<String>) {
    every(1000) { println("test ${Date().time}") }
    Thread.sleep(Long.MAX_VALUE)
}

Workaround: use the agent instead.

Jire commented 8 years ago

@circlespainter If I use non-AoT it does work properly as expected which is fine for development purposes. Unfortunately I need AoT because my software needs to be given to regular clients. Hopefully the bug will be fixed soon, thanks!

Jire commented 8 years ago

@circlespainter When using Quasar's agent instrumentation the following code fails:

val TYPE_TO_BYTES = mapOf(Boolean::class.qualifiedName to 1, Byte::class.qualifiedName to 1,
        Short::class.qualifiedName to 2, Int::class.qualifiedName to 4, Long::class.qualifiedName to 8,
        Float::class.qualifiedName to 4, Double::class.qualifiedName to 8)

due to

java.lang.NoClassDefFoundError: Could not initialize class kotlin.reflect.jvm.internal.impl.platform.JavaToKotlinClassMap
    at kotlin.reflect.jvm.internal.RuntimeTypeMapper.mapJvmClassToKotlinClassId(RuntimeTypeMapper.kt:244)
    at kotlin.reflect.jvm.internal.KClassImpl.getClassId(KClassImpl.kt:56)
    at kotlin.reflect.jvm.internal.KClassImpl.getQualifiedName(KClassImpl.kt:109)
circlespainter commented 8 years ago

Is the above problem somewhat related to the previous one? If not, do you mind opening a separate issue? Also, does it happen with AoT too? What does it say if you turn instrumentation debugging on (append =vdc to the agent)?

As for agent-based instrumentation: why is it a problem to ship a final product to clients using it? It is perfectly fine in production and actually it's even the only way to use some Comsat integrations without having to AoT-instrument and repackage 3rd-party libraries. If your only concern is to simplify deployment (and I can see no other) just use Capsule.

Jire commented 8 years ago

@circlespainter I will open a new issue with more information about my problem.

I intend to deliver my app as a single JAR and I thought I'd have to include scripts to pass the Quasar arguments and wouldn't I also have to include the Quasar core JAR? Does Capsule let me pack Quasar core inside of a single JAR and run it with AoT?

circlespainter commented 8 years ago

@Jire Sorry, missed your comment. Yes, Capsule allows you to pack Java/native agents and everything you need in a single JAR.

circlespainter commented 8 years ago

@Jire Ok, I now believe this is a duplicate of #112: if you remove inlining it works (for me at least). If you confirm I'll close this issue and bring the code to the existing one.