Fork me on GitHub
#mount
<
2017-04-07
>
tolitius01:04:32

@oahner ^:on-reload :noop should only care about namespace recompilations / state redefinition. start / stop should work as usual.

tolitius01:04:42

can you give an example?

oahner01:04:23

it's the update-meta! in mount-it; it always runs even with :noop so the state gets overwritten with the blank state in @meta-state

oahner01:04:34

I had assumed it would behave like a defonce but no; the derefable state is defined with defonce but the content always gets reset by the defstate call

oahner01:04:33

What I wanted out of :noop is a defonce-like defstate that does absolutely nothing on a subsequent call and keeps the initial state running as-is

oahner01:04:58

While it does keep the initial state running through a reload (a webserver that binds to port 80 in the :start fn will not stop), it still replaces the state's @meta-state entry with a new, not running state, so the next (stop) does nothing and the subsequent (start) fails to start the actual webserver since port 80 is still in use by the previous state

tolitius01:04:39

yep, I see it. it does not cleanup the second time, since it "thinks" the state is stopped:

boot.user=> (require '[mount.tools.graph :as g])
nil
boot.user=> (pprint (g/states-with-deps))
({:name "#'boot.user/beatles",
  :order 1,
  :status #{:stopped},
  :deps #{}})

oahner01:04:54

exactly 🙂

tolitius01:04:14

wonder why I have not noticed it before..

tolitius01:04:32

since I frequently have http server as a reload noop

tolitius02:04:00

I guess the kicker is in the second call to start

tolitius02:04:23

which usually does not happen, since it's a noop to begin with: i.e. the same state keeps running

oahner02:04:54

well, the second start sees a stopped state since mount-it replaces the entry with a blank one

tolitius02:04:03

this is an interesting corner case: you redefined a noop state with a noop

oahner02:04:08

the only trace of it is in @mount/running

tolitius02:04:30

right you can:

boot.user=> (mount/running-states)
#{"#'boot.user/beatles"}

tolitius02:04:39

not to get down to atoms

oahner02:04:52

I've been poking around 😉

tolitius02:04:10

that's very good 🙂

tolitius02:04:18

so let's replay this scenario from the way you work with the real namespace

tolitius02:04:39

you have something like:

(defstate ^{:on-reload :noop} http-server 
                              :start (start-www (config :hubble))
                              :stop ((:stop-server http-server)))

tolitius02:04:53

then you recompile the ns

tolitius02:04:00

http-server keeps running

oahner02:04:52

correct, but after the recompile, @meta-state's #'http-server entry now points to a new var

oahner02:04:23

as an aside, if :on-reload :noop really was a noop on subsequent calls, there is a possibility that the initial running service (in this case http-server) could break with clojure.tools.namespace's refresh, since it undefs pretty much everything, even a defonce, but I'm not sure it actually deletes anything from memory, so the thread spawned by start-www probably wouldn't get affected

tolitius02:04:45

java.net.BindException: Address already in use
java.lang.RuntimeException: could not start [#'app.www/web-server] due to
hmm.. I wonder what "broke?" it. looks like a noop state should not be marked as :stopped

oahner02:04:05

that's the behavior I get alright

oahner02:04:24

also weird that it just said "due to" and stops there

oahner02:04:34

I get that too

tolitius02:04:37

mount would still remember states in t.n.refresh

tolitius02:04:48

you can do *e

oahner02:04:51

yep, it sure does, thx to disable-reload!

tolitius02:04:53

to see the exception

oahner02:04:21

I suppose t.n.refresh can't entirely unmap the existing web-service since a) mount still has a reference to it and b) the whole environment gets captured by the closure that calls start-www, but I'm pulling facts out of thin air on this subject

oahner02:04:42

I sure am glad I'm not the only one getting this behavior tho, I spent the entire day looking at examples and other projects to see where I was using mount wrong

tolitius02:04:49

ok, so https://github.com/tolitius/mount/blob/master/src/mount/core.cljc#L150 should not be called (when (and ((running-states) s-name) (= :noop on-reload)))

tolitius02:04:25

I am sure something has introduced this.. I wonder what it was

tolitius02:04:36

:reload stuff is really hard to test in cljs

tolitius02:04:07

it might be the cljs direction that pushed a bad commit to clj side

oahner02:04:57

that sounds like a good explanation

oahner02:04:06

it must be rather recent too, this is a pretty standard setup

tolitius02:04:40

so now I wonder whether a :noop state could/should be ever redefined..

tolitius02:04:48

i.e. one thing is to recompile a namespace

tolitius02:04:01

another thing is to manually redefine a state

oahner02:04:08

well, a t.n.refresh doesn't break a running web server, even tho everything under it gets undef'd

oahner02:04:13

the lesser recompiles, either (require ... :reload) and just copy/pasting the defstate block into the repl, don't actually undef vars defined with defonce either, so they're pretty safe

oahner02:04:38

meanwhile, when running a webserver in debug mode, most projects I've seen refer to the handler by ref so it always points to the latest defined routes anyway

oahner02:04:29

I think a :noop should only be lifecycled through an explicit start/`stop` call

tolitius02:04:07

right, that is how it was designed to work 🙂

tolitius02:04:21

but.. how do you redefined that state if you need to?

oahner02:04:56

I suppose swap-states could be made to be the recommended way to do such a thing

oahner02:04:38

make it so a :noop state can only be redefined if it's not running

oahner02:04:45

but that's less explicit

oahner02:04:46

what I've been doing so far is comment out the on-reload meta on the defstate, running it, then uncommenting it with the modified defstate again

oahner02:04:02

but that's clearly inferior to the alternatives

tolitius02:04:17

I am looking at it.. the fix is not hard. the tricky part is to make "macro + var + ns" work in both clj and cljs

tolitius02:04:46

since cljs does not really have vars and ns 🙂

oahner02:04:23

yeah, that's why I still use a good old ctrl-r for cljs ^^

oahner02:04:48

figwheel makes development painless enough

tolitius02:04:35

I guess I have not noticed it lately since iff I change the ns where I have things like server(s) defined, I want the server to restart..

oahner02:04:18

that's probably it, I haven't seen :noop used much outside of the luminus template

tolitius02:04:14

yea, I love these challenges.. only when you think "ok, this is finally rock solid" people who pay attention have a question 🙂

oahner02:04:42

heck, for now I think I'll just put the web-server into its own namespace without making it :noop and disable-reload! it

oahner02:04:02

that should do the trick

tolitius02:04:58

why do you have it :noop to being with: what do you change in that namespace that does not require a server restart?

oahner02:04:26

ah, see this is where my scenario is unique

oahner02:04:32

I have everything in core.clj ^^'

oahner02:04:54

it's a brand new project, haven't started splitting it off into multiple files yet

oahner02:04:05

nobody in their right mind would do that ,right?

oahner02:04:41

s/cljs/clj

oahner02:04:29

this is a fun bug tho

oahner02:04:15

I learned more about how clojure deals with namespaces in the last 24 hours than in the 2 years before

tolitius02:04:13

I guarantee you there is even more to learn in that direction 🙂

oahner02:04:49

yeeeah, quite the rabbit hole

tolitius02:04:11

those times when I thought I'll just use a bootstrapped cljs to deal with ns api..

tolitius02:04:46

it was a couple of years ago, I was too naive back then

oahner03:04:31

oh wait, I was wrong

oahner03:04:11

t.n.refresh does break the initial state with :noop

oahner03:04:29

well, I mean, it still works, it'll just never pick up any further changes

tolitius03:04:01

can you try a new 0.1.12-SNAPSHOT?

tolitius03:04:15

with your previous (i.e. initial) setup

oahner03:04:23

sure thing

oahner03:04:17

taking a while, had to nuke my .m2 repo

oahner03:04:06

weird, my local copy wouldn't update itself

tolitius03:04:14

even after you delete it?

oahner03:04:22

oh yes that always works

oahner03:04:28

it's fine now

oahner03:04:50

that actually happens to me quite often with SNAPSHOT releases

oahner03:04:02

I like the alphaN convention for that

oahner03:04:12

beautiful!

oahner03:04:14

works flawlessly

oahner03:04:24

many, many thanks!

oahner03:04:52

well, for clj at least

tolitius03:04:02

yea, I am testing for cljs.. it is a bit more involved 🙂

oahner03:04:24

so now ring's wrap-reload works without restarting the state, and t.n.refresh :after 'reset works by restarting the state

oahner03:04:32

just perfect

oahner03:04:59

I can't think of a nicer REPL environment, got all the bases covered

tolitius03:04:01

great 🙂 do you need to refresh it often?

tolitius03:04:45

where I might add a mount/stop-except to (stop)

tolitius03:04:04

and mostly use (reset)

oahner03:04:08

not unless I break something in my REPL really

oahner03:04:29

for the most part ring-reload does everything

oahner03:04:11

but having t.n.refresh is helpful for those edge cases where the state has just diverged too badly

oahner03:04:26

ohh, didn't know about mount-up

oahner03:04:27

interesting

oahner03:04:56

ohh, I love cprop btw, I had a small utils module in my projects that pretty much did exactly that but worse and with less features, was an easy swap

tolitius03:04:58

but then I had spare 30 min "to decouple"..

tolitius03:04:12

ah, thanks 🙂

tolitius03:04:49

yea, cprop also started as a traveler file from project to project, until "I got my self together"..

tolitius03:04:36

I mostly use it with envoy the last couple of years: https://github.com/tolitius/envoy

tolitius04:04:06

i.e. merging overrides from consul

tolitius04:04:28

when I land to places with no consul, I start my own 🙂

oahner04:04:45

that's neat!

tolitius04:04:48

and then slowly convince people that it is a good idea

oahner04:04:41

a centralized server for all your projects' configuration and credentials that auto updates to your clojure config at runtime? what's not to like 🙂

tolitius04:04:13

right.. "auto updates to your clojure config at runtime" may not be to everyone's liking (ops people don't find this too appealing), but merging overrides (and secrets from vault) on start instead of carrying different files / ENV for different environments is a big win

tolitius04:04:03

tested ":noop" in cljs. visually works

tolitius04:04:13

can also redefine it after it is stopped

tolitius04:04:19

cljs.user=> (defstate ^{:on-reload :noop} web-server :start 28 :stop :stopped)
#'cljs.user/web-server

cljs.user=> (mount/start)
{:started ["#'cljs.user/web-server"]}

cljs.user=> @@#'cljs.user/web-server
28

cljs.user=> (defstate ^{:on-reload :noop} web-server :start 28 :stop :stopped)
#'cljs.user/web-server

cljs.user=> @@#'cljs.user/web-server
28

cljs.user=> (mount/stop)
{:stopped ["#'cljs.user/web-server"]}

cljs.user=> (defstate ^{:on-reload :noop} web-server :start 42 :stop :stopped)
#'cljs.user/web-server

cljs.user=> (mount/start)
{:started ["#'cljs.user/web-server"]}

cljs.user=> @@#'cljs.user/web-server
42

oahner04:04:41

oh, so a noop state gets redefined if it's stopped?

tolitius04:04:53

I thought it should be

oahner04:04:59

makes sense to me

oahner04:04:32

so I clicked and clicked and clicked from those last links you shared and now I'm looking at hazelcast

oahner04:04:58

how I never heard of it before is beyond me, but damn, that looks nice

tolitius04:04:14

yea, I love it

oahner04:04:23

am I reading correctly that this is on par with redis?

tolitius04:04:32

especially if you are coming from JVM, it is really great

oahner04:04:40

well, yeah, pretty much

oahner04:04:04

I've been fighting with my IT guys to get redis and so far it's been blocked because "not supported on Windows Server" well enough and we're a microsoft shop

oahner04:04:12

but this looks like it might fit the bill

tolitius04:04:58

hazelcast is quite robust. redis is definitely a lot more known

tolitius04:04:12

but "hz" guys are really active

tolitius04:04:34

and it has lots of solid features

oahner04:04:45

well that and I skimmed through the readme on https://github.com/tolitius/chazel

tolitius04:04:49

chazel only implements features I actively use. it is minor compare to what hazelcast can do

tolitius04:04:43

I am thinking on adding "Continuous Query Cache"

tolitius04:04:15

they had it in the paid version, but now it is open sourced

oahner04:04:23

well, either way, there goes my weekend

tolitius04:04:33

haha, yea, good luck 🙂

tolitius04:04:10

I am sure you'll get a kick out of hazelcast, it is really simple, straight forward and quite powerful

tolitius04:04:27

problems start when you have large heaps

tolitius04:04:34

i.e. over 70, 80 gigs

tolitius04:04:50

they have an "off heap" storage as well, but it is paid

tolitius04:04:23

but usage with a reasonable heap is quite solid

oahner04:04:44

heh, if we ever have a system at work that requires so much heap, it'll be a nice problem to have

oahner04:04:11

the problems I deal with aren't exactly web scale or big data

tolitius04:04:42

once you have memory to put things in, you put more every day 🙂