janet-lang / janet

A dynamic language and bytecode vm
https://janet-lang.org
MIT License
3.43k stars 221 forks source link

(gensym) occassionally generates non-unique values #1298

Closed fuxoft closed 11 months ago

fuxoft commented 11 months ago

During my Janet experiments with coroutines, fibers and channels, I found out that gensym does not always generate unique values (even when my script does not use threads).

I managed to isolate the problem in this 50-line script.

When you run it, it endlessly prints (gensym) values (at line 30), which are constant:

_00000x
_00000x
_00000x
_00000x
_00000x
_00000x
...

When I attempt to simplify the code further, the printed values become unique again.

When I change the constant 10000 at line 46 to 10, the printed values also become unique.

I am using Janet 1.31.0-c31314be linux/x64/gcc.

sogaiu commented 11 months ago

No real answers, but I found that changing the end of the code to be [1]:

(def wait
  (do
    (def render-fun
      (do
        (def cor (:renderer-coroutine track1))
        (fn [] (resume cor))))
    (fn []
      (repeat 600
        (render-fun)))))

(repeat 6
  (:note-on track1 60)
  (wait))

yields the output:

_00000y
_00000z
_00000A
_00000B
_00000B
_00000C

So it looks like gensym values are not constant the whole time.

Neither does the repetition seem to be from some point onwards.

$ janet
Janet 1.31.0-7b4c3bdb linux/x64/gcc

Script below for archival purposes [2]

(defn generator
  []
  (def factory @{})
  (def generator-fun
    (fn [output]
      (ev/give output :stream-start)
      (ev/give output 0)
      (ev/give output :stream-end)))
  (put factory :generate
       (fn [self batch]
         (def stream-out (ev/chan 1))
         (def generator (ev/chan))
         (ev/spawn (generator-fun generator))
         (assert (= :stream-start (ev/take generator)))
         (ev/give stream-out (ev/take generator))
         (assert (= :stream-end (ev/take generator)))
         stream-out))
  (put factory :new
       (fn [&]
         (def object (thaw factory))
         object))
  factory)

(def sinetone
  (generator))

(def track1
  @{:generators @{}
    :note-on (fn [self note-num]
               (print (gensym))
               (def generator (:new sinetone))
               (put (self :generators) :just-one generator))
    :renderer-coroutine (fn [self]
                          (coro
                            (forever
                              (map (fn [gen] (:generate gen 1)) (self :generators))
                              (yield 0))))})

(def wait
  (do
    (def render-fun
      (do
        (def cor (:renderer-coroutine track1))
        (fn [] (resume cor))))
    (fn []
      (repeat 600
        (render-fun)))))

(repeat 6
  (:note-on track1 60)
  (wait))

[1] So the 10000 has been replaced with 600 and the forever loop has been replaced with a repeat 6 loop.

[2] Formatting somehow gets messed up when rendered...looks ok raw...how strange.

bakpakin commented 11 months ago

You have discovered the garbage collector cleaning up the old symbols :). If you keep references to the old symbols around in the program, our turn off gc via (gcsetinterval 0xFFFFFFFF), the issue disappears. Specifically, you will find you can't have gensym generate a symbol that is currently interned. When a symbol is garbage collected, it is uninterned. Sort of like how when you allocate memory, then free the memory, later allocations could return the same pointer.