Skip to content

Incorrect refcounting with async exceptions / finalize #51

Description

@timbertson

I noticed this because in my uv branch, the close(handle) function acts as an "assert-unique-reference". This seemed to be working correctly in the successful case, but failing (with a dangling refcount of 2) in the case where an exception is thrown, and the handle is attempted to be closed in a finally block.

This is a similar issue to koka-lang/koka#854, however the koka compiler fix in koka-lang/koka#863 is not sufficient on its own.

The only combination of koka & async.kk which I've found to work correctly is:

output:

async finally: 0
async error: failed...
sync finally: 0
sync error: failed...

Using any other branch of koka or std results in the following incorrect output:

async finally: 2
async error: failed...
sync finally: 0
sync error: failed...

This implies that the refcount can leak either via lack of the improve-continuation-refcount, or by using the version of async.kk in both libuv-basics and koka-community/std/main (they're identical). It's not obvious what changes in my async-cancel branch cause this change in behaviour.

Test case:

import std/num/int32
import std/async/async

extern create-box(): io-noexn any
  c inline "kk_cptr_raw_box(&kk_free_fun, kk_malloc(sizeof(int), _ctx), _ctx)"

extern get-refcount(^box: any): io-noexn int32
  c inline "kk_block_refcount(kk_box_to_ptr(box, _ctx))"

fun main()
  try (finalizer-async) fn(e)
    println("async error: " ++ e.show)
    0.int32
  ()

  try (finalizer-sync) fn(e)
    println("sync error: " ++ e.show)
    0.int32
  ()

fun finalizer-async()
  val box = create-box()
  with finally {
    println("async finally: " ++ box.get-refcount().show)
  }
  box-fail-async()
  box.get-refcount()

fun finalizer-sync()
  val box = create-box()
  with finally {
    println("sync finally: " ++ box.get-refcount().show)
  }
  box-fail-sync()
  box.get-refcount()

fun failure()
  Exception("failed...", ExnAssert)

fun box-fail-async()
  val _: () = await1(fn(cb) -> cb(Error(failure()))).untry
  ()

fun box-fail-sync()
  throw-exn(failure())

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions