garbageman 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm 1.9.3-p327@garbageman
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source "http://rubygems.org"
2
+
3
+ group :development do
4
+ gem "rdoc", "~> 3.12"
5
+ gem "jeweler", "~> 1.8.4"
6
+ gem "rspec", ">= 0"
7
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,30 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ diff-lcs (1.2.4)
5
+ git (1.2.5)
6
+ jeweler (1.8.4)
7
+ bundler (~> 1.0)
8
+ git (>= 1.2.5)
9
+ rake
10
+ rdoc
11
+ json (1.8.0)
12
+ rake (10.0.4)
13
+ rdoc (3.12.2)
14
+ json (~> 1.4)
15
+ rspec (2.13.0)
16
+ rspec-core (~> 2.13.0)
17
+ rspec-expectations (~> 2.13.0)
18
+ rspec-mocks (~> 2.13.0)
19
+ rspec-core (2.13.1)
20
+ rspec-expectations (2.13.0)
21
+ diff-lcs (>= 1.1.3, < 2.0)
22
+ rspec-mocks (2.13.1)
23
+
24
+ PLATFORMS
25
+ ruby
26
+
27
+ DEPENDENCIES
28
+ jeweler (~> 1.8.4)
29
+ rdoc (~> 3.12)
30
+ rspec
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2013 Doug Youch
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,19 @@
1
+ = garbageman
2
+
3
+ Description goes here.
4
+
5
+ == Contributing to garbageman
6
+
7
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
8
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
9
+ * Fork the project.
10
+ * Start a feature/bugfix branch.
11
+ * Commit and push until you are happy with your contribution.
12
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
13
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
14
+
15
+ == Copyright
16
+
17
+ Copyright (c) 2013 Doug Youch. See LICENSE.txt for
18
+ further details.
19
+
data/Rakefile ADDED
@@ -0,0 +1,36 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "garbageman"
18
+ gem.homepage = "http://github.com/dyouch5@yahoo.com/garbageman"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{Process requests without garbage collection}
21
+ gem.description = %Q{Disable GC while processing requests. By using nginx upstream health checks to garbage collect when no one is there.}
22
+ gem.email = "doug@sessionm.com"
23
+ gem.authors = ["Doug Youch"]
24
+ # dependencies defined in Gemfile
25
+ end
26
+ Jeweler::RubygemsDotOrgTasks.new
27
+
28
+ require 'rdoc/task'
29
+ Rake::RDocTask.new do |rdoc|
30
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
31
+
32
+ rdoc.rdoc_dir = 'rdoc'
33
+ rdoc.title = "garbageman #{version}"
34
+ rdoc.rdoc_files.include('README*')
35
+ rdoc.rdoc_files.include('lib/**/*.rb')
36
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,138 @@
1
+ require 'singleton'
2
+
3
+ module GarbageMan
4
+ class Collector
5
+ include Singleton
6
+
7
+ attr_accessor :request_count, :will_collect
8
+ attr_reader :fiber_poll
9
+
10
+ def initialize
11
+ reset
12
+ end
13
+
14
+ def register_fiber_pool(pool)
15
+ @fiber_poll = pool
16
+ end
17
+
18
+ def healthy?
19
+ unless can_disable?
20
+ debug "can not disable gc"
21
+ GC.enable
22
+ return true
23
+ end
24
+
25
+ if should_collect?
26
+ write_gc_yaml server_index, 'will_collect'
27
+ false
28
+ else
29
+ true
30
+ end
31
+ end
32
+
33
+ def collect
34
+ return unless can_collect?
35
+
36
+ write_gc_yaml server_index, 'starting'
37
+ debug "starting gc"
38
+ starts = Time.now
39
+ GC.enable
40
+ GC.start
41
+ diff = (Time.now - starts) * 1000
42
+ info "GC took #{'%.2f' % diff}ms"
43
+ write_gc_yaml server_index, 'finished'
44
+
45
+ reset
46
+
47
+ if can_disable? && select_next_server
48
+ debug "disabling gc"
49
+ GC.disable
50
+ else
51
+ debug "enabling gc, can not turn off"
52
+ GC.enable
53
+ end
54
+ end
55
+
56
+ def create_gc_yaml
57
+ return unless server_index
58
+ return if File.exists?(Config.gc_yaml_file)
59
+ write_gc_yaml server_index, 'selected'
60
+ end
61
+
62
+ private
63
+
64
+ def server_index
65
+ Thin::Backends::Base.server_index
66
+ end
67
+
68
+ def num_servers
69
+ self.thin_config['servers']
70
+ end
71
+
72
+ def write_gc_yaml(index, status)
73
+ config = {'gc' => {'server' => index, 'status' => status}}
74
+ File.open(Config.gc_yaml_file, 'w+') { |f| f.write config.to_yaml }
75
+ end
76
+
77
+ def select_next_server
78
+ Config.thin_config['servers'].times do |i|
79
+ next_server_index = (server_index + i + 1) % num_servers
80
+ file = self.thin_config['socket'].sub '.sock', ".#{next_server_index}.sock"
81
+ next unless File.exists?(file)
82
+ debug "selected #{next_server_index}"
83
+ write_gc_yaml next_server_index, 'selected'
84
+ return true
85
+ end
86
+ false
87
+ end
88
+
89
+ def reset
90
+ @request_count = 0
91
+ @will_collect = false
92
+ end
93
+
94
+ # no traffic and we've been selected by health check
95
+ def can_collect?
96
+ @will_collect && fiber_pool.busy_fibers.size == 0 && Thin::Backends::Base.num_connections == 0
97
+ end
98
+
99
+ # if the request count is high enough and it is our turn
100
+ def should_collect?
101
+ @will_collect = (@request_count >= Config.num_request_before_collecting && current_server?)
102
+ end
103
+
104
+ def current_server?
105
+ config = Config.gc_config
106
+ config && config['gc'] && config['gc']['server'] && config['gc']['server'] == server_index
107
+ end
108
+
109
+ def can_disable?
110
+ Config.thin_config.has_key?('socket') && not_alone?
111
+ end
112
+
113
+ # make sure I'm not the only server running
114
+ def not_alone?
115
+ Config.thin_config['servers'].times do |i|
116
+ next if i == server_index
117
+ file = self.thin_config['socket'].sub '.sock', ".#{i}.sock"
118
+ if File.exists?(file)
119
+ return true
120
+ end
121
+ end
122
+
123
+ debug "no other servers found"
124
+ false
125
+ end
126
+
127
+ def logger; GarbageMan.logger; end
128
+
129
+ def debug(msg)
130
+ logger.debug msg
131
+ end
132
+
133
+ def info(msg)
134
+ logger.info msg
135
+ end
136
+ end
137
+ end
138
+
@@ -0,0 +1,23 @@
1
+ module GarbageMan
2
+ class Config
3
+ @@gc_health_check_request_path = '/gc_health_check'
4
+ def self.gc_health_check_request_path; @@gc_health_check_request_path; end
5
+
6
+ @@gc_yaml_file = nil
7
+ def self.gc_yaml_file; @@gc_yaml_file ||= "#{Rails.root}/data/gc.yml"; end
8
+ def self.gc_yaml_file=(file); @@gc_yaml_file = file; end
9
+
10
+ def self.gc_config
11
+ begin
12
+ File.exists?(self.gc_yaml_file) ? YAML.load_file(self.gc_yaml_file) : nil
13
+ rescue Errno::ENOENT => e
14
+ nil
15
+ end
16
+ end
17
+
18
+ @@thin_config = nil
19
+ def self.thin_config; @@thin_config ||= YAML.load_file("#{Rails.root}/config/thin.yml"); end
20
+
21
+ def self.num_request_before_collecting; 10; end
22
+ end
23
+ end
@@ -0,0 +1,86 @@
1
+ module Thin
2
+ module Backends
3
+ class Base
4
+ cattr_reader :num_connections
5
+ @@num_connections = 0
6
+ cattr_accessor :server_index
7
+ @@server_index = nil
8
+
9
+ def connection_finished_with_count(connection)
10
+ connection_finished_without_count(connection).tap { @@num_connections -= 1 }
11
+ end
12
+ alias_method_chain :connection_finished, :count
13
+
14
+ protected
15
+
16
+ def initialize_connection_with_count(connection)
17
+ initialize_connection_without_count(connection).tap { @@num_connections += 1 }
18
+ end
19
+ alias initialize_connection_without_count count
20
+ alias count initialize_connection_with_count
21
+ end
22
+
23
+ class TcpServer
24
+ def connect_with_callbacks
25
+ Thin::Server.run_before_startup_callbacks
26
+ connect_without_callbacks.tap do
27
+ Thin::Server.run_after_startup_callbacks
28
+ end
29
+ end
30
+ alias connect_without_callbacks connect
31
+ alias connect connect_with_callbacks
32
+ end
33
+
34
+ class UnixServer
35
+ def connect_with_callbacks
36
+ Thin::Server.run_before_startup_callbacks
37
+ connect_without_callbacks.tap do
38
+ Thin::Backends::Base.server_index = @socket.to_s.sub(/^.*?\.(\d+)\.sock$/, '\\1').to_i
39
+ Thin::Server.run_after_startup_callbacks
40
+ end
41
+ end
42
+ alias connect_without_callbacks connect
43
+ alias connect connect_with_callbacks
44
+ end
45
+ end
46
+
47
+ class Server
48
+ @@before_startup_callbacks = []
49
+ @@after_startup_callbacks = []
50
+ @@close_callbacks = []
51
+
52
+ # thin is not yet excepting requests, but EM has started
53
+ def self.add_before_startup_callback(proc=nil, &block)
54
+ @@before_startup_callbacks << (proc || block)
55
+ end
56
+
57
+ # this is excepting requests and has written the socket file
58
+ def self.add_after_startup_callback(proc=nil, &block)
59
+ @@after_startup_callbacks << (proc || block)
60
+ end
61
+
62
+ # these callbacks are called after all the requests have been processed
63
+ def self.add_close_callback(proc=nil, &block)
64
+ @@close_callbacks << (proc || block)
65
+ end
66
+
67
+ def self.run_before_startup_callbacks
68
+ @@before_startup_callbacks.each { |c| c.call } if @@before_startup_callbacks
69
+ @@before_startup_callbacks = nil
70
+ end
71
+
72
+ def self.run_after_startup_callbacks
73
+ @@after_startup_callbacks.each { |c| c.call } if @@after_startup_callbacks
74
+ @@after_startup_callbacks = nil
75
+ end
76
+
77
+ def stop_with_callbacks!
78
+ stop_without_callbacks!.tap do
79
+ @@close_callbacks.each { |c| c.call } if @@close_callbacks
80
+ @@close_callbacks = nil
81
+ end
82
+ end
83
+ alias stop_without_callbacks! stop!
84
+ alias stop! stop_with_callbacks!
85
+ end
86
+ end
@@ -0,0 +1,22 @@
1
+ module GarbageMan
2
+ module Rack
3
+ class Middleware
4
+ def initialize(app)
5
+ @app = app
6
+ end
7
+
8
+ @@ok_response = [200, {'Content-Length' => '0'}, '']
9
+ @@gc_response = [589, {'Content-Length' => '0'}, '']
10
+ def call(env)
11
+ GcCollector.instance.request_count += 1
12
+
13
+ if env['REQUEST_PATH'] == GarbageMan::Config.gc_health_check_request_path
14
+ GarbageMan::Collector.instance.healthy? ? @@ok_response : @@gc_response
15
+ else
16
+ GcCollector.instance.debug("still receiving traffic even though I'm waiting to GC") if GcCollector.instance.will_collect
17
+ @app.call(env)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
data/lib/garbageman.rb ADDED
@@ -0,0 +1,22 @@
1
+ # copied from dalli
2
+ module GarbageMan
3
+ def self.logger
4
+ @logger ||= (rails_logger || default_logger)
5
+ end
6
+
7
+ def self.rails_logger
8
+ (defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger) ||
9
+ (defined?(RAILS_DEFAULT_LOGGER) && RAILS_DEFAULT_LOGGER.respond_to?(:debug) && RAILS_DEFAULT_LOGGER)
10
+ end
11
+
12
+ def self.default_logger
13
+ require 'logger'
14
+ l = Logger.new(STDOUT)
15
+ l.level = Logger::INFO
16
+ l
17
+ end
18
+
19
+ def self.logger=(logger)
20
+ @logger = logger
21
+ end
22
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,18 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'test/unit'
11
+ require 'shoulda'
12
+
13
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
14
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
15
+ require 'garbageman'
16
+
17
+ class Test::Unit::TestCase
18
+ end
@@ -0,0 +1,7 @@
1
+ require 'helper'
2
+
3
+ class TestGarbageman < Test::Unit::TestCase
4
+ should "probably rename this file and start testing for real" do
5
+ flunk "hey buddy, you should probably rename this file and start testing for real"
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,114 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: garbageman
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Doug Youch
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-06-10 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rdoc
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '3.12'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '3.12'
30
+ - !ruby/object:Gem::Dependency
31
+ name: jeweler
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: 1.8.4
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 1.8.4
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ description: Disable GC while processing requests. By using nginx upstream health
63
+ checks to garbage collect when no one is there.
64
+ email: doug@sessionm.com
65
+ executables: []
66
+ extensions: []
67
+ extra_rdoc_files:
68
+ - LICENSE.txt
69
+ - README.rdoc
70
+ files:
71
+ - .document
72
+ - .rvmrc
73
+ - Gemfile
74
+ - Gemfile.lock
75
+ - LICENSE.txt
76
+ - README.rdoc
77
+ - Rakefile
78
+ - VERSION
79
+ - lib/garbageman.rb
80
+ - lib/garbageman/collector.rb
81
+ - lib/garbageman/config.rb
82
+ - lib/garbageman/ext/thin.rb
83
+ - lib/garbageman/rack/middleware.rb
84
+ - test/helper.rb
85
+ - test/test_garbageman.rb
86
+ homepage: http://github.com/dyouch5@yahoo.com/garbageman
87
+ licenses:
88
+ - MIT
89
+ post_install_message:
90
+ rdoc_options: []
91
+ require_paths:
92
+ - lib
93
+ required_ruby_version: !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ! '>='
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ segments:
100
+ - 0
101
+ hash: -3258579490044163618
102
+ required_rubygems_version: !ruby/object:Gem::Requirement
103
+ none: false
104
+ requirements:
105
+ - - ! '>='
106
+ - !ruby/object:Gem::Version
107
+ version: '0'
108
+ requirements: []
109
+ rubyforge_project:
110
+ rubygems_version: 1.8.23
111
+ signing_key:
112
+ specification_version: 3
113
+ summary: Process requests without garbage collection
114
+ test_files: []