reqless 0.0.1

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.
Files changed (81) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +8 -0
  3. data/README.md +648 -0
  4. data/Rakefile +117 -0
  5. data/bin/docker-build-and-test +22 -0
  6. data/exe/reqless-web +11 -0
  7. data/lib/reqless/config.rb +31 -0
  8. data/lib/reqless/failure_formatter.rb +43 -0
  9. data/lib/reqless/job.rb +496 -0
  10. data/lib/reqless/job_reservers/ordered.rb +29 -0
  11. data/lib/reqless/job_reservers/round_robin.rb +46 -0
  12. data/lib/reqless/job_reservers/shuffled_round_robin.rb +21 -0
  13. data/lib/reqless/lua/reqless-lib.lua +2965 -0
  14. data/lib/reqless/lua/reqless.lua +2545 -0
  15. data/lib/reqless/lua_script.rb +90 -0
  16. data/lib/reqless/middleware/requeue_exceptions.rb +94 -0
  17. data/lib/reqless/middleware/retry_exceptions.rb +72 -0
  18. data/lib/reqless/middleware/sentry.rb +66 -0
  19. data/lib/reqless/middleware/timeout.rb +63 -0
  20. data/lib/reqless/queue.rb +189 -0
  21. data/lib/reqless/queue_priority_pattern.rb +16 -0
  22. data/lib/reqless/server/static/css/bootstrap-responsive.css +686 -0
  23. data/lib/reqless/server/static/css/bootstrap-responsive.min.css +12 -0
  24. data/lib/reqless/server/static/css/bootstrap.css +3991 -0
  25. data/lib/reqless/server/static/css/bootstrap.min.css +689 -0
  26. data/lib/reqless/server/static/css/codemirror.css +112 -0
  27. data/lib/reqless/server/static/css/docs.css +839 -0
  28. data/lib/reqless/server/static/css/jquery.noty.css +105 -0
  29. data/lib/reqless/server/static/css/noty_theme_twitter.css +137 -0
  30. data/lib/reqless/server/static/css/style.css +200 -0
  31. data/lib/reqless/server/static/favicon.ico +0 -0
  32. data/lib/reqless/server/static/img/glyphicons-halflings-white.png +0 -0
  33. data/lib/reqless/server/static/img/glyphicons-halflings.png +0 -0
  34. data/lib/reqless/server/static/js/bootstrap-alert.js +94 -0
  35. data/lib/reqless/server/static/js/bootstrap-scrollspy.js +125 -0
  36. data/lib/reqless/server/static/js/bootstrap-tab.js +130 -0
  37. data/lib/reqless/server/static/js/bootstrap-tooltip.js +270 -0
  38. data/lib/reqless/server/static/js/bootstrap-typeahead.js +285 -0
  39. data/lib/reqless/server/static/js/bootstrap.js +1726 -0
  40. data/lib/reqless/server/static/js/bootstrap.min.js +6 -0
  41. data/lib/reqless/server/static/js/codemirror.js +2972 -0
  42. data/lib/reqless/server/static/js/jquery.noty.js +220 -0
  43. data/lib/reqless/server/static/js/mode/javascript.js +360 -0
  44. data/lib/reqless/server/static/js/theme/cobalt.css +18 -0
  45. data/lib/reqless/server/static/js/theme/eclipse.css +25 -0
  46. data/lib/reqless/server/static/js/theme/elegant.css +10 -0
  47. data/lib/reqless/server/static/js/theme/lesser-dark.css +45 -0
  48. data/lib/reqless/server/static/js/theme/monokai.css +28 -0
  49. data/lib/reqless/server/static/js/theme/neat.css +9 -0
  50. data/lib/reqless/server/static/js/theme/night.css +21 -0
  51. data/lib/reqless/server/static/js/theme/rubyblue.css +21 -0
  52. data/lib/reqless/server/static/js/theme/xq-dark.css +46 -0
  53. data/lib/reqless/server/views/_job.erb +259 -0
  54. data/lib/reqless/server/views/_job_list.erb +8 -0
  55. data/lib/reqless/server/views/_pagination.erb +7 -0
  56. data/lib/reqless/server/views/about.erb +130 -0
  57. data/lib/reqless/server/views/completed.erb +11 -0
  58. data/lib/reqless/server/views/config.erb +14 -0
  59. data/lib/reqless/server/views/failed.erb +48 -0
  60. data/lib/reqless/server/views/failed_type.erb +18 -0
  61. data/lib/reqless/server/views/job.erb +17 -0
  62. data/lib/reqless/server/views/layout.erb +451 -0
  63. data/lib/reqless/server/views/overview.erb +137 -0
  64. data/lib/reqless/server/views/queue.erb +125 -0
  65. data/lib/reqless/server/views/queues.erb +45 -0
  66. data/lib/reqless/server/views/tag.erb +6 -0
  67. data/lib/reqless/server/views/throttles.erb +38 -0
  68. data/lib/reqless/server/views/track.erb +75 -0
  69. data/lib/reqless/server/views/worker.erb +34 -0
  70. data/lib/reqless/server/views/workers.erb +14 -0
  71. data/lib/reqless/server.rb +549 -0
  72. data/lib/reqless/subscriber.rb +74 -0
  73. data/lib/reqless/test_helpers/worker_helpers.rb +55 -0
  74. data/lib/reqless/throttle.rb +57 -0
  75. data/lib/reqless/version.rb +5 -0
  76. data/lib/reqless/worker/base.rb +237 -0
  77. data/lib/reqless/worker/forking.rb +215 -0
  78. data/lib/reqless/worker/serial.rb +41 -0
  79. data/lib/reqless/worker.rb +5 -0
  80. data/lib/reqless.rb +309 -0
  81. metadata +399 -0
data/Rakefile ADDED
@@ -0,0 +1,117 @@
1
+ #!/usr/bin/env rake
2
+ require 'bundler/gem_helper'
3
+ Bundler::GemHelper.install_tasks
4
+
5
+ require 'rspec/core/rake_task'
6
+ RSpec::Core::RakeTask.new(:spec) do |t|
7
+ t.rspec_opts = %w[--profile]
8
+ t.ruby_opts = "-Ispec -rsimplecov_setup"
9
+ end
10
+
11
+ min_coverage_threshold = 85.0
12
+ desc "Checks the spec coverage and fails if it is less than #{min_coverage_threshold}%"
13
+ task :check_coverage do
14
+ percent = File.read("./coverage/coverage_percent.txt").to_f
15
+ if percent < min_coverage_threshold
16
+ raise "Spec coverage was not high enough: #{percent.round(2)}%"
17
+ else
18
+ puts "Nice job! Spec coverage is still at least #{min_coverage_threshold}%"
19
+ end
20
+ end
21
+
22
+ task default: [:spec, :check_coverage]
23
+
24
+ namespace :core do
25
+ reqless_core_dir = "./lib/reqless/reqless-core"
26
+
27
+ desc "Builds the reqless-core lua scripts"
28
+ task :build do
29
+ Dir.chdir(reqless_core_dir) do
30
+ sh "make clean && make"
31
+ sh "cp reqless.lua ../lua"
32
+ sh "cp reqless-lib.lua ../lua"
33
+ end
34
+ end
35
+
36
+ task :update_submodule do
37
+ Dir.chdir(reqless_core_dir) do
38
+ sh "git checkout main"
39
+ sh "git pull --rebase"
40
+ end
41
+ end
42
+
43
+ desc "Updates reqless-core and rebuilds it"
44
+ task update: [:update_submodule, :build]
45
+
46
+ namespace :verify do
47
+ script_files = %w[ lib/reqless/lua/reqless.lua lib/reqless/lua/reqless-lib.lua ]
48
+
49
+ desc "Verifies the script has no uncommitted changes"
50
+ task :clean do
51
+ script_files.each do |file|
52
+ git_status = `git status -- #{file}`
53
+ unless /working directory clean/.match(git_status)
54
+ raise "#{file} is dirty: \n\n#{git_status}\n\n"
55
+ end
56
+ end
57
+ end
58
+
59
+ desc "Verifies the script is current"
60
+ task :current do
61
+ require 'digest/md5'
62
+ our_md5s = script_files.map do |file|
63
+ Digest::MD5.hexdigest(File.read file)
64
+ end
65
+
66
+ canonical_md5s = Dir.chdir(reqless_core_dir) do
67
+ sh "make clean && make"
68
+ script_files.map do |file|
69
+ Digest::MD5.hexdigest(File.read(File.basename file))
70
+ end
71
+ end
72
+
73
+ unless our_md5s == canonical_md5s
74
+ raise "The current scripts are out of date with reqless-core"
75
+ end
76
+ end
77
+ end
78
+
79
+ desc "Verifies the committed script is current"
80
+ task verify: %w[ verify:clean verify:current ]
81
+ end
82
+
83
+ desc "Starts a reqless console"
84
+ task :console do
85
+ ENV['PUBLIC_SEQUEL_API'] = 'true'
86
+ ENV['NO_NEW_RELIC'] = 'true'
87
+ exec "bundle exec pry -r./conf/console"
88
+ end
89
+
90
+ namespace :reqless do
91
+ desc "Runs a test worker so you can send signals to it for testing"
92
+ task :run_test_worker do
93
+ require 'reqless'
94
+ require 'reqless/job_reservers/ordered'
95
+ require 'reqless/worker'
96
+ queue = Reqless::Client.new.queues["example"]
97
+ queue.client.redis.flushdb
98
+
99
+ ENV['VVERBOSE'] = '1'
100
+
101
+ class ExampleJob
102
+ def self.perform(job)
103
+ sleep_time = job.data.fetch("sleep")
104
+ print "Sleeping for #{sleep_time}..."
105
+ sleep sleep_time
106
+ puts "done"
107
+ end
108
+ end
109
+
110
+ 20.times do |i|
111
+ queue.put(ExampleJob, sleep: i)
112
+ end
113
+
114
+ reserver = Reqless::JobReservers::Ordered.new([queue])
115
+ Reqless::Workers::ForkingWorker.new(reserver, log_level: Logger::INFO).run
116
+ end
117
+ end
@@ -0,0 +1,22 @@
1
+ #!/bin/bash
2
+
3
+ set -e -o pipefail
4
+
5
+ # From https://stackoverflow.com/a/4774063
6
+ REPO_DIR="$( cd -- "$(dirname "$0")/.." >/dev/null 2>&1 ; pwd -P )"
7
+
8
+ COMMAND="$@"
9
+ if [ -z "$COMMAND" ]; then
10
+ COMMAND="bundle exec rspec"
11
+ fi
12
+
13
+ test -f "$REPO_DIR/Gemfile.lock" && rm -f "$REPO_DIR/Gemfile.lock"
14
+ redis-cli flushdb
15
+ docker build -t reqless-rb "$REPO_DIR"
16
+ docker run \
17
+ -it \
18
+ --publish 4000:4000 \
19
+ --rm \
20
+ --volume $REPO_DIR:/app \
21
+ reqless-rb \
22
+ $COMMAND
data/exe/reqless-web ADDED
@@ -0,0 +1,11 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
4
+
5
+ require "rackup"
6
+
7
+ rackup_file = File.realpath(File.dirname(__FILE__) + '/../config.ru')
8
+
9
+ ARGV << rackup_file
10
+ server = Rackup::Server.new
11
+ server.start
@@ -0,0 +1,31 @@
1
+ # Encoding: utf-8
2
+
3
+ require 'json'
4
+
5
+ module Reqless
6
+ # A configuration class associated with a reqless client
7
+ class Config
8
+ def initialize(client)
9
+ @client = client
10
+ end
11
+
12
+ def [](key)
13
+ @client.call('config.get', key)
14
+ end
15
+
16
+ def []=(key, value)
17
+ @client.call('config.set', key, value)
18
+ end
19
+
20
+ # Get the specified `reqless` configuration option, or if
21
+ # none is provided, get the complete current configuration
22
+ def all
23
+ JSON.parse(@client.call('config.getAll'))
24
+ end
25
+
26
+ # Restore this option to the default (remove this option)
27
+ def clear(option)
28
+ @client.call('config.unset', option)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,43 @@
1
+ # Encoding: utf-8
2
+
3
+ module Reqless
4
+ # A helper for formatting failure messages
5
+ class FailureFormatter
6
+ Failure = Struct.new(:group, :message) do
7
+ # allow de-structring assignment
8
+ def to_ary
9
+ [group, message]
10
+ end
11
+ end
12
+
13
+ def initialize
14
+ @replacements = { Dir.pwd => '.' }
15
+ @replacements[ENV['GEM_HOME']] = '<GEM_HOME>' if ENV.key?('GEM_HOME')
16
+ end
17
+
18
+ def format(job, error, lines_to_remove = caller(2))
19
+ group = "#{job.klass_name}:#{error.class}"
20
+ message = "#{truncated_message(error)}\n\n" +
21
+ "#{format_failure_backtrace(error.backtrace, lines_to_remove)}"
22
+ Failure.new(group, message)
23
+ end
24
+
25
+ private
26
+
27
+ # TODO: pull this out into a config option.
28
+ MAX_ERROR_MESSAGE_SIZE = 10_000
29
+ def truncated_message(error)
30
+ return error.message if error.message.length <= MAX_ERROR_MESSAGE_SIZE
31
+ error.message.slice(0, MAX_ERROR_MESSAGE_SIZE) +
32
+ "\n... (truncated due to length)"
33
+ end
34
+
35
+ def format_failure_backtrace(error_backtrace, lines_to_remove)
36
+ (error_backtrace - lines_to_remove).map do |line|
37
+ @replacements.reduce(line) do |formatted, (original, new)|
38
+ formatted.sub(original, new)
39
+ end
40
+ end.join("\n")
41
+ end
42
+ end
43
+ end