bcardiff / crystal-fswatch

MIT License
16 stars 2 forks source link

BUG: Process hang on M1 #5

Open robacarp opened 4 months ago

robacarp commented 4 months ago

This code will cause the process to go unresponsive and hang on an M1:

require "fswatch"

FSWatch.watch "." do |event|
  puts "got event for #{event.path}"
end

I'm not sure if this is also an issue on other systems. I have fswatch installed from brew, and I didn't see anything in their issues which might indicate that it's related:

> brew info fswatch
==> fswatch: stable 1.17.1 (bottled)
bcardiff commented 4 months ago

FSWatch.watch will not block the main fiber. I think the program is finishing right away.

bcardiff commented 4 months ago

Validated with

% crystal --version
Crystal 1.11.2 (2024-01-18)

LLVM: 17.0.6
Default target: aarch64-apple-darwin23.2.0

% brew info fswatch
==> fswatch: stable 1.17.1 (bottled)

macOS 14.2.1 (23C71)

robacarp commented 4 months ago

FSWatch.watch will not block the main fiber. I think the program is finishing right away.

I'm also able to trip into the scenario while using Kemal to block the main fiber:

require "kemal"
require "fswatch"

FSWatch.watch "." do |event|
  puts "got event for #{event.path}"
end

get "/" do |env|
  "Hello World!"
end

Kemal.run

When I run watch -n 1 curl http://localhost:3000 I can see kemal responding to requests until a single FSWatch event fires, and then it gets blocked. This is how I ran into the bug myself, I should have posted the full context before.

bcardiff commented 4 months ago

Single thread or multi thread?

I'll try to repro tomorrow

robacarp commented 4 months ago

Just single thread. I'm not doing anything fancy with mt. Thanks for looking.

bcardiff commented 4 months ago

It seems that there is an issue with crystal 1.11.x . The specs don't even finish to run which is consistent with your experience with kemal.

@robacarp can you check if crystal 1.10.x works for you until I can narrow it down better?

Call graph:
    2490 Thread_45538412   DispatchQueue_1: com.apple.main-thread  (serial)
    + 2490 start  (in dyld) + 2360  [0x1884050e0]
    +   2490 main  (in crystal-run-spec.tmp) + 12  [0x1045870a0]  main.cr:141
    +     2490 *Crystal::main<Int32, Pointer(Pointer(UInt8))>:Int32  (in crystal-run-spec.tmp) + 108  [0x10476dd90]  main.cr:59
    +       2490 *Crystal::exit<Int32, (Exception+ | Nil)>:Int32  (in crystal-run-spec.tmp) + 40  [0x10476de28]  main.cr:64
    +         2490 *Crystal::AtExitHandlers::run<Int32, (Exception+ | Nil)>:Int32  (in crystal-run-spec.tmp) + 148  [0x1046cc844]  at_exit_handlers.cr:14
    +           2490 ~procProc(Int32, (Exception | Nil), Nil)@/Users/bcardiff/Downloads/crystal-1.11.2-1/src/spec/dsl.cr:208  (in crystal-run-spec.tmp) + 76  [0x1045ed084]  dsl.cr:216
    +             2490 *Spec::execute_examples:Nil  (in crystal-run-spec.tmp) + 28  [0x1046f9d3c]  dsl.cr:234
    +               2490 *Spec::RootContext#run:Nil  (in crystal-run-spec.tmp) + 28  [0x104714e9c]  context.cr:158
    +                 2490 *Spec::RootContext@Spec::Context#internal_run:Nil  (in crystal-run-spec.tmp) + 200  [0x104714f70]  context.cr:18
    +                   2490 *Spec::ExampleGroup#run:Nil  (in crystal-run-spec.tmp) + 364  [0x10474500c]  context.cr:340
    +                     2490 *Spec::ExampleGroup@Spec::Context#internal_run:Nil  (in crystal-run-spec.tmp) + 144  [0x10474516c]  context.cr:18
    +                       2490 *Spec::Example#run:Nil  (in crystal-run-spec.tmp) + 1208  [0x104742340]  example.cr:38
    +                         2490 *Spec::Example#internal_run<Time::Span, Proc(Nil)>:(Array(Spec::Result) | Nil)  (in crystal-run-spec.tmp) + 212  [0x104742444]  example.cr:50
    +                           2490 ~procProc(Nil)@spec/session_spec.cr:9  (in crystal-run-spec.tmp) + 708  [0x1045ee4f8]  session_spec.cr:20
    +                             2490 ~procProc(FSWatch::Session, Channel(FSWatch::Event), String, Nil)@spec/session_spec.cr:41  (in crystal-run-spec.tmp) + 192  [0x1045ed9fc]  session_spec.cr:47
    +                               2490 *File::join<String, String>:String  (in crystal-run-spec.tmp) + 64  [0x1046a5d38]  file.cr:852
    +                                 2490 *Path::new<String, String>:Path  (in crystal-run-spec.tmp) + 64  [0x104661f8c]  path.cr:109
    +                                   2490 *Path#join<String>:Path  (in crystal-run-spec.tmp) + 872  [0x104662310]  string.cr:254
    +                                     2490 *GC::malloc_atomic<UInt32>:Pointer(Void)  (in crystal-run-spec.tmp) + 16  [0x10462fc70]  gc.cr:88
    +                                       2490 *GC::malloc_atomic<UInt64>:Pointer(Void)  (in crystal-run-spec.tmp) + 12  [0x10462fc84]  boehm.cr:152
    +                                         2490 GC_malloc_kind_global  (in crystal-run-spec.tmp) + 60  [0x1047f3048]
    +                                           2490 _pthread_mutex_firstfit_lock_slow  (in libsystem_pthread.dylib) + 248  [0x188780af8]
    +                                             2490 _pthread_mutex_firstfit_lock_wait  (in libsystem_pthread.dylib) + 84  [0x188783168]
    +                                               2490 __psynch_mutexwait  (in libsystem_kernel.dylib) + 8  [0x188748524]
ysbaddaden commented 4 months ago

Specs are failing on Linux (Ubuntu 20.04, fswatch 1.14, Crystal 1.11.2) with or without preview_mt: they detect nothing, so I can't reproduce.

FSWatch.watch will not block the main fiber. I think the program is finishing right away.

Same behavior. That's the expected behavior: watch won't block. I added a sleep and it's working correctly. Weirdly the specs won't pass :shrug:

robacarp commented 4 months ago

@bcardiff on Crystal 1.10.1, it is indeed working. That'll do fine for me, thank you!

bcardiff commented 3 months ago

(Half) Found it!

I bisected the problem to https://github.com/crystal-lang/crystal/pull/14130 I am not sure why that is causing issues yet.

But the good thing is that at least for now you can monkey-patch your project with the following code and it should work with Crystal 1.11.x (and Crystal master)

lib LibGC
  $stackbottom = GC_stackbottom : Void*
end

module GC
  def self.current_thread_stack_bottom
    {Pointer(Void).null, LibGC.stackbottom}
  end

  def self.set_stackbottom(stack_bottom : Void*)
    LibGC.stackbottom = stack_bottom
  end
end