background_queue 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. data/.document +5 -0
  2. data/.rspec +1 -0
  3. data/.rvmrc +48 -0
  4. data/Gemfile +19 -0
  5. data/LICENSE.txt +20 -0
  6. data/README.md +69 -0
  7. data/Rakefile +42 -0
  8. data/TODO +2 -0
  9. data/VERSION +1 -0
  10. data/background_queue.gemspec +158 -0
  11. data/bin/bg_queue +26 -0
  12. data/lib/background_queue.rb +8 -0
  13. data/lib/background_queue/client.rb +96 -0
  14. data/lib/background_queue/client_lib/command.rb +36 -0
  15. data/lib/background_queue/client_lib/config.rb +109 -0
  16. data/lib/background_queue/client_lib/connection.rb +105 -0
  17. data/lib/background_queue/client_lib/job_handle.rb +19 -0
  18. data/lib/background_queue/command.rb +49 -0
  19. data/lib/background_queue/config.rb +118 -0
  20. data/lib/background_queue/server_lib/balanced_queue.rb +108 -0
  21. data/lib/background_queue/server_lib/config.rb +339 -0
  22. data/lib/background_queue/server_lib/event_connection.rb +133 -0
  23. data/lib/background_queue/server_lib/event_server.rb +35 -0
  24. data/lib/background_queue/server_lib/job.rb +252 -0
  25. data/lib/background_queue/server_lib/job_registry.rb +30 -0
  26. data/lib/background_queue/server_lib/lru.rb +193 -0
  27. data/lib/background_queue/server_lib/owner.rb +54 -0
  28. data/lib/background_queue/server_lib/priority_queue.rb +156 -0
  29. data/lib/background_queue/server_lib/queue_registry.rb +123 -0
  30. data/lib/background_queue/server_lib/server.rb +314 -0
  31. data/lib/background_queue/server_lib/sorted_workers.rb +52 -0
  32. data/lib/background_queue/server_lib/task.rb +79 -0
  33. data/lib/background_queue/server_lib/task_registry.rb +51 -0
  34. data/lib/background_queue/server_lib/thread_manager.rb +121 -0
  35. data/lib/background_queue/server_lib/worker.rb +18 -0
  36. data/lib/background_queue/server_lib/worker_balancer.rb +97 -0
  37. data/lib/background_queue/server_lib/worker_client.rb +94 -0
  38. data/lib/background_queue/server_lib/worker_thread.rb +70 -0
  39. data/lib/background_queue/utils.rb +40 -0
  40. data/lib/background_queue/worker/base.rb +46 -0
  41. data/lib/background_queue/worker/calling.rb +59 -0
  42. data/lib/background_queue/worker/config.rb +35 -0
  43. data/lib/background_queue/worker/environment.rb +70 -0
  44. data/lib/background_queue/worker/worker_loader.rb +94 -0
  45. data/lib/background_queue_server.rb +21 -0
  46. data/lib/background_queue_worker.rb +5 -0
  47. data/spec/background_queue/client_lib/command_spec.rb +75 -0
  48. data/spec/background_queue/client_lib/config_spec.rb +156 -0
  49. data/spec/background_queue/client_lib/connection_spec.rb +170 -0
  50. data/spec/background_queue/client_spec.rb +95 -0
  51. data/spec/background_queue/command_spec.rb +34 -0
  52. data/spec/background_queue/config_spec.rb +134 -0
  53. data/spec/background_queue/server_lib/balanced_queue_spec.rb +122 -0
  54. data/spec/background_queue/server_lib/config_spec.rb +443 -0
  55. data/spec/background_queue/server_lib/event_connection_spec.rb +190 -0
  56. data/spec/background_queue/server_lib/event_server_spec.rb +48 -0
  57. data/spec/background_queue/server_lib/integration/full_test_spec.rb +247 -0
  58. data/spec/background_queue/server_lib/integration/queue_integration_spec.rb +98 -0
  59. data/spec/background_queue/server_lib/integration/serialize_spec.rb +127 -0
  60. data/spec/background_queue/server_lib/job_registry_spec.rb +65 -0
  61. data/spec/background_queue/server_lib/job_spec.rb +525 -0
  62. data/spec/background_queue/server_lib/owner_spec.rb +33 -0
  63. data/spec/background_queue/server_lib/priority_queue_spec.rb +182 -0
  64. data/spec/background_queue/server_lib/server_spec.rb +353 -0
  65. data/spec/background_queue/server_lib/sorted_workers_spec.rb +122 -0
  66. data/spec/background_queue/server_lib/task_registry_spec.rb +69 -0
  67. data/spec/background_queue/server_lib/task_spec.rb +20 -0
  68. data/spec/background_queue/server_lib/thread_manager_spec.rb +106 -0
  69. data/spec/background_queue/server_lib/worker_balancer_spec.rb +127 -0
  70. data/spec/background_queue/server_lib/worker_client_spec.rb +196 -0
  71. data/spec/background_queue/server_lib/worker_thread_spec.rb +125 -0
  72. data/spec/background_queue/utils_spec.rb +27 -0
  73. data/spec/background_queue/worker/base_spec.rb +35 -0
  74. data/spec/background_queue/worker/calling_spec.rb +103 -0
  75. data/spec/background_queue/worker/environment_spec.rb +67 -0
  76. data/spec/background_queue/worker/worker_loader_spec.rb +103 -0
  77. data/spec/background_queue_spec.rb +7 -0
  78. data/spec/resources/config-client.yml +7 -0
  79. data/spec/resources/config-serialize.yml +12 -0
  80. data/spec/resources/config.yml +12 -0
  81. data/spec/resources/example_worker.rb +4 -0
  82. data/spec/resources/example_worker_with_error.rb +4 -0
  83. data/spec/resources/test_worker.rb +8 -0
  84. data/spec/shared/queue_registry_shared.rb +216 -0
  85. data/spec/spec_helper.rb +15 -0
  86. data/spec/support/default_task.rb +9 -0
  87. data/spec/support/private.rb +23 -0
  88. data/spec/support/simple_server.rb +28 -0
  89. data/spec/support/simple_task.rb +58 -0
  90. data/spec/support/test_worker_server.rb +205 -0
  91. metadata +254 -0
@@ -0,0 +1,54 @@
1
+ module BackgroundQueue::ServerLib
2
+
3
+ class Owner < PriorityQueue
4
+ include BackgroundQueue::ServerLib::QueueRegistry
5
+
6
+ attr_accessor :id
7
+
8
+ def initialize(id, balanced_queues)
9
+ @id = id
10
+ @balanced_queues = balanced_queues
11
+ super()
12
+ end
13
+
14
+
15
+ def ==(other)
16
+ @id == other.id
17
+ end
18
+
19
+ def server
20
+ @balanced_queues.server
21
+ end
22
+
23
+ def inspect
24
+ "OwnerQueue:#{self.id} (#{self.object_id})"
25
+ end
26
+
27
+ def self.queue_class
28
+ BackgroundQueue::ServerLib::Job
29
+ end
30
+
31
+ def synchronous?
32
+ false
33
+ end
34
+
35
+ private
36
+
37
+ def create_queue(queue_id)
38
+ job = super(queue_id)
39
+ @balanced_queues.register_job(job)
40
+ job
41
+ end
42
+
43
+ def get_queue_id_from_item(item)
44
+ item.job_id
45
+ end
46
+
47
+ def add_item_to_queue(queue, item)
48
+ queue.add_item(item)
49
+ end
50
+
51
+
52
+
53
+ end
54
+ end
@@ -0,0 +1,156 @@
1
+ module BackgroundQueue::ServerLib
2
+ #internally implemented using a list of queues
3
+ #this does not do any locking, subclasses should look after any locking
4
+ class PriorityQueue
5
+
6
+ def initialize
7
+ @queues = []
8
+ @items = {}
9
+ @stalled_items = {}
10
+ @stalled = false
11
+ end
12
+
13
+ def pop
14
+ q = get_next_queue
15
+ return nil if q.nil?
16
+ item = q.shift
17
+ if q.empty?
18
+ remove_queue(q)
19
+ end
20
+ item
21
+ end
22
+
23
+ def push(item)
24
+ q = get_queue_for_priority(item.priority, true)
25
+ q.push(item)
26
+ end
27
+
28
+ def remove(item, override_priority=nil)
29
+ override_priority = item.priority if override_priority.nil?
30
+ q = get_queue_for_priority(override_priority, false)
31
+ raise "unable to get queue at priority #{override_priority} when removing" if q.nil?
32
+ q.delete_if { |q_item| q_item.id == item.id }
33
+ if q.empty?
34
+ remove_queue(q)
35
+ end
36
+ end
37
+
38
+ #the highest priority queue
39
+ def priority
40
+ return nil if @queues.empty?
41
+ get_next_queue.priority
42
+ end
43
+
44
+ def stalled?
45
+ @stalled
46
+ end
47
+
48
+ def stalled=(stall)
49
+ @stalled = stall
50
+ end
51
+
52
+ def empty?
53
+ @queues.empty?
54
+ end
55
+
56
+ def number_of_priorities
57
+ @queues.length
58
+ end
59
+
60
+ def number_if_items_at_priority(priority)
61
+ q = get_queue_for_priority(priority, false)
62
+ return 0 if q.nil?
63
+ q.length
64
+ end
65
+
66
+ def peek
67
+ q = get_next_queue
68
+ return nil if q.nil?
69
+ q.first
70
+ end
71
+
72
+ def each_item(&block)
73
+ for queue in @queues
74
+ for item in queue
75
+ block.call(item)
76
+ end
77
+ end
78
+ end
79
+
80
+ private
81
+
82
+ def get_queue_for_priority(priority, create)
83
+ @queues.each_with_index do |q, idx|
84
+ return q if q.priority == priority
85
+ if q.priority > priority #passed it.. insert here...
86
+ return nil unless create
87
+ return insert_queue_at_index(priority, idx)
88
+ end
89
+ end
90
+ return insert_queue_at_index(priority, -1)
91
+ end
92
+
93
+ def insert_queue_at_index(priority, index)
94
+ q = PriorityArray.new(priority)
95
+ @queues.insert(index, q)
96
+ q
97
+ end
98
+
99
+ def get_next_queue
100
+ @queues.first
101
+ end
102
+
103
+ def remove_queue(queue)
104
+ @queues.delete(queue)
105
+ end
106
+
107
+ def get_queues
108
+ @queues
109
+ end
110
+ end
111
+
112
+
113
+ #this is an array with a priority attribute
114
+ class PriorityArray < Array
115
+
116
+ attr_accessor :priority
117
+ def initialize(priority)
118
+ @priority = priority
119
+ super(0)
120
+ end
121
+
122
+ def ==(other)
123
+ other.priority == self.priority
124
+ end
125
+
126
+ def inspect
127
+ "#{self.priority}:#{super}"
128
+ end
129
+ end
130
+ end
131
+
132
+
133
+ #balanced_queue => owners => jobs => items
134
+ #items can change owner/jobs
135
+ #balanced_queue, and owner are priority queues
136
+ #job is a normal queue
137
+ #as an item is popped from the queue, its priority is recalculated
138
+
139
+ #bq.next_task gets pops owner with highest priority. Calls owner.next_task(). If no tasks, will wait()
140
+ #owner.next_task pops next job with highest priority. Calls job.next_task
141
+ #job.next_task pops next task with highest priority
142
+ #job is added back to owner with current priority (unless empty?). Task is returned
143
+ #owner is added back to bq with current priority (unless empty?), Task is returned
144
+
145
+ #task is run()
146
+ #mark task as running
147
+ #call http to call the worker
148
+ #deregister task if run_version is the same (run_version is incremented each time the same task is reregistered)
149
+
150
+
151
+ #task registry keeps track of items so they can be found/re-inserted
152
+ #the queue keeps track of all items using hashes until item is removed (not popped)
153
+ #this allows a get(id) method and a pop() method
154
+ #get will add an instance to the hash if not found
155
+
156
+
@@ -0,0 +1,123 @@
1
+ module BackgroundQueue::ServerLib
2
+ module QueueRegistry
3
+
4
+ def add_item(item)
5
+ in_queue, queue = get_queue(get_queue_id_from_item(item), true)
6
+ priority_increased, original_priority = track_priority_when_adding_to_queue(queue, item)
7
+ if !queue.stalled? && (!in_queue || priority_increased)
8
+ if in_queue #remove from existing priority queue
9
+ remove(queue, original_priority)
10
+ end
11
+ push(queue)
12
+ end
13
+ end
14
+
15
+ def next_item
16
+ item = nil
17
+ queue = pop
18
+ unless queue.nil?
19
+ priority_decreased, original_priority, item = remove_item_from_queue(queue, :next)
20
+
21
+ # some items must run synchronously, so we dont want to add it back until the task is finished.
22
+ # if the queue it empty we still want to keep it there until the task is finished, incase the running task queues more tasks against the job.
23
+ if queue.empty? || queue.synchronous?
24
+ @items.delete(queue.id)
25
+ stall_queue(queue)
26
+ else
27
+ push(queue)
28
+ end
29
+ end
30
+ item
31
+ end
32
+
33
+ def remove_item(item)
34
+ in_queue, queue = get_queue(get_queue_id_from_item(item), false)
35
+ raise "Unable to remove task #{item.id} at priority #{item.priority} (no queue at that priority)" if queue.nil?
36
+ priority_decreased, original_priority, item = remove_item_from_queue(queue, item)
37
+ if queue.empty?
38
+ remove(queue, original_priority)
39
+ @items.delete(queue.id)
40
+ elsif priority_decreased
41
+ remove(queue, original_priority)
42
+ push(queue)
43
+ end
44
+ end
45
+
46
+ def finish_item(item)
47
+ #puts "#{self.class.name}:finish item: #{item.inspect}"
48
+ in_queue, queue = get_queue(get_queue_id_from_item(item), false)
49
+ raise "Queue #{get_queue_id_from_item(item)} unavailble when finishing item" if queue.nil?
50
+ queue.finish_item(item)
51
+ resume_queue(queue)
52
+ end
53
+
54
+ private
55
+
56
+ def create_queue(queue_id)
57
+ self.class.queue_class.new(queue_id, self)
58
+ end
59
+
60
+ def track_priority_when_adding_to_queue(queue, item)
61
+ original_priority = queue.priority
62
+ add_item_to_queue(queue, item) #queue.add_item(item)
63
+
64
+ if original_priority.nil? || original_priority > queue.priority
65
+ return [true, original_priority]
66
+ end
67
+ [false, original_priority]
68
+ end
69
+
70
+ def remove_item_from_queue(queue, target_item)
71
+ original_priority = queue.priority
72
+
73
+ if target_item == :next
74
+ item = queue.next_item
75
+ else
76
+ item = queue.remove_item(target_item)
77
+ end
78
+
79
+ if queue.priority.nil? || original_priority < queue.priority
80
+ return [true, original_priority, item]
81
+ end
82
+ [false, original_priority, item]
83
+ end
84
+
85
+ def get_queue(queue_id, create)
86
+ queue = @items[queue_id]
87
+ queue = @stalled_items[queue_id] if queue.nil?
88
+ return [true, queue] unless queue.nil?
89
+ return [false, nil] unless create
90
+ queue = create_queue(queue_id) #BackgroundQueue::ServerLib::Owner.new(owner_id)
91
+ @items[queue_id] = queue
92
+ return [false, queue]
93
+ end
94
+
95
+ def stall_queue(queue)
96
+ queue.stalled = true
97
+ #puts "stalling queue #{queue.inspect}"
98
+ @stalled_items[queue.id] = queue
99
+ end
100
+
101
+ def resume_queue(queue)
102
+ if queue.stalled?
103
+ @stalled_items.delete(queue.id)
104
+ unless queue.empty?
105
+ queue.stalled = false
106
+ push(queue)
107
+ @items[queue.id] = queue
108
+ #puts "returned q: #{queue.inspect}"
109
+ else
110
+ @items.delete(queue.id)
111
+ #puts "q empty"
112
+ end
113
+ #else
114
+ # puts "q not stalled"
115
+ end
116
+ end
117
+
118
+ def stalled_items
119
+ @stalled_items
120
+ end
121
+
122
+ end
123
+ end
@@ -0,0 +1,314 @@
1
+ require 'optparse'
2
+ require 'logger'
3
+
4
+ module BackgroundQueue::ServerLib
5
+ class Server
6
+
7
+ attr_accessor :config
8
+ attr_accessor :thread_manager
9
+ attr_accessor :task_queue
10
+ attr_accessor :event_server
11
+ attr_accessor :workers
12
+ attr_accessor :jobs
13
+ attr_accessor :logger
14
+
15
+ def initialize
16
+ @running = false
17
+ @stat_mutex = Mutex.new
18
+ @stats = {
19
+ :tasks=>0,
20
+ :run_tasks=>0,
21
+ :running=>0
22
+ }
23
+ end
24
+
25
+ def process_args(argv)
26
+ argv = argv.clone
27
+ cmd = argv.shift
28
+
29
+ if cmd.nil?
30
+ raise BackgroundQueue::ServerLib::InitError, "Usage: server command [options]"
31
+ end
32
+
33
+ options = {:command=>cmd.nil? ? nil : cmd.downcase.intern}
34
+
35
+ env_to_load = "development"
36
+
37
+ OptionParser.new do |opts|
38
+ opts.banner = "Usage: server command [options]"
39
+ case options[:command]
40
+ when :start, :test
41
+ opts.on("-c", "--config PATH", "Configuration Path") do |cp|
42
+ options[:config] = cp
43
+ end
44
+ when :stop
45
+
46
+ when nil
47
+
48
+ else
49
+ raise "Invalid Command: #{cmd}"
50
+ end
51
+ opts.on("-l", "--logfile [PATH]", "Logfile Path") do |lf|
52
+ options[:log_file] = lf
53
+ end
54
+ opts.on("-v", "--loglevel [LEVEL]", "Log Level") do |ll|
55
+ options[:log_level] = ll
56
+ end
57
+ opts.on("-p", "--pidfile [PATH]", "Pid file Path (/var/run/background_queue.pid)") do |pf|
58
+ options[:pid_file] = pf
59
+ end
60
+ opts.on("-e", "--environment [RAILS_ENV]", "testing/development/production (development)") do |env|
61
+ env_to_load = env
62
+ end
63
+ end.parse!(argv)
64
+
65
+ ENV['RAILS_ENV']=env_to_load
66
+
67
+ raise BackgroundQueue::ServerLib::InitError, "Missing config argument (-c)" if options[:config].nil? && ([:test, :start].include?(options[:command]) )
68
+
69
+ options
70
+ end
71
+
72
+ def load_configuration(path)
73
+ @config = BackgroundQueue::ServerLib::Config.load_file(path)
74
+ true
75
+ end
76
+
77
+ def resolve_logging_path(path)
78
+ File.expand_path(path)
79
+ end
80
+
81
+ def set_logging_level(log, level)
82
+ if level.nil? || level.strip.length == 0
83
+ level = "warn"
84
+ else
85
+ level = level.to_s.downcase
86
+ end
87
+ case level
88
+ when 'debug'
89
+ log.level = Logger::DEBUG
90
+ when 'info'
91
+ log.level = Logger::INFO
92
+ when 'warn'
93
+ log.level = Logger::WARN
94
+ when 'error'
95
+ log.level = Logger::ERROR
96
+ when 'fatal'
97
+ log.level = Logger::FATAL
98
+ else
99
+ raise BackgroundQueue::ServerLib::InitError, "Unknown logging level: #{level}"
100
+ end
101
+ end
102
+
103
+ def init_logging(path, level)
104
+ unless path.nil? || path.to_s.strip.length == 0
105
+ path = resolve_logging_path(path)
106
+ begin
107
+ @logger = Logger.new(path, "daily")
108
+ set_logging_level(@logger, level)
109
+ rescue Exception=>e
110
+ raise BackgroundQueue::ServerLib::InitError, "Error initializing log file #{path}: #{e.message}"
111
+ end
112
+ end
113
+ if @logger.nil?
114
+ #just make a fallback logger...
115
+ @logger = Logger.new($stderr)
116
+ set_logging_level(@logger, "fatal")
117
+ end
118
+ end
119
+
120
+ def get_pid_path(options)
121
+ if options[:pid_file]
122
+ options[:pid_file]
123
+ else
124
+ "/var/run/background_queue.pid"
125
+ end
126
+ end
127
+
128
+ def get_pid(options)
129
+ sPid = nil
130
+ begin
131
+ sPid = File.open(get_pid_path(options)) { |f|
132
+ f.read
133
+ }
134
+ rescue
135
+ return nil
136
+ end
137
+ return nil if sPid.nil? || sPid.to_i == 0
138
+ nPid = sPid.to_i
139
+ begin
140
+ Process.kill(0, nPid)
141
+ return nPid
142
+ rescue
143
+ return nil
144
+ end
145
+ end
146
+
147
+ def check_not_running(options)
148
+ proc_id = get_pid(options)
149
+ raise BackgroundQueue::ServerLib::InitError, "Process #{proc_id} already running" unless proc_id.nil?
150
+ nil
151
+ end
152
+
153
+ def stop_pid(options)
154
+ proc_id = get_pid(options)
155
+ unless proc_id.nil?
156
+ begin
157
+ Process.kill(15, proc_id)
158
+ rescue
159
+ #dont care... the process may have died already?
160
+ end
161
+ count = 0
162
+ while get_pid(options) && count < 10
163
+ puts "Waiting..."
164
+ sleep(1)
165
+ end
166
+ kill_pid(options) #make sure
167
+ end
168
+ end
169
+
170
+ def kill_pid(options)
171
+ proc_id = get_pid(options)
172
+ begin
173
+ Process.kill(9, proc_id) unless proc_id.nil?
174
+ rescue
175
+ #dont care... the process may have died already?
176
+ end
177
+ end
178
+
179
+ def write_pid(options)
180
+ proc_id = Process.pid
181
+ begin
182
+ File.open(get_pid_path(options), "w") { |f|
183
+ f.write(proc_id.to_s)
184
+ }
185
+ rescue Exception=>e
186
+ raise BackgroundQueue::ServerLib::InitError, "Unable to write to pid file #{get_pid_path(options)}: #{e.message}"
187
+ end
188
+ end
189
+
190
+ def remove_pid(options)
191
+ begin
192
+ File.delete(get_pid_path(options))
193
+ rescue
194
+ end
195
+ end
196
+
197
+ def trap_signals
198
+ Signal.trap("TERM") do
199
+ puts "Terminating..."
200
+ self.stop()
201
+ end
202
+ end
203
+
204
+
205
+ def daemonize(options)
206
+ fork{
207
+ stdin = open '/dev/null', 'r'
208
+ stdout = open '/dev/null', 'w'
209
+ stderr = open '/dev/null', 'w'
210
+ STDIN.reopen stdin
211
+ STDOUT.reopen stdout
212
+ STDERR.reopen stderr
213
+ fork{
214
+ write_pid(options) unless options[:skip_pid]
215
+ run(options)
216
+ } and exit!
217
+ }
218
+ end
219
+
220
+ def start(options)
221
+ begin
222
+ load_configuration(options[:config])
223
+ init_logging(options[:log_file], options[:log_level])
224
+ check_not_running(options) unless options[:skip_pid]
225
+ write_pid(options) unless options[:skip_pid] #this will make sure we can write the pid file... the daemon will write it again
226
+ if options[:command] == :start
227
+ daemonize(options)
228
+ elsif options[:command] == :run
229
+ run(options)
230
+ else
231
+ raise BackgroundQueue::ServerLib::InitError, "Unknown Command: #{options[:command]}"
232
+ end
233
+ rescue BackgroundQueue::ServerLib::InitError=>ie
234
+ STDERR.puts ie.message
235
+ rescue Exception=>e
236
+ STDERR.puts e.message
237
+ STDERR.puts e.backtrace.join("\n")
238
+ end
239
+ end
240
+
241
+ def running?
242
+ @running
243
+ end
244
+
245
+ def run(options)
246
+ trap_signals
247
+ @running = true
248
+ @thread_manager = BackgroundQueue::ServerLib::ThreadManager.new(self, self.config.connections_per_worker)
249
+
250
+ @workers = BackgroundQueue::ServerLib::WorkerBalancer.new(self)
251
+ @task_queue = BackgroundQueue::ServerLib::BalancedQueue.new(self)
252
+
253
+ @thread_manager.start(BackgroundQueue::ServerLib::WorkerThread)
254
+
255
+ @event_server = BackgroundQueue::ServerLib::EventServer.new(self)
256
+
257
+ @jobs = BackgroundQueue::ServerLib::JobRegistry.new
258
+
259
+ load_tasks(config.task_file)
260
+
261
+ @event_server.start
262
+ end
263
+
264
+ def stop(timeout_secs=10)
265
+ @running = false
266
+ @event_server.stop
267
+ @thread_manager.wait(timeout_secs)
268
+ save_tasks(config.task_file)
269
+ end
270
+
271
+ def change_stat(stat, delta)
272
+ @stat_mutex.synchronize {
273
+ @stats[stat] += delta
274
+ }
275
+ end
276
+
277
+ def get_stats
278
+ @stat_mutex.synchronize {
279
+ @stats.clone
280
+ }
281
+ end
282
+
283
+ def load_tasks(path)
284
+ return if path.nil?
285
+ if File.exist?(path)
286
+ begin
287
+ File.open(path, 'r') { |io|
288
+ task_queue.load_from_file(io)
289
+ }
290
+ rescue Exception=>e
291
+ logger.error("Error loading tasks from #{path}: #{e.message}")
292
+ logger.debug(e.backtrace.join("\n"))
293
+ end
294
+ end
295
+ end
296
+
297
+ def save_tasks(path)
298
+ return if path.nil?
299
+
300
+ begin
301
+ File.open(path, 'w') { |io|
302
+ task_queue.save_to_file(io)
303
+ }
304
+ rescue Exception=>e
305
+ logger.error("Error saving tasks to #{path}: #{e.message}")
306
+ logger.debug(e.backtrace.join("\n"))
307
+ end
308
+ end
309
+ end
310
+
311
+ class InitError < Exception
312
+
313
+ end
314
+ end