Fork me on GitHub
#core-async
<
2019-06-10
>
pmooser18:06:48

@hiredman It appears the promise-chan issue we discussed last week is somewhat worse than I thought. It looks like a fulfilled promise-chan will always be preferred by alts! to a timeout chan, even if the timeout chan has already been triggered (ie, x millis have passed).

ghadi18:06:31

alts! randomizes the order in which it evaluates channels unless you set :priority @pmooser

ghadi18:06:02

If you alts! over ready channels (like a fulfilled promise or an elapsed timeout) you should get an even distribution of chosen ops

ghadi18:06:54

If that isn’t the case, I’d like to see a minimal repro case

hiredman18:06:36

@ghadi if you have an alts! in a loop, even if the timeout expires, the js thread isn't yielded, so the fact that the timeout has expired is never registered

hiredman18:06:08

because alts!, if it can proceed right way, never yields control of the js thread

pmooser19:06:03

@ghadi As requested, here is your case:

ghadi19:06:33

javascript, right? @pmooser

pmooser19:06:17

I actually have not run that snippet in clj, so I'm not sure if it would be the same.

pmooser19:06:33

Actually I suspect it won't be, based on some tests I had before, but I'm not 100%.

ghadi19:06:53

it makes sense given the background from @hiredman

ghadi19:06:04

99% sure it is different in clj

pmooser19:06:25

I don't agree. I mean I "understand why" from the implementation, but it doesn't make sense in "this is non-surprising and what everyone would expect from a supposedly non-deterministic choice".

pmooser19:06:38

It's basically an implementation detail, and it is undesirable.

ghadi19:06:19

yeah I also don't think it is correct -- I meant it makes sense given the impl in cljs

pmooser19:06:41

In my opinion, we shouldn't have to know the type of chans to know if our code will behave as expected - especially since chans have no mechanism for tracking identity, like a name or even an IMeta implementation.

pmooser19:06:46

Anyway, I am guessing this will never be addressed but it is definitely unfortunate. I just provided my own implementation of promise-chan that doesn't do this (because it is not based on an infinite buffer) and we will probably always avoid the other one just to be sure not to run into this.

pmooser19:06:13

I think it's basically an edge case of an implementation detail that rears its head because most buffers won't infinitely provide values (and thus be preferred indefinitely).

ghadi19:06:15

it's not a problem with promise-chan

ghadi19:06:38

it's a problem that the timeout is never fulfilled

pmooser19:06:42

That is not true.

pmooser19:06:50

The code fulfills the timeout before alts-ing.

pmooser19:06:06

Note the:

(<! t)

ghadi19:06:10

oh -- I misread

ghadi19:06:38

what mod did you make to promise-chan?

ghadi19:06:51

(in clj it behaves)

pmooser19:06:09

I wrote my own, and didn't implement it as a buffer. I implement ReadPort and WritePort myself, with a go-loop handling reads and writes (and deferring to some internal chans).

pmooser19:06:16

Thank you for testing it in clj!

pmooser19:06:01

Basically the trick (I think) is that I'm providing the same API, but because I'm falling back to operations on normal many-to-many chans and using a go-loop to ferry values around, it does yield as expected.

pmooser19:06:05

I'm sure it is marginally less efficient,

pmooser19:06:16

but for my use, the efficiency of this won't pose a problem,

pmooser19:06:24

especially given the alternatives.

pmooser19:06:59

One moment, I want to write another test to confirm a suspicion ...

pmooser19:06:01

Ok, yes, it works how I thought.

pmooser19:06:32

So it looks like any buffered chans with ready values will always be preferred over the timeout chan, even if the timeout has already triggered.

ghadi19:06:18

yup ^ it's not a promise-chan problem

pmooser19:06:50

Sure, but the reason a promise chan is "the problem" for me is that no other chan (that I know of) will indefinitely provide values. Ie, a normal buffered chan will exhaust its buffer pretty soon, and then at least timeouts/etc will get a chance.

pmooser19:06:15

So while it isn't specific to promise chan technically, the fact that it can basically get "stuck" doesn't really occur with any other buffer implementation I've seen.

ghadi20:06:48

cljs.user=> (dotimes [i 10] (println (clojure.core.async/random-array 2)))
WARNING: var: cljs.core.async/random-array is not public at line 1 <cljs repl>
#js [1 0]
#js [1 0]
#js [1 0]
#js [1 0]
#js [1 0]
#js [1 0]
#js [1 0]
#js [1 0]
#js [1 0]
#js [1 0]
@pmooser @hiredman I had a feeling we didn't have all the facts straight. alts! is broken in the 2 channel but not in the three+ channel case

ghadi20:06:40

cljs.user=> (dotimes [i 10] (println (clojure.core.async/random-array 3)))
WARNING: var: cljs.core.async/random-array is not public at line 1 <cljs repl>
#js [1 2 0]
#js [2 0 1]
#js [2 0 1]
#js [1 2 0]
#js [1 2 0]
#js [1 2 0]
#js [2 0 1]
#js [1 2 0]
#js [2 0 1]
#js [2 0 1]

ghadi20:06:47

thanks for the report @pmooser, we'll get that fixed

pmooser21:06:54

@ghadi Really? That would be great!! Thank you.

ghadi21:06:10

so it has nothing to do with the channels 🙂

ghadi21:06:20

it's alts! always trying in the same order

pmooser21:06:39

Yeah, in my case, it wasn't just two chans, but the other chan had no values

ghadi21:06:42

even the 3+ channel case it looks like channel 1 is never tried first

pmooser21:06:54

But I am glad to hear that it is something understandable and that you think it might even get fixed!!