-
Notifications
You must be signed in to change notification settings - Fork 194
Description
I found this because I was working on some async code in koka-community/uv, where the relevant resource gets uv_close()d when its last reference is dropped, rather than requiring the programmer remember to close everything explicitly.
Basically, it seems that when async code is involved the reference count of an object (specifically a wrapped C pointer) doesn't ever drop to 0.
using:
- koka-lang/koka @ timwhiting/uv-basics (9e28d76)
- koka-community/std @ main (f2e9e77)
I wrote the following test module to demonstrate the issue:
import std/test
import std/async/async
import std/num/int32
import std/time/duration
import uv/event-loop
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 get-refcount-copy(box: any)
box.get-refcount
fun get-refcount-async(^box: any)
val count = box.get-refcount
wait(1.milli-seconds)
count
fun main()
with default-event-loop
with async/handle
run-tests(suite)
fun suite()
effectful-test("single-use")
expect(0)
val box = create-box()
get-refcount(box).int
effectful-test("get refcount from a copy")
expect((1, 0))
val box = create-box()
val from-copy = box.get-refcount-copy().int
val from-ref = box.get-refcount().int
(from-copy, from-ref)
effectful-test("get refcount from a copy with async effect")
expect((1, 0, 0))
val box = create-box()
val from-copy = box.get-refcount-copy().int
val from-ref-async = box.get-refcount-async().int
val from-ref-after-async = box.get-refcount().int
(from-copy, from-ref-async, from-ref-after-async)
It prints:
single-use
- ok
get refcount from a copy
- ok
get refcount from a copy with async effect
Expectation failed (ref-test:41)
expected: (1,0,0)
got: (1,2,1)
- failed
# ref-test:40
1 failures, 2 successes
Failed tests:
- get refcount from a copy with async effect
error : command failed (exit code 1)
The second and third tests are essentially the same, except the third uses an async effect.
Here's a simpler reproduction without using the test module:
import std/async/async
import std/num/int32
import std/time/duration
import uv/event-loop
import std/core/debug
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 get-refcount-copy(box: any)
box.get-refcount
fun get-refcount-async(^box: any)
val count = box.get-refcount
wait(1.milli-seconds)
count
fun print-result(action: () -> <exn,console,div|e> a, ?a/show: (a) -> div string): <console,div|e> ()
val result = try(action)
match result
Ok(result) -> println(result.show)
Error(err) ->
println(err.show)
impossible("error")
fun main()
with default-event-loop
with async/handle
print-result
val box = create-box()
box.get-refcount().int
print-result
val box = create-box()
box.get-refcount-async().int
This prints:
0
1
It seems to cap out at two references if I change the second block to run many async actions:
val box = create-box()
val a = box.get-refcount-async().int
val b = box.get-refcount-async().int
val c = box.get-refcount-async().int
(a,b,c)
-> prints (1,2,2,2)
This could be somehow caused by the async implementation in C (in which case it's just a problem for #852), but it seems more likely this is a bug with how the compiler handles the various raw ctl operations in the std/async/async effect.