theine2 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 615c630c33ec9fd5f782deea81fddd51bcbdc52c974e99af20f445367c76b09f
4
+ data.tar.gz: 486d8591356ff29c79b6156391ff17758e27046886427c6281c78bd814661b7f
5
+ SHA512:
6
+ metadata.gz: 72db5c2e5ae02fceb6d279dcabc21c13cdd11bc7c4357c2e585456e23a1fc47eef0fcffec7c0d9e853bd6696942f95b1d8fc3e4793b4d22924f68456f27b7c8f
7
+ data.tar.gz: 6287b46b78472aadb565160588c53352cd37be889caf642bd116338ec31b2f5721a1a8b080fe604aed9e429a7d1747a8ef06c31f8d22d949dbe8a8e60b2379db
data/bin/theine ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env ruby
2
+ require 'theine/client'
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env ruby
2
+ require 'theine/client'
data/bin/theine_server ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env ruby
2
+ require 'theine/server'
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env ruby
2
+ script = <<EOS
3
+ #!/bin/sh
4
+ RUBY_CMD="%RUBY_CMD%"
5
+
6
+ $RUBY_CMD -e "require '%CLIENT_RB_PATH%'" "$@"
7
+ EOS
8
+
9
+ if ARGV[0]
10
+ path = %x[which theine]
11
+ client_rb_path = File.expand_path('../../lib/theine/client.rb', __FILE__)
12
+ File.open(path.strip, "w") do |f|
13
+ script.gsub!("%CLIENT_RB_PATH%", client_rb_path)
14
+ script.gsub!("%RUBY_CMD%", ARGV[0])
15
+ f.write(script)
16
+ end
17
+ puts "Set theine to run using #{ARGV[0]}."
18
+ else
19
+ puts "Usage: theine_set_ruby /path/to/ruby_executable"
20
+ end
@@ -0,0 +1,84 @@
1
+ require 'drb/drb'
2
+ require 'readline'
3
+ require_relative './config'
4
+
5
+ module Theine
6
+ class Client
7
+ def self.start
8
+ new
9
+ end
10
+
11
+ attr_reader :config
12
+
13
+ def initialize
14
+ @config = ConfigReader.new(Dir.pwd)
15
+ @argv = ARGV.dup
16
+ begin
17
+ connect_worker
18
+ run_command
19
+ attach_screen
20
+ exit_prompt
21
+ end
22
+ end
23
+
24
+ private
25
+ def attach_screen
26
+ # Using vt100 because it does not have smcup/rmcup support,
27
+ # which means the output of the screen will stay shown after
28
+ # screen closes.
29
+ set_vt_100 = "export TERM=vt100; tset"
30
+ erase_screen_message = "echo '\\033[2A\\033[K'"
31
+ exec("#{set_vt_100}; screen -r theine#{@port}; #{erase_screen_message}")
32
+ end
33
+
34
+ def run_command
35
+ argv = @argv.dup
36
+ command = argv.shift
37
+
38
+ case command
39
+ when "rake"
40
+ @worker.command_rake(argv)
41
+ when "rspec"
42
+ @worker.command_rspec(argv)
43
+ when 'cucumber'
44
+ @worker.command_cucumber(argv)
45
+ else
46
+ @worker.command_rails([command] + argv)
47
+ end
48
+ rescue DRb::DRbConnError
49
+ $stderr.puts "\nTheine closed the connection."
50
+ end
51
+
52
+ def connect_worker
53
+ balancer = wait_until_result("Cannot connect to theine server. Waiting") do
54
+ object = DRbObject.new_with_uri("druby://localhost:#{config.base_port}")
55
+ object.respond_to?(:get_port) # test if connected
56
+ object
57
+ end
58
+ @port = wait_until_result("Waiting for Theine worker...") do
59
+ balancer.get_port
60
+ end
61
+ @worker = DRbObject.new_with_uri("druby://localhost:#{@port}")
62
+ end
63
+
64
+ WaitResultNoResultError = Class.new(StandardError)
65
+ def wait_until_result(wait_message)
66
+ result = nil
67
+ dots = 0
68
+ begin
69
+ result = yield
70
+ raise WaitResultNoResultError unless result
71
+ rescue DRb::DRbConnError, WaitResultNoResultError
72
+ print dots == 0 ? wait_message : "."
73
+ dots += 1
74
+ sleep 0.5
75
+ retry
76
+ end
77
+ print "\n" if dots > 0
78
+ result
79
+ end
80
+ end
81
+ end
82
+
83
+ DRb.start_service
84
+ Theine::Client.start
@@ -0,0 +1,27 @@
1
+ require 'yaml'
2
+
3
+ module Theine
4
+ class ConfigReader
5
+ attr_reader :rails_root
6
+ attr_accessor :base_port, :max_port, :min_free_workers, :spawn_parallel
7
+ def initialize(rails_root)
8
+ @rails_root = rails_root
9
+ @base_port = 11000
10
+ @max_port = 11100
11
+ @min_free_workers = 2
12
+ @spawn_parallel = true
13
+ load_config(File.expand_path("~/.theine"))
14
+ load_config("#{rails_root}/.theine")
15
+ end
16
+
17
+ def load_config(path)
18
+ if File.exist?(path)
19
+ config = YAML.load(File.read(path))
20
+ config.each_pair do |k, v|
21
+ setter = :"#{k}="
22
+ send(setter, v) if respond_to?(setter)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,137 @@
1
+ require 'drb/drb'
2
+ require 'thread'
3
+ require 'yaml'
4
+ require_relative './config'
5
+
6
+ module Theine
7
+ class Server
8
+ include DRb::DRbUndumped
9
+ attr_reader :config
10
+
11
+ def initialize
12
+ @config = ConfigReader.new(Dir.pwd)
13
+
14
+ @workers = []
15
+ @workers_in_use = []
16
+ @worker_pids = {}
17
+ @spawning_workers = []
18
+
19
+ @available_ports = ((config.base_port + 1)..config.max_port).to_a
20
+ @check_mutex = Mutex.new
21
+ @workers_mutex = Mutex.new
22
+
23
+ run
24
+ end
25
+
26
+ def add_worker
27
+ path = File.expand_path('../worker.rb', __FILE__)
28
+ port = @workers_mutex.synchronize { @available_ports.shift }
29
+ puts "(spawn #{"#{port} #{debug}".strip})"
30
+ spawn("screen", "-d", "-m", "-S", worker_session_name(port),
31
+ "sh", "-c",
32
+ "jruby #{debug} #{path} #{config.base_port.to_s} #{port.to_s} #{config.rails_root}")
33
+ @workers_mutex.synchronize { @spawning_workers << port }
34
+ end
35
+
36
+ def worker_session_name(port)
37
+ "theine#{port}"
38
+ end
39
+
40
+ def set_worker_pid(port, pid)
41
+ @workers_mutex.synchronize do
42
+ @worker_pids[port] = pid
43
+ end
44
+ end
45
+
46
+ def worker_boot(port)
47
+ puts "+ worker #{port}"
48
+
49
+ @workers_mutex.synchronize do
50
+ @spawning_workers.delete(port)
51
+ @workers << port
52
+ end
53
+ end
54
+
55
+ def worker_done(port)
56
+ puts "- worker #{port}"
57
+ @workers_mutex.synchronize do
58
+ @workers_in_use.delete(port)
59
+ @available_ports << port
60
+ end
61
+ end
62
+
63
+ def get_port(spawn_new = true)
64
+ add_worker if spawn_new && all_size == 0
65
+
66
+ port = @workers_mutex.synchronize { @workers.shift }
67
+ @workers_mutex.synchronize { @workers_in_use << port } if port
68
+
69
+ Thread.new { check_min_free_workers } if spawn_new
70
+
71
+ port
72
+ end
73
+
74
+ def check_min_free_workers
75
+ if @check_mutex.try_lock
76
+ # TODO: mutex, and dont do it if already in progress
77
+ # do this in thread
78
+ while all_size < config.min_free_workers
79
+ unless config.spawn_parallel
80
+ sleep 0.1 until @workers_mutex.synchronize { @spawning_workers.empty? }
81
+ end
82
+ add_worker
83
+ end
84
+ @check_mutex.unlock
85
+ end
86
+ end
87
+
88
+ def all_size
89
+ @workers_mutex.synchronize { @workers.size + @spawning_workers.size }
90
+ end
91
+
92
+ def stop!
93
+ if spawning_worker_pids.include?(nil)
94
+ puts "Waiting for workers to quit..."
95
+ sleep 0.1 while spawning_worker_pids.include?(nil)
96
+ end
97
+
98
+ @workers_mutex.synchronize do
99
+ (@spawning_workers + @workers_in_use + @workers).each do |port|
100
+ kill_worker(port)
101
+ end
102
+ end
103
+ exit(0)
104
+ end
105
+ private
106
+ def kill_worker(port)
107
+ print "- worker #{port}"
108
+ worker_pid = @worker_pids[port]
109
+ worker_pid ||= DRbObject.new_with_uri("druby://localhost:#{port}").pid
110
+ system("kill -9 #{worker_pid} > /dev/null 2>&1")
111
+ session_name = worker_session_name(port)
112
+ system("screen -S #{session_name} -X quit > /dev/null 2>&1")
113
+ puts "."
114
+ rescue
115
+ end
116
+
117
+ def spawning_worker_pids
118
+ @spawning_workers.map { |port| @worker_pids[port] }
119
+ end
120
+
121
+ def run
122
+ trap("INT") { stop! }
123
+ trap("TERM") { stop! }
124
+ system("screen -wipe > /dev/null 2>&1")
125
+
126
+ DRb.start_service("druby://localhost:#{config.base_port}", self)
127
+ check_min_free_workers
128
+ DRb.thread.join
129
+ end
130
+
131
+ def debug
132
+ "--debug" if ARGV.any? {|a| a.to_s.strip == "--debug" }
133
+ end
134
+ end
135
+ end
136
+
137
+ Theine::Server.new
@@ -0,0 +1,170 @@
1
+ RAILS_ROOT_PATH = ARGV[2]
2
+ APP_PATH = "#{RAILS_ROOT_PATH}/config/application"
3
+ require 'drb/drb'
4
+
5
+ module Theine
6
+ class Worker
7
+ attr_reader :port, :balancer
8
+
9
+ COMMANDS = {
10
+ rails: proc {
11
+ require 'rails/commands'
12
+ },
13
+ rake: proc {
14
+ require 'rake'
15
+ ::Rails.application.load_tasks if defined? ::Rails
16
+
17
+ tasks = []
18
+ ARGV.each do |arg|
19
+ if arg =~ /^(\w+)=(.*)$/m
20
+ ENV[$1] = $2
21
+ else
22
+ tasks << arg
23
+ end
24
+ end
25
+
26
+ tasks.each do |task|
27
+ is_test_task = task =~ /^(spec|test)$/
28
+ if is_test_task
29
+ previous_env = ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
30
+ change_rails_env_to("test")
31
+ end
32
+
33
+ ::Rake::Task[task].invoke
34
+
35
+ change_rails_env_to(previous_env) if is_test_task
36
+ end
37
+ },
38
+ rspec: proc {
39
+ change_rails_env_to("test")
40
+
41
+ require 'rspec/core'
42
+ RSpec::Core::Runner.autorun
43
+ },
44
+ cucumber: proc {
45
+ change_rails_env_to("test")
46
+ require 'active_support/descendants_tracker'
47
+ require 'cucumber/rspec/disable_option_parser'
48
+ require 'cucumber/cli/main'
49
+ cucumber_runtime = Cucumber::Runtime.new
50
+ cucumber_main = Cucumber::Cli::Main.new(ARGV.dup)
51
+ cucumber_main.execute!(cucumber_runtime)
52
+ }
53
+ }
54
+
55
+ def initialize(port, balancer)
56
+ @port = port
57
+ @balancer = balancer
58
+ @command_proc = proc { }
59
+ end
60
+
61
+ def run
62
+ boot
63
+ begin
64
+ DRb.thread.join
65
+ screen_move_to_bottom
66
+ sleep 0.1 while !screen_attached?
67
+
68
+ puts "command: #{@command_name} #{argv_to_s}"
69
+ instance_exec(&@command_proc)
70
+ ensure
71
+ balancer.worker_done(port)
72
+ end
73
+ end
74
+
75
+ COMMANDS.each_pair do |command_name, command|
76
+ define_method("command_#{command_name}") do |argv|
77
+ set_argv(argv)
78
+ set_command(command_name, &command)
79
+ end
80
+ end
81
+
82
+ def pid
83
+ ::Process.pid
84
+ end
85
+
86
+ def stop!
87
+ exit(1)
88
+ end
89
+
90
+ def screen_attached?
91
+ !system("screen -ls | grep theine#{@port} | grep Detached > /dev/null")
92
+ end
93
+
94
+ def screen_move_to_bottom
95
+ puts "\033[22B"
96
+ end
97
+
98
+ private
99
+ def set_command(command_name, &block)
100
+ rails_reload!
101
+ @command_name = command_name
102
+ @command_proc = block
103
+ DRb.stop_service
104
+ end
105
+
106
+ def change_rails_env_to(env)
107
+ ENV['RAILS_ENV'] = env
108
+ ENV['RACK_ENV'] = env
109
+ if defined? ::Rails
110
+ ::Rails.env = env
111
+
112
+ # load config/environments/test.rb
113
+ test_env_rb = ::Rails.root.join("config/environments/#{env}.rb")
114
+ load(test_env_rb) if File.exist?(test_env_rb)
115
+
116
+ if defined? ActiveRecord
117
+ ActiveRecord::Base.establish_connection rescue nil
118
+ end
119
+
120
+ if defined? SequelRails
121
+ Sequel::Model.db = SequelRails.setup env
122
+ end
123
+ end
124
+ end
125
+
126
+ def argv_to_s
127
+ ARGV.map { |arg|
128
+ if !arg.nil? && arg.include?(" ")
129
+ "\"#{arg}\""
130
+ else
131
+ arg
132
+ end
133
+ }.join(' ')
134
+ end
135
+
136
+ def set_argv(argv)
137
+ ARGV.clear
138
+ ARGV.concat(argv)
139
+ end
140
+
141
+ def rails_reload!
142
+ if Rails.version.to_f >= 5.1
143
+ Rails.application.reloader.reload!
144
+ Rails.application.reloader.prepare!
145
+ else
146
+ ActionDispatch::Reloader.cleanup!
147
+ ActionDispatch::Reloader.prepare!
148
+ end
149
+ end
150
+
151
+ def start_service
152
+ DRb.start_service("druby://localhost:#{@port}", self)
153
+ end
154
+
155
+ def boot
156
+ balancer.set_worker_pid(port, pid)
157
+
158
+ require "#{RAILS_ROOT_PATH}/config/boot"
159
+ require "#{RAILS_ROOT_PATH}/config/environment"
160
+ start_service
161
+
162
+ balancer.worker_boot(port)
163
+ end
164
+ end
165
+ end
166
+
167
+ balancer = DRbObject.new_with_uri("druby://localhost:#{ARGV[0]}")
168
+ worker = Theine::Worker.new(ARGV[1].to_i, balancer)
169
+
170
+ worker.run
data/lib/theine.rb ADDED
File without changes
metadata ADDED
@@ -0,0 +1,56 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: theine2
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Jan Berdajs
8
+ - Ron Williams
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2019-11-26 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: A Rails preloader for JRuby
15
+ email: ron.a.williams@gmail.com
16
+ executables:
17
+ - theine
18
+ - theine_current_ruby
19
+ - theine_server
20
+ - theine_set_ruby
21
+ extensions: []
22
+ extra_rdoc_files: []
23
+ files:
24
+ - bin/theine
25
+ - bin/theine_current_ruby
26
+ - bin/theine_server
27
+ - bin/theine_set_ruby
28
+ - lib/theine.rb
29
+ - lib/theine/client.rb
30
+ - lib/theine/config.rb
31
+ - lib/theine/server.rb
32
+ - lib/theine/worker.rb
33
+ homepage: https://github.com/rwilliams/theine2
34
+ licenses:
35
+ - MIT
36
+ metadata: {}
37
+ post_install_message:
38
+ rdoc_options: []
39
+ require_paths:
40
+ - lib
41
+ required_ruby_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ required_rubygems_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: '0'
51
+ requirements: []
52
+ rubygems_version: 3.0.3
53
+ signing_key:
54
+ specification_version: 4
55
+ summary: Theine2
56
+ test_files: []