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

TypeError: Cannot read properties of undefined (reading 'hnd') #626

Open
osa1 opened this issue Dec 18, 2024 · 2 comments
Open

TypeError: Cannot read properties of undefined (reading 'hnd') #626

osa1 opened this issue Dec 18, 2024 · 2 comments

Comments

@osa1
Copy link
Contributor

osa1 commented Dec 18, 2024

Code from "Structured Asynchrony with Algebraic Effects":

import std/num/int32

fun main()
  with async-handle

  with handler
    ctl throw_(s: string)
      throw(s)

  println("before wait")
  wait(0.i32)
  println("after wait")

alias result<a> = either<string, a>

effect async
  ctl await(initiate: (result<a> -> io ()) -> io ()): result<a>

val async-handle = handler
  ctl await(initiate)
    initiate(resume)

fun await1(initiate: (a -> io ()) -> io ()): <async, exn_> a
  untry_(await(fn(cb)
    initiate(fn(x) cb(Right(x)))
  ))

fun await0(initiate: (() -> io ()) -> io ()): <async, exn_> ()
  await1(fn(cb) initiate(fn() cb(())))

fun wait(secs: int32): <async, exn_> ()
  await0 fn(cb)
    set-timeout(cb, secs * 1000.i32)
    ()

alias timeout-id = int

extern set-timeout(cb: () -> io (), ms: int32): io int
  js inline "setTimeout(#1, #2)"

effect exn_
  ctl throw_(s: string): a

fun untry_(ex: either<string, a>): <exn_> a
  match ex
    Left(exn) -> throw_(exn)
    Right(x) -> x

Output:

$ node .koka/v3.1.2/js-debug-463762/async__main.mjs
before wait
file:///home/omer/koka/why-effects/.koka/v3.1.2/js-debug-463762/std_core_hnd.mjs:61
    _evv._cfc = ev.hnd._cfc;
                   ^

TypeError: Cannot read properties of undefined (reading 'hnd')
    at Object._evv_swap_create1 (file:///home/omer/koka/why-effects/.koka/v3.1.2/js-debug-463762/std_core_hnd.mjs:61:20)
    at Module._open_at1 (file:///home/omer/koka/why-effects/.koka/v3.1.2/js-debug-463762/std_core_hnd.mjs:1457:25)
    at _mlift_await1_10046 (file:///home/omer/koka/why-effects/.koka/v3.1.2/js-debug-463762/async.mjs:144:24)
    at Array.<anonymous> (file:///home/omer/koka/why-effects/.koka/v3.1.2/js-debug-463762/async.mjs:171:14)
    at file:///home/omer/koka/why-effects/.koka/v3.1.2/js-debug-463762/std_core_hnd.mjs:220:21
    at open1 (file:///home/omer/koka/why-effects/.koka/v3.1.2/js-debug-463762/std_core_hnd.mjs:1527:11)
    at file:///home/omer/koka/why-effects/.koka/v3.1.2/js-debug-463762/std_core_hnd.mjs:1566:14
    at _yield.conts.<computed> (file:///home/omer/koka/why-effects/.koka/v3.1.2/js-debug-463762/std_core_hnd.mjs:243:41)
    at file:///home/omer/koka/why-effects/.koka/v3.1.2/js-debug-463762/std_core_hnd.mjs:220:21
    at file:///home/omer/koka/why-effects/.koka/v3.1.2/js-debug-463762/std_core_hnd.mjs:649:49

Node.js v22.11.0

The error seems to be happening before the setTimeout call. Replacing the inline JS to setTimeout as something invalid like inline js "blah" doesn't change how it fails.

@TimWhiting
Copy link
Collaborator

import std/num/int32

fun main()
  with handler
    ctl await(initiate)
      initiate(resume)
  try {
    println("before wait")
    wait(1.i32)
    println("after wait")
  } fn(err) {
    println(err)
  }

alias aio = <async,io-noexn>
alias aiox = <async,io>
alias result<a> = either<string, a>

effect async
  ctl await(initiate: (result<a> -> io-noexn ()) -> io-noexn ()): result<a>

fun await1(initiate: (a -> io-noexn ()) -> io-noexn ()): aiox a
  match await(fn(cb) initiate(fn(x) cb(Right(x))))
    Left(r) -> throw(r)
    Right(g) -> g

fun await0(initiate: (() -> io-noexn ()) -> io-noexn ()): aiox ()
  await1(fn(cb) initiate(fn() cb(())))

fun wait(secs: int32): aiox ()
  await0 fn(cb)
    set-timeout(cb, secs * 1000.i32)
    ()

alias timeout-id = int

extern set-timeout(cb: () -> io-noexn (), ms: int32): io-noexn int
  js inline "setTimeout(#1, #2)"

This is the code you want.

Unfortunately there isn't very good runtime error messages when handlers don't exists.

The issue is that this callback function has type io, which includes the user handled effect exn. However, this callback gets called from the javascript event loop, which means that it is called at a point where no handlers are in scope. However, the callback function passed here expects the handlers to be at specific indices. That is where the error comes from.

extern set-timeout(cb: () -> io (), ms: int32): io int
  js inline "setTimeout(#1, #2)"

The fix is to use io-noexn which are all of the io effects that are "unhandled" (div, ndet, ui, etc), or handled by the runtime (console, etc).

All callbacks passed to the native code that will be executed at the event loop should never include handled effects. I believe at the time the paper was published exn was not user handled and only handled by the runtime.

@TimWhiting
Copy link
Collaborator

As far as this issue goes, I believe we should add some debug statements to the handler code to catch things like this at runtime (in debug mode), and a check to the compiler for all first class function types to extern don't include handled effects. (There might be rare cases in the std library or integrating external libraries where you are guaranteed to call the function synchronously, so I don't think it should be a hard error, but a loud warning).

@TimWhiting TimWhiting added this to the Async / Next milestone Dec 21, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants