gush 2.0.1 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ffcdce12d2f530e27afdd048328d8942e60efcddfbe20a18426b8d245589c759
4
- data.tar.gz: 3c95194d44f446a694af83b06b06eac55a3e5ed1f8555fe86958fa110b357647
3
+ metadata.gz: dc70498c39ad506749bed1f55c139d29691e540b849dd39e2ba132b54b511d66
4
+ data.tar.gz: 700d093574e0ff4772c7d36bfeb7792cda44550dd162a48afe17bec5551250cd
5
5
  SHA512:
6
- metadata.gz: e75e7f0ae5c5c83302d0507bb502a30a860f276e0706edd5a4119bb9c50bf88c73c9839ac1f141f4569017301ebb61fe943317d214c0c9a10f49309e674bd183
7
- data.tar.gz: 38f3baaa43a4d512d5d5ec6c476d21965d27116e7dc8bc1ae3cc3fa4a4fac13e6d18272ed236fd413aba0c304f0e79aee59f3c06cdad19db7fa02e41305fe491
6
+ metadata.gz: ac4c1647f57a87600466445a8d24660e3d95016f17adf9e2fe8f99260ba82a2b3e22a339b9456f653834c89c6e4b9ed6bcb50ebc337b804e07355f9a513ce8ca
7
+ data.tar.gz: 1ae01511acfc2e23bb0a671328a7b3e9b650b8589b9620461c2b9667ff9d80a47f03b8a156f249746b4f69e613863db0833be8d6221acdabd211d7eba2daee15
@@ -0,0 +1,71 @@
1
+ # This workflow uses actions that are not certified by GitHub.
2
+ # They are provided by a third-party and are governed by
3
+ # separate terms of service, privacy policy, and support
4
+ # documentation.
5
+ # This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
6
+ # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
7
+
8
+ name: Ruby
9
+
10
+ on:
11
+ pull_request:
12
+ paths-ignore:
13
+ - 'README.md'
14
+ push:
15
+ paths-ignore:
16
+ - 'README.md'
17
+
18
+ jobs:
19
+ test:
20
+ services:
21
+ redis:
22
+ image: redis:alpine
23
+ ports: ["6379:6379"]
24
+ options: --entrypoint redis-server
25
+
26
+ runs-on: ubuntu-latest
27
+ strategy:
28
+ matrix:
29
+ rails_version: ['4.2.7', '5.1.0', '5.2.0', '6.0.0', '6.1.0', '7.0']
30
+ ruby-version: ['2.6', '2.7', '3.0', '3.1']
31
+ exclude:
32
+ - ruby-version: '3.0'
33
+ rails_version: '4.2.7'
34
+ - ruby-version: '3.1'
35
+ rails_version: '4.2.7'
36
+ - ruby-version: '3.0'
37
+ rails_version: '5.0'
38
+ - ruby-version: '3.1'
39
+ rails_version: '5.0'
40
+ - ruby-version: '3.0'
41
+ rails_version: '5.1'
42
+ - ruby-version: '3.1'
43
+ rails_version: '5.1'
44
+ - ruby-version: '3.0'
45
+ rails_version: '5.2'
46
+ - ruby-version: '3.1'
47
+ rails_version: '5.2'
48
+ - ruby-version: '3.0'
49
+ rails_version: '6.0'
50
+ - ruby-version: '3.1'
51
+ rails_version: '6.0'
52
+ - ruby-version: '3.1'
53
+ rails_version: '6.1'
54
+ - ruby-version: '2.6'
55
+ rails_version: '7.0'
56
+ steps:
57
+ - uses: actions/checkout@v2
58
+ - name: Set up Ruby
59
+ uses: ruby/setup-ruby@v1
60
+ with:
61
+ ruby-version: ${{ matrix.ruby-version }}
62
+ bundler-cache: true # runs 'bundle install' and caches installed gems automatically
63
+ env:
64
+ RAILS_VERSION: "${{ matrix.rails_version }}"
65
+ - name: Install Graphviz
66
+ run: sudo apt-get install graphviz
67
+ - name: Run tests
68
+ run: bundle exec rspec
69
+ env:
70
+ REDIS_URL: redis://localhost:6379/1
71
+ RAILS_VERSION: "${{ matrix.rails_version }}"
data/.gitignore CHANGED
@@ -19,3 +19,5 @@ tmp
19
19
  test.rb
20
20
  /Gushfile
21
21
  dump.rdb
22
+ .ruby-version
23
+ .ruby-gemset
data/CHANGELOG.md CHANGED
@@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
6
6
  and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## 2.1.0
9
+
10
+ ### Added
11
+
12
+ - Allow RedisMutex’s locking duration and polling interval to be customizable, thanks to @thukim! [See pull request](https://github.com/chaps-io/gush/pull/74)
13
+ - Support for Rails 7.0 and Ruby 3.0-3.1, thanks to @joshRpowell and @kzkn!
14
+
8
15
  ## 2.0.1
9
16
 
10
17
  ### Fixed
@@ -13,31 +20,31 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
13
20
 
14
21
  ## 2.0.0
15
22
 
16
- ## Changed
23
+ ### Changed
17
24
 
18
25
  - *[BREAKING]* Store gush jobs on redis hash instead of plain keys - this improves performance when retrieving keys (Thanks to @Saicheg! [See pull request](https://github.com/chaps-io/gush/pull/56))
19
26
 
20
27
 
21
- ## Added
28
+ ### Added
22
29
 
23
30
  - Allow setting queue for each job via `:queue` option in `run` method (Thanks to @devilankur18! [See pull request](https://github.com/chaps-io/gush/pull/58))
24
31
 
25
32
 
26
33
  ## 1.1.1 - 2018-06-09
27
34
 
28
- ## Changed
35
+ ### Changed
29
36
 
30
37
  - Relax dependency on ActiveSupport to work with 4.2 up to 5.X (Thanks to @iacobus! [See pull request](https://github.com/chaps-io/gush/pull/54))
31
38
 
32
39
 
33
40
  ## 1.1.0 - 2018-02-05
34
41
 
35
- ## Added
42
+ ### Added
36
43
 
37
44
  - Added ability to specify TTL for Redis keys and manually expire whole workflows (Thanks to @dmitrypol! [See pull request](https://github.com/chaps-io/gush/pull/48))
38
45
  - Loosened dependency on redis-rb library to >= 3.2 and < 5.0 (Thanks to @mofumofu3n! [See pull request](https://github.com/chaps-io/gush/pull/52))
39
46
 
40
- ## Fixed
47
+ ### Fixed
41
48
 
42
49
  - Improved performance of (de)serializing workflows by not storing job array inside workflow JSON and other smaller improvements ([See pull request](https://github.com/chaps-io/gush/pull/53))
43
50
 
data/Gemfile CHANGED
@@ -1,6 +1,10 @@
1
1
  source 'https://rubygems.org'
2
2
  gemspec
3
3
 
4
+ rails_version = ENV['RAILS_VERSION'] || '< 7.0'
5
+ rails_version = "~> #{rails_version}" if rails_version =~ /^\d/
6
+ gem 'activejob', rails_version
7
+
4
8
  platforms :mri, :ruby do
5
9
  gem 'yajl-ruby'
6
10
  end
data/README.md CHANGED
@@ -1,6 +1,4 @@
1
- # Gush [![Build Status](https://travis-ci.org/chaps-io/gush.svg?branch=master)](https://travis-ci.org/chaps-io/gush)
2
-
3
- ## [![](http://i.imgur.com/ya8Wnyl.png)](https://chaps.io) proudly made by [Chaps](https://chaps.io)
1
+ # Gush
4
2
 
5
3
  Gush is a parallel workflow runner using only Redis as storage and [ActiveJob](http://guides.rubyonrails.org/v4.2/active_job_basics.html#introduction) for scheduling and executing jobs.
6
4
 
@@ -10,14 +8,14 @@ Gush relies on directed acyclic graphs to store dependencies, see [Parallelizing
10
8
 
11
9
  ## **WARNING - version notice**
12
10
 
13
- This README is about the `1.0.0` version, which has breaking changes compared to < 1.0.0 versions. [See here for 0.4.1 documentation](https://github.com/chaps-io/gush/blob/349c5aff0332fd14b1cb517115c26d415aa24841/README.md).
11
+ This README is about the latest `master` code, which might differ from what is released on RubyGems. See tags to browse previous READMEs.
14
12
 
15
13
  ## Installation
16
14
 
17
15
  ### 1. Add `gush` to Gemfile
18
16
 
19
17
  ```ruby
20
- gem 'gush', '~> 1.0.0'
18
+ gem 'gush', '~> 2.0'
21
19
  ```
22
20
 
23
21
  ### 2. Create `Gushfile`
@@ -276,7 +274,7 @@ class EncodeVideo < Gush::Job
276
274
  end
277
275
  ```
278
276
 
279
- `payloads` is an array containing outputs from all ancestor jobs. So for our `EncodeVide` job from above, the array will look like:
277
+ `payloads` is an array containing outputs from all ancestor jobs. So for our `EncodeVideo` job from above, the array will look like:
280
278
 
281
279
 
282
280
  ```ruby
@@ -323,6 +321,23 @@ it will generate a workflow with 5 `NotificationJob`s and one `AdminNotification
323
321
 
324
322
  ![DynamicWorkflow](https://i.imgur.com/HOI3fjc.png)
325
323
 
324
+ ### Dynamic queue for jobs
325
+
326
+ There might be a case you want to configure different jobs in the workflow using different queues. Based on the above the example, we want to config `AdminNotificationJob` to use queue `admin` and `NotificationJob` use queue `user`.
327
+
328
+ ```ruby
329
+
330
+ class NotifyWorkflow < Gush::Workflow
331
+ def configure(user_ids)
332
+ notification_jobs = user_ids.map do |user_id|
333
+ run NotificationJob, params: {user_id: user_id}, queue: 'user'
334
+ end
335
+
336
+ run AdminNotificationJob, after: notification_jobs, queue: 'admin'
337
+ end
338
+ end
339
+ ```
340
+
326
341
  ## Command line interface (CLI)
327
342
 
328
343
  ### Checking status
@@ -348,9 +363,23 @@ This requires that you have imagemagick installed on your computer:
348
363
  bundle exec gush viz <NameOfTheWorkflow>
349
364
  ```
350
365
 
366
+ ### Customizing locking options
367
+
368
+ In order to prevent getting the RedisMutex::LockError error when having a large number of jobs, you can customize these 2 fields `locking_duration` and `polling_interval` as below
369
+
370
+ ```ruby
371
+ # config/initializers/gush.rb
372
+ Gush.configure do |config|
373
+ config.redis_url = "redis://localhost:6379"
374
+ config.concurrency = 5
375
+ config.locking_duration = 2 # how long you want to wait for the lock to be released, in seconds
376
+ config.polling_interval = 0.3 # how long the polling interval should be, in seconds
377
+ end
378
+ ```
379
+
351
380
  ### Cleaning up afterwards
352
381
 
353
- Running `NotifyWorkflow.create` inserts multiple keys into Redis every time it is ran. This data might be useful for analysis but at a certain point it can be purged via Redis TTL. By default gush and Redis will keep keys forever. To configure expiration you need to 2 things. Create initializer (specify config.ttl in seconds, be different per environment).
382
+ Running `NotifyWorkflow.create` inserts multiple keys into Redis every time it is ran. This data might be useful for analysis but at a certain point it can be purged via Redis TTL. By default gush and Redis will keep keys forever. To configure expiration you need to 2 things. Create initializer (specify config.ttl in seconds, be different per environment).
354
383
 
355
384
  ```ruby
356
385
  # config/initializers/gush.rb
@@ -361,7 +390,7 @@ Gush.configure do |config|
361
390
  end
362
391
  ```
363
392
 
364
- And you need to call `flow.expire!` (optionally passing custom TTL value overriding `config.ttl`). This gives you control whether to expire data for specific workflow. Best NOT to set TTL to be too short (like minutes) but about a week in length. And you can run `Client.expire_workflow` and `Client.expire_job` passing appropriate IDs and TTL (pass -1 to NOT expire) values.
393
+ And you need to call `flow.expire!` (optionally passing custom TTL value overriding `config.ttl`). This gives you control whether to expire data for specific workflow. Best NOT to set TTL to be too short (like minutes) but about a week in length. And you can run `Client.expire_workflow` and `Client.expire_job` passing appropriate IDs and TTL (pass -1 to NOT expire) values.
365
394
 
366
395
  ### Avoid overlapping workflows
367
396
 
data/bin/gush CHANGED
@@ -12,7 +12,7 @@ require 'gush'
12
12
  begin
13
13
  Gush::CLI.start(ARGV)
14
14
  rescue Gush::WorkflowNotFound
15
- puts "Workflow not found".red
15
+ puts Paint["Workflow not found", :red]
16
16
  rescue Gush::DependencyLevelTooDeep
17
- puts "Dependency level too deep. Perhaps you have a dependency cycle?".red
17
+ puts Paint["Dependency level too deep. Perhaps you have a dependency cycle?", :red]
18
18
  end
data/gush.gemspec CHANGED
@@ -4,7 +4,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
 
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = "gush"
7
- spec.version = "2.0.1"
7
+ spec.version = "2.1.0"
8
8
  spec.authors = ["Piotrek Okoński"]
9
9
  spec.email = ["piotrek@okonski.org"]
10
10
  spec.summary = "Fast and distributed workflow runner based on ActiveJob and Redis"
@@ -17,20 +17,19 @@ Gem::Specification.new do |spec|
17
17
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
18
  spec.require_paths = ["lib"]
19
19
 
20
- spec.add_dependency "activejob", ">= 4.2.7", "< 6.0"
21
- spec.add_dependency "connection_pool", "~> 2.2.1"
20
+ spec.add_dependency "activejob", ">= 4.2.7", "< 7.1"
21
+ spec.add_dependency "concurrent-ruby", "~> 1.0"
22
22
  spec.add_dependency "multi_json", "~> 1.11"
23
23
  spec.add_dependency "redis", ">= 3.2", "< 5"
24
24
  spec.add_dependency "redis-mutex", "~> 4.0.1"
25
25
  spec.add_dependency "hiredis", "~> 0.6"
26
- spec.add_dependency "ruby-graphviz", "~> 1.2"
27
- spec.add_dependency "terminal-table", "~> 1.4"
28
- spec.add_dependency "colorize", "~> 0.7"
29
- spec.add_dependency "thor", "~> 0.19"
26
+ spec.add_dependency "graphviz", "~> 1.2"
27
+ spec.add_dependency "terminal-table", ">= 1.4", "< 3.1"
28
+ spec.add_dependency "paint", "~> 2.2"
29
+ spec.add_dependency "thor", ">= 0.19", "< 1.3"
30
30
  spec.add_dependency "launchy", "~> 2.4"
31
- spec.add_development_dependency "bundler", "~> 1.5"
31
+ spec.add_development_dependency "bundler"
32
32
  spec.add_development_dependency "rake", "~> 10.4"
33
33
  spec.add_development_dependency "rspec", '~> 3.0'
34
34
  spec.add_development_dependency "pry", '~> 0.10'
35
- spec.add_development_dependency 'fakeredis', '~> 0.5'
36
35
  end
@@ -17,11 +17,11 @@ module Gush
17
17
  elsif workflow.running?
18
18
  running_status
19
19
  elsif workflow.finished?
20
- "done".green
20
+ Paint["done", :green]
21
21
  elsif workflow.stopped?
22
- "stopped".red
22
+ Paint["stopped", :red]
23
23
  else
24
- "ready to start".blue
24
+ Paint["ready to start", :blue]
25
25
  end
26
26
  end
27
27
 
@@ -48,10 +48,10 @@ module Gush
48
48
  "ID" => workflow.id,
49
49
  "Name" => workflow.class.to_s,
50
50
  "Jobs" => workflow.jobs.count,
51
- "Failed jobs" => failed_jobs_count.red,
52
- "Succeeded jobs" => succeeded_jobs_count.green,
53
- "Enqueued jobs" => enqueued_jobs_count.yellow,
54
- "Running jobs" => running_jobs_count.blue,
51
+ "Failed jobs" => Paint[failed_jobs_count, :red],
52
+ "Succeeded jobs" => Paint[succeeded_jobs_count, :green],
53
+ "Enqueued jobs" => Paint[enqueued_jobs_count, :yellow],
54
+ "Running jobs" => Paint[running_jobs_count, :blue],
55
55
  "Remaining jobs" => remaining_jobs_count,
56
56
  "Started at" => started_at,
57
57
  "Status" => status
@@ -60,7 +60,7 @@ module Gush
60
60
 
61
61
  def running_status
62
62
  finished = succeeded_jobs_count.to_i
63
- status = "running".yellow
63
+ status = Paint["running", :yellow]
64
64
  status += "\n#{finished}/#{total_jobs_count} [#{(finished*100)/total_jobs_count}%]"
65
65
  end
66
66
 
@@ -69,7 +69,7 @@ module Gush
69
69
  end
70
70
 
71
71
  def failed_status
72
- status = "failed".light_red
72
+ status = Paint["failed", :red]
73
73
  status += "\n#{failed_job} failed"
74
74
  end
75
75
 
@@ -77,13 +77,13 @@ module Gush
77
77
  name = job.name
78
78
  case
79
79
  when job.failed?
80
- "[✗] #{name.red} \n"
80
+ "[✗] #{Paint[name, :red]} \n"
81
81
  when job.finished?
82
- "[✓] #{name.green} \n"
82
+ "[✓] #{Paint[name, :green]} \n"
83
83
  when job.enqueued?
84
- "[•] #{name.yellow} \n"
84
+ "[•] #{Paint[name, :yellow]} \n"
85
85
  when job.running?
86
- "[•] #{name.blue} \n"
86
+ "[•] #{Paint[name, :blue]} \n"
87
87
  else
88
88
  "[ ] #{name} \n"
89
89
  end
data/lib/gush/cli.rb CHANGED
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'terminal-table'
2
- require 'colorize'
4
+ require 'paint'
3
5
  require 'thor'
4
6
  require 'launchy'
5
7
 
@@ -12,43 +14,45 @@ module Gush
12
14
  def initialize(*)
13
15
  super
14
16
  Gush.configure do |config|
15
- config.gushfile = options.fetch("gushfile", config.gushfile)
16
- config.concurrency = options.fetch("concurrency", config.concurrency)
17
- config.redis_url = options.fetch("redis", config.redis_url)
18
- config.namespace = options.fetch("namespace", config.namespace)
19
- config.ttl = options.fetch("ttl", config.ttl)
17
+ config.gushfile = options.fetch("gushfile", config.gushfile)
18
+ config.concurrency = options.fetch("concurrency", config.concurrency)
19
+ config.redis_url = options.fetch("redis", config.redis_url)
20
+ config.namespace = options.fetch("namespace", config.namespace)
21
+ config.ttl = options.fetch("ttl", config.ttl)
22
+ config.locking_duration = options.fetch("locking_duration", config.locking_duration)
23
+ config.polling_interval = options.fetch("polling_interval", config.polling_interval)
20
24
  end
21
25
  load_gushfile
22
26
  end
23
27
 
24
- desc "create [WorkflowClass]", "Registers new workflow"
28
+ desc "create WORKFLOW_CLASS", "Registers new workflow"
25
29
  def create(name)
26
30
  workflow = client.create_workflow(name)
27
31
  puts "Workflow created with id: #{workflow.id}"
28
32
  puts "Start it with command: gush start #{workflow.id}"
29
33
  end
30
34
 
31
- desc "start [workflow_id]", "Starts Workflow with given ID"
35
+ desc "start WORKFLOW_ID [ARG ...]", "Starts Workflow with given ID"
32
36
  def start(*args)
33
37
  id = args.shift
34
38
  workflow = client.find_workflow(id)
35
39
  client.start_workflow(workflow, args)
36
40
  end
37
41
 
38
- desc "create_and_start [WorkflowClass]", "Create and instantly start the new workflow"
42
+ desc "create_and_start WORKFLOW_CLASS [ARG ...]", "Create and instantly start the new workflow"
39
43
  def create_and_start(name, *args)
40
44
  workflow = client.create_workflow(name)
41
45
  client.start_workflow(workflow.id, args)
42
46
  puts "Created and started workflow with id: #{workflow.id}"
43
47
  end
44
48
 
45
- desc "stop [workflow_id]", "Stops Workflow with given ID"
49
+ desc "stop WORKFLOW_ID", "Stops Workflow with given ID"
46
50
  def stop(*args)
47
51
  id = args.shift
48
52
  client.stop_workflow(id)
49
53
  end
50
54
 
51
- desc "show [workflow_id]", "Shows details about workflow with given ID"
55
+ desc "show WORKFLOW_ID", "Shows details about workflow with given ID"
52
56
  option :skip_overview, type: :boolean
53
57
  option :skip_jobs, type: :boolean
54
58
  option :jobs, default: :all
@@ -60,7 +64,7 @@ module Gush
60
64
  display_jobs_list_for(workflow, options[:jobs]) unless options[:skip_jobs]
61
65
  end
62
66
 
63
- desc "rm [workflow_id]", "Delete workflow with given ID"
67
+ desc "rm WORKFLOW_ID", "Delete workflow with given ID"
64
68
  def rm(workflow_id)
65
69
  workflow = client.find_workflow(workflow_id)
66
70
  client.destroy_workflow(workflow)
@@ -81,13 +85,39 @@ module Gush
81
85
  puts Terminal::Table.new(headings: headers, rows: rows)
82
86
  end
83
87
 
84
- desc "viz [WorkflowClass]", "Displays graph, visualising job dependencies"
85
- def viz(name)
88
+ desc "viz {WORKFLOW_CLASS|WORKFLOW_ID}", "Displays graph, visualising job dependencies"
89
+ option :filename, type: :string, default: nil
90
+ option :open, type: :boolean, default: nil
91
+ def viz(class_or_id)
86
92
  client
87
- workflow = name.constantize.new
88
- graph = Graph.new(workflow)
93
+
94
+ begin
95
+ workflow = client.find_workflow(class_or_id)
96
+ rescue WorkflowNotFound
97
+ workflow = nil
98
+ end
99
+
100
+ unless workflow
101
+ begin
102
+ workflow = class_or_id.constantize.new
103
+ rescue NameError => e
104
+ STDERR.puts Paint["'#{class_or_id}' is not a valid workflow class or id", :red]
105
+ exit 1
106
+ end
107
+ end
108
+
109
+ opts = {}
110
+
111
+ if options[:filename]
112
+ opts[:filename], opts[:path] = File.split(options[:filename])
113
+ end
114
+
115
+ graph = Graph.new(workflow, **opts)
89
116
  graph.viz
90
- Launchy.open graph.path
117
+
118
+ if (options[:open].nil? && !options[:filename]) || options[:open]
119
+ Launchy.open Pathname.new(graph.path).realpath.to_s
120
+ end
91
121
  end
92
122
 
93
123
  private
@@ -118,13 +148,14 @@ module Gush
118
148
 
119
149
  def load_gushfile
120
150
  file = client.configuration.gushfile
121
- if !gushfile.exist?
122
- raise Thor::Error, "#{file} not found, please add it to your project".colorize(:red)
151
+
152
+ unless gushfile.exist?
153
+ raise Thor::Error, Paint["#{file} not found, please add it to your project", :red]
123
154
  end
124
155
 
125
156
  load file.to_s
126
157
  rescue LoadError
127
- raise Thor::Error, "failed to require #{file}".colorize(:red)
158
+ raise Thor::Error, Paint["failed to require #{file}", :red]
128
159
  end
129
160
  end
130
161
  end