Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

File watching fails on MacOS Big Sur beta #253

Closed
chancerussell opened this issue Jul 3, 2020 · 15 comments
Closed

File watching fails on MacOS Big Sur beta #253

chancerussell opened this issue Jul 3, 2020 · 15 comments

Comments

@chancerussell
Copy link

When starting up file watching via wkf/hawk, an exception is thrown. After a little digging, this looks to be caused by changes to how MacOS loads dylibs in 11.0+.

It looks like the JNA folks are already working on a fix, so once that lands, we can update (or fork) BarbaryWatchService, then update wkf/hawk to use it.

Stack trace:

[Figwheel:SEVERE] java.lang.UnsatisfiedLinkError: Unable to load library 'Carbon': dlopen(libCarbon.dylib, 9): image not found
Exception in thread "main" java.lang.NoClassDefFoundError: Could not initialize class com.barbarysoftware.jna.CarbonAPI
        at com.barbarysoftware.jna.CFStringRef.toCFString(CFStringRef.java:10)
        at com.barbarysoftware.watchservice.MacOSXListeningWatchService.register(MacOSXListeningWatchService.java:30)
        at com.barbarysoftware.watchservice.WatchableFile.register(WatchableFile.java:30)
        at com.barbarysoftware.watchservice.WatchableFile.register(WatchableFile.java:39)
        at hawk.watcher$fn__133.invokeStatic(watcher.clj:102)
        at hawk.watcher$fn__133.invoke(watcher.clj:99)
        at hawk.watcher$fn__38$G__29__47.invoke(watcher.clj:24)
        at hawk.core$watch_BANG_.invokeStatic(core.clj:83)
        at hawk.core$watch_BANG_.doInvoke(core.clj:59)
        at clojure.lang.RestFn.invoke(RestFn.java:410)
        at clojure.lang.AFn.applyToHelper(AFn.java:154)
        at clojure.lang.RestFn.applyTo(RestFn.java:132)
        at clojure.core$apply.invokeStatic(core.clj:657)
        at clojure.core$apply.invoke(core.clj:652)
        at figwheel.main.watching$alter_watches.invokeStatic(watching.clj:17)
        at figwheel.main.watching$alter_watches.invoke(watching.clj:11)
        at clojure.lang.Atom.swap(Atom.java:51)
        at clojure.core$swap_BANG_.invokeStatic(core.clj:2345)
        at clojure.core$swap_BANG_.invoke(core.clj:2337)
        at figwheel.main.watching$add_watch_BANG_.invokeStatic(watching.clj:22)
        at figwheel.main.watching$add_watch_BANG_.invoke(watching.clj:21)
        at figwheel.main.css_reload$start_STAR_.invokeStatic(css_reload.cljc:188)
        at figwheel.main.css_reload$start_STAR_.invoke(css_reload.cljc:187)
        at clojure.lang.Var.invoke(Var.java:381)
        at figwheel.main$watch_css.invokeStatic(main.cljc:1188)
        at figwheel.main$watch_css.invoke(main.cljc:1185)
        at figwheel.main$config_watch_css$fn__7373.invoke(main.cljc:1195)
        at figwheel.main$default_compile.invokeStatic(main.cljc:1848)
        at figwheel.main$default_compile.invoke(main.cljc:1812)
        at figwheel.main$build_main_opt.invokeStatic(main.cljc:526)
        at figwheel.main$build_main_opt.invoke(main.cljc:521)
        at cljs.cli$main.invokeStatic(cli.clj:636)
        at cljs.cli$main.doInvoke(cli.clj:625)
        at clojure.lang.RestFn.applyTo(RestFn.java:139)
        at clojure.core$apply.invokeStatic(core.clj:659)
        at clojure.core$apply.invoke(core.clj:652)
        at cljs.main$_main.invokeStatic(main.clj:61)
        at cljs.main$_main.doInvoke(main.clj:52)
        at clojure.lang.RestFn.applyTo(RestFn.java:137)
        at clojure.core$apply.invokeStatic(core.clj:657)
        at clojure.core$apply.invoke(core.clj:652)
        at figwheel.main$_main$fn__7768.invoke(main.cljc:2165)
        at clojure.core$with_redefs_fn.invokeStatic(core.clj:7434)
        at clojure.core$with_redefs_fn.invoke(core.clj:7418)
        at figwheel.main$_main.invokeStatic(main.cljc:2163)
        at figwheel.main$_main.doInvoke(main.cljc:2142)
        at clojure.lang.RestFn.applyTo(RestFn.java:137)
        at clojure.lang.Var.applyTo(Var.java:702)
        at clojure.core$apply.invokeStatic(core.clj:657)
        at clojure.main$main_opt.invokeStatic(main.clj:317)
        at clojure.main$main_opt.invoke(main.clj:313)
        at clojure.main$main.invokeStatic(main.clj:424)
        at clojure.main$main.doInvoke(main.clj:387)
        at clojure.lang.RestFn.applyTo(RestFn.java:137)
        at clojure.lang.Var.applyTo(Var.java:702)
        at clojure.main.main(main.java:37)
@bhauman
Copy link
Owner

bhauman commented Jul 3, 2020

@chancerussell does the following watcher work on Big Sur?

https://github.com/gmethvin/directory-watcher

@bhauman
Copy link
Owner

bhauman commented Jul 3, 2020

Actually forget that.

It would be nice if we could fall back on a simple java based watcher when things like this happen.

@chancerussell
Copy link
Author

Yeah, according to their issues they’re bit by the same exact problem.

@rgm
Copy link
Contributor

rgm commented Jul 7, 2020

I found a temporary workaround by falling back to polling, so Figwheel can at least run in a (slightly) degraded fashion. As far as I can tell, the major cost is degraded battery / higher power consumption.

The hawk lib checks to see if it's working on macOS, and uses a multimethod to set up a Barbary watcher. Thus, I can just swap in that multimethod before I start up figwheel. In my user.clj I've added:

(ns user
  (:require [figwheel.main.api :as fig]))

;; Patch hawk for macOS Big Sur
;; hawk can't find the carbon lib any more, so the underlying barbary watcher
;; doesn't work for getting OS-level file change events. We have to fall back to
;; polling by monkey-patching the barbary-vending multimethod for now.
(import [hawk SensitivityWatchEventModifier])
(require '[hawk.watcher])
(defmethod hawk.watcher/new-watcher :barbary [_]
  (prn "WARN - falling back to filesystem polling on macOS")
  (let [sensitivity SensitivityWatchEventModifier/HIGH]
    (hawk.PollingWatchService. sensitivity)))

(fig/start {:mode :serve} "dev")

It doesn't have to be in user.clj, but it does have to run before Figwheel gets going. I start the Figwheel process manually, so I haven't tried it with clj -m figwheel.main, but I would hope this would load and patch OK in that circumstance too.

rgm added a commit to rgm/figwheel-main that referenced this issue Jul 7, 2020
Changes to the latest beta version of macOS broke the mechanism that
we're using to observe filesystem change events. It's more than just
filesystem observation that has broken: any attempt to run a
hot-reloading workflow crashes the Figwheel process with an uncaught
exception at startup. (Note that build-once still works, since it
doesn't try to access filesystem events).

The underlying hawk library does have an implementation of a
less-efficient polling watcher. Figwheel can use this to provide
hot-reloading even when native events aren't available.

This commit wraps the `hawk.core/watch!` function to catch any exception
that's thrown, and re-try with the same options *except* that it
explicitly starts a polling watcher.

See also

- gjoseph/BarbaryWatchService#13
- java-native-access/jna#1216

Part of bhauman#253.
@rgm
Copy link
Contributor

rgm commented Jul 7, 2020

Of course, now that I've taken enough time to look through the code in preparing a PR, I see that we can explicitly opt-out of using the OS's FS events with https://figwheel.org/config-options.html#hawk-options . I don't think I understood why that would be useful before. This is a lot simpler than my shenanigans above.

@bhauman
Copy link
Owner

bhauman commented Jul 7, 2020

In summary, this problem is a temporary problem that is being worked on and will hopefully be fixed by the time Big Sur is released this fall.

Folks that are using the Big Sur preview need to fall back to a :polling watcher in the :hawk-options Figwheel option.

I.E.

:hawk-options {:watcher :polling}

Big thanks to @rgm for taking the time to track this down.

@rgm
Copy link
Contributor

rgm commented Jul 7, 2020

I'm not sure where this leaves the "real" fix ... is it something like having figwheel rely directly on the patched jna dependency in deps.edn once one's available? Or do we work through getting the upstream deps updated through barbary -> hawk -> figwheel?

@bhauman
Copy link
Owner

bhauman commented Jul 7, 2020

We'll make sure that hawk has this fix.

@dbwiddis
Copy link

FYI, JNA 5.6.0 has been released.

rgm added a commit to rgm/BarbaryWatchService that referenced this issue Aug 4, 2020
Mac OS 11 (aka. "Big Sur") made some changes to the way that the Carbon
library is located, causing JNA calls to fail. This has been fixed in
JNA 5.6.0; see java-native-access/jna#1216

Since Barbary is implemented on JNA, this is causing downstream projects
that rely on file watching to crash on JVM start when used on Mac OS 11.

Examples:
- bhauman/figwheel-main#253
- thheller/shadow-cljs#767
@rgm
Copy link
Contributor

rgm commented Aug 4, 2020

@bhauman I got to thinking, dealing with this issue: would the ability to manually trigger builds be a useful add? I used to run into polling battery-drains in Meteor.js because of how they originally implemented their own file watcher and so I've tried to rely as much as possible on more dedicated tools like http://eradman.com/entrproject/ instead of a hodgepodge of a SASS watcher here and a gulp thing there, with another for the test watcher, etc. etc.

In Gary Bernhardt's old Destroy All Software videos he would set up a manual unit test trigger in his editor (eg. for vim it would be something like :map ,t :wa\|!ruby -Ilib %<CR> so that every time you hit ,t in normal mode, the focused unit test on the current file would run). Over time, I've come to personally prefer this approach. I know it's not a beginner thing so I don't think dumping all file watching code is very realistic.

But it did get me thinking: could the default Figwheel server on 9500 listen for a POST, instead of watching the filesystem? This would be reasonably straightforward to hook up per the above, eg. :map ,t :wa\|!curl -X POST http://localhost:9500/_build for us vimmers, or if using the Entr util, in another shell window:

> find src -name ".clj*" | entr curl -X POST http://localhost:9500/_build`.

I just got to thinking I'd been here before with Javascript file watchers rotting, and maybe this could add some extra advanced abilities, eg. being able to observe static EDN or JSON data under /resources, or rebuild when .clj sources change instead of just .cljs or .cljc files change.

I guess the challenge would be to communicate to Figwheel which namespaces to hot-reload ... maybe by hashing the contents of known files on the classpath?

I'm happy to try taking a crack at this if you think it's a reasonable idea.

(Apologies if some or all of this already exists. Often I don't appreciate the words in the manual until I re-read them after musing like this for a while).

@bhauman
Copy link
Owner

bhauman commented Aug 5, 2020

@rgm In order to capture the full behavior of figwheel a triggered build would have to run through all the files for changes, because figwheel watches javascript and plain Clojure files as well. After the build figwheel can do its own detection of what has changed and do its magic. So really this would be a manual file poll.

This is what gets called when the watcher finds changes:
https://github.com/bhauman/figwheel-main/blob/master/src/figwheel/main.cljc#L266

This of course would also affect CSS watching etc.

Creating an end point for this makes sense. But of course there is also the REPL connection so you could add a build function to the figwheel.main.api which is intended to be used from the cljs repl. There is currently a figwheel.main/build-once in the repl but it's not very efficient and it doesn't handle the js and clj files.

This deserves more thought.

@rgm
Copy link
Contributor

rgm commented Sep 2, 2020

Creating an end point for this makes sense. But of course there is also the REPL connection so you could add a build function to the figwheel.main.api which is intended to be used from the cljs repl. There is currently a figwheel.main/build-once in the repl but it's not very efficient and it doesn't handle the js and clj files.

Hm, I hadn't really followed but I had some time to read through tonight. Perhaps it's enough to bind that (bound-fn ,,,) to something we can get a hold of from an editor repl connection, presuming that calling it is enough to kick off the whole rebuild -> hot-reload process. (Which I presume it does if it's all hawk knows about; I had some trouble following things all the way through). If it were bound in the build registry, it'd be pretty straightforward to map a shortcut to it, and just leave the file watching off entirely.

@il-tmfv
Copy link

il-tmfv commented Aug 9, 2021

After upgrading to macOS 11.5.1 with M1 chip I started to get similar error when starting figwheel-main:

...
[Figwheel:SEVERE] /private/var/folders/m8/xtnsq6gn2k9d3l4n362z9p080000gn/T/jna4678795419772323478.tmp: dlopen(/private/var/folders/m8/xtnsq6gn2k9d3l4n362z9p080000gn/T/jna4678795419772323478.tmp, 1): no suitable image found.  Did find:
	/private/var/folders/m8/xtnsq6gn2k9d3l4n362z9p080000gn/T/jna4678795419772323478.tmp: no matching architecture in universal wrapper
	/private/var/folders/m8/xtnsq6gn2k9d3l4n362z9p080000gn/T/jna4678795419772323478.tmp: no matching architecture in universal wrapper
[Figwheel] Validating figwheel-main.edn
...
Syntax error (NoClassDefFoundError) compiling at (/private/var/folders/m8/xtnsq6gn2k9d3l4n362z9p080000gn/T/form-init5239701982387845518.clj:1:125).
Could not initialize class com.barbarysoftware.jna.CarbonAPI
$ java -version                              
openjdk version "1.8.0_302"
OpenJDK Runtime Environment (Zulu 8.56.0.23-CA-macos-aarch64) (build 1.8.0_302-b08)
OpenJDK 64-Bit Server VM (Zulu 8.56.0.23-CA-macos-aarch64) (build 25.302-b08, mixed mode)

This setting helps:

:hawk-options {:watcher :polling}

but feels not great.

I've tried to run master branch version and it works fine. Looks like #299 helps not only with the speed 🙂

Could you, please publish it if possible?

@kendagriff
Copy link

kendagriff commented Sep 29, 2021

I'm experiencing the same thing on the Apple M1. Any update on a new release?

EDIT: Ack, my bad! My issue is purely wkf/hawk related, not figwheel. Sorry!

@timothypratley
Copy link
Collaborator

@bhauman this issue can be closed (see comment by @il-tmfv) a release has seen been made, so AFAIK this is resolved.

@bhauman bhauman closed this as completed Apr 27, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants