subserver 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +2 -0
- data/CONTRIBUTING.md +54 -0
- data/LICENSE +21 -0
- data/README.md +5 -0
- data/bin/subserver +16 -0
- data/lib/subserver.rb +185 -0
- data/lib/subserver/cli.rb +353 -0
- data/lib/subserver/exception_handler.rb +29 -0
- data/lib/subserver/health.rb +33 -0
- data/lib/subserver/launcher.rb +71 -0
- data/lib/subserver/listener.rb +157 -0
- data/lib/subserver/logging.rb +122 -0
- data/lib/subserver/manager.rb +153 -0
- data/lib/subserver/message_logger.rb +24 -0
- data/lib/subserver/middleware/active_record.rb +21 -0
- data/lib/subserver/middleware/chain.rb +128 -0
- data/lib/subserver/pubsub.rb +20 -0
- data/lib/subserver/rails.rb +57 -0
- data/lib/subserver/subscriber.rb +25 -0
- data/lib/subserver/util.rb +62 -0
- data/lib/subserver/version.rb +4 -0
- metadata +86 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ed0057cefa344204a08c579efd92a9f0df33aae3
|
4
|
+
data.tar.gz: 1d76ebad4bcf2f65e7dda873a21a39c13cebcd6c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 07745b6c86ef5001384bf36fa6195f01ffa8c73676c4012936ae3d33a174eedad014336a3b94b2873b931eafec3b2fa56af4659234266f11a943a57799a72a77
|
7
|
+
data.tar.gz: 8ca91c9f88a06a5ab50ba7290da4754bf6f98fbfdb7ce6798054f31e359f431673dc1006d5c07222c642907f88f2172ad32158d466d6c97444f59faaa88cc7c8
|
data/CHANGELOG.md
ADDED
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
# Contribution Guide
|
2
|
+
|
3
|
+
Subserver is one of Life.Church's Open Source projects as part of our [opendigerati](https://www.opendigerati.com/) initiative.
|
4
|
+
Because of this we are continuously trying to improve the process for contributing to our projects.
|
5
|
+
This document outlines our current process, but please check back here every time you wish to contribute.
|
6
|
+
|
7
|
+
## Open Development
|
8
|
+
All work on Subserver happens directly on GitHub. Both Life.Church team members and external contributors send pull requests which go through the same review process.
|
9
|
+
|
10
|
+
## Branch Organization
|
11
|
+
We will do our best to keep the master branch in good shape, with tests passing at all times. But in order to move fast, we will make API changes that your application might not be compatible with. We recommend that you use the latest stable version of Subserver in your application.
|
12
|
+
If you send a pull request, please do it against the master branch. We maintain stable branches for major versions separately but we don’t accept pull requests to them directly. Instead, we cherry-pick non-breaking changes from master to the latest stable major version.
|
13
|
+
|
14
|
+
## Semantic Versioning
|
15
|
+
Subserver follows [semantic versioning](http://semver.org/). We release patch versions for bugfixes, minor versions for new features, and major versions for any breaking changes. When we make breaking changes, we also introduce deprecation warnings in a minor version so that our users learn about the upcoming changes and migrate their code in advance.
|
16
|
+
|
17
|
+
We will add a version milestone to every approved pull request marking whether the change should go in the next patch, minor, or a major version.
|
18
|
+
|
19
|
+
Every significant change is documented in the [changelog](./CHANGELOG.md) file.
|
20
|
+
|
21
|
+
## Bugs
|
22
|
+
### Where to Find Known Issues
|
23
|
+
We are using GitHub Issues for our public bugs. We keep a close eye on this and try to make it clear when we have an internal fix in progress. Before filing a new task, try to make sure your problem doesn’t already exist.
|
24
|
+
|
25
|
+
### Reporting New Issues
|
26
|
+
When reporting new issues, please be as detailed as possible and provide the following:
|
27
|
+
* Version of Subserver
|
28
|
+
* Version of Ruby
|
29
|
+
* Version of Rails (If using)
|
30
|
+
* Steps to Reproduce the Bug
|
31
|
+
* Any steps you have tried to resolve the issue
|
32
|
+
|
33
|
+
### Security Bugs
|
34
|
+
For bugs dealing with security vulnerabilities please **do not** post a public issue. Please email the code maintainers [@wintheday](https://github.com/wintheday).
|
35
|
+
|
36
|
+
### Proposing a Change
|
37
|
+
If you intend to change the API, or make any non-trivial changes to the implementation, we recommend filing an issue. This lets us reach an agreement on your proposal before you put significant effort into it.
|
38
|
+
If you’re only fixing a bug, it’s fine to submit a pull request right away but we still recommend to file an issue detailing what you’re fixing. This is helpful in case we don’t accept that specific fix but want to keep track of the issue.
|
39
|
+
|
40
|
+
### Your First Pull Request
|
41
|
+
If this is your first ever Pull Request we are honored you have chosen to join Open Source through our community. You can learn the basics of Github and how Pull Requests work from this free video series:
|
42
|
+
[How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github)
|
43
|
+
|
44
|
+
### Sending a Pull Request
|
45
|
+
The code maintainers are monitoring for pull requests. We will review your pull request and either merge it, request changes to it, or close it with an explanation. For breaking changes we may need to fix our internal uses of Validstate, which could cause some delay. We’ll do our best to provide updates and feedback throughout the process.
|
46
|
+
|
47
|
+
#### Before submitting a pull request, please make sure the following is done:
|
48
|
+
1. Fork the repository and create your branch from master.
|
49
|
+
2. Run `bundle install` from the repository root.
|
50
|
+
3. If you’ve fixed a bug or added code that should be tested, add tests!
|
51
|
+
4. Ensure the test suite passes `rspec`.
|
52
|
+
|
53
|
+
### Making changes to the Documentation
|
54
|
+
Please ensure that any change made to Subserver that introduce new functionality or a breaking change to the API are well documented via the wiki.
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2018 Life.Church
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
data/bin/subserver
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$TESTING = false
|
4
|
+
|
5
|
+
require_relative '../lib/subserver/cli'
|
6
|
+
|
7
|
+
begin
|
8
|
+
cli = Subserver::CLI.instance
|
9
|
+
cli.parse
|
10
|
+
cli.run
|
11
|
+
rescue => e
|
12
|
+
raise e if $DEBUG
|
13
|
+
STDERR.puts e.message
|
14
|
+
STDERR.puts e.backtrace.join("\n")
|
15
|
+
exit 1
|
16
|
+
end
|
data/lib/subserver.rb
ADDED
@@ -0,0 +1,185 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
require 'subserver/version'
|
4
|
+
fail "Subserver #{Subserver::VERSION} does not support Ruby versions below 2.3.1." if RUBY_PLATFORM != 'java' && Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.3.1')
|
5
|
+
|
6
|
+
require 'subserver/logging'
|
7
|
+
require 'subserver/pubsub'
|
8
|
+
require 'subserver/health'
|
9
|
+
require 'subserver/subscriber'
|
10
|
+
require 'subserver/middleware/chain'
|
11
|
+
|
12
|
+
module Subserver
|
13
|
+
NAME = 'Subserver'
|
14
|
+
LICENSE = 'Subserver is licensed under MIT.'
|
15
|
+
|
16
|
+
DEFAULTS = {
|
17
|
+
project_id: nil,
|
18
|
+
credentials: nil,
|
19
|
+
queues: [],
|
20
|
+
labels: [],
|
21
|
+
require: '.',
|
22
|
+
subscriber_dir: './subscribers',
|
23
|
+
environment: nil,
|
24
|
+
timeout: 35,
|
25
|
+
error_handlers: [],
|
26
|
+
death_handlers: [],
|
27
|
+
lifecycle_events: {
|
28
|
+
startup: [],
|
29
|
+
quiet: [],
|
30
|
+
shutdown: [],
|
31
|
+
heartbeat: [],
|
32
|
+
},
|
33
|
+
reloader: proc { |&block| block.call },
|
34
|
+
}
|
35
|
+
|
36
|
+
DEFAULT_SUBSCRIBER_OPTIONS = {
|
37
|
+
subscription: nil,
|
38
|
+
deadline: 60,
|
39
|
+
streams: 2,
|
40
|
+
threads: {
|
41
|
+
callback: 4,
|
42
|
+
push: 2
|
43
|
+
},
|
44
|
+
inventory: 1000,
|
45
|
+
queue: 'default'
|
46
|
+
}
|
47
|
+
|
48
|
+
|
49
|
+
def self.options
|
50
|
+
@options ||= load_config
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.options=(opts)
|
54
|
+
@options = opts
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.configure
|
58
|
+
yield self
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.load_config(file=nil)
|
62
|
+
opts = DEFAULTS.dup
|
63
|
+
file = Dir["config/subserver.yml*"].first if file.nil?
|
64
|
+
return opts unless file && File.exists?(file)
|
65
|
+
opts.merge(parse_config(file))
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.load_json(string)
|
69
|
+
JSON.parse(string)
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.dump_json(object)
|
73
|
+
JSON.generate(object)
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.logger
|
77
|
+
Subserver::Logging.logger
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.logger=(log)
|
81
|
+
Subserver::Logging.logger = log
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.health_server
|
85
|
+
@health_server ||= Subserver::Health.new
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.pubsub_client
|
89
|
+
Subserver::Pubsub.client
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.middleware
|
93
|
+
@chain ||= default_middleware
|
94
|
+
yield @chain if block_given?
|
95
|
+
@chain
|
96
|
+
end
|
97
|
+
|
98
|
+
def self.default_middleware
|
99
|
+
Middleware::Chain.new
|
100
|
+
end
|
101
|
+
|
102
|
+
def self.default_subscriber_options=(hash)
|
103
|
+
@default_subscriber_options = default_subscriber_options.merge(Hash[hash.map{|k, v| [k.to_s, v]}])
|
104
|
+
end
|
105
|
+
|
106
|
+
def self.default_subscriber_options
|
107
|
+
defined?(@default_subscriber_options) ? @default_subscriber_options : DEFAULT_SUBSCRIBER_OPTIONS
|
108
|
+
end
|
109
|
+
|
110
|
+
# Death handlers are called when all retries for a job have been exhausted and
|
111
|
+
# the job dies. It's the notification to your application
|
112
|
+
# that this job will not succeed without manual intervention.
|
113
|
+
#
|
114
|
+
# Subserver.configure do |config|
|
115
|
+
# config.death_handlers << ->(job, ex) do
|
116
|
+
# end
|
117
|
+
# end
|
118
|
+
def self.death_handlers
|
119
|
+
options[:death_handlers]
|
120
|
+
end
|
121
|
+
|
122
|
+
# Register a proc to handle any error which occurs within the Subserver process.
|
123
|
+
#
|
124
|
+
# Subserver.configure do |config|
|
125
|
+
# config.error_handlers << proc {|ex,ctx_hash| MyErrorService.notify(ex, ctx_hash) }
|
126
|
+
# end
|
127
|
+
#
|
128
|
+
# The default error handler logs errors to Subserver.logger.
|
129
|
+
def self.error_handlers
|
130
|
+
self.options[:error_handlers]
|
131
|
+
end
|
132
|
+
|
133
|
+
# Register a block to run at a point in the Subserver lifecycle.
|
134
|
+
# :startup, :quiet or :shutdown are valid events.
|
135
|
+
#
|
136
|
+
# Subserver.configure do |config|
|
137
|
+
# config.on(:shutdown) do
|
138
|
+
# puts "Goodbye cruel world!"
|
139
|
+
# end
|
140
|
+
# end
|
141
|
+
def self.on(event, &block)
|
142
|
+
raise ArgumentError, "Symbols only please: #{event}" unless event.is_a?(Symbol)
|
143
|
+
raise ArgumentError, "Invalid event name: #{event}" unless options[:lifecycle_events].key?(event)
|
144
|
+
options[:lifecycle_events][event] << block
|
145
|
+
end
|
146
|
+
|
147
|
+
# We are shutting down Subserver but what about workers that
|
148
|
+
# are working on some long job? This error is
|
149
|
+
# raised in workers that have not finished within the hard
|
150
|
+
# timeout limit. This is needed to rollback db transactions,
|
151
|
+
# otherwise Ruby's Thread#kill will commit.
|
152
|
+
# DO NOT RESCUE THIS ERROR IN YOUR SUBSCRIBERS
|
153
|
+
class Shutdown < Interrupt; end
|
154
|
+
|
155
|
+
private
|
156
|
+
|
157
|
+
def self.parse_config(cfile)
|
158
|
+
opts = {}
|
159
|
+
if File.exist?(cfile)
|
160
|
+
opts = YAML.load(ERB.new(IO.read(cfile)).result) || opts
|
161
|
+
|
162
|
+
if opts.respond_to? :deep_symbolize_keys!
|
163
|
+
opts.deep_symbolize_keys!
|
164
|
+
else
|
165
|
+
symbolize_keys_deep!(opts)
|
166
|
+
end
|
167
|
+
|
168
|
+
else
|
169
|
+
# allow a non-existent config file so Subserver
|
170
|
+
# can be deployed by cap with just the defaults.
|
171
|
+
end
|
172
|
+
opts
|
173
|
+
end
|
174
|
+
|
175
|
+
def self.symbolize_keys_deep!(hash)
|
176
|
+
hash.keys.each do |k|
|
177
|
+
symkey = k.respond_to?(:to_sym) ? k.to_sym : k
|
178
|
+
hash[symkey] = hash.delete k
|
179
|
+
symbolize_keys_deep! hash[symkey] if hash[symkey].kind_of? Hash
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
end
|
184
|
+
|
185
|
+
require 'subserver/rails' if defined?(::Rails::Engine)
|
@@ -0,0 +1,353 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
$stdout.sync = true
|
3
|
+
|
4
|
+
require 'yaml'
|
5
|
+
require 'singleton'
|
6
|
+
require 'optparse'
|
7
|
+
require 'erb'
|
8
|
+
require 'fileutils'
|
9
|
+
|
10
|
+
require 'subserver'
|
11
|
+
require 'subserver/util'
|
12
|
+
|
13
|
+
module Subserver
|
14
|
+
class CLI
|
15
|
+
include Util
|
16
|
+
include Singleton unless $TESTING
|
17
|
+
|
18
|
+
attr_accessor :code
|
19
|
+
attr_accessor :launcher
|
20
|
+
attr_accessor :environment
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
@code = nil
|
24
|
+
end
|
25
|
+
|
26
|
+
def parse(args=ARGV)
|
27
|
+
@code = nil
|
28
|
+
|
29
|
+
setup_options(args)
|
30
|
+
initialize_logger
|
31
|
+
validate!
|
32
|
+
daemonize
|
33
|
+
write_pid
|
34
|
+
end
|
35
|
+
|
36
|
+
def jruby?
|
37
|
+
defined?(::JRUBY_VERSION)
|
38
|
+
end
|
39
|
+
|
40
|
+
def run
|
41
|
+
boot_system
|
42
|
+
print_banner
|
43
|
+
|
44
|
+
self_read, self_write = IO.pipe
|
45
|
+
sigs = %w(INT TERM TTIN TSTP)
|
46
|
+
# USR1 and USR2 don't work on the JVM
|
47
|
+
if !jruby?
|
48
|
+
sigs << 'USR1'
|
49
|
+
sigs << 'USR2'
|
50
|
+
end
|
51
|
+
|
52
|
+
sigs.each do |sig|
|
53
|
+
begin
|
54
|
+
trap sig do
|
55
|
+
self_write.write("#{sig}\n")
|
56
|
+
end
|
57
|
+
rescue ArgumentError
|
58
|
+
puts "Signal #{sig} not supported"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
logger.info "Running in #{RUBY_DESCRIPTION}"
|
63
|
+
logger.info Subserver::LICENSE
|
64
|
+
|
65
|
+
# cache process identity
|
66
|
+
Subserver.options[:identity] = identity
|
67
|
+
|
68
|
+
# Touch middleware so it isn't lazy loaded by multiple threads.
|
69
|
+
Subserver.middleware
|
70
|
+
|
71
|
+
# Before this point, the process is initializing with just the main thread.
|
72
|
+
# Starting here the process will now have multiple threads running.
|
73
|
+
fire_event(:startup, reverse: false, reraise: true)
|
74
|
+
|
75
|
+
logger.debug { "Middleware: #{Subserver.middleware.map(&:klass).join(', ')}" }
|
76
|
+
|
77
|
+
if !options[:daemon]
|
78
|
+
logger.info 'Starting processing, hit Ctrl-C to stop'
|
79
|
+
end
|
80
|
+
|
81
|
+
# Start Health Server
|
82
|
+
@health_thread = safe_thread("health_server") do
|
83
|
+
Subserver.health_server.start
|
84
|
+
end
|
85
|
+
|
86
|
+
require 'subserver/launcher'
|
87
|
+
@launcher = Subserver::Launcher.new(options)
|
88
|
+
|
89
|
+
begin
|
90
|
+
launcher.run
|
91
|
+
|
92
|
+
while readable_io = IO.select([self_read])
|
93
|
+
signal = readable_io.first[0].gets.strip
|
94
|
+
handle_signal(signal)
|
95
|
+
end
|
96
|
+
rescue Interrupt
|
97
|
+
logger.info 'Shutting down'
|
98
|
+
launcher.stop
|
99
|
+
exit(0)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def self.banner
|
104
|
+
%q{
|
105
|
+
================================
|
106
|
+
Subserver
|
107
|
+
================================
|
108
|
+
}
|
109
|
+
end
|
110
|
+
|
111
|
+
SIGNAL_HANDLERS = {
|
112
|
+
# Ctrl-C in terminal
|
113
|
+
'INT' => ->(cli) { raise Interrupt },
|
114
|
+
# TERM is the signal that Subserver must exit.
|
115
|
+
# Heroku sends TERM and then waits 30 seconds for process to exit.
|
116
|
+
'TERM' => ->(cli) { raise Interrupt },
|
117
|
+
'USR1' => ->(cli) {
|
118
|
+
Subserver.logger.info "Received USR1, no longer accepting new work"
|
119
|
+
cli.launcher.quiet
|
120
|
+
},
|
121
|
+
'TSTP' => ->(cli) {
|
122
|
+
Subserver.logger.info "Received TSTP, no longer accepting new work"
|
123
|
+
cli.launcher.quiet
|
124
|
+
},
|
125
|
+
'USR2' => ->(cli) {
|
126
|
+
if Subserver.options[:logfile]
|
127
|
+
Subserver.logger.info "Received USR2, reopening log file"
|
128
|
+
Subserver::Logging.reopen_logs
|
129
|
+
end
|
130
|
+
},
|
131
|
+
'TTIN' => ->(cli) {
|
132
|
+
Thread.list.each do |thread|
|
133
|
+
Subserver.logger.warn "Thread TID-#{(thread.object_id ^ ::Process.pid).to_s(36)} #{thread['subserver_label']}"
|
134
|
+
if thread.backtrace
|
135
|
+
Subserver.logger.warn thread.backtrace.join("\n")
|
136
|
+
else
|
137
|
+
Subserver.logger.warn "<no backtrace available>"
|
138
|
+
end
|
139
|
+
end
|
140
|
+
},
|
141
|
+
}
|
142
|
+
|
143
|
+
def handle_signal(sig)
|
144
|
+
Subserver.logger.debug "Got #{sig} signal"
|
145
|
+
handy = SIGNAL_HANDLERS[sig]
|
146
|
+
if handy
|
147
|
+
handy.call(self)
|
148
|
+
else
|
149
|
+
Subserver.logger.info { "No signal handler for #{sig}" }
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
private unless $TESTING
|
154
|
+
|
155
|
+
def print_banner
|
156
|
+
# Print logo and banner for development
|
157
|
+
if environment == 'development' && $stdout.tty?
|
158
|
+
puts Subserver::CLI.banner
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def daemonize
|
163
|
+
return unless options[:daemon]
|
164
|
+
|
165
|
+
raise ArgumentError, "You really should set a logfile if you're going to daemonize" unless options[:logfile]
|
166
|
+
files_to_reopen = []
|
167
|
+
ObjectSpace.each_object(File) do |file|
|
168
|
+
files_to_reopen << file unless file.closed?
|
169
|
+
end
|
170
|
+
|
171
|
+
::Process.daemon(true, true)
|
172
|
+
|
173
|
+
files_to_reopen.each do |file|
|
174
|
+
begin
|
175
|
+
file.reopen file.path, "a+"
|
176
|
+
file.sync = true
|
177
|
+
rescue ::Exception
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
[$stdout, $stderr].each do |io|
|
182
|
+
File.open(options[:logfile], 'ab') do |f|
|
183
|
+
io.reopen(f)
|
184
|
+
end
|
185
|
+
io.sync = true
|
186
|
+
end
|
187
|
+
$stdin.reopen('/dev/null')
|
188
|
+
|
189
|
+
initialize_logger
|
190
|
+
end
|
191
|
+
|
192
|
+
def set_environment(cli_env)
|
193
|
+
@environment = cli_env || ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
|
194
|
+
end
|
195
|
+
|
196
|
+
alias_method :die, :exit
|
197
|
+
alias_method :☠, :exit
|
198
|
+
|
199
|
+
def setup_options(args)
|
200
|
+
opts = parse_options(args)
|
201
|
+
set_environment opts[:environment]
|
202
|
+
|
203
|
+
cfile = opts[:config_file]
|
204
|
+
opts = Subserver.load_config(cfile).merge(opts)
|
205
|
+
|
206
|
+
Subserver.options = opts
|
207
|
+
end
|
208
|
+
|
209
|
+
def options
|
210
|
+
Subserver.options
|
211
|
+
end
|
212
|
+
|
213
|
+
def boot_system
|
214
|
+
ENV['RACK_ENV'] = ENV['RAILS_ENV'] = environment
|
215
|
+
|
216
|
+
raise ArgumentError, "#{options[:require]} does not exist" unless File.exist?(options[:require])
|
217
|
+
|
218
|
+
if File.directory?(options[:require])
|
219
|
+
require 'rails'
|
220
|
+
if ::Rails::VERSION::MAJOR < 4
|
221
|
+
raise "Subserver does not support this version of Rails."
|
222
|
+
elsif ::Rails::VERSION::MAJOR == 4
|
223
|
+
require File.expand_path("#{options[:require]}/config/application.rb")
|
224
|
+
::Rails::Application.initializer "subserver.eager_load" do
|
225
|
+
::Rails.application.config.eager_load = true
|
226
|
+
end
|
227
|
+
require 'subserver/rails'
|
228
|
+
require File.expand_path("#{options[:require]}/config/environment.rb")
|
229
|
+
else
|
230
|
+
require 'subserver/rails'
|
231
|
+
require File.expand_path("#{options[:require]}/config/environment.rb")
|
232
|
+
end
|
233
|
+
options[:tag] ||= default_tag
|
234
|
+
else
|
235
|
+
not_required_message = "#{options[:require]} was not required, you should use an explicit path: " +
|
236
|
+
"./#{options[:require]} or /path/to/#{options[:require]}"
|
237
|
+
|
238
|
+
require(options[:require]) || raise(ArgumentError, not_required_message)
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
def default_tag
|
243
|
+
dir = ::Rails.root
|
244
|
+
name = File.basename(dir)
|
245
|
+
if name.to_i != 0 && prevdir = File.dirname(dir) # Capistrano release directory?
|
246
|
+
if File.basename(prevdir) == 'releases'
|
247
|
+
return File.basename(File.dirname(prevdir))
|
248
|
+
end
|
249
|
+
end
|
250
|
+
name
|
251
|
+
end
|
252
|
+
|
253
|
+
def validate!
|
254
|
+
options[:queues] << 'default' if options[:queues].empty?
|
255
|
+
|
256
|
+
if !File.exist?(options[:require]) ||
|
257
|
+
(File.directory?(options[:require]) && !File.exist?("#{options[:require]}/config/application.rb"))
|
258
|
+
logger.info "=================================================================="
|
259
|
+
logger.info " Please point subserver to a Rails 4/5 application or a Ruby file "
|
260
|
+
logger.info " to load your subscriber classes with -r [DIR|FILE]."
|
261
|
+
logger.info "=================================================================="
|
262
|
+
logger.info @parser
|
263
|
+
die(1)
|
264
|
+
end
|
265
|
+
|
266
|
+
raise ArgumentError, "#{timeout}: #{options[:timeout]} is not a valid value" if options.has_key?(:timeout) && options[:timeout].to_i <= 0
|
267
|
+
end
|
268
|
+
|
269
|
+
def parse_options(argv)
|
270
|
+
opts = {}
|
271
|
+
|
272
|
+
@parser = OptionParser.new do |o|
|
273
|
+
o.on "-c", "--credentials PATH", "Path to Google Cloud credentials JSON file." do |arg|
|
274
|
+
opts[:credentials] = arg
|
275
|
+
end
|
276
|
+
|
277
|
+
o.on '-d', '--daemon', "Daemonize process" do |arg|
|
278
|
+
opts[:daemon] = arg
|
279
|
+
end
|
280
|
+
|
281
|
+
o.on '-e', '--environment ENV', "Application environment" do |arg|
|
282
|
+
opts[:environment] = arg
|
283
|
+
end
|
284
|
+
|
285
|
+
o.on '-g', '--tag TAG', "Process tag for procline" do |arg|
|
286
|
+
opts[:tag] = arg
|
287
|
+
end
|
288
|
+
|
289
|
+
o.on '-p', '--project ID', "Google Cloud Project ID" do |arg|
|
290
|
+
opts[:project_id] = arg
|
291
|
+
end
|
292
|
+
|
293
|
+
o.on "-q", "--queue QUEUE", "Subscriber queues to process with this server" do |arg|
|
294
|
+
queue = arg
|
295
|
+
opts = (opts[:queues] ||= []) << queue
|
296
|
+
end
|
297
|
+
|
298
|
+
o.on '-r', '--require [PATH|DIR]', "Location of Rails application with subscribers or file to require" do |arg|
|
299
|
+
opts[:require] = arg
|
300
|
+
end
|
301
|
+
|
302
|
+
o.on '-t', '--timeout NUM', "Shutdown timeout" do |arg|
|
303
|
+
opts[:timeout] = Integer(arg)
|
304
|
+
end
|
305
|
+
|
306
|
+
o.on "-v", "--verbose", "Print more verbose output" do |arg|
|
307
|
+
opts[:verbose] = arg
|
308
|
+
end
|
309
|
+
|
310
|
+
o.on '-C', '--config PATH', "path to YAML config file" do |arg|
|
311
|
+
opts[:config_file] = arg
|
312
|
+
end
|
313
|
+
|
314
|
+
o.on '-L', '--logfile PATH', "path to writable logfile" do |arg|
|
315
|
+
opts[:logfile] = arg
|
316
|
+
end
|
317
|
+
|
318
|
+
o.on '-P', '--pidfile PATH', "path to pidfile" do |arg|
|
319
|
+
opts[:pidfile] = arg
|
320
|
+
end
|
321
|
+
|
322
|
+
o.on '-V', '--version', "Print version and exit" do |arg|
|
323
|
+
puts "Subserver #{Subserver::VERSION}"
|
324
|
+
die(0)
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
@parser.banner = "subserver [options]"
|
329
|
+
@parser.on_tail "-h", "--help", "Show help" do
|
330
|
+
logger.info @parser
|
331
|
+
die 1
|
332
|
+
end
|
333
|
+
@parser.parse!(argv)
|
334
|
+
|
335
|
+
opts
|
336
|
+
end
|
337
|
+
|
338
|
+
def initialize_logger
|
339
|
+
Subserver::Logging.initialize_logger(options[:logfile]) if options[:logfile]
|
340
|
+
Subserver.logger.level = ::Logger::DEBUG if options[:verbose]
|
341
|
+
end
|
342
|
+
|
343
|
+
def write_pid
|
344
|
+
if path = options[:pidfile]
|
345
|
+
pidfile = File.expand_path(path)
|
346
|
+
File.open(pidfile, 'w') do |f|
|
347
|
+
f.puts ::Process.pid
|
348
|
+
end
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
end
|
353
|
+
end
|