workhorse 0.0.1 → 0.0.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 76af1f05b3bf26fa8ecf78fdef9bd16d651f4c21
4
- data.tar.gz: 71b70edd2c90872a13f5c27c9f512a9b61808b63
3
+ metadata.gz: cf7da416379ae17a4defae96289beca3c4dd1d58
4
+ data.tar.gz: 5b4e9cb886632f4a78982506747e1bcbdef1a7f3
5
5
  SHA512:
6
- metadata.gz: 9f29aaab73b1c7e52adcbb1ab224a8c13c2223760e52d95ee458166a8cb7d252db9d294dfeab2487c481b40be4bd4fd449beef475ad70293385501f9432dc8c5
7
- data.tar.gz: e1c5c223f41f677c65d3e932719833c2771f394c2132c406d2f10621b1f45edbf729a13e46690f16c51ebde996d364855291fe1755e901f25f2b94e181f8835a
6
+ metadata.gz: c7f8b1730ada8f2613b63ee87bf570acae14fe057bfd2650ba1ea4daa479f641a99f6d77e74b2f9484cd62aedc2899a2fd5fed8b138b09b52a2191855439d884
7
+ data.tar.gz: ee19f3e7a11eafda737333a7c41b6a83444e927c37033d8230adb26987c8bbd60c17346da5e4abfa61521500991b9856dc5fba91f5f37368fc72941a8fce60d6
data/.gitignore CHANGED
@@ -1,5 +1,6 @@
1
1
  *.gem
2
2
  *.rbc
3
+ *.log
3
4
  /.config
4
5
  /coverage/
5
6
  /InstalledFiles
@@ -42,7 +43,7 @@ build-iPhoneSimulator/
42
43
 
43
44
  # for a library or gem, you might want to ignore these files since the code is
44
45
  # intended to run in multiple environments; otherwise, check them in:
45
- # Gemfile.lock
46
+ Gemfile.lock
46
47
  # .ruby-version
47
48
  # .ruby-gemset
48
49
 
data/.releaser_config ADDED
@@ -0,0 +1,3 @@
1
+ version_file: VERSION
2
+ always_from_master: true
3
+ gem_style: github
data/.rubocop.yml ADDED
@@ -0,0 +1,84 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.3
3
+
4
+ Exclude:
5
+ - 'vendor/**/*'
6
+ - 'tmp/**/*'
7
+ - 'log/**/*'
8
+ - '*.gemspec'
9
+
10
+ DisplayCopNames: true
11
+
12
+ Style/FrozenStringLiteralComment:
13
+ Enabled: false
14
+
15
+ Style/DoubleNegation:
16
+ Enabled: false
17
+
18
+ Style/SignalException:
19
+ EnforcedStyle: only_fail
20
+
21
+ Lint/RescueWithoutErrorClass:
22
+ Enabled: False
23
+
24
+ Style/ConditionalAssignment:
25
+ Enabled: false
26
+
27
+ Layout/IndentArray:
28
+ EnforcedStyle: consistent
29
+
30
+ Metrics/MethodLength:
31
+ Enabled: false
32
+
33
+ Metrics/ClassLength:
34
+ Enabled: false
35
+
36
+ Metrics/ModuleLength:
37
+ Enabled: false
38
+
39
+ Metrics/ParameterLists:
40
+ Max: 5
41
+ CountKeywordArgs: false
42
+
43
+ Metrics/AbcSize:
44
+ Enabled: False
45
+
46
+ Metrics/CyclomaticComplexity:
47
+ Enabled: False
48
+
49
+ Metrics/PerceivedComplexity:
50
+ Enabled: False
51
+
52
+ Metrics/LineLength:
53
+ Max: 160
54
+
55
+ Metrics/BlockNesting:
56
+ Enabled: false
57
+
58
+ Metrics/BlockLength:
59
+ Enabled: false
60
+
61
+ Style/IfUnlessModifier:
62
+ Enabled: false
63
+
64
+ Style/Documentation:
65
+ Enabled: false
66
+
67
+ Style/RedundantReturn:
68
+ Enabled: false
69
+
70
+ Style/AsciiComments:
71
+ Enabled: false
72
+
73
+ Style/GuardClause:
74
+ Enabled: false
75
+
76
+ Style/ClassAndModuleChildren:
77
+ Enabled: false
78
+ EnforcedStyle: compact
79
+ SupportedStyles:
80
+ - nested
81
+ - compact
82
+
83
+ Style/NumericPredicate:
84
+ Enabled: false
data/.travis.yml ADDED
@@ -0,0 +1,11 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.3.0
4
+ services:
5
+ - mysql
6
+ before_install:
7
+ - mysql -e 'CREATE DATABASE workhorse;'
8
+ script:
9
+ - bundle install
10
+ - bundle exec rake test
11
+ - bundle exec rubocop
data/README.md CHANGED
@@ -1,20 +1,172 @@
1
1
  [![Build Status](https://travis-ci.org/sitrox/workhorse.svg?branch=master)](https://travis-ci.org/sitrox/workhorse)
2
2
  [![Gem Version](https://badge.fury.io/rb/workhorse.svg)](https://badge.fury.io/rb/workhorse)
3
3
 
4
- **This gem is considered work in progress. Do not use this in production yet.**
5
-
6
4
  # Workhorse
7
5
 
6
+ **This Gem is still in an early stage of development. Please not use this in production yet.**
7
+
8
8
  Multi-threaded job backend with database queuing for ruby.
9
9
 
10
+ ## Introduction
11
+
12
+ What it is:
13
+
14
+ * Jobs are instances of classes that support the `perform` method.
15
+ * Jobs are persisted in the database using ActiveRecord.
16
+ * You can start one or more worker processes.
17
+ * Each worker is configurable as to which queue(s) it processes.
18
+ * Each worker polls the database and spawns a number of threads to execute jobs
19
+ of different queues simultaneously.
20
+
21
+ What it isn't:
22
+
23
+ * It cannot spawn new processes. Jobs are run in separate threads but not in
24
+ separate processes (unless you start multiple worker processes manually).
25
+ * It does not support retries.
26
+
10
27
  ## Installation
11
28
 
12
- To install this gem using bundler, add it to your `Gemfile`:
29
+ ### Requirements
30
+
31
+ * A database and table handler that properly supports row-level locking (such as
32
+ MySQL with InnoDB, PostgreSQL or Oracle).
33
+ * An operating system and file system that supports file locking.
34
+
35
+ ### Installing under Rails
36
+
37
+ 1. Add `workhorse` to your `Gemfile`:
38
+
39
+ ```ruby
40
+ gem 'workhorse'
41
+ ```
42
+
43
+ 2. Run the install generator:
44
+
45
+ ```bash
46
+ bin/rails generate workhorse:install
47
+ ```
48
+
49
+ This generates:
50
+
51
+ * A database migration for creating the `jobs` table
52
+ * An initializer `config/initializers/workhorse.rb` for global configuration
53
+ * A daemon worker script under `bin/workhorse.rb`
54
+
55
+ Please customize the configuration files to your liking.
56
+
57
+ ## Queuing jobs
58
+
59
+ ### Basic jobs
60
+
61
+ Workhorse can handle any jobs that support the `perform` method and are
62
+ serializable. To queue a basic job, use the static method `Workhorse.enqueue`:
63
+
64
+ ```ruby
65
+ class MyJob
66
+ def initialize(name)
67
+ @name = name
68
+ end
69
+
70
+ def perform
71
+ puts "Hello #{@name}"
72
+ end
73
+ end
74
+
75
+ Workhorse.enqueue MyJob.new('John'), queue: :test
76
+ ```
77
+
78
+ In the above example, we also specify a queue named `:test`. This means that
79
+ this job will never run simoultaneously with other jobs in the same queue. If no
80
+ queue is given, the job can always be executed simoultaneously with any other
81
+ job.
82
+
83
+ ### RailsOps operations
84
+
85
+ Workhorse allows you to easily queue
86
+ [RailsOps](https://github.com/sitrox/rails_ops) operations using the static
87
+ method `Workhorse.enqueue_op`:
13
88
 
14
89
  ```ruby
15
- gem 'workhorse'
90
+ Workhorse.enqueue Operations::Jobs::CleanUpDatabase, quiet: true
16
91
  ```
17
92
 
93
+ Params passed using the second argument will be used for operation instantiation
94
+ at job execution.
95
+
96
+ You can also specify a queue:
97
+
98
+ ```ruby
99
+ Workhorse.enqueue Operations::Jobs::CleanUpDatabase, { quiet: true }, queue: :maintenance
100
+ ```
101
+
102
+ ## Configuring and starting workers
103
+
104
+ Workers poll the database for new jobs and execute them in one or more threads.
105
+ Typically, one worker is started per process. While you can start workers
106
+ manually, either in your main application process(es) or in a separate one,
107
+ workhorse also provides you with a convenient way of starting one or multiple
108
+ worker processes as a daemon.
109
+
110
+ ### Start workers manually
111
+
112
+ Workers are created by instantiatating, configuring and starting a new
113
+ `Workhorse::Worker` instance.
114
+
115
+ ```ruby
116
+ Workhorse::Worker.start_and_wait(
117
+ pool_size: 5, # Processes 5 jobs concurrently
118
+ quiet: false, # Logs to STDOUT
119
+ logger: Rails.logger # Logs to Rails log. You can also
120
+ # provide any custom logger.
121
+ )
122
+ ```
123
+
124
+ See [code
125
+ documentation](http://www.rubydoc.info/github/sitrox/workhorse/Workhorse%2FWorker:initialize)
126
+ for more information on the arguments.
127
+
128
+ ### Start workers using a daemon script
129
+
130
+ Using `Workhorse::Daemon` (`Workhorse::Daemon::ShellHandler`), you can spawn one
131
+ or multiple worker processes automatically. This is useful for cases where you
132
+ want the workers to exist in separate processes as opposed to in your main
133
+ application process(es).
134
+
135
+ For this case, the workhorse install routine automatically creates a file called
136
+ `bin/workhorse.rb` which can be used to start one or more worker processes.
137
+
138
+ To start the daemon:
139
+
140
+ ```bash
141
+ bin/workhorse.rb start[|stop|status|watch|restart|usage]
142
+ ```
143
+
144
+ #### Background and customization
145
+
146
+ The daemon-part allows you to run arbitrary code as a daemon:
147
+
148
+ ```ruby
149
+ Workhorse::Daemon::ShellHandler.run count: 5 do
150
+ # This runs as a daemon and will be started 5 times
151
+ end
152
+ ```
153
+
154
+ Within this shell handler, you can now instantiate, configure and start a worker
155
+ as described under *Start workers manually*:
156
+
157
+ ```ruby
158
+ Workhorse::Daemon::ShellHandler.run count: 5 do
159
+ # This will be run 5 times, each time in a separate process. Per process, it
160
+ # will be able to process 3 jobs concurrently.
161
+ Workhorse::Worker.start_and_wait(pool_size: 3, logger: Rails.logger)
162
+ end
163
+ ```
164
+
165
+ ## Roadmap
166
+
167
+ * [ ] ActiveJob integration for Rails
168
+ * [ ] Job timeouts
169
+
18
170
  ## Copyright
19
171
 
20
172
  Copyright (c) 2017 Sitrox. See `LICENSE` for further details.
data/Rakefile ADDED
@@ -0,0 +1,35 @@
1
+ task :gemspec do
2
+ gemspec = Gem::Specification.new do |spec|
3
+ spec.name = 'workhorse'
4
+ spec.version = IO.read('VERSION').chomp
5
+ spec.authors = ['Sitrox']
6
+ spec.summary = %(
7
+ Multi-threaded job backend with database queuing for ruby.
8
+ )
9
+ spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
10
+ spec.executables = []
11
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
12
+ spec.require_paths = ['lib']
13
+
14
+ spec.add_development_dependency 'bundler', '~> 1.3'
15
+ spec.add_development_dependency 'rake'
16
+ spec.add_development_dependency 'rubocop', '0.51.0'
17
+ spec.add_development_dependency 'minitest'
18
+ spec.add_development_dependency 'mysql2', '~> 0.3.13'
19
+ spec.add_development_dependency 'benchmark-ips'
20
+ spec.add_dependency 'activesupport'
21
+ spec.add_dependency 'activerecord'
22
+ spec.add_dependency 'schemacop', '~> 2.0'
23
+ spec.add_dependency 'concurrent-ruby'
24
+ end
25
+
26
+ File.open('workhorse.gemspec', 'w') { |f| f.write(gemspec.to_ruby.strip) }
27
+ end
28
+
29
+ require 'rake/testtask'
30
+
31
+ Rake::TestTask.new do |t|
32
+ t.pattern = 'test/workhorse/**/*_test.rb'
33
+ t.verbose = false
34
+ t.libs << 'test/lib'
35
+ end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.1
1
+ 0.0.2
data/bin/rubocop ADDED
@@ -0,0 +1 @@
1
+ bundle exec rubocop "$@"
@@ -0,0 +1,24 @@
1
+ module Workhorse
2
+ class InstallGenerator < Rails::Generators::Base
3
+ include Rails::Generators::Migration
4
+
5
+ source_root File.expand_path('../templates', __FILE__)
6
+
7
+ def self.next_migration_number(_dir)
8
+ Time.now.utc.strftime('%Y%m%d%H%M%S')
9
+ end
10
+
11
+ def install_migration
12
+ migration_template 'create_table_jobs.rb', 'db/migrate/create_table_jobs.rb'
13
+ end
14
+
15
+ def install_daemon_script
16
+ template 'bin/workhorse.rb'
17
+ chmod 'bin/workhorse.rb', 0o755
18
+ end
19
+
20
+ def install_initializer
21
+ template 'config/initializers/workhorse.rb'
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require './config/environment'
4
+
5
+ Workhorse::Daemon::ShellHandler.run do
6
+ Workhorse::Worker.start_and_wait(pool_size: 5, logger: Rails.logger)
7
+ end
@@ -0,0 +1,11 @@
1
+ Workhorse.setup do |config|
2
+ # Set this to false in order to prevent jobs from being automatically
3
+ # wrapped into a transaction. The built-in workhorse logic will still run
4
+ # in transactions.
5
+ config.perform_jobs_in_tx = true
6
+
7
+ # Enable this to specify an alternative callback for handling transactions.
8
+ # config.tx_callback = proc do |&block|
9
+ # ActiveRecord::Base.transaction&(&block)
10
+ # end
11
+ end
@@ -0,0 +1,23 @@
1
+ class CreateTableJobs < ActiveRecord::Migration
2
+ def change
3
+ create_table :jobs, force: true do |t|
4
+ t.string :state, null: false, default: 'waiting'
5
+ t.string :queue, null: true
6
+ t.text :handler, null: false, limit: 4_294_967_295
7
+
8
+ t.string :locked_by
9
+ t.datetime :locked_at
10
+
11
+ t.datetime :started_at
12
+
13
+ t.datetime :succeeded_at
14
+ t.datetime :failed_at
15
+ t.text :last_error, limit: 4_294_967_295
16
+
17
+ t.timestamps null: false
18
+ end
19
+
20
+ add_index :jobs, :queue
21
+ add_index :jobs, :state
22
+ end
23
+ end
data/lib/workhorse.rb ADDED
@@ -0,0 +1,42 @@
1
+ require 'socket'
2
+ require 'active_support/all'
3
+ require 'active_record'
4
+
5
+ module Workhorse
6
+ @set_up = false
7
+
8
+ # Returns the performer currently performing the active job. This can only be
9
+ # called from within a job and the same thread.
10
+ def self.performer
11
+ Thread.current[:workhorse_current_performer]\
12
+ || fail('No performer is associated with the current thread. This method must always be called inside of a job.')
13
+ end
14
+
15
+ cattr_accessor :tx_callback
16
+ self.tx_callback = proc do |&block|
17
+ ActiveRecord::Base.transaction(&block)
18
+ end
19
+
20
+ cattr_accessor :perform_jobs_in_tx
21
+ self.perform_jobs_in_tx = true
22
+
23
+ def self.setup
24
+ fail 'Workhorse is already set up.' if @set_up
25
+ yield self
26
+ @set_up = true
27
+ end
28
+ end
29
+
30
+ require 'workhorse/db_job'
31
+ require 'workhorse/enqueuer'
32
+ require 'workhorse/performer'
33
+ require 'workhorse/poller'
34
+ require 'workhorse/pool'
35
+ require 'workhorse/worker'
36
+ require 'workhorse/jobs/run_rails_op'
37
+
38
+ # Daemon functionality is not available on java platforms
39
+ if RUBY_PLATFORM != 'java'
40
+ require 'workhorse/daemon'
41
+ require 'workhorse/daemon/shell_handler'
42
+ end