theine2 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []