inst-jobs 0.11.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 +7 -0
- data/bin/inst_job +4 -0
- data/db/migrate/20101216224513_create_delayed_jobs.rb +40 -0
- data/db/migrate/20110208031356_add_delayed_jobs_tag.rb +14 -0
- data/db/migrate/20110426161613_add_delayed_jobs_max_attempts.rb +13 -0
- data/db/migrate/20110516225834_add_delayed_jobs_strand.rb +14 -0
- data/db/migrate/20110531144916_cleanup_delayed_jobs_indexes.rb +26 -0
- data/db/migrate/20110610213249_optimize_delayed_jobs.rb +40 -0
- data/db/migrate/20110831210257_add_delayed_jobs_next_in_strand.rb +52 -0
- data/db/migrate/20120510004759_delayed_jobs_delete_trigger_lock_for_update.rb +31 -0
- data/db/migrate/20120531150712_drop_psql_jobs_pop_fn.rb +15 -0
- data/db/migrate/20120607164022_delayed_jobs_use_advisory_locks.rb +80 -0
- data/db/migrate/20120607181141_index_jobs_on_locked_by.rb +15 -0
- data/db/migrate/20120608191051_add_jobs_run_at_index.rb +15 -0
- data/db/migrate/20120927184213_change_delayed_jobs_handler_to_text.rb +13 -0
- data/db/migrate/20140505215131_add_failed_jobs_original_job_id.rb +13 -0
- data/db/migrate/20140505215510_copy_failed_jobs_original_id.rb +13 -0
- data/db/migrate/20140505223637_drop_failed_jobs_original_id.rb +13 -0
- data/db/migrate/20140512213941_add_source_to_jobs.rb +15 -0
- data/db/migrate/20150807133223_add_max_concurrent_to_jobs.rb +70 -0
- data/db/migrate/20151123210429_add_expires_at_to_jobs.rb +15 -0
- data/db/migrate/20151210162949_improve_max_concurrent.rb +50 -0
- data/lib/delayed/backend/active_record.rb +340 -0
- data/lib/delayed/backend/base.rb +335 -0
- data/lib/delayed/backend/redis/bulk_update.lua +50 -0
- data/lib/delayed/backend/redis/destroy_job.lua +2 -0
- data/lib/delayed/backend/redis/enqueue.lua +29 -0
- data/lib/delayed/backend/redis/fail_job.lua +5 -0
- data/lib/delayed/backend/redis/find_available.lua +3 -0
- data/lib/delayed/backend/redis/functions.rb +57 -0
- data/lib/delayed/backend/redis/get_and_lock_next_available.lua +17 -0
- data/lib/delayed/backend/redis/includes/jobs_common.lua +203 -0
- data/lib/delayed/backend/redis/job.rb +497 -0
- data/lib/delayed/backend/redis/set_running.lua +5 -0
- data/lib/delayed/backend/redis/tickle_strand.lua +2 -0
- data/lib/delayed/batch.rb +56 -0
- data/lib/delayed/cli.rb +101 -0
- data/lib/delayed/daemon.rb +103 -0
- data/lib/delayed/engine.rb +4 -0
- data/lib/delayed/job_tracking.rb +31 -0
- data/lib/delayed/lifecycle.rb +90 -0
- data/lib/delayed/log_tailer.rb +22 -0
- data/lib/delayed/message_sending.rb +134 -0
- data/lib/delayed/performable_method.rb +52 -0
- data/lib/delayed/periodic.rb +85 -0
- data/lib/delayed/plugin.rb +22 -0
- data/lib/delayed/pool.rb +161 -0
- data/lib/delayed/server/helpers.rb +28 -0
- data/lib/delayed/server/public/css/app.css +12 -0
- data/lib/delayed/server/public/js/app.js +132 -0
- data/lib/delayed/server/views/index.erb +90 -0
- data/lib/delayed/server/views/layout.erb +47 -0
- data/lib/delayed/server.rb +120 -0
- data/lib/delayed/settings.rb +90 -0
- data/lib/delayed/testing.rb +32 -0
- data/lib/delayed/version.rb +3 -0
- data/lib/delayed/work_queue/in_process.rb +13 -0
- data/lib/delayed/work_queue/parent_process.rb +180 -0
- data/lib/delayed/worker.rb +234 -0
- data/lib/delayed/yaml_extensions.rb +109 -0
- data/lib/delayed_job.rb +46 -0
- data/lib/inst-jobs.rb +1 -0
- data/spec/active_record_job_spec.rb +246 -0
- data/spec/delayed/cli_spec.rb +23 -0
- data/spec/delayed/daemon_spec.rb +35 -0
- data/spec/delayed/server_spec.rb +63 -0
- data/spec/delayed/settings_spec.rb +32 -0
- data/spec/delayed/work_queue/in_process_spec.rb +31 -0
- data/spec/delayed/work_queue/parent_process_spec.rb +159 -0
- data/spec/delayed/worker_spec.rb +16 -0
- data/spec/gemfiles/32.gemfile +6 -0
- data/spec/gemfiles/40.gemfile +5 -0
- data/spec/gemfiles/41.gemfile +5 -0
- data/spec/gemfiles/42.gemfile +5 -0
- data/spec/migrate/20140924140513_add_story_table.rb +7 -0
- data/spec/redis_job_spec.rb +140 -0
- data/spec/sample_jobs.rb +28 -0
- data/spec/shared/delayed_batch.rb +85 -0
- data/spec/shared/delayed_method.rb +419 -0
- data/spec/shared/performable_method.rb +66 -0
- data/spec/shared/shared_backend.rb +819 -0
- data/spec/shared/testing.rb +48 -0
- data/spec/shared/worker.rb +378 -0
- data/spec/shared_jobs_specs.rb +15 -0
- data/spec/spec_helper.rb +97 -0
- metadata +390 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module Delayed
|
|
2
|
+
class Server
|
|
3
|
+
module Helpers
|
|
4
|
+
def h(text)
|
|
5
|
+
Rack::Utils.escape_html(text)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def url_path(*path_parts)
|
|
9
|
+
[path_prefix, path_parts].join('/').squeeze('/')
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def path_prefix
|
|
13
|
+
request.env['SCRIPT_NAME']
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def render_javascript_env
|
|
17
|
+
{
|
|
18
|
+
Routes: {
|
|
19
|
+
root: path_prefix,
|
|
20
|
+
running: url_path('running'),
|
|
21
|
+
tags: url_path('tags'),
|
|
22
|
+
jobs: url_path('jobs'),
|
|
23
|
+
}
|
|
24
|
+
}.to_json
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
(function () {
|
|
2
|
+
var Delayed = {};
|
|
3
|
+
|
|
4
|
+
Delayed.Render = {};
|
|
5
|
+
Delayed.Render.attempts = function (previousAttempts, callType, jobData) {
|
|
6
|
+
var thisAttempt = parseInt(previousAttempts, 10) + 1;
|
|
7
|
+
switch(callType) {
|
|
8
|
+
case "display":
|
|
9
|
+
return thisAttempt + "/" + jobData.max_attempts;
|
|
10
|
+
default:
|
|
11
|
+
return thisAttempt;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
Delayed.Render.runTime = function (lockedAt, callType) {
|
|
16
|
+
var elapsed, days, hours, minutes, seconds, remaining, formattedTime;
|
|
17
|
+
elapsed = Math.round((new Date().getTime() - Date.parse(lockedAt)) / 1000);
|
|
18
|
+
switch(callType) {
|
|
19
|
+
case "display": {
|
|
20
|
+
days = Math.floor(elapsed / 86400);
|
|
21
|
+
remaining = elapsed - (days * 86400);
|
|
22
|
+
hours = Math.floor(remaining / 3600);
|
|
23
|
+
remaining = remaining - (hours * 3600);
|
|
24
|
+
minutes = Math.floor(remaining / 60);
|
|
25
|
+
seconds = remaining - (minutes * 60);
|
|
26
|
+
formattedTime = ""
|
|
27
|
+
if(days > 0) {
|
|
28
|
+
formattedTime = days + "d "
|
|
29
|
+
}
|
|
30
|
+
formattedTime = formattedTime + ("0" + hours).slice(-2) + ":" + ("0" + minutes).slice(-2) + ":" + ("0" + seconds).slice(-2);
|
|
31
|
+
return formattedTime;
|
|
32
|
+
}
|
|
33
|
+
default:
|
|
34
|
+
return elapsed;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
$(document).ready(function () {
|
|
39
|
+
var runningTable, runningInterval, tagsTable, tagsInterval, jobsTable, jobsInterval
|
|
40
|
+
runningTable = $('#running').DataTable({
|
|
41
|
+
"autoWidth": false,
|
|
42
|
+
"bSort": false,
|
|
43
|
+
"paging": false,
|
|
44
|
+
"processing": true,
|
|
45
|
+
"searching": false,
|
|
46
|
+
"scrollY": "200px",
|
|
47
|
+
"serverSide": true,
|
|
48
|
+
"order": [[5, "desc"]],
|
|
49
|
+
"ajax": ENV.Routes.running,
|
|
50
|
+
"columns": [
|
|
51
|
+
{"data": "id"},
|
|
52
|
+
{"data": "locked_by", "className": "worker"},
|
|
53
|
+
{"data": "tag", "className": "tag"},
|
|
54
|
+
{"data": "attempts", "render": Delayed.Render.attempts, "className": "attempts"},
|
|
55
|
+
{"data": "strand", "className": "strand"},
|
|
56
|
+
{"data": "locked_at", "render": Delayed.Render.runTime}
|
|
57
|
+
]
|
|
58
|
+
});
|
|
59
|
+
runningInterval = setInterval(function () { runningTable.ajax.reload(); }, 2000);
|
|
60
|
+
|
|
61
|
+
tagsTable = $("#tags").DataTable({
|
|
62
|
+
"autoWidth": false,
|
|
63
|
+
"bSort": false,
|
|
64
|
+
"paging": false,
|
|
65
|
+
"processing": true,
|
|
66
|
+
"searching": false,
|
|
67
|
+
"scrollY": "200px",
|
|
68
|
+
"order": [[1, "desc"]],
|
|
69
|
+
"ajax": ENV.Routes.tags,
|
|
70
|
+
"columns": [
|
|
71
|
+
{"data": "tag", "className": "tag"},
|
|
72
|
+
{"data": "count"}
|
|
73
|
+
]
|
|
74
|
+
});
|
|
75
|
+
tagsInterval = setInterval(function () { tagsTable.ajax.reload(); }, 10000);
|
|
76
|
+
|
|
77
|
+
jobsTable = $("#jobs").DataTable({
|
|
78
|
+
"autoWidth": false,
|
|
79
|
+
"bSort": false,
|
|
80
|
+
"paging": true,
|
|
81
|
+
"processing": true,
|
|
82
|
+
"searching": false,
|
|
83
|
+
"scrollY": "200px",
|
|
84
|
+
"serverSide": true,
|
|
85
|
+
"order": [[1, "desc"]],
|
|
86
|
+
"ajax": {
|
|
87
|
+
"url": ENV.Routes.jobs,
|
|
88
|
+
"data": function (data) {
|
|
89
|
+
data.flavor = $("select#current_jobs_flavor").val();
|
|
90
|
+
data.search_term = $("input#jobs_search_term").val();
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
"columns": [
|
|
94
|
+
{"data": "id"},
|
|
95
|
+
{"data": "tag", "className": "tag"},
|
|
96
|
+
{"data": "attempts", "render": Delayed.Render.attempts},
|
|
97
|
+
{"data": "priority"},
|
|
98
|
+
{"data": "strand"},
|
|
99
|
+
{"data": "run_at"}
|
|
100
|
+
]
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
$('body').on('click', '.refresh_jobs_link', function(event) {
|
|
104
|
+
event.preventDefault();
|
|
105
|
+
jobsTable.ajax.reload();
|
|
106
|
+
return true
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
$('body').on('change', 'select#current_jobs_flavor', function (event) {
|
|
110
|
+
event.preventDefault();
|
|
111
|
+
$selectBox = $(this);
|
|
112
|
+
$selectedOption = $($selectBox.children(':selected'));
|
|
113
|
+
$searchDiv = $('div#job_search');
|
|
114
|
+
|
|
115
|
+
if ($selectedOption.data('requires-search')) {
|
|
116
|
+
$searchDiv.show();
|
|
117
|
+
} else {
|
|
118
|
+
$searchDiv.hide();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
jobsTable.ajax.reload();
|
|
122
|
+
return true
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
$('body').on('keyup', '#jobs_search_term', function (event) {
|
|
126
|
+
if (event.keyCode == 13) {
|
|
127
|
+
jobsTable.ajax.reload();
|
|
128
|
+
}
|
|
129
|
+
return true
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
})();
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
<div class="row">
|
|
2
|
+
<div class="col-md-8">
|
|
3
|
+
<h2>Running</h2>
|
|
4
|
+
<table id="running" class="table table-striped table-bordered">
|
|
5
|
+
<thead>
|
|
6
|
+
<tr>
|
|
7
|
+
<th>ID</th>
|
|
8
|
+
<th>Worker</th>
|
|
9
|
+
<th>Tag</th>
|
|
10
|
+
<th>Attempt</th>
|
|
11
|
+
<th>Strand</th>
|
|
12
|
+
<th>Runtime</th>
|
|
13
|
+
</tr>
|
|
14
|
+
</thead>
|
|
15
|
+
<tbody>
|
|
16
|
+
</tbody>
|
|
17
|
+
</table>
|
|
18
|
+
</div>
|
|
19
|
+
<div class="col-md-4">
|
|
20
|
+
<h2>Tags</h2>
|
|
21
|
+
<table id="tags" class="table table-striped table-bordered">
|
|
22
|
+
<thead>
|
|
23
|
+
<tr>
|
|
24
|
+
<th>Tag</th>
|
|
25
|
+
<th>Count</th>
|
|
26
|
+
</tr>
|
|
27
|
+
</thead>
|
|
28
|
+
<tbody>
|
|
29
|
+
</tbody>
|
|
30
|
+
</table>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<div class="row">
|
|
35
|
+
<div class="col-md-12">
|
|
36
|
+
<h2>Jobs List</h2>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<div class="row">
|
|
41
|
+
<div class="form-group">
|
|
42
|
+
<div class="col-md-2">
|
|
43
|
+
<select id="current_jobs_flavor" class="form-control">
|
|
44
|
+
<option value="current">Current</option>
|
|
45
|
+
<option value="future">Future</option>
|
|
46
|
+
<option value="failed">Failed</option>
|
|
47
|
+
<option value="id" data-requires-search="true">ID</option>
|
|
48
|
+
<option value="strand" data-requires-search="true">Strand</option>
|
|
49
|
+
<option value="tag" data-requires-search="true">Tag</option>
|
|
50
|
+
</select>
|
|
51
|
+
</div>
|
|
52
|
+
<div class="col-md-3">
|
|
53
|
+
<!-- this inline style makes me sad but doing it in the stylesheet seems to break bootstrap -->
|
|
54
|
+
<div class="input-group" id="job_search" style="display: none;">
|
|
55
|
+
<input type="text" class="form-control" id="jobs_search_term" placeholder="">
|
|
56
|
+
<span class="input-group-btn">
|
|
57
|
+
<a href="" class="btn btn-default refresh_jobs_link" aria-label="Run Search">
|
|
58
|
+
Filter <span class="glyphicon glyphicon-filter"></span>
|
|
59
|
+
</a>
|
|
60
|
+
</span>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
<div class="col-md-6">
|
|
64
|
+
</div>
|
|
65
|
+
<div class="col-md-1">
|
|
66
|
+
<a href="" class="btn btn-default pull-right refresh_jobs_link" aria-label="Refresh Jobs Table">
|
|
67
|
+
<span class="glyphicon glyphicon-refresh"></span> Refresh
|
|
68
|
+
</a>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
<div class="row job-list-row">
|
|
74
|
+
<div class="col-md-12">
|
|
75
|
+
<table id="jobs" class="table table-striped table-bordered">
|
|
76
|
+
<thead>
|
|
77
|
+
<tr>
|
|
78
|
+
<th>ID</th>
|
|
79
|
+
<th>Tag</th>
|
|
80
|
+
<th>Attempt</th>
|
|
81
|
+
<th>Priority</th>
|
|
82
|
+
<th>Strand</th>
|
|
83
|
+
<th>Run At</th>
|
|
84
|
+
</tr>
|
|
85
|
+
</thead>
|
|
86
|
+
<tbody>
|
|
87
|
+
</tbody>
|
|
88
|
+
</table>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
7
|
+
<script type="text/javascript" charset="utf8">
|
|
8
|
+
window.ENV = <%= render_javascript_env %>
|
|
9
|
+
</script>
|
|
10
|
+
<script type="text/javascript" charset="utf8 "src="//code.jquery.com/jquery-2.1.3.min.js" defer></script>
|
|
11
|
+
<script type="text/javascript" charset="utf8" src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js" defer></script>
|
|
12
|
+
<script type="text/javascript" charset="utf8" src="//cdn.datatables.net/1.10.6/js/jquery.dataTables.js" defer></script>
|
|
13
|
+
<script type="text/javascript" charset="utf8" src="//cdn.datatables.net/plug-ins/1.10.6/integration/bootstrap/3/dataTables.bootstrap.js" defer></script>
|
|
14
|
+
<script type="text/javascript" charset="utf8" src="<%= url_path 'js/app.js' %>" defer></script>
|
|
15
|
+
|
|
16
|
+
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css">
|
|
17
|
+
<link rel="stylesheet" href="//cdn.datatables.net/plug-ins/1.10.6/integration/bootstrap/3/dataTables.bootstrap.css">
|
|
18
|
+
<link rel="stylesheet" href="<%= url_path 'css/app.css' %>">
|
|
19
|
+
|
|
20
|
+
<title>Delayed Jobs</title>
|
|
21
|
+
</head>
|
|
22
|
+
<body>
|
|
23
|
+
<nav class="navbar navbar-default">
|
|
24
|
+
<div class="container-fluid">
|
|
25
|
+
<!-- Brand and toggle get grouped for better mobile display -->
|
|
26
|
+
<div class="navbar-header">
|
|
27
|
+
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
|
|
28
|
+
<span class="sr-only">Toggle navigation</span>
|
|
29
|
+
<span class="icon-bar"></span>
|
|
30
|
+
<span class="icon-bar"></span>
|
|
31
|
+
<span class="icon-bar"></span>
|
|
32
|
+
</button>
|
|
33
|
+
<a class="navbar-brand" href="<%= url_path '/' %>">Delayed Jobs</a>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<!-- Collect the nav links, forms, and other content for toggling -->
|
|
37
|
+
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
|
|
38
|
+
<ul class="nav navbar-nav">
|
|
39
|
+
</ul>
|
|
40
|
+
</div><!-- /.navbar-collapse -->
|
|
41
|
+
</div><!-- /.container-fluid -->
|
|
42
|
+
</nav>
|
|
43
|
+
<div class="container">
|
|
44
|
+
<%= yield %>
|
|
45
|
+
</div>
|
|
46
|
+
</body>
|
|
47
|
+
</html>
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
require 'sinatra/base'
|
|
2
|
+
require 'sinatra/json'
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'delayed_job'
|
|
5
|
+
|
|
6
|
+
module Delayed
|
|
7
|
+
class Server < Sinatra::Base
|
|
8
|
+
APP_DIR = File.dirname(File.expand_path(__FILE__))
|
|
9
|
+
set :views, File.join(APP_DIR, 'server', 'views')
|
|
10
|
+
set :public_folder, File.join(APP_DIR, 'server', 'public')
|
|
11
|
+
|
|
12
|
+
def initialize(*args, &block)
|
|
13
|
+
super()
|
|
14
|
+
# Rails will take care of establishing the DB connection for us if there is
|
|
15
|
+
# an application present
|
|
16
|
+
if using_active_record? && !ActiveRecord::Base.connected?
|
|
17
|
+
ActiveRecord::Base.establish_connection(ENV['DATABASE_URL'])
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def using_active_record?
|
|
22
|
+
Delayed::Job == Delayed::Backend::ActiveRecord::Job
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Ensure we're connected to the DB before processing the request
|
|
26
|
+
before do
|
|
27
|
+
if ActiveRecord::Base.respond_to?(:verify_active_connections!) && using_active_record?
|
|
28
|
+
ActiveRecord::Base.verify_active_connections!
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Release any used connections back to the pool
|
|
33
|
+
after do
|
|
34
|
+
ActiveRecord::Base.clear_active_connections! if using_active_record?
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
configure :development do
|
|
38
|
+
require 'sinatra/reloader'
|
|
39
|
+
register Sinatra::Reloader
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
helpers do
|
|
43
|
+
# this can't get required until the class has been opened for the first time
|
|
44
|
+
require 'delayed/server/helpers'
|
|
45
|
+
include Delayed::Server::Helpers
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
get '/' do
|
|
49
|
+
erb :index
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
get '/running' do
|
|
53
|
+
content_type :json
|
|
54
|
+
json({
|
|
55
|
+
draw: params['draw'].to_i,
|
|
56
|
+
recordsTotal: Delayed::Job.running.count,
|
|
57
|
+
recordsFiltered: Delayed::Job.running.count,
|
|
58
|
+
data: Delayed::Job.running_jobs.map{ |j|
|
|
59
|
+
j.as_json(include_root: false, except: [:handler, :last_error])
|
|
60
|
+
},
|
|
61
|
+
})
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
get '/tags' do
|
|
65
|
+
content_type :json
|
|
66
|
+
json({
|
|
67
|
+
draw: params['draw'].to_i,
|
|
68
|
+
data: Delayed::Job.tag_counts('current', 10)
|
|
69
|
+
})
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
DEFAULT_PAGE_SIZE = 10
|
|
73
|
+
MAX_PAGE_SIZE = 100
|
|
74
|
+
get '/jobs' do
|
|
75
|
+
content_type :json
|
|
76
|
+
flavor = params['flavor'] || 'current'
|
|
77
|
+
page_size = extract_page_size
|
|
78
|
+
offset = Integer(params['start'] || 0)
|
|
79
|
+
case flavor
|
|
80
|
+
when 'id'
|
|
81
|
+
jobs = Delayed::Job.where(id: params['search_term'])
|
|
82
|
+
total_records = 1
|
|
83
|
+
when 'future', 'current', 'failed'
|
|
84
|
+
jobs = Delayed::Job.list_jobs(flavor, page_size, offset)
|
|
85
|
+
total_records = Delayed::Job.jobs_count(flavor)
|
|
86
|
+
else
|
|
87
|
+
query = params['search_term']
|
|
88
|
+
if query.present?
|
|
89
|
+
jobs = Delayed::Job.list_jobs(flavor, page_size, offset, query)
|
|
90
|
+
else
|
|
91
|
+
jobs = []
|
|
92
|
+
end
|
|
93
|
+
total_records = Delayed::Job.jobs_count(flavor, query)
|
|
94
|
+
end
|
|
95
|
+
json({
|
|
96
|
+
draw: params['draw'].to_i,
|
|
97
|
+
recordsTotal: total_records,
|
|
98
|
+
recordsFiltered: jobs.size,
|
|
99
|
+
data: build_jobs_json(jobs),
|
|
100
|
+
})
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
private
|
|
104
|
+
|
|
105
|
+
def extract_page_size
|
|
106
|
+
page_size = Integer(params['length'] || DEFAULT_PAGE_SIZE)
|
|
107
|
+
# if dataTables wants all of the records it will send us -1 but we don't
|
|
108
|
+
# want the potential to kill our servers with this request so we'll limit it
|
|
109
|
+
page_size = DEFAULT_PAGE_SIZE if page_size == -1
|
|
110
|
+
[page_size, MAX_PAGE_SIZE].min
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def build_jobs_json(jobs)
|
|
115
|
+
json = jobs.map{ |j|
|
|
116
|
+
j.as_json(root: false, except: [:handler, :last_error])
|
|
117
|
+
}
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
require 'yaml'
|
|
2
|
+
require 'erb'
|
|
3
|
+
|
|
4
|
+
module Delayed
|
|
5
|
+
module Settings
|
|
6
|
+
SETTINGS = [
|
|
7
|
+
:queue,
|
|
8
|
+
:max_attempts,
|
|
9
|
+
:sleep_delay,
|
|
10
|
+
:sleep_delay_stagger,
|
|
11
|
+
:fetch_batch_size,
|
|
12
|
+
:select_random_from_batch,
|
|
13
|
+
:worker_procname_prefix,
|
|
14
|
+
:pool_procname_suffix,
|
|
15
|
+
:default_job_options,
|
|
16
|
+
:silence_periodic_log,
|
|
17
|
+
:disable_periodic_jobs,
|
|
18
|
+
:disable_automatic_orphan_unlocking,
|
|
19
|
+
:last_ditch_logfile,
|
|
20
|
+
:parent_process_client_timeout,
|
|
21
|
+
]
|
|
22
|
+
SETTINGS_WITH_ARGS = [ :num_strands ]
|
|
23
|
+
|
|
24
|
+
SETTINGS.each do |setting|
|
|
25
|
+
mattr_writer(setting)
|
|
26
|
+
self.send("#{setting}=", nil)
|
|
27
|
+
define_singleton_method(setting) do
|
|
28
|
+
val = class_variable_get(:"@@#{setting}")
|
|
29
|
+
val.respond_to?(:call) ? val.call() : val
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
mattr_accessor(*SETTINGS_WITH_ARGS)
|
|
34
|
+
|
|
35
|
+
def self.queue=(queue_name)
|
|
36
|
+
raise(ArgumentError, "queue_name must not be blank") if queue_name.blank?
|
|
37
|
+
@@queue = queue_name
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
self.queue = "queue"
|
|
41
|
+
self.max_attempts = 1
|
|
42
|
+
self.sleep_delay = 2.0
|
|
43
|
+
self.sleep_delay_stagger = 2.0
|
|
44
|
+
self.fetch_batch_size = 5
|
|
45
|
+
self.select_random_from_batch = false
|
|
46
|
+
self.silence_periodic_log = false
|
|
47
|
+
self.parent_process_client_timeout = 10.0
|
|
48
|
+
|
|
49
|
+
self.num_strands = ->(strand_name){ nil }
|
|
50
|
+
self.default_job_options = ->{ Hash.new }
|
|
51
|
+
|
|
52
|
+
def self.worker_config(config_filename = nil)
|
|
53
|
+
config_filename ||= default_worker_config_name
|
|
54
|
+
config = YAML.load(ERB.new(File.read(config_filename)).result)
|
|
55
|
+
env = defined?(RAILS_ENV) ? RAILS_ENV : ENV['RAILS_ENV'] || 'development'
|
|
56
|
+
config = config[env] || config['default']
|
|
57
|
+
# Backwards compatibility from when the config was just an array of queues
|
|
58
|
+
config = { :workers => config } if config.is_a?(Array)
|
|
59
|
+
unless config && config.is_a?(Hash)
|
|
60
|
+
raise ArgumentError,
|
|
61
|
+
"Invalid config file #{config_filename}"
|
|
62
|
+
end
|
|
63
|
+
config = config.with_indifferent_access
|
|
64
|
+
config[:workers].map! do |worker_config|
|
|
65
|
+
config.except(:workers).merge(worker_config.with_indifferent_access)
|
|
66
|
+
end
|
|
67
|
+
config
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def self.apply_worker_config!(config)
|
|
71
|
+
SETTINGS.each do |setting|
|
|
72
|
+
self.send("#{setting}=", config[setting.to_s]) if config.key?(setting.to_s)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def self.default_worker_config_name
|
|
77
|
+
expand_rails_path("config/delayed_jobs.yml")
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Expands rails-relative paths, without depending on rails being loaded.
|
|
81
|
+
def self.expand_rails_path(path)
|
|
82
|
+
root = if defined?(Rails) && Rails.root
|
|
83
|
+
(Rails.root+"Gemfile").to_s
|
|
84
|
+
else
|
|
85
|
+
ENV.fetch('BUNDLE_GEMFILE')
|
|
86
|
+
end
|
|
87
|
+
File.expand_path("../#{path}", root)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module Delayed
|
|
2
|
+
module Testing
|
|
3
|
+
def self.run_job(job)
|
|
4
|
+
Delayed::Worker.new.perform(job)
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def self.drain
|
|
8
|
+
while job = Delayed::Job.get_and_lock_next_available(
|
|
9
|
+
'spec run_jobs',
|
|
10
|
+
Delayed::Settings.queue,
|
|
11
|
+
0,
|
|
12
|
+
Delayed::MAX_PRIORITY)
|
|
13
|
+
run_job(job)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.track_created
|
|
18
|
+
job_tracking = JobTracking.track { yield }
|
|
19
|
+
job_tracking.created
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.clear_all!
|
|
23
|
+
case Delayed::Job.name
|
|
24
|
+
when /Redis/
|
|
25
|
+
Delayed::Job.redis.flushdb
|
|
26
|
+
when /ActiveRecord/
|
|
27
|
+
Delayed::Job.delete_all
|
|
28
|
+
Delayed::Job::Failed.delete_all
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module Delayed
|
|
2
|
+
module WorkQueue
|
|
3
|
+
# The simplest possible implementation of a WorkQueue -- just turns around and
|
|
4
|
+
# queries the queue inline.
|
|
5
|
+
class InProcess
|
|
6
|
+
def get_and_lock_next_available(worker_name, queue_name, min_priority, max_priority)
|
|
7
|
+
Delayed::Worker.lifecycle.run_callbacks(:work_queue_pop, self) do
|
|
8
|
+
Delayed::Job.get_and_lock_next_available(worker_name, queue_name, min_priority, max_priority)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|