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 +7 -0
- data/bin/theine +2 -0
- data/bin/theine_current_ruby +2 -0
- data/bin/theine_server +2 -0
- data/bin/theine_set_ruby +20 -0
- data/lib/theine/client.rb +84 -0
- data/lib/theine/config.rb +27 -0
- data/lib/theine/server.rb +137 -0
- data/lib/theine/worker.rb +170 -0
- data/lib/theine.rb +0 -0
- metadata +56 -0
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
data/bin/theine_server
ADDED
data/bin/theine_set_ruby
ADDED
@@ -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: []
|