postjob 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +23 -0
  3. data/bin/postjob +11 -0
  4. data/lib/postjob/cli/db.rb +39 -0
  5. data/lib/postjob/cli/job.rb +67 -0
  6. data/lib/postjob/cli/ps.rb +110 -0
  7. data/lib/postjob/cli/run.rb +19 -0
  8. data/lib/postjob/cli.rb +31 -0
  9. data/lib/postjob/error.rb +16 -0
  10. data/lib/postjob/job.rb +66 -0
  11. data/lib/postjob/migrations.rb +97 -0
  12. data/lib/postjob/queue/encoder.rb +40 -0
  13. data/lib/postjob/queue/notifications.rb +72 -0
  14. data/lib/postjob/queue/search.rb +82 -0
  15. data/lib/postjob/queue.rb +331 -0
  16. data/lib/postjob/registry.rb +52 -0
  17. data/lib/postjob/runner.rb +153 -0
  18. data/lib/postjob/workflow.rb +60 -0
  19. data/lib/postjob.rb +170 -0
  20. data/spec/postjob/enqueue_spec.rb +86 -0
  21. data/spec/postjob/full_workflow_spec.rb +86 -0
  22. data/spec/postjob/job_control/manual_spec.rb +45 -0
  23. data/spec/postjob/job_control/max_attempts_spec.rb +70 -0
  24. data/spec/postjob/job_control/timeout_spec.rb +31 -0
  25. data/spec/postjob/job_control/workflow_status_spec.rb +52 -0
  26. data/spec/postjob/process_job_spec.rb +25 -0
  27. data/spec/postjob/queue/encoder_spec.rb +46 -0
  28. data/spec/postjob/queue/search_spec.rb +141 -0
  29. data/spec/postjob/run_spec.rb +69 -0
  30. data/spec/postjob/step_spec.rb +26 -0
  31. data/spec/postjob/sub_workflow_spec.rb +27 -0
  32. data/spec/spec_helper.rb +35 -0
  33. data/spec/support/configure_active_record.rb +18 -0
  34. data/spec/support/configure_database.rb +19 -0
  35. data/spec/support/configure_simple_sql.rb +17 -0
  36. data/spec/support/connect_active_record.rb +6 -0
  37. data/spec/support/test_helper.rb +53 -0
  38. metadata +269 -0
@@ -0,0 +1,141 @@
1
+ require "spec_helper"
2
+
3
+ # rubocop:disable Metrics/BlockLength
4
+
5
+ module SearchWorkflow
6
+ def self.run
7
+ set_workflow_status "starting-up"
8
+
9
+ job = async :manual, timeout: 10
10
+ set_workflow_status "created-manual"
11
+
12
+ _token = workflow_token job
13
+ set_workflow_status "created-manual-token"
14
+
15
+ manual_result = await job
16
+ set_workflow_status "got-manual-token"
17
+
18
+ "manual-result:#{manual_result}"
19
+ end
20
+
21
+ Postjob.register_workflow self
22
+ end
23
+
24
+ describe "Postjob::Queue::Search" do
25
+ let!(:id) { Postjob.enqueue! "SearchWorkflow", tags: { "initial" => "yay" } }
26
+
27
+ include TestHelper
28
+
29
+ Search = Postjob::Queue::Search
30
+
31
+ before do
32
+ Postjob.process_all
33
+ end
34
+
35
+ def load_child_job
36
+ TestHelper.load_job "SELECT * FROM postjobs WHERE parent_id=$1", id
37
+ end
38
+
39
+ def token
40
+ load_token(load_child_job)
41
+ end
42
+
43
+ it "creates a token" do
44
+ expect! token => /[A-F0-9]{8}-[A-F0-9]{4}-[A-F0-9]{4}-[a-fA-F0-9]{4}-[A-F0-9]{12}/i
45
+ end
46
+
47
+ describe "returned data shape" do
48
+ it "returns a specific set of attributes" do
49
+ result = Search.one(id)
50
+ expected_keys = [
51
+ :id,
52
+ :full_id,
53
+ :job,
54
+ :workflow_status,
55
+ :status,
56
+ :error,
57
+ :result,
58
+ :next_run_at,
59
+ :error_backtrace,
60
+ :age,
61
+ :runtime,
62
+ :tags
63
+ ]
64
+ expect(result.keys).to contain_exactly(*expected_keys)
65
+ end
66
+ end
67
+
68
+ describe "search_one" do
69
+ it "can find a job by id" do
70
+ result = Search.one(id)
71
+ expect(result).to be_a(Hash)
72
+ expect(Search.one(id)[:id]).to eq(id)
73
+ end
74
+
75
+ it "writes into the into: type" do
76
+ result = Search.one(id, into: OpenStruct)
77
+ expect(result).to be_a(OpenStruct)
78
+ expect(result.id).to eq(id)
79
+ end
80
+ end
81
+
82
+ describe "search_all" do
83
+ it "can find a job by id" do
84
+ result = Search.all into: OpenStruct
85
+ expect(result.map(&:id)).to eq([id])
86
+ end
87
+
88
+ context "with multiple jobs" do
89
+ before do
90
+ Postjob.enqueue! "SearchWorkflow", tags: { "secondary" => "yay" }
91
+ end
92
+
93
+ it "returns all entries" do
94
+ result = Search.all into: OpenStruct
95
+ expect(result.length).to eq(2)
96
+ end
97
+
98
+ it "honors page and per arguments" do
99
+ result = Search.all into: OpenStruct, page: 0, per: 1
100
+ expect(result.length).to eq(1)
101
+ expect(result.first.id).to eq(id)
102
+
103
+ result = Search.all into: OpenStruct, page: 1, per: 1
104
+ expect(result.length).to eq(1)
105
+ expect(result.first.id).not_to eq(id)
106
+
107
+ result = Search.all into: OpenStruct, page: 2, per: 1
108
+ expect(result.length).to eq(0)
109
+
110
+ expect do
111
+ Search.all into: OpenStruct, page: -10
112
+ end.to raise_error(ArgumentError)
113
+
114
+ expect do
115
+ Search.all into: OpenStruct, per: -10
116
+ end.to raise_error(ArgumentError)
117
+ end
118
+
119
+ it "sorts by id" do
120
+ # Note that the second job has a id larger than the original job's id.
121
+ result = Search.all into: OpenStruct
122
+ expect(result.first.id).to eq(id)
123
+ end
124
+ end
125
+
126
+ describe "filtering" do
127
+ it "positive filters by tags" do
128
+ filter = { "initial" => "yay" }
129
+ result = Search.all filter: filter, into: OpenStruct
130
+ expect(result.length).to eq(1)
131
+ expect(result.first.id).to eq(id)
132
+ end
133
+
134
+ it "negative filters by tags" do
135
+ filter = { "initial" => "nay" }
136
+ result = Search.all filter: filter, into: OpenStruct
137
+ expect(result.length).to eq(0)
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,69 @@
1
+ # rubocop:disable Metrics/BlockLength
2
+
3
+ require "spec_helper"
4
+
5
+ module TwoLevelWorkflow
6
+ module StringWorker
7
+ def self.run
8
+ "my string"
9
+ end
10
+
11
+ Postjob.register_workflow self
12
+ end
13
+
14
+ def self.run
15
+ r = async StringWorker
16
+ await r
17
+ end
18
+
19
+ Postjob.register_workflow self
20
+ end
21
+
22
+ describe "Postjob.Postjob.process_all" do
23
+ include TestHelper
24
+
25
+ let!(:id) { Postjob.enqueue! "TwoLevelWorkflow" }
26
+
27
+ it "runs the job returning the result" do
28
+ expect(Postjob.process_all).to eq(3)
29
+ processed_job = load_job(id)
30
+ expect(processed_job.status).to eq("ok")
31
+ expect(processed_job.resolve).to eq("my string")
32
+ end
33
+
34
+ context "when scheduled to run in the future" do
35
+ before do
36
+ Simple::SQL.ask <<~SQL, id
37
+ UPDATE postjobs
38
+ SET next_run_at=next_run_at + interval '10 seconds'
39
+ WHERE id=$1
40
+ SQL
41
+ end
42
+
43
+ it "does not run the job" do
44
+ expect(Postjob.process_all).to eq(0)
45
+ processed_job = load_job(id)
46
+ # expect(processed_job.status).to eq("ready")
47
+ expect(processed_job.resolve).to eq(:pending)
48
+ end
49
+ end
50
+
51
+ context "when scheduled to time out" do
52
+ before do
53
+ Simple::SQL.ask <<~SQL, id
54
+ UPDATE postjobs
55
+ SET timing_out_at=next_run_at
56
+ WHERE id=$1
57
+ SQL
58
+ end
59
+
60
+ it "times out when job is running" do
61
+ Postjob.process_all
62
+ processed_job = load_job(id)
63
+
64
+ expect do
65
+ processed_job.resolve
66
+ end.to raise_error(Timeout::Error)
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,26 @@
1
+ require "spec_helper"
2
+
3
+ module StepWorkflow
4
+ def self.run
5
+ "Foo"
6
+ end
7
+
8
+ Postjob.register_workflow self, version: "1.2"
9
+ end
10
+
11
+ describe "Postjob.step" do
12
+ include TestHelper
13
+
14
+ let!(:job_id) { Postjob.enqueue! "StepWorkflow" }
15
+
16
+ it "runs the job is there is one" do
17
+ processed_job = Postjob.step
18
+ expect(processed_job.id).to eq(job_id)
19
+ end
20
+
21
+ it "returns nil if there is none" do
22
+ Postjob.process_all
23
+ processed_job = Postjob.step
24
+ expect(processed_job).to be_nil
25
+ end
26
+ end
@@ -0,0 +1,27 @@
1
+ require "spec_helper"
2
+
3
+ module SubWorkflow
4
+ def self.my_string
5
+ "my string"
6
+ end
7
+
8
+ def self.run
9
+ r = async :my_string
10
+ await r
11
+ end
12
+
13
+ Postjob.register_workflow self
14
+ end
15
+
16
+ describe "Sub Workflows" do
17
+ include TestHelper
18
+
19
+ let!(:id) { Postjob.enqueue! "SubWorkflow" }
20
+
21
+ it "runs the job returning the result" do
22
+ Postjob.process_all
23
+ processed_job = load_job(id)
24
+ expect(processed_job.status).to eq("ok")
25
+ expect(processed_job.resolve).to eq("my string")
26
+ end
27
+ end
@@ -0,0 +1,35 @@
1
+ ENV["RACK_ENV"] = "test"
2
+
3
+ require "rspec"
4
+ require "pry"
5
+ require "simplecov"
6
+ require "awesome_print"
7
+
8
+ unless ENV["SKIP_SIMPLE_COV"]
9
+ SimpleCov.start do
10
+ minimum_coverage 91
11
+ add_filter "/spec/"
12
+ end
13
+ end
14
+
15
+ require "postjob"
16
+ Postjob.fast_mode = true
17
+
18
+ logger = Logger.new("log/test.log")
19
+ logger.formatter = proc do |severity, _datetime, _progname, msg|
20
+ "#{severity}: #{msg}\n"
21
+ end
22
+ Simple::SQL.logger = Postjob.logger = logger
23
+
24
+ require "./spec/support/configure_database"
25
+ require "./spec/support/test_helper"
26
+
27
+ RSpec.configure do |config|
28
+ config.run_all_when_everything_filtered = true
29
+ config.filter_run focus: (ENV["CI"] != "true")
30
+ config.expect_with(:rspec) { |c| c.syntax = :expect }
31
+ config.order = "random"
32
+
33
+ config.before(:all) {}
34
+ config.after {}
35
+ end
@@ -0,0 +1,18 @@
1
+ require "active_record"
2
+ ActiveRecord::Base.establish_connection(adapter: "postgresql",
3
+ database: "postjob_test",
4
+ username: "postjob",
5
+ password: "postjob")
6
+
7
+ # RSpec.configure do |config|
8
+ # config.around(:each) do |example|
9
+ # if example.metadata[:transactions] == false
10
+ # example.run
11
+ # else
12
+ # ::ActiveRecord::Base.connection.transaction do
13
+ # example.run
14
+ # raise ActiveRecord::Rollback, "clean up"
15
+ # end
16
+ # end
17
+ # end
18
+ # end
@@ -0,0 +1,19 @@
1
+ if ENV["USE_ACTIVE_RECORD"]
2
+ require_relative "configure_active_record"
3
+ else
4
+ require_relative "configure_simple_sql"
5
+ end
6
+
7
+ RSpec.configure do |config|
8
+ config.around(:each) do |example|
9
+ Simple::SQL.ask "DELETE FROM postjob.postjobs"
10
+ example.run
11
+ end
12
+ end
13
+
14
+ require "postjob/migrations"
15
+
16
+ Postjob::Migrations.unmigrate!
17
+ Postjob::Migrations.migrate!
18
+
19
+ Simple::SQL.ask "SET search_path = postjob, public, pg_catalog"
@@ -0,0 +1,17 @@
1
+ ENV["DATABASE_URL"] = "postgresql://postjob:postjob@localhost/postjob_test"
2
+ ::Simple::SQL.connect!
3
+
4
+ # RSpec.configure do |config|
5
+ # config.around(:each) do |example|
6
+ # if example.metadata[:transactions] == false
7
+ # example.run
8
+ # else
9
+ # catch(:rollback) do
10
+ # ::Simple::SQL.transaction do
11
+ # example.run
12
+ # throw :rollback
13
+ # end
14
+ # end
15
+ # end
16
+ # end
17
+ # end
@@ -0,0 +1,6 @@
1
+ require "active_record"
2
+ ActiveRecord::Base.establish_connection(adapter: "postgresql",
3
+ database: "postjob_test",
4
+ username: "postjob",
5
+ password: "postjob")
6
+
@@ -0,0 +1,53 @@
1
+ Postjob.send :public, :process_job
2
+
3
+ module TestHelper
4
+ extend self
5
+
6
+ def jobs_count
7
+ Simple::SQL.ask "SELECT count(*) FROM postjobs"
8
+ end
9
+
10
+ def newest_job
11
+ Simple::SQL.record "SELECT * FROM postjobs ORDER BY id DESC", into: Postjob::Job
12
+ end
13
+
14
+ def load_job(id_or_sql, *args)
15
+ case id_or_sql
16
+ when Integer
17
+ Simple::SQL.record "SELECT * FROM postjobs WHERE id=$1", id_or_sql, into: Postjob::Job
18
+ else
19
+ Simple::SQL.record id_or_sql, *args, into: Postjob::Job
20
+ end
21
+ end
22
+
23
+ def load_token(job)
24
+ Simple::SQL.ask "SELECT token FROM tokens WHERE postjob_id=$1", job.id
25
+ end
26
+
27
+ def print_sql(sql, *args)
28
+ require "table_print"
29
+
30
+ records = Simple::SQL.records(sql, *args)
31
+ tp records
32
+ end
33
+
34
+ def print_jobs
35
+ print_sql <<~SQL
36
+ SELECT
37
+ id,
38
+ workflow AS job,
39
+ workflow_method AS m,
40
+ args,
41
+ status,
42
+ (results->0)::varchar AS result,
43
+ failed_attempts AS attempts,
44
+ EXTRACT(EPOCH FROM ((now() at time zone 'utc') - created_at)) AS age,
45
+ EXTRACT(EPOCH FROM (next_run_at - (now() at time zone 'utc'))) AS next_run_in,
46
+ error,
47
+ error_message,
48
+ (error_backtrace->0)::varchar AS backtrace
49
+ FROM postjobs
50
+ ORDER BY id
51
+ SQL
52
+ end
53
+ end
metadata ADDED
@@ -0,0 +1,269 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: postjob
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - radiospiel
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-03-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 3.5.0
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 3.5.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: pry
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.10'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.10'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry-byebug
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '11'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '11'
69
+ - !ruby/object:Gem::Dependency
70
+ name: simplecov
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 0.7.1
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 0.7.1
83
+ - !ruby/object:Gem::Dependency
84
+ name: activerecord
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '4.2'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '4.2'
97
+ - !ruby/object:Gem::Dependency
98
+ name: timecop
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rubocop
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: awesome_print
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: simple-sql
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: 0.2.5
146
+ type: :runtime
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: 0.2.5
153
+ - !ruby/object:Gem::Dependency
154
+ name: simple-cli
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '0.2'
160
+ - - ">="
161
+ - !ruby/object:Gem::Version
162
+ version: 0.2.4
163
+ type: :runtime
164
+ prerelease: false
165
+ version_requirements: !ruby/object:Gem::Requirement
166
+ requirements:
167
+ - - "~>"
168
+ - !ruby/object:Gem::Version
169
+ version: '0.2'
170
+ - - ">="
171
+ - !ruby/object:Gem::Version
172
+ version: 0.2.4
173
+ - !ruby/object:Gem::Dependency
174
+ name: table_print
175
+ requirement: !ruby/object:Gem::Requirement
176
+ requirements:
177
+ - - ">="
178
+ - !ruby/object:Gem::Version
179
+ version: '0'
180
+ type: :runtime
181
+ prerelease: false
182
+ version_requirements: !ruby/object:Gem::Requirement
183
+ requirements:
184
+ - - ">="
185
+ - !ruby/object:Gem::Version
186
+ version: '0'
187
+ - !ruby/object:Gem::Dependency
188
+ name: expectation
189
+ requirement: !ruby/object:Gem::Requirement
190
+ requirements:
191
+ - - "~>"
192
+ - !ruby/object:Gem::Version
193
+ version: 1.1.1
194
+ type: :runtime
195
+ prerelease: false
196
+ version_requirements: !ruby/object:Gem::Requirement
197
+ requirements:
198
+ - - "~>"
199
+ - !ruby/object:Gem::Version
200
+ version: 1.1.1
201
+ description: restartable, asynchronous, and distributed processes
202
+ email:
203
+ - radiospiel@open-lab.org
204
+ executables:
205
+ - postjob
206
+ extensions: []
207
+ extra_rdoc_files: []
208
+ files:
209
+ - README.md
210
+ - bin/postjob
211
+ - lib/postjob.rb
212
+ - lib/postjob/cli.rb
213
+ - lib/postjob/cli/db.rb
214
+ - lib/postjob/cli/job.rb
215
+ - lib/postjob/cli/ps.rb
216
+ - lib/postjob/cli/run.rb
217
+ - lib/postjob/error.rb
218
+ - lib/postjob/job.rb
219
+ - lib/postjob/migrations.rb
220
+ - lib/postjob/queue.rb
221
+ - lib/postjob/queue/encoder.rb
222
+ - lib/postjob/queue/notifications.rb
223
+ - lib/postjob/queue/search.rb
224
+ - lib/postjob/registry.rb
225
+ - lib/postjob/runner.rb
226
+ - lib/postjob/workflow.rb
227
+ - spec/postjob/enqueue_spec.rb
228
+ - spec/postjob/full_workflow_spec.rb
229
+ - spec/postjob/job_control/manual_spec.rb
230
+ - spec/postjob/job_control/max_attempts_spec.rb
231
+ - spec/postjob/job_control/timeout_spec.rb
232
+ - spec/postjob/job_control/workflow_status_spec.rb
233
+ - spec/postjob/process_job_spec.rb
234
+ - spec/postjob/queue/encoder_spec.rb
235
+ - spec/postjob/queue/search_spec.rb
236
+ - spec/postjob/run_spec.rb
237
+ - spec/postjob/step_spec.rb
238
+ - spec/postjob/sub_workflow_spec.rb
239
+ - spec/spec_helper.rb
240
+ - spec/support/configure_active_record.rb
241
+ - spec/support/configure_database.rb
242
+ - spec/support/configure_simple_sql.rb
243
+ - spec/support/connect_active_record.rb
244
+ - spec/support/test_helper.rb
245
+ homepage: https://github.com/radiospiel/postjob
246
+ licenses:
247
+ - MIT
248
+ metadata: {}
249
+ post_install_message:
250
+ rdoc_options: []
251
+ require_paths:
252
+ - lib
253
+ required_ruby_version: !ruby/object:Gem::Requirement
254
+ requirements:
255
+ - - ">="
256
+ - !ruby/object:Gem::Version
257
+ version: '0'
258
+ required_rubygems_version: !ruby/object:Gem::Requirement
259
+ requirements:
260
+ - - ">="
261
+ - !ruby/object:Gem::Version
262
+ version: '0'
263
+ requirements: []
264
+ rubyforge_project:
265
+ rubygems_version: 2.5.1
266
+ signing_key:
267
+ specification_version: 4
268
+ summary: restartable, asynchronous, and distributed processes
269
+ test_files: []