perfectqueue 0.7.32 → 0.8.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.
- data/.gitignore +6 -0
- data/ChangeLog +0 -62
- data/Gemfile +3 -0
- data/README.md +239 -0
- data/Rakefile +19 -0
- data/lib/perfectqueue.rb +68 -4
- data/lib/perfectqueue/application.rb +30 -0
- data/lib/perfectqueue/application/base.rb +27 -0
- data/lib/perfectqueue/application/dispatch.rb +73 -0
- data/lib/perfectqueue/application/router.rb +69 -0
- data/lib/perfectqueue/backend.rb +44 -47
- data/lib/perfectqueue/backend/rdb_compat.rb +298 -0
- data/lib/perfectqueue/blocking_flag.rb +84 -0
- data/lib/perfectqueue/client.rb +117 -0
- data/lib/perfectqueue/command/perfectqueue.rb +108 -323
- data/lib/perfectqueue/daemons_logger.rb +80 -0
- data/lib/perfectqueue/engine.rb +85 -123
- data/lib/perfectqueue/error.rb +53 -0
- data/lib/perfectqueue/model.rb +37 -0
- data/lib/perfectqueue/multiprocess.rb +31 -0
- data/lib/perfectqueue/multiprocess/child_process.rb +108 -0
- data/lib/perfectqueue/multiprocess/child_process_monitor.rb +109 -0
- data/lib/perfectqueue/multiprocess/fork_processor.rb +164 -0
- data/lib/perfectqueue/multiprocess/thread_processor.rb +123 -0
- data/lib/perfectqueue/queue.rb +58 -0
- data/lib/perfectqueue/runner.rb +39 -0
- data/lib/perfectqueue/signal_queue.rb +112 -0
- data/lib/perfectqueue/task.rb +103 -0
- data/lib/perfectqueue/task_metadata.rb +98 -0
- data/lib/perfectqueue/task_monitor.rb +189 -0
- data/lib/perfectqueue/task_status.rb +27 -0
- data/lib/perfectqueue/version.rb +1 -3
- data/lib/perfectqueue/worker.rb +114 -196
- data/perfectqueue.gemspec +24 -0
- data/spec/queue_spec.rb +234 -0
- data/spec/spec_helper.rb +44 -0
- data/spec/worker_spec.rb +81 -0
- metadata +93 -40
- checksums.yaml +0 -7
- data/README.rdoc +0 -224
- data/lib/perfectqueue/backend/null.rb +0 -33
- data/lib/perfectqueue/backend/rdb.rb +0 -181
- data/lib/perfectqueue/backend/simpledb.rb +0 -139
- data/test/backend_test.rb +0 -259
- data/test/cat.sh +0 -2
- data/test/echo.sh +0 -4
- data/test/exec_test.rb +0 -61
- data/test/fail.sh +0 -2
- data/test/huge.sh +0 -2
- data/test/stress.rb +0 -99
- data/test/success.sh +0 -2
- data/test/test_helper.rb +0 -19
@@ -0,0 +1,73 @@
|
|
1
|
+
#
|
2
|
+
# PerfectQueue
|
3
|
+
#
|
4
|
+
# Copyright (C) 2012 FURUHASHI Sadayuki
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
module PerfectQueue
|
20
|
+
module Application
|
21
|
+
|
22
|
+
class Dispatch < Runner
|
23
|
+
# Runner interface
|
24
|
+
def initialize(task)
|
25
|
+
base = self.class.router.route(task.type)
|
26
|
+
unless base
|
27
|
+
task.retry!
|
28
|
+
raise "Unknown task type #{task.type.inspect}" # TODO error class
|
29
|
+
end
|
30
|
+
@runner = base.new(task)
|
31
|
+
super
|
32
|
+
end
|
33
|
+
|
34
|
+
attr_reader :runner
|
35
|
+
|
36
|
+
def run
|
37
|
+
@runner.run
|
38
|
+
end
|
39
|
+
|
40
|
+
def kill(reason)
|
41
|
+
@runner.kill(reason)
|
42
|
+
end
|
43
|
+
|
44
|
+
# DSL interface
|
45
|
+
class << self
|
46
|
+
def route(options)
|
47
|
+
patterns = options.keys.select {|k| !k.is_a?(Symbol) }
|
48
|
+
klasses = patterns.map {|k| options.delete(k) }
|
49
|
+
patterns.zip(klasses).each {|pattern,sym|
|
50
|
+
add_route(pattern, sym, options)
|
51
|
+
}
|
52
|
+
nil
|
53
|
+
end
|
54
|
+
|
55
|
+
def add_route(pattern, klass, options)
|
56
|
+
router.add(pattern, klass, options)
|
57
|
+
end
|
58
|
+
|
59
|
+
def router=(router)
|
60
|
+
(class<<self;self;end).instance_eval do
|
61
|
+
self.__send__(:define_method, :router) { router }
|
62
|
+
end
|
63
|
+
router
|
64
|
+
end
|
65
|
+
|
66
|
+
def router
|
67
|
+
self.router = Router.new
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
@@ -0,0 +1,69 @@
|
|
1
|
+
#
|
2
|
+
# PerfectQueue
|
3
|
+
#
|
4
|
+
# Copyright (C) 2012 FURUHASHI Sadayuki
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
module PerfectQueue
|
20
|
+
module Application
|
21
|
+
|
22
|
+
class Router
|
23
|
+
def initialize
|
24
|
+
@patterns = []
|
25
|
+
@cache = {}
|
26
|
+
end
|
27
|
+
|
28
|
+
def add(pattern, sym, options={})
|
29
|
+
case pattern
|
30
|
+
when Regexp
|
31
|
+
# ok
|
32
|
+
when String, Symbol
|
33
|
+
pattern = /#{Regexp.escape(pattern)}/
|
34
|
+
else
|
35
|
+
raise ArguementError, "pattern should be String or Regexp but got #{pattern.class}: #{pattern.inspect}"
|
36
|
+
end
|
37
|
+
|
38
|
+
@patterns << [pattern, sym]
|
39
|
+
end
|
40
|
+
|
41
|
+
def route(type)
|
42
|
+
if @cache.has_key?(type)
|
43
|
+
return @cache[type]
|
44
|
+
end
|
45
|
+
|
46
|
+
@patterns.each {|(pattern,sym)|
|
47
|
+
if pattern.match(type)
|
48
|
+
base = resolve_application_base(sym)
|
49
|
+
return @cache[type] = base
|
50
|
+
end
|
51
|
+
}
|
52
|
+
return @cache[type] = nil
|
53
|
+
end
|
54
|
+
attr_reader :patterns
|
55
|
+
|
56
|
+
private
|
57
|
+
def resolve_application_base(sym)
|
58
|
+
case sym
|
59
|
+
when Symbol
|
60
|
+
self.class.const_get(sym)
|
61
|
+
else
|
62
|
+
sym
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
data/lib/perfectqueue/backend.rb
CHANGED
@@ -1,52 +1,49 @@
|
|
1
|
+
#
|
2
|
+
# PerfectQueue
|
3
|
+
#
|
4
|
+
# Copyright (C) 2012 FURUHASHI Sadayuki
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
1
18
|
|
2
19
|
module PerfectQueue
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
20
|
+
module Backend
|
21
|
+
def self.new_backend(client, config)
|
22
|
+
case config[:type]
|
23
|
+
when nil
|
24
|
+
raise ConfigError, "'type' option is not set"
|
25
|
+
when 'rdb_compat'
|
26
|
+
require_backend('rdb_compat')
|
27
|
+
RDBCompatBackend.new(client, config)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.require_backend(fname)
|
32
|
+
require File.expand_path("backend/#{fname}", File.dirname(__FILE__))
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
module BackendHelper
|
37
|
+
def initialize(client, config)
|
38
|
+
@client = client
|
39
|
+
@config = config
|
40
|
+
end
|
41
|
+
|
42
|
+
attr_reader :client
|
43
|
+
|
44
|
+
def close
|
45
|
+
# do nothing by default
|
46
|
+
end
|
11
47
|
end
|
12
|
-
|
13
|
-
attr_reader :id, :created_at, :data, :resource
|
14
|
-
end
|
15
|
-
|
16
|
-
|
17
|
-
class CanceledError < RuntimeError
|
18
|
-
end
|
19
|
-
|
20
|
-
|
21
|
-
class Backend
|
22
|
-
# => list {|id,created_at,data,timeout| ... }
|
23
|
-
def list(&block)
|
24
|
-
end
|
25
|
-
|
26
|
-
# => token, task
|
27
|
-
def acquire(timeout, now=Time.now.to_i)
|
28
|
-
end
|
29
|
-
|
30
|
-
# => true (success) or false (canceled)
|
31
|
-
def finish(token, delete_timeout=3600, now=Time.now.to_i)
|
32
|
-
end
|
33
|
-
|
34
|
-
# => nil
|
35
|
-
def update(token, timeout)
|
36
|
-
end
|
37
|
-
|
38
|
-
# => true (success) or false (not found, canceled or finished)
|
39
|
-
def cancel(id, delete_timeout=3600, now=Time.now.to_i)
|
40
|
-
end
|
41
|
-
|
42
|
-
# => true (success) or nil (already exists)
|
43
|
-
def submit(id, data, time=Time.now.to_i, resource=nil)
|
44
|
-
end
|
45
|
-
|
46
|
-
def close
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
|
51
48
|
end
|
52
49
|
|
@@ -0,0 +1,298 @@
|
|
1
|
+
#
|
2
|
+
# PerfectQueue
|
3
|
+
#
|
4
|
+
# Copyright (C) 2012 FURUHASHI Sadayuki
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
module PerfectQueue
|
20
|
+
module Backend
|
21
|
+
class RDBCompatBackend
|
22
|
+
include BackendHelper
|
23
|
+
|
24
|
+
class Token < Struct.new(:key)
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize(client, config)
|
28
|
+
super
|
29
|
+
|
30
|
+
require 'sequel'
|
31
|
+
url = config[:url]
|
32
|
+
@table = config[:table]
|
33
|
+
unless @table
|
34
|
+
raise ConfigError, ":table option is required"
|
35
|
+
end
|
36
|
+
|
37
|
+
#password = config[:password]
|
38
|
+
#user = config[:user]
|
39
|
+
@db = Sequel.connect(url, :max_connections=>1)
|
40
|
+
@mutex = Mutex.new
|
41
|
+
|
42
|
+
connect {
|
43
|
+
# connection test
|
44
|
+
}
|
45
|
+
|
46
|
+
@sql = <<SQL
|
47
|
+
SELECT id, timeout, data, created_at, resource
|
48
|
+
FROM `#{@table}`
|
49
|
+
LEFT JOIN (
|
50
|
+
SELECT resource AS res, COUNT(1) AS running
|
51
|
+
FROM `#{@table}` AS T
|
52
|
+
WHERE timeout > ? AND created_at IS NOT NULL AND resource IS NOT NULL
|
53
|
+
GROUP BY resource
|
54
|
+
) AS R ON resource = res
|
55
|
+
WHERE timeout <= ? AND (running IS NULL OR running < #{MAX_RESOURCE})
|
56
|
+
ORDER BY timeout ASC LIMIT #{MAX_SELECT_ROW}
|
57
|
+
SQL
|
58
|
+
|
59
|
+
# sqlite doesn't support SELECT ... FOR UPDATE but
|
60
|
+
# sqlite doesn't need it because the db is not shared
|
61
|
+
unless url.split('//',2)[0].to_s.include?('sqlite')
|
62
|
+
@sql << 'FOR UPDATE'
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
attr_reader :db
|
67
|
+
|
68
|
+
MAX_SELECT_ROW = 8
|
69
|
+
MAX_RESOURCE = (ENV['PQ_MAX_RESOURCE'] || 4).to_i
|
70
|
+
#KEEPALIVE = 10
|
71
|
+
MAX_RETRY = 10
|
72
|
+
|
73
|
+
def init_database(options)
|
74
|
+
sql = %[
|
75
|
+
CREATE TABLE IF NOT EXISTS `#{@table}` (
|
76
|
+
id VARCHAR(256) NOT NULL,
|
77
|
+
timeout INT NOT NULL,
|
78
|
+
data BLOB NOT NULL,
|
79
|
+
created_at INT,
|
80
|
+
resource VARCHAR(256),
|
81
|
+
PRIMARY KEY (id)
|
82
|
+
);]
|
83
|
+
connect {
|
84
|
+
@db.run sql
|
85
|
+
}
|
86
|
+
end
|
87
|
+
|
88
|
+
# => TaskStatus
|
89
|
+
def get_task_metadata(key, options)
|
90
|
+
now = (options[:now] || Time.now).to_i
|
91
|
+
|
92
|
+
connect {
|
93
|
+
row = @db.fetch("SELECT timeout, data, created_at, resource FROM `#{@table}` LIMIT 1").first
|
94
|
+
unless row
|
95
|
+
raise NotFoundError, "task key=#{key} does no exist"
|
96
|
+
end
|
97
|
+
attributes = create_attributes(now, row)
|
98
|
+
return TaskMetadata.new(@client, key, attributes)
|
99
|
+
}
|
100
|
+
end
|
101
|
+
|
102
|
+
# => AcquiredTask
|
103
|
+
def preempt(key, alive_time, options)
|
104
|
+
raise NotSupportedError.new("preempt is not supported by rdb_compat backend")
|
105
|
+
end
|
106
|
+
|
107
|
+
# yield [TaskWithMetadata]
|
108
|
+
def list(options, &block)
|
109
|
+
now = (options[:now] || Time.now).to_i
|
110
|
+
|
111
|
+
connect {
|
112
|
+
#@db.fetch("SELECT id, timeout, data, created_at, resource FROM `#{@table}` WHERE !(created_at IS NULL AND timeout <= ?) ORDER BY timeout ASC;", now) {|row|
|
113
|
+
@db.fetch("SELECT id, timeout, data, created_at, resource FROM `#{@table}` ORDER BY timeout ASC;", now) {|row|
|
114
|
+
attributes = create_attributes(now, row)
|
115
|
+
task = TaskWithMetadata.new(@client, row[:id], attributes)
|
116
|
+
yield task
|
117
|
+
}
|
118
|
+
}
|
119
|
+
end
|
120
|
+
|
121
|
+
# => Task
|
122
|
+
def submit(key, type, data, options)
|
123
|
+
now = (options[:now] || Time.now).to_i
|
124
|
+
run_at = (options[:run_at] || now).to_i
|
125
|
+
user = options[:user]
|
126
|
+
priority = options[:priority] # not supported
|
127
|
+
data ||= {}
|
128
|
+
data['type'] = type
|
129
|
+
|
130
|
+
connect {
|
131
|
+
begin
|
132
|
+
n = @db["INSERT INTO `#{@table}` (id, timeout, data, created_at, resource) VALUES (?, ?, ?, ?, ?);", key, run_at, data.to_json, now, user].insert
|
133
|
+
return Task.new(@client, key)
|
134
|
+
rescue Sequel::DatabaseError
|
135
|
+
raise AlreadyExistsError, "task key=#{key} already exists"
|
136
|
+
end
|
137
|
+
}
|
138
|
+
end
|
139
|
+
|
140
|
+
# => [AcquiredTask]
|
141
|
+
def acquire(alive_time, max_acquire, options)
|
142
|
+
now = (options[:now] || Time.now).to_i
|
143
|
+
next_timeout = now + alive_time
|
144
|
+
|
145
|
+
connect {
|
146
|
+
while true
|
147
|
+
rows = 0
|
148
|
+
@db.transaction do
|
149
|
+
@db.fetch(@sql, now, now) {|row|
|
150
|
+
unless row[:created_at]
|
151
|
+
# finished task
|
152
|
+
@db["DELETE FROM `#{@table}` WHERE id=?;", row[:id]].delete
|
153
|
+
|
154
|
+
else
|
155
|
+
## optimistic lock is not needed because the row is locked for update
|
156
|
+
#n = @db["UPDATE `#{@table}` SET timeout=? WHERE id=? AND timeout=?", timeout, row[:id], row[:timeout]].update
|
157
|
+
n = @db["UPDATE `#{@table}` SET timeout=? WHERE id=?", next_timeout, row[:id]].update
|
158
|
+
if n > 0
|
159
|
+
attributes = create_attributes(nil, row)
|
160
|
+
task_token = Token.new(row[:id])
|
161
|
+
task = AcquiredTask.new(@client, row[:id], attributes, task_token)
|
162
|
+
return [task]
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
rows += 1
|
167
|
+
}
|
168
|
+
end
|
169
|
+
break nil if rows < MAX_SELECT_ROW
|
170
|
+
end
|
171
|
+
}
|
172
|
+
end
|
173
|
+
|
174
|
+
# => nil
|
175
|
+
def cancel_request(key, options)
|
176
|
+
now = (options[:now] || Time.now).to_i
|
177
|
+
|
178
|
+
# created_at=-1 means cancel_requested
|
179
|
+
connect {
|
180
|
+
n = @db["UPDATE `#{@table}` SET created_at=-1 WHERE id=? AND created_at IS NOT NULL;", key].update
|
181
|
+
if n <= 0
|
182
|
+
raise AlreadyFinishedError, "task key=#{key} does not exist or already finished."
|
183
|
+
end
|
184
|
+
}
|
185
|
+
nil
|
186
|
+
end
|
187
|
+
|
188
|
+
def force_finish(key, retention_time, options)
|
189
|
+
finish(Token.new(key), retention_time, options)
|
190
|
+
end
|
191
|
+
|
192
|
+
# => nil
|
193
|
+
def finish(task_token, retention_time, options)
|
194
|
+
now = (options[:now] || Time.now).to_i
|
195
|
+
delete_timeout = now + retention_time
|
196
|
+
key = task_token.key
|
197
|
+
|
198
|
+
connect {
|
199
|
+
n = @db["UPDATE `#{@table}` SET timeout=?, created_at=NULL, resource=NULL WHERE id=? AND created_at IS NOT NULL;", delete_timeout, key].update
|
200
|
+
if n <= 0
|
201
|
+
raise AlreadyFinishedError, "task key=#{key} does not exist or already finished."
|
202
|
+
end
|
203
|
+
}
|
204
|
+
nil
|
205
|
+
end
|
206
|
+
|
207
|
+
# => nil
|
208
|
+
def heartbeat(task_token, alive_time, options)
|
209
|
+
now = (options[:now] || Time.now).to_i
|
210
|
+
next_timeout = now + alive_time
|
211
|
+
key = task_token.key
|
212
|
+
|
213
|
+
connect {
|
214
|
+
n = @db["UPDATE `#{@table}` SET timeout=? WHERE id=? AND created_at IS NOT NULL;", next_timeout, key].update
|
215
|
+
if n <= 0
|
216
|
+
row = @db.fetch("SELECT id, created_at FROM `#{@table}` WHERE id=? LIMIT 1", key).first
|
217
|
+
if row == nil
|
218
|
+
raise AlreadyFinishedError, "task key=#{key} already finished."
|
219
|
+
elsif row[:created_at] == -1
|
220
|
+
raise CancelRequestedError, "task key=#{key} is cancel requested."
|
221
|
+
else
|
222
|
+
raise AlreadyFinishedError, "task key=#{key} already finished."
|
223
|
+
end
|
224
|
+
end
|
225
|
+
}
|
226
|
+
nil
|
227
|
+
end
|
228
|
+
|
229
|
+
protected
|
230
|
+
def connect(&block)
|
231
|
+
#now = Time.now.to_i
|
232
|
+
@mutex.synchronize do
|
233
|
+
#if now - @last_time > KEEPALIVE
|
234
|
+
# @db.disconnect
|
235
|
+
#end
|
236
|
+
#@last_time = now
|
237
|
+
retry_count = 0
|
238
|
+
begin
|
239
|
+
block.call
|
240
|
+
rescue
|
241
|
+
# workaround for "Mysql2::Error: Deadlock found when trying to get lock; try restarting transaction" error
|
242
|
+
if $!.to_s.include?('try restarting transaction')
|
243
|
+
err = ([$!] + $!.backtrace.map {|bt| " #{bt}" }).join("\n")
|
244
|
+
retry_count += 1
|
245
|
+
if retry_count < MAX_RETRY
|
246
|
+
STDERR.puts err + "\n retrying."
|
247
|
+
sleep 0.5
|
248
|
+
retry
|
249
|
+
else
|
250
|
+
STDERR.puts err + "\n abort."
|
251
|
+
end
|
252
|
+
end
|
253
|
+
raise
|
254
|
+
ensure
|
255
|
+
@db.disconnect
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
def create_attributes(now, row)
|
261
|
+
if row[:created_at] === nil
|
262
|
+
created_at = nil # unknown creation time
|
263
|
+
status = TaskStatus::FINISHED
|
264
|
+
elsif row[:created_at] == -1
|
265
|
+
created_at = 0
|
266
|
+
status = TaskStatus::CANCEL_REQUESTED
|
267
|
+
elsif now && row[:timeout] < now
|
268
|
+
created_at = row[:created_at]
|
269
|
+
status = TaskStatus::WAITING
|
270
|
+
else
|
271
|
+
created_at = row[:created_at]
|
272
|
+
status = TaskStatus::RUNNING
|
273
|
+
end
|
274
|
+
|
275
|
+
begin
|
276
|
+
data = JSON.parse(row[:data] || '{}')
|
277
|
+
rescue
|
278
|
+
data = {}
|
279
|
+
end
|
280
|
+
|
281
|
+
type = data['type'] || ''
|
282
|
+
|
283
|
+
attributes = {
|
284
|
+
:status => status,
|
285
|
+
:created_at => created_at,
|
286
|
+
:data => data,
|
287
|
+
:type => type,
|
288
|
+
:user => row[:resource],
|
289
|
+
:timeout => row[:timeout],
|
290
|
+
:message => nil, # not supported
|
291
|
+
:node => nil, # not supported
|
292
|
+
}
|
293
|
+
end
|
294
|
+
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|