batchy 0.0.1

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.
@@ -0,0 +1,20 @@
1
+ Copyright 2012 YOURNAME
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.
@@ -0,0 +1,34 @@
1
+ = Batchy
2
+
3
+ A wrapper for batch processes. It was designed to be used with an asynchronous library like delayed_job
4
+ or resque. Code wrapped in a batchy block will have it's start and end time logged. Batchy will keep track of
5
+ the current state of the batch from new to running to completion. Errors not handled in the batched code will
6
+ be caught by batchy and logged. Callbacks can be defined for success, failure or both.
7
+
8
+ {<img src="https://secure.travis-ci.org/Raybeam/batchy.png?branch=master" />}[http://travis-ci.org/Raybeam/batchy]
9
+
10
+ == Installation
11
+
12
+ gem install batchy
13
+
14
+ == Examples
15
+
16
+ === Configuration
17
+
18
+ For a full list of configuration options, check the documentation.
19
+
20
+ Batchy.configure do | c |
21
+ c.allow_duplicates = false
22
+ c.raise_errors = false
23
+ c.name_process = true
24
+ c.allow_mass_sigkill = false
25
+ c.add_global_success_callback do | bch |
26
+ # do something on success
27
+ end
28
+ c.add_global_failure_callback do | bch |
29
+ # do something on failure
30
+ end
31
+ c.add_global_ensure_callback do | bch |
32
+ # do something regardless of end state
33
+ end
34
+ end
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+
8
+ begin
9
+ require 'rdoc/task'
10
+ rescue LoadError
11
+ require 'rdoc/rdoc'
12
+ require 'rake/rdoctask'
13
+ RDoc::Task = Rake::RDocTask
14
+ end
15
+
16
+ RDoc::Task.new(:rdoc) do |rdoc|
17
+ rdoc.rdoc_dir = 'rdoc'
18
+ rdoc.title = 'Batchy'
19
+ rdoc.options << '--line-numbers'
20
+ rdoc.rdoc_files.include('README.rdoc')
21
+ rdoc.rdoc_files.include('lib/**/*.rb')
22
+ end
23
+
24
+ Bundler::GemHelper.install_tasks
25
+
26
+ Dir['lib/tasks/*.rake'].each do | f |
27
+ load f
28
+ end
29
+
@@ -0,0 +1,84 @@
1
+ require 'active_record'
2
+ require 'active_support'
3
+ require 'addressable/uri'
4
+ require 'andand'
5
+ require 'state_machine/core'
6
+ require 'sys/proctable'
7
+
8
+ require 'batchy/batch'
9
+ require 'batchy/configuration'
10
+ require 'batchy/version'
11
+
12
+ require 'generators/batchy/templates/migration'
13
+
14
+ module Batchy
15
+ extend self
16
+
17
+ # Get configuration options
18
+ # If a block is provided, it sets the contained
19
+ # configuration options.
20
+ def configure
21
+ @configuration ||= Configuration.new
22
+ if block_given?
23
+ yield @configuration
24
+ end
25
+ @configuration
26
+ end
27
+
28
+ # Issue a SIGTERM to all expired batches. This will result in an error
29
+ # being raised inside the batchy block and a final error state
30
+ # for the batch.
31
+ def clean_expired
32
+ Batchy::Batch.expired.each do | b |
33
+ b.kill
34
+ end
35
+ end
36
+
37
+ # This is dangerous. If you open a batch inside your main program
38
+ # this process could kill your main application.
39
+ # It is turned off by default.
40
+ def clean_expired!
41
+ unless Batchy.configure.allow_mass_sigkill
42
+ Kernel.warn 'Mass sigkill is not allowed. Use "clean_expired" to issue a mass SIGTERM, or set the "allow_mass_sigkill" config option to true'
43
+ return false
44
+ end
45
+
46
+ Batchy::Batch.expired.each do | b |
47
+ b.kill!
48
+ end
49
+ end
50
+
51
+ # Sets all configuration options back to
52
+ # default
53
+ def clear_configuration
54
+ @configuration = nil
55
+ end
56
+
57
+ # Sets the logger for batchy
58
+ def logger
59
+ @logger ||= Logger.new(STDOUT)
60
+ @logger
61
+ end
62
+
63
+ def logger=(logger)
64
+ @logger = logger
65
+ end
66
+
67
+ # The main entry point for batchy. It wraps code in a batchy
68
+ # block. Batchy handles errors, logging and allows for
69
+ # callbacks.
70
+ def run *args
71
+ options = args.extract_options!
72
+
73
+ batch = Batch.create options
74
+ batch.start!
75
+ return false if batch.ignored?
76
+ begin
77
+ yield batch
78
+ rescue Exception => e
79
+ batch.error = "{#{e.message}\n#{e.backtrace.join('\n')}"
80
+ ensure
81
+ batch.finish!
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,195 @@
1
+ module Batchy
2
+ class Batch < ActiveRecord::Base
3
+ extend StateMachine::MacroMethods
4
+
5
+ self.table_name = 'batchy_batches'
6
+
7
+ attr_reader :failure_callbacks
8
+ attr_reader :success_callbacks
9
+ attr_reader :ensure_callbacks
10
+ attr_reader :ignore_callbacks
11
+
12
+ validates_presence_of :name
13
+
14
+ state_machine :state, :initial => :new do
15
+ event :start do
16
+ transition :new => :ignored, :if => :invalid_duplication
17
+ transition :new => :running
18
+ end
19
+ after_transition :new => :running do | batch, transition |
20
+ batch.started_at = DateTime.now
21
+ batch.pid = Process.pid
22
+ batch.save!
23
+
24
+ # Set the proclist process name
25
+ $0 = batch.name if Batchy.configure.name_process
26
+ end
27
+
28
+ # Ignored
29
+ after_transition :new => :ignored do | batch, transition |
30
+ batch.started_at = DateTime.now
31
+ batch.finished_at = DateTime.now
32
+ batch.pid = Process.pid
33
+ batch.save!
34
+ end
35
+ after_transition :new => :ignored, :do => :run_ignore_callbacks
36
+ after_transition :new => :ignored, :do => :run_ensure_callbacks
37
+
38
+ event :finish do
39
+ transition :running => :success, :unless => :has_errors
40
+ transition :running => :errored
41
+ end
42
+ after_transition :running => [:success, :errored] do | batch, transition |
43
+ batch.finished_at = DateTime.now
44
+ batch.save!
45
+ end
46
+ after_transition :running => :success, :do => :run_success_callbacks
47
+ after_transition :running => :errored, :do => :run_failure_callbacks
48
+ after_transition :running => [:success, :errored], :do => :run_ensure_callbacks
49
+ end
50
+
51
+ class << self
52
+ # Return all expired batches that are still running
53
+ def expired
54
+ where('expire_at < ? and state = ?', DateTime.now, 'running')
55
+ end
56
+ end
57
+
58
+ # Set up callback queues and states
59
+ def initialize *args
60
+ create_callback_queues
61
+ super
62
+ end
63
+
64
+ # Is there batch with the same guid already running?
65
+ def already_running
66
+ duplicate_batches.count > 0
67
+ end
68
+
69
+ # Find all batches with the same guid that are
70
+ # running.
71
+ def duplicate_batches
72
+ rel = self.class.where(:guid => guid, :state => "running")
73
+ return rel if new_record?
74
+
75
+ rel.where("id <> ?", id)
76
+ end
77
+
78
+ # Is this batch expired
79
+ def expired?
80
+ expire_at < DateTime.now && state == 'running'
81
+ end
82
+
83
+ # Does this batch have errors?
84
+ def has_errors
85
+ error?
86
+ end
87
+
88
+ # Is this an unwanted duplicate process?
89
+ def invalid_duplication
90
+ !Batchy.configure.allow_duplicates && already_running
91
+ end
92
+
93
+ # Issues a SIGTERM to the process running this batch
94
+ def kill
95
+ Process.kill('TERM', pid)
96
+ end
97
+
98
+ # Issue a SIGKILL to the process running this batch
99
+ # BE CAREFUL! This will kill your application or
100
+ # server if this batch has the same PID.
101
+ def kill!
102
+ Process.kill('KILL', pid)
103
+ end
104
+
105
+ # Adds a callback that runs no matter what the
106
+ # exit state of the batchy block
107
+ def on_ensure *args, &block
108
+ if block_given?
109
+ @ensure_callbacks << block
110
+ else
111
+ @ensure_callbacks << args.shift
112
+ end
113
+ end
114
+
115
+ # Add a callback to run on failure of the
116
+ # batch
117
+ def on_failure *args, &block
118
+ if block_given?
119
+ @failure_callbacks << block
120
+ else
121
+ @failure_callbacks << args.shift
122
+ end
123
+ end
124
+
125
+ # Adds a callback that runs if the
126
+ # process is a duplicate
127
+ def on_ignore *args, &block
128
+ if block_given?
129
+ @ignore_callbacks << block
130
+ else
131
+ @ignore_callbacks << args.shift
132
+ end
133
+ end
134
+
135
+ # Add a callback to run on successful
136
+ # completion of the batch
137
+ def on_success *args, &block
138
+ if block_given?
139
+ @success_callbacks << block
140
+ else
141
+ @success_callbacks << args.shift
142
+ end
143
+ end
144
+
145
+ # :nodoc:
146
+ def run_ensure_callbacks
147
+ Batchy.configure.global_ensure_callbacks.each do | ec |
148
+ ec.call(self)
149
+ end
150
+ ensure_callbacks.each do | ec |
151
+ ec.call(self)
152
+ end
153
+ end
154
+
155
+ # :nodoc:
156
+ def run_ignore_callbacks
157
+ Batchy.configure.global_ignore_callbacks.each do | ic |
158
+ ic.call(self)
159
+ end
160
+ ignore_callbacks.each do | ic |
161
+ ic.call(self)
162
+ end
163
+ end
164
+
165
+ # :nodoc:
166
+ def run_success_callbacks
167
+ Batchy.configure.global_success_callbacks.each do | sc |
168
+ sc.call(self)
169
+ end
170
+ success_callbacks.each do | sc |
171
+ sc.call(self)
172
+ end
173
+ end
174
+
175
+ # :nodoc:
176
+ def run_failure_callbacks
177
+ Batchy.configure.global_failure_callbacks.each do | fc |
178
+ fc.call(self)
179
+ end
180
+ failure_callbacks.each do | fc |
181
+ fc.call(self)
182
+ end
183
+ end
184
+
185
+ private
186
+
187
+ # :nodoc:
188
+ def create_callback_queues
189
+ @success_callbacks = []
190
+ @failure_callbacks = []
191
+ @ensure_callbacks = []
192
+ @ignore_callbacks = []
193
+ end
194
+ end
195
+ end
@@ -0,0 +1,81 @@
1
+ module Batchy
2
+ class Configuration
3
+
4
+ def initialize
5
+ @global_success_callbacks = []
6
+ @global_failure_callbacks = []
7
+ @global_ensure_callbacks = []
8
+ @global_ignore_callbacks = []
9
+ end
10
+
11
+ # Whether the batch can run multiple processes
12
+ # with the same GUID. If set to false,
13
+ # a request to run a batch while another
14
+ # with the same GUID is still running will result
15
+ # in an ignored state and the batch will not run
16
+ # Defaults to true
17
+ attr_writer :allow_duplicates
18
+ def allow_duplicates
19
+ @allow_duplicates.nil? ? true : @allow_duplicates
20
+ end
21
+
22
+ # When a batch encounters an error, the error is caught
23
+ # and logged while the batch exits with an "errored" state.
24
+ # If you wish to error to continue to be raised up
25
+ # the stack, set this to true
26
+ # Defaults to false
27
+ attr_writer :raise_errors
28
+ def raise_errors
29
+ @raise_errors.nil? ? false : @raise_errors
30
+ end
31
+
32
+ # This sets the name of the process in the proclist to
33
+ # the name of the current batch. It defaults to true
34
+ attr_writer :name_process
35
+ def name_process
36
+ @name_process.nil? ? true : @name_process
37
+ end
38
+
39
+ # This library has the ability to issue a SIGKILL
40
+ # to all expired batch processes. If batchy is
41
+ # being used by a server process or the main
42
+ # application process, this will kill it. Only
43
+ # set this to true if you're sure you're only
44
+ # going to use batches as async processes that
45
+ # nothing else depends on. Defaults to false.
46
+ attr_writer :allow_mass_sigkill
47
+ def allow_mass_sigkill
48
+ @allow_mass_sigkill.nil? ? false : @allow_mass_sigkill
49
+ end
50
+
51
+ # Global callbacks will be called on all batches. They
52
+ # will be added to the batch on initialization and
53
+ # so they will be executed first, before any batch-specific
54
+ # callbacks
55
+
56
+ # Global callback for failures
57
+ attr_reader :global_failure_callbacks
58
+ def add_global_failure_callback *args, &block
59
+ @global_failure_callbacks << block
60
+ end
61
+
62
+ # Global callback for ignore
63
+ attr_reader :global_ignore_callbacks
64
+ def add_global_ignore_callback *args, &block
65
+ @global_ignore_callbacks << block
66
+ end
67
+
68
+ # Global callback for successes
69
+ attr_reader :global_success_callbacks
70
+ def add_global_success_callback *args, &block
71
+ @global_success_callbacks << block
72
+ end
73
+
74
+ # Global callbacks that execute no matter
75
+ # what the end state
76
+ attr_reader :global_ensure_callbacks
77
+ def add_global_ensure_callback *args, &block
78
+ @global_ensure_callbacks << block
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,3 @@
1
+ module Batchy
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,24 @@
1
+ class CreateBatchy < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :batchy_batches, :force => true do |table|
4
+ table.datetime :started_at # When the batch started
5
+ table.datetime :finished_at # When the batch finished
6
+ table.datetime :expire_at # When this batch should expire
7
+ table.string :state # Current state of the batch
8
+ table.text :error # Reason for failure (if there is one)
9
+ table.string :hostname # Host the batch is running on
10
+ table.integer :pid # Process ID of the current batch
11
+ table.string :name # Name of the batch job
12
+ table.string :guid # Field to be used for unique identification of the calling job
13
+ table.integer :batch_id # Self-referential ID for identifying parent batches
14
+ table.timestamps
15
+ end
16
+
17
+ add_index :batchy_batches, :guid
18
+ add_index :batchy_batches, :state
19
+ end
20
+
21
+ def self.down
22
+ drop_table :batchy_batches
23
+ end
24
+ end
@@ -0,0 +1,11 @@
1
+ namespace :batchy do
2
+ task :rename do
3
+ $0 = 'Doing things .... xxxxx'
4
+ puts $1
5
+ sleep 60
6
+ end
7
+
8
+ task :check do
9
+ puts Sys::ProcTable.fields
10
+ end
11
+ end
metadata ADDED
@@ -0,0 +1,189 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: batchy
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.0.1
6
+ platform: ruby
7
+ authors:
8
+ - Bob Briski
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2012-03-24 00:00:00 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: activerecord
17
+ requirement: &id001 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ~>
21
+ - !ruby/object:Gem::Version
22
+ version: "3.0"
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: *id001
26
+ - !ruby/object:Gem::Dependency
27
+ name: activesupport
28
+ requirement: &id002 !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: "3.0"
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: *id002
37
+ - !ruby/object:Gem::Dependency
38
+ name: addressable
39
+ requirement: &id003 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ~>
43
+ - !ruby/object:Gem::Version
44
+ version: "2.2"
45
+ type: :runtime
46
+ prerelease: false
47
+ version_requirements: *id003
48
+ - !ruby/object:Gem::Dependency
49
+ name: andand
50
+ requirement: &id004 !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ~>
54
+ - !ruby/object:Gem::Version
55
+ version: "1.3"
56
+ type: :runtime
57
+ prerelease: false
58
+ version_requirements: *id004
59
+ - !ruby/object:Gem::Dependency
60
+ name: state_machine
61
+ requirement: &id005 !ruby/object:Gem::Requirement
62
+ none: false
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: "0"
67
+ type: :runtime
68
+ prerelease: false
69
+ version_requirements: *id005
70
+ - !ruby/object:Gem::Dependency
71
+ name: rake
72
+ requirement: &id006 !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: "0"
78
+ type: :runtime
79
+ prerelease: false
80
+ version_requirements: *id006
81
+ - !ruby/object:Gem::Dependency
82
+ name: sys-proctable
83
+ requirement: &id007 !ruby/object:Gem::Requirement
84
+ none: false
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: "0"
89
+ type: :runtime
90
+ prerelease: false
91
+ version_requirements: *id007
92
+ - !ruby/object:Gem::Dependency
93
+ name: rspec
94
+ requirement: &id008 !ruby/object:Gem::Requirement
95
+ none: false
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: "0"
100
+ type: :development
101
+ prerelease: false
102
+ version_requirements: *id008
103
+ - !ruby/object:Gem::Dependency
104
+ name: factory_girl
105
+ requirement: &id009 !ruby/object:Gem::Requirement
106
+ none: false
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: "0"
111
+ type: :development
112
+ prerelease: false
113
+ version_requirements: *id009
114
+ - !ruby/object:Gem::Dependency
115
+ name: sqlite3
116
+ requirement: &id010 !ruby/object:Gem::Requirement
117
+ none: false
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ version: "0"
122
+ type: :development
123
+ prerelease: false
124
+ version_requirements: *id010
125
+ - !ruby/object:Gem::Dependency
126
+ name: database_cleaner
127
+ requirement: &id011 !ruby/object:Gem::Requirement
128
+ none: false
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: "0"
133
+ type: :development
134
+ prerelease: false
135
+ version_requirements: *id011
136
+ description: For handling all of the exceptions, states, timeouts, etc of batch processes
137
+ email:
138
+ - bbriski@raybeam.com
139
+ executables: []
140
+
141
+ extensions: []
142
+
143
+ extra_rdoc_files: []
144
+
145
+ files:
146
+ - lib/batchy/batch.rb
147
+ - lib/batchy/configuration.rb
148
+ - lib/batchy/version.rb
149
+ - lib/batchy.rb
150
+ - lib/generators/batchy/templates/migration.rb
151
+ - lib/tasks/batchy_tasks.rake
152
+ - MIT-LICENSE
153
+ - Rakefile
154
+ - README.rdoc
155
+ homepage: https://github.com/Raybeam/batchy
156
+ licenses: []
157
+
158
+ post_install_message:
159
+ rdoc_options: []
160
+
161
+ require_paths:
162
+ - lib
163
+ required_ruby_version: !ruby/object:Gem::Requirement
164
+ none: false
165
+ requirements:
166
+ - - ">="
167
+ - !ruby/object:Gem::Version
168
+ hash: 1244919966576900379
169
+ segments:
170
+ - 0
171
+ version: "0"
172
+ required_rubygems_version: !ruby/object:Gem::Requirement
173
+ none: false
174
+ requirements:
175
+ - - ">="
176
+ - !ruby/object:Gem::Version
177
+ hash: 1244919966576900379
178
+ segments:
179
+ - 0
180
+ version: "0"
181
+ requirements: []
182
+
183
+ rubyforge_project:
184
+ rubygems_version: 1.8.15
185
+ signing_key:
186
+ specification_version: 3
187
+ summary: Set up and tear down a batch
188
+ test_files: []
189
+