gush 2.0.1 → 2.1.0

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
  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