diff --git a/Gemfile b/Gemfile index 29b129d0d..ac050f735 100644 --- a/Gemfile +++ b/Gemfile @@ -120,6 +120,11 @@ gem 'viewpoint' # in production environments by default. group :development, :test do + # app boottime improvement + gem 'spring' + gem 'spring-commands-rspec' + gem 'spring-commands-testunit' + # debugging gem 'byebug' gem 'pry-rails' diff --git a/Gemfile.lock b/Gemfile.lock index 592e36c5f..662adbd31 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -415,6 +415,12 @@ GEM simplecov (>= 0.4.1) slack-notifier (2.3.1) slop (3.6.0) + spring (2.0.2) + activesupport (>= 4.2) + spring-commands-rspec (1.0.4) + spring (>= 0.9.1) + spring-commands-testunit (1.0.1) + spring (>= 0.9.1) sprockets (3.7.2) concurrent-ruby (~> 1.0) rack (> 1, < 3) @@ -558,6 +564,9 @@ DEPENDENCIES simplecov simplecov-rcov slack-notifier + spring + spring-commands-rspec + spring-commands-testunit sprockets sqlite3 tcr diff --git a/bin/rails b/bin/rails index 5191e6927..0138d79b7 100755 --- a/bin/rails +++ b/bin/rails @@ -1,4 +1,9 @@ #!/usr/bin/env ruby +begin + load File.expand_path('../spring', __FILE__) +rescue LoadError => e + raise unless e.message.include?('spring') +end APP_PATH = File.expand_path('../../config/application', __FILE__) require_relative '../config/boot' require 'rails/commands' diff --git a/bin/rake b/bin/rake index 17240489f..d87d5f578 100755 --- a/bin/rake +++ b/bin/rake @@ -1,4 +1,9 @@ #!/usr/bin/env ruby +begin + load File.expand_path('../spring', __FILE__) +rescue LoadError => e + raise unless e.message.include?('spring') +end require_relative '../config/boot' require 'rake' Rake.application.run diff --git a/bin/rspec b/bin/rspec index d72fadf39..d7be13f3f 100755 --- a/bin/rspec +++ b/bin/rspec @@ -1,3 +1,8 @@ #!/usr/bin/env ruby +begin + load File.expand_path('spring', __dir__) +rescue LoadError => e + raise unless e.message.include?('spring') +end require 'bundler/setup' load Gem.bin_path('rspec-core', 'rspec') diff --git a/bin/spring b/bin/spring index 7fe232c3a..d3f9faab0 100755 --- a/bin/spring +++ b/bin/spring @@ -3,13 +3,15 @@ # This file loads spring without using Bundler, in order to be fast. # It gets overwritten when you run the `spring binstub` command. -unless defined?(Spring) +if ENV['ENABLE_SPRING'] && !defined?(Spring) require 'rubygems' require 'bundler' - if (match = Bundler.default_lockfile.read.match(/^GEM$.*?^ (?: )*spring \((.*?)\)$.*?^$/m)) - Gem.paths = { 'GEM_PATH' => [Bundler.bundle_path.to_s, *Gem.path].uniq.join(Gem.path_separator) } - gem 'spring', match[1] + lockfile = Bundler::LockfileParser.new(Bundler.default_lockfile.read) + spring = lockfile.specs.detect { |spec| spec.name == "spring" } + if spring + Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path + gem 'spring', spring.version require 'spring/binstub' end end diff --git a/config/spring.rb b/config/spring.rb new file mode 100644 index 000000000..d50143742 --- /dev/null +++ b/config/spring.rb @@ -0,0 +1,37 @@ +module Spring + module Commands + class SchedulerRb + + def call + load ::Rails.root.join('script/scheduler.rb') + end + end + + Spring.register_command 'scheduler.rb', Spring::Commands::SchedulerRb.new + end +end + +module Spring + module Commands + class WebsocketServerRb + + def call + load ::Rails.root.join('script/websocket-server.rb') + end + end + + Spring.register_command 'websocket-server.rb', Spring::Commands::WebsocketServerRb.new + end +end + +module Spring + module Commands + class RailsServer < Rails + def command_name + 'server' + end + end + + Spring.register_command 'rails_server', RailsServer.new + end +end diff --git a/config/spring_client.rb b/config/spring_client.rb new file mode 100644 index 000000000..35b3a0eb5 --- /dev/null +++ b/config/spring_client.rb @@ -0,0 +1,15 @@ +module Spring + module Client + class Rails < Command + + DEFAULT_COMMANDS = COMMANDS.dup + DEFAULT_ALIASES = ALIASES.dup + + remove_const('COMMANDS') + remove_const('ALIASES') + + const_set('COMMANDS', DEFAULT_COMMANDS + %w[server]) + const_set('ALIASES', DEFAULT_ALIASES.merge('s' => 'server')) + end + end +end diff --git a/script/scheduler.rb b/script/scheduler.rb index 42d528334..f82aa8801 100755 --- a/script/scheduler.rb +++ b/script/scheduler.rb @@ -1,27 +1,18 @@ #!/usr/bin/env ruby # Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ +begin + load File.expand_path('../bin/spring', __dir__) +rescue LoadError => e + raise unless e.message.include?('spring') +end -$LOAD_PATH << './lib' -require 'rubygems' - -# load rails env dir = File.expand_path(File.join(File.dirname(__FILE__), '..')) Dir.chdir dir -RAILS_ENV = ENV['RAILS_ENV'] || 'development' -require 'rails/all' require 'bundler' -require File.join(dir, 'config', 'environment') require 'daemons' def before_fork - - # clear all connections before for, reconnect later ActiveRecord::Base.connection.reconnect! - # issue #1405 - Scheduler not running because of Bad file descriptor in PGConsumeInput() - # https://github.com/zammad/zammad/issues/1405 - # see also https://bitbucket.org/ged/ruby-pg/issues/260/frequent-crashes-with-multithreading - ActiveRecord::Base.clear_all_connections! - # remember open file handles @files_to_reopen = [] ObjectSpace.each_object(File) do |file| @@ -38,6 +29,15 @@ def after_fork(dir) file.sync = true end + # Spring redirects STDOUT and STDERR to /dev/null + # before we get here. This causes the `reopen` lines + # below to fail because the handles are already + # opened for write + if defined?(Spring) + $stdout.close + $stderr.close + end + $stdout.reopen("#{dir}/log/scheduler_out.log", 'w') $stderr.reopen("#{dir}/log/scheduler_err.log", 'w') end @@ -45,29 +45,22 @@ end before_fork daemon_options = { - multiple: false, - dir_mode: :normal, - dir: File.join(dir, 'tmp', 'pids'), + multiple: false, + dir_mode: :normal, + dir: File.join(dir, 'tmp', 'pids'), backtrace: true } -name = 'scheduler' -Daemons.run_proc(name, daemon_options) do - - if ARGV.include?('--') - ARGV.slice! 0..ARGV.index('--') - else - ARGV.clear - end +Daemons.run_proc('scheduler', daemon_options) do after_fork(dir) - Rails.logger.info 'Scheduler started.' + require File.join(dir, 'config', 'environment') + Rails.logger.info 'Scheduler started.' at_exit do Rails.logger.info 'Scheduler stopped.' end - require 'scheduler' Scheduler.threads end diff --git a/script/websocket-server.rb b/script/websocket-server.rb index cdee2cb15..94f8d7fcb 100755 --- a/script/websocket-server.rb +++ b/script/websocket-server.rb @@ -1,27 +1,26 @@ #!/usr/bin/env ruby # Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ +begin + load File.expand_path('../bin/spring', __dir__) +rescue LoadError => e + raise unless e.message.include?('spring') +end -$LOAD_PATH << './lib' -require 'rubygems' - -# load rails env -dir = File.expand_path('..', __dir__) +dir = File.expand_path(File.join(File.dirname(__FILE__), '..')) Dir.chdir dir -RAILS_ENV = ENV['RAILS_ENV'] || 'development' -require 'rails/all' require 'bundler' + require File.join(dir, 'config', 'environment') + require 'eventmachine' require 'em-websocket' require 'json' require 'fileutils' require 'optparse' require 'daemons' -require 'sessions' def before_fork - # remember open file handles @files_to_reopen = [] ObjectSpace.each_object(File) do |file| @@ -38,8 +37,17 @@ def after_fork(dir) file.sync = true end - $stdout.reopen( "#{dir}/log/websocket-server_out.log", 'w').sync = true - $stderr.reopen( "#{dir}/log/websocket-server_err.log", 'w').sync = true + # Spring redirects STDOUT and STDERR to /dev/null + # before we get here. This causes the `reopen` lines + # below to fail because the handles are already + # opened for write + if defined?(Spring) + $stdout.close + $stderr.close + end + + $stdout.reopen("#{dir}/log/websocket-server_out.log", 'w').sync = true + $stderr.reopen("#{dir}/log/websocket-server_err.log", 'w').sync = true end before_fork @@ -53,7 +61,7 @@ before_fork d: false, k: '/path/to/server.key', c: '/path/to/server.crt', - i: Dir.pwd.to_s + '/tmp/pids/websocket.pid' + i: "#{dir}/tmp/pids/websocket.pid" } tls_options = {} @@ -111,7 +119,7 @@ if ARGV[0] == 'start' && @options[:d] Daemons.daemonize( app_name: File.basename(@options[:i], '.pid'), dir_mode: :normal, - dir: File.dirname(@options[:i]) + dir: File.dirname(@options[:i]) ) after_fork(dir)