batchy 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+