Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: ScalaJS's KyoApp #807

Merged
merged 1 commit into from
Dec 6, 2024
Merged

Conversation

HollandDM
Copy link
Contributor

@HollandDM HollandDM commented Nov 1, 2024

resolve #784

@HollandDM
Copy link
Contributor Author

It's quite late in my time zone, I'll put some descriptions tomorrow

Copy link
Collaborator

@hearnadam hearnadam left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for taking this on!


final def main(args: Array[String]) =
this._args = args
if initCode ne null then initCode()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could throw an exception in case the App has no run calls. I imagine some users might try to define def run like in other effect systems and it'd just not execute anything silently.

Copy link
Collaborator

@hearnadam hearnadam left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mind adding a test with multiple runs if we don't have one already?

I think it can be as simple as building a list of runs

val x = ListBuffer...
object TestApp extends KyoApp:
  run { IO(x += 1) }
  run { IO(x += 2) }
TestApp.main(Array.empty)
assert(x == Seq(1, 2, 3....)

@HollandDM
Copy link
Contributor Author

Updated the code a bit, and added a small test for KyoApp in js

@HollandDM HollandDM force-pushed the ScalaJS-for-KyoApp branch 2 times, most recently from 87e52f1 to 60a85a5 Compare November 7, 2024 04:17
@HollandDM
Copy link
Contributor Author

Updated, fix and add one more test for KyoApp on JS

@HollandDM
Copy link
Contributor Author

Updated, squash commits

@@ -30,6 +31,16 @@ class KyoAppTest extends Test:
runs <- ref.get
yield assert(runs == 3)
}
"multiple runs on JS" taggedAs jsOnly in run {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both JVM and JS should pass this test. I notice we have a similar test, but it doesn't check ordering which is important. You can merge them if you want.

Suggested change
"multiple runs on JS" taggedAs jsOnly in run {
"multiple ordered runs" in run {

kyo-core/shared/src/main/scala/kyo/KyoApp.scala Outdated Show resolved Hide resolved
@@ -30,6 +34,17 @@ class KyoAppTest extends Test:
runs <- ref.get
yield assert(runs == 3)
}
"multiple runs on JS" taggedAs jsOnly in {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is necessary for JVM and JS. We can delete the other one.

Suggested change
"multiple runs on JS" taggedAs jsOnly in {
"ordered runs" in {


final protected def args: Array[String] = _args

private var _args: Array[String] = null

private val initCode = new ListBuffer[() => Unit]
protected var maybeProc: Maybe[() => Unit] = Absent
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be Iterable[() => Unit]? Or IterableOnce? I think the changes in this PR break multiple runs on JVM.

val result = IO.Unsafe.run(Abort.run(Async.runAndBlock(timeout)(handle(v)))).eval
printResult(result)
)
if maybeProc.isEmpty then
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't this only be true once? I think we can make the maybeProc a generic Iterable:

private var initCode: Iterable[() => Unit] = Nil

Then we can just always update it. Both versions should be able to conform to this signature.

Scala JS:
initCode = maybeProc.toList
jvm
initCode = procListBuffer.toList

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I understand what you're trying to say now, I'll try to update this.


final override protected def run[A: Flat](v: => A < (Async & Resource & Abort[Throwable]))(using Frame): Unit =
import AllowUnsafe.embrace.danger
initCode += (() =>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JVM side, code is pretty much the same

@@ -30,20 +34,47 @@ class KyoAppTest extends Test:
runs <- ref.get
yield assert(runs == 3)
}
"effects" taggedAs jvmOnly in {
"orderred runs" in {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

update this test for both platforms

@@ -30,20 +34,47 @@ class KyoAppTest extends Test:
runs <- ref.get
yield assert(runs == 3)
}
"effects" taggedAs jvmOnly in {
"orderred runs" in {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"orderred runs" in {
"ordered runs" in {

final override protected def run[A: Flat](v: => A < (Async & Resource & Abort[Throwable]))(using Frame): Unit =
import AllowUnsafe.embrace.danger
val currentAsync: Unit < (Async & Abort[Throwable]) =
Async.timeout(timeout)(handle(v)).map(value => printResult(Result.success(value)))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you put the timeout within the initiation of the Unit function, (when you update initCode) you will avoid some sleep fibers and potentially racy behavior.

I think it makes sense to timeout the whole application anyways.

Copy link
Contributor Author

@HollandDM HollandDM Nov 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mimicked the code in JVM (timeout for each runs). Will updated this

maybePreviousAsync = maybePreviousAsync match
case Absent => Present(currentAsync)
case Present(previousAsync) => Present(previousAsync.map(_ => currentAsync))
if initCode.isEmpty then
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was initially wondering why this would work, when only the first fiber would be initialized, but then I realized the first fiber would begin the execution of the next...

In my testing, I think this code also causes repeatWithDelay to run forever. The run fiber is already complete when the timeout expires.

Regardless, I re-wrote this a bit which I think is a simplification:

package kyo

import kyo.Maybe.Absent
import kyo.Maybe.Present

abstract class KyoAppPlatformSpecific extends KyoApp.Base[Async & Resource & Abort[Throwable]]:

    private var previous: Maybe[Unit < (Async & Abort[Throwable])] = Absent

    final override protected def run[A: Flat](v: => A < (Async & Resource & Abort[Throwable]))(using Frame): Unit =
        import AllowUnsafe.embrace.danger
        val current: Unit < (Async & Abort[Throwable]) =
            Abort.run(handle(v)).map(result => IO(printResult(result)).andThen(Abort.get(result)).unit)

        previous = previous match
            case Absent            => Present(current)
            case Present(p) => Present(p.map(_ => current))

        initCode = previous.map { async => () =>
            val raced = Clock.repeatWithDelay(1.hour)(()).map { fiber =>
                val race = Async.race(fiber.get, async)
                Async.timeout(timeout)(race)
            }
            val _ = IO.Unsafe.run(Async.run(raced)).eval
        }.toList

    end run

end KyoAppPlatformSpecific

In KyoApp I made initCode a variable with a List for ease of conversion with Maybe.

protected var initCode: List[() => Unit] = List.empty

You can check if the Clock fiber is still running by changing () to a Console.println and increase the frequency of heartbeat.

Comment on lines 41 to 43
run { IO(x += 1) }
run { IO(x += 2) }
run { IO(x += 3) }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's mix in Async just for posterity:

            run { Abort.delay(10.millis)(IO(x += 1)) }
            run { Async.delay(10.millis)(IO(x += 2)) }
            run { Async.delay(10.millis)(IO(x += 3)) }

@fwbrasil
Copy link
Collaborator

fwbrasil commented Dec 4, 2024

folks, anything pending to merge this? We can follow up with improvements if needed. It'd be nice to get the issue fixed.

@HollandDM
Copy link
Contributor Author

I'll try to finalize this in the upcoming days, I think there aer only some little code conventions/styles left to adjust

@HollandDM HollandDM force-pushed the ScalaJS-for-KyoApp branch 2 times, most recently from 36ea6a1 to 169cded Compare December 5, 2024 06:51
@HollandDM HollandDM requested a review from hearnadam December 5, 2024 06:54
@HollandDM
Copy link
Contributor Author

@fwbrasil @hearnadam I've updated the code, it should be good now


import scala.collection.mutable.ListBuffer

abstract class KyoAppPlatformSpecific extends KyoApp.Base[Async & Resource & Abort[Throwable]]:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The build is failing in Native. I think it's just a matter of moving this file to kyo-core/jvm-native/src/

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated and tested, should work now

@HollandDM HollandDM requested a review from fwbrasil December 6, 2024 14:59
@hearnadam hearnadam merged commit 997bbcb into getkyo:main Dec 6, 2024
3 checks passed
@fwbrasil
Copy link
Collaborator

fwbrasil commented Dec 6, 2024

@HollandDM congrats on your first contribution 🙌

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

ScalaJS with KyoApp
3 participants