Skip to content

Instantly share code, notes, and snippets.

@brainwire
Created February 3, 2017 09:08
Show Gist options
  • Save brainwire/b69ef5251d78a217e62d9a87a47d6978 to your computer and use it in GitHub Desktop.
Save brainwire/b69ef5251d78a217e62d9a87a47d6978 to your computer and use it in GitHub Desktop.
development server Puma + Listen + FSEvent (macOS) : Fix running too many fsevent_watch processes

https://gist.github.com/steakknife/b318a570803b08c1548c7f51c18c0753

License

Dual-licensed: MIT or DWFTYL

Problem (on macOS only)

  • Listen fires up a fsevent_watch for every directory watched
  • Puma is a threaded server, restarting it doesn't restart the Ruby process
  • The development server + spring uses Listen to hot-reload code (models, views, controllers, etc.)
  • Restarting the server doesn't call #stop on the Listen instances
  • Tons and tons of fsevent_watch appear until fork() fails

Solution

  • Call #stop on all Listen instances (including yours), to kill off unneeded fsevent_watch

Installation

  1. Add all the other files included in this gist as per their names
  2. Add this to your config/puma.rb
# config/puma.rb

#...

# FSEvent cleanup -- begin
# let puma find custom, project-included puma plugins
lib = File.expand_path('lib')
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include(lib)

# kill off fsevent_watch in development
plugin :fsevent_cleanup
# FSEvent cleanup -- end

Notes

  1. Works with MRI
  2. Won't work with JRuby unless ObjectSpace is enabled
  3. Might not work on Rubinius
  4. Don't use bin/rails s use bin/bundle exec puma because Rails + Puma + rails c is broken (both try to write tmp/pids/server.pid, rails doesn't like threaded servers restarting themselves)
# lib/puma/plugin/add_plugin_restart_hook.rb
require 'puma/plugin'
Puma::Plugin.create do
def config(dsl)
Puma::Launcher.class_eval do
def fire_plugins_restart
@config.plugins.fire_restarts self
end
end
Puma::PluginLoader.class_eval do
def fire_restarts(launcher)
@instances.each do |i|
i.restart(launcher) if i.respond_to? :restart
end
end
end
dsl.on_restart do |launcher|
launcher.fire_plugins_restart
end
end
end
# lib/puma/plugin/listen_cleanup.rb
require 'puma/plugin'
Puma::Plugin.create do
def config(dsl)
dsl.plugin :add_plugin_restart_hook
end
# call #stop on all FSEvent instances
# this should close pipes and make fsevent_watch'es die
def restart(_launcher)
return unless defined? FSEvent
ObjectSpace.each_object(FSEvent) { |x| x.stop }
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment