Fork me on GitHub
#cljs-dev
<
2017-06-22
>
dpsutton00:06:04

i think i remember this being discussed recently, but are clojurescript keywords immutable and allocated? The reason being can someone flood the environment with many keywords and eventually exhaust the storage of them?

dpsutton00:06:42

i thought i remembered a discussion that both clojure and cljs intern keywords and you cannot unallocate them?

mfikes00:06:36

But there would only be a bounded number of those. I suppose you are concerned with dynamically-created keywords.

dpsutton00:06:45

is that what was optimized recently? I lurk in here but don't usually participate

dpsutton01:06:04

someone asking if their framework pulls them out of query strings can someone be malicious

mfikes01:06:12

No, the recent optimization you are recalling is likely related to ^:const inlining

dpsutton01:06:22

ah yes i confused the two

mfikes01:06:57

A dynamically-created keyword (created via cljs.core/keyword) is just a deftype instance.

mfikes01:06:28

In other words, (->Keyword nil "foo" "foo" 1268894036) is :foo

dpsutton01:06:01

so just an object that can be collected then?

dpsutton01:06:14

i thought i heard about something like a weak reference cache

mfikes01:06:02

I can’t see any other way of looking at it. (source keyword) doesn’t seem to register them (there’s only about 20 lines of code there.)

mfikes01:06:54

Perhaps there is something interesting in Clojure, and thus identical? vs. keyword-identical? being needed in ClojureScript.

dpsutton01:06:22

i'll look into that. thanks @mfikes

mfikes01:06:58

Yeah @dpsutton in Clojure you can see (source keyword) using clojure.lang.Keyword/intern

dpsutton01:06:25

yeah looks like a weak reference hash which means it can never be gc'd i believe

mfikes01:06:51

Well, it is not a WeakHashMap, so the ConcurrentHashMap entries would at least stick around, even if the guts of the values are gc’d

dpsutton01:06:53

hmmm

(defn emits-keyword [kw]
  (let [ns   (namespace kw)
        name (name kw)]
    (emits "new cljs.core.Keyword(")
    (emit-constant ns)
    (emits ",")
    (emit-constant name)
    (emits ",")
    (emit-constant (if ns
                     (str ns "/" name)
                     name))
    (emits ",")
    (emit-constant (hash kw))
    (emits ")")))

mfikes01:06:10

Right, if you fire up a REPL in :repl-verbose mode you will see that :foo gets emitted as

new cljs.core.Keyword(null,"foo","foo",(1268894036))

dpsutton01:06:45

and that's just the deftype you were mentioned before

dpsutton01:06:55

interesting that the hash is a parameter passed in

mfikes01:06:52

The hash is not pre-calculated if instead you do (keyword "foo")

mfikes01:06:24

So runtime keywords are lazy, compile-time are eager, wrt hashing

dpsutton01:06:11

totally makes sense. thanks for taking the time! super helpful

mfikes01:06:07

This loop is not causing memory to grow for me (empirical verification)

(loop [n 0]
  (keyword (str "kw" n))
  (recur (inc n)))

favila01:06:48

In cljs, if compiler option :optimize-constants is true, keyword and symbol literals will be "interned" (defined only once globally and included by reference in code) at compile time. The compiler will also precompute their hash, since clj and cljs hash symbols and keywords the same

favila01:06:37

But runtime keywords/symbols are never interned and don't precompute hashes.

favila01:06:50

I think clj used to intern more but stopped

favila01:06:06

In js interning is impossible without leaking memory

favila01:06:18

It has no weak refs

dpsutton03:06:25

thanks @favila. side note, we need to do lunch again soon