garcun 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitattributes +17 -0
- data/.gitignore +197 -0
- data/.rspec +2 -0
- data/Gemfile +22 -0
- data/LICENSE +201 -0
- data/README.md +521 -0
- data/Rakefile +47 -0
- data/garcun.gemspec +83 -0
- data/lib/garcon.rb +290 -0
- data/lib/garcon/chef/chef_helpers.rb +343 -0
- data/lib/garcon/chef/coerce/coercer.rb +134 -0
- data/lib/garcon/chef/coerce/coercions/boolean_definitions.rb +34 -0
- data/lib/garcon/chef/coerce/coercions/date_definitions.rb +32 -0
- data/lib/garcon/chef/coerce/coercions/date_time_definitions.rb +32 -0
- data/lib/garcon/chef/coerce/coercions/fixnum_definitions.rb +34 -0
- data/lib/garcon/chef/coerce/coercions/float_definitions.rb +32 -0
- data/lib/garcon/chef/coerce/coercions/hash_definitions.rb +29 -0
- data/lib/garcon/chef/coerce/coercions/integer_definitions.rb +31 -0
- data/lib/garcon/chef/coerce/coercions/string_definitions.rb +45 -0
- data/lib/garcon/chef/coerce/coercions/time_definitions.rb +32 -0
- data/lib/garcon/chef/handler/devreporter.rb +127 -0
- data/lib/garcon/chef/log.rb +64 -0
- data/lib/garcon/chef/node.rb +100 -0
- data/lib/garcon/chef/provider/civilize.rb +209 -0
- data/lib/garcon/chef/provider/development.rb +159 -0
- data/lib/garcon/chef/provider/download.rb +420 -0
- data/lib/garcon/chef/provider/house_keeping.rb +265 -0
- data/lib/garcon/chef/provider/node_cache.rb +31 -0
- data/lib/garcon/chef/provider/partial.rb +183 -0
- data/lib/garcon/chef/provider/recovery.rb +80 -0
- data/lib/garcon/chef/provider/zip_file.rb +271 -0
- data/lib/garcon/chef/resource/attribute.rb +52 -0
- data/lib/garcon/chef/resource/base_dsl.rb +174 -0
- data/lib/garcon/chef/resource/blender.rb +140 -0
- data/lib/garcon/chef/resource/lazy_eval.rb +66 -0
- data/lib/garcon/chef/resource/resource_name.rb +109 -0
- data/lib/garcon/chef/secret_bag.rb +204 -0
- data/lib/garcon/chef/validations.rb +76 -0
- data/lib/garcon/chef_inclusions.rb +151 -0
- data/lib/garcon/configuration.rb +138 -0
- data/lib/garcon/core_ext.rb +39 -0
- data/lib/garcon/core_ext/array.rb +27 -0
- data/lib/garcon/core_ext/binding.rb +64 -0
- data/lib/garcon/core_ext/boolean.rb +66 -0
- data/lib/garcon/core_ext/duration.rb +271 -0
- data/lib/garcon/core_ext/enumerable.rb +34 -0
- data/lib/garcon/core_ext/file.rb +127 -0
- data/lib/garcon/core_ext/filetest.rb +62 -0
- data/lib/garcon/core_ext/hash.rb +279 -0
- data/lib/garcon/core_ext/kernel.rb +159 -0
- data/lib/garcon/core_ext/lazy.rb +222 -0
- data/lib/garcon/core_ext/method_access.rb +243 -0
- data/lib/garcon/core_ext/module.rb +92 -0
- data/lib/garcon/core_ext/nil.rb +53 -0
- data/lib/garcon/core_ext/numeric.rb +44 -0
- data/lib/garcon/core_ext/object.rb +342 -0
- data/lib/garcon/core_ext/pathname.rb +152 -0
- data/lib/garcon/core_ext/process.rb +41 -0
- data/lib/garcon/core_ext/random.rb +497 -0
- data/lib/garcon/core_ext/string.rb +312 -0
- data/lib/garcon/core_ext/struct.rb +49 -0
- data/lib/garcon/core_ext/symbol.rb +170 -0
- data/lib/garcon/core_ext/time.rb +234 -0
- data/lib/garcon/exceptions.rb +101 -0
- data/lib/garcon/inflections.rb +237 -0
- data/lib/garcon/inflections/defaults.rb +79 -0
- data/lib/garcon/inflections/inflections.rb +182 -0
- data/lib/garcon/inflections/rules_collection.rb +37 -0
- data/lib/garcon/secret.rb +271 -0
- data/lib/garcon/stash/format.rb +114 -0
- data/lib/garcon/stash/journal.rb +226 -0
- data/lib/garcon/stash/queue.rb +83 -0
- data/lib/garcon/stash/serializer.rb +86 -0
- data/lib/garcon/stash/store.rb +435 -0
- data/lib/garcon/task.rb +31 -0
- data/lib/garcon/task/atomic.rb +151 -0
- data/lib/garcon/task/atomic_boolean.rb +127 -0
- data/lib/garcon/task/condition.rb +99 -0
- data/lib/garcon/task/copy_on_notify_observer_set.rb +154 -0
- data/lib/garcon/task/copy_on_write_observer_set.rb +153 -0
- data/lib/garcon/task/count_down_latch.rb +92 -0
- data/lib/garcon/task/delay.rb +196 -0
- data/lib/garcon/task/dereferenceable.rb +144 -0
- data/lib/garcon/task/event.rb +119 -0
- data/lib/garcon/task/executor.rb +275 -0
- data/lib/garcon/task/executor_options.rb +59 -0
- data/lib/garcon/task/future.rb +107 -0
- data/lib/garcon/task/immediate_executor.rb +84 -0
- data/lib/garcon/task/ivar.rb +171 -0
- data/lib/garcon/task/lazy_reference.rb +74 -0
- data/lib/garcon/task/monotonic_time.rb +69 -0
- data/lib/garcon/task/obligation.rb +256 -0
- data/lib/garcon/task/observable.rb +101 -0
- data/lib/garcon/task/priority_queue.rb +234 -0
- data/lib/garcon/task/processor_count.rb +128 -0
- data/lib/garcon/task/read_write_lock.rb +304 -0
- data/lib/garcon/task/safe_task_executor.rb +58 -0
- data/lib/garcon/task/single_thread_executor.rb +97 -0
- data/lib/garcon/task/thread_pool/cached.rb +71 -0
- data/lib/garcon/task/thread_pool/executor.rb +294 -0
- data/lib/garcon/task/thread_pool/fixed.rb +61 -0
- data/lib/garcon/task/thread_pool/worker.rb +90 -0
- data/lib/garcon/task/timer.rb +44 -0
- data/lib/garcon/task/timer_set.rb +194 -0
- data/lib/garcon/task/timer_task.rb +377 -0
- data/lib/garcon/task/waitable_list.rb +58 -0
- data/lib/garcon/utility/ansi.rb +199 -0
- data/lib/garcon/utility/at_random.rb +77 -0
- data/lib/garcon/utility/crypto.rb +292 -0
- data/lib/garcon/utility/equalizer.rb +146 -0
- data/lib/garcon/utility/faker/extensions/array.rb +22 -0
- data/lib/garcon/utility/faker/extensions/symbol.rb +9 -0
- data/lib/garcon/utility/faker/faker.rb +164 -0
- data/lib/garcon/utility/faker/faker/company.rb +17 -0
- data/lib/garcon/utility/faker/faker/hacker.rb +30 -0
- data/lib/garcon/utility/faker/faker/version.rb +3 -0
- data/lib/garcon/utility/faker/locales/en-US.yml +83 -0
- data/lib/garcon/utility/faker/locales/en.yml +21 -0
- data/lib/garcon/utility/file_helper.rb +170 -0
- data/lib/garcon/utility/hookers.rb +178 -0
- data/lib/garcon/utility/interpolation.rb +90 -0
- data/lib/garcon/utility/memstash.rb +364 -0
- data/lib/garcon/utility/misc.rb +54 -0
- data/lib/garcon/utility/msg_from_god.rb +62 -0
- data/lib/garcon/utility/retry.rb +238 -0
- data/lib/garcon/utility/timeout.rb +58 -0
- data/lib/garcon/utility/uber/builder.rb +91 -0
- data/lib/garcon/utility/uber/callable.rb +7 -0
- data/lib/garcon/utility/uber/delegates.rb +13 -0
- data/lib/garcon/utility/uber/inheritable_attr.rb +37 -0
- data/lib/garcon/utility/uber/options.rb +101 -0
- data/lib/garcon/utility/uber/uber_version.rb +3 -0
- data/lib/garcon/utility/uber/version.rb +33 -0
- data/lib/garcon/utility/url_helper.rb +100 -0
- data/lib/garcon/utils.rb +29 -0
- data/lib/garcon/version.rb +62 -0
- data/lib/garcun.rb +24 -0
- metadata +680 -0
@@ -0,0 +1,61 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#
|
3
|
+
# Author: Stefano Harding <riddopic@gmail.com>
|
4
|
+
# License: Apache License, Version 2.0
|
5
|
+
# Copyright: (C) 2014-2015 Stefano Harding
|
6
|
+
#
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
8
|
+
# you may not use this file except in compliance with the License.
|
9
|
+
# You may obtain a copy of the License at
|
10
|
+
#
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
12
|
+
#
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
16
|
+
# See the License for the specific language governing permissions and
|
17
|
+
# limitations under the License.
|
18
|
+
#
|
19
|
+
|
20
|
+
require_relative 'executor'
|
21
|
+
|
22
|
+
module Garcon
|
23
|
+
|
24
|
+
class FixedThreadPool < ThreadPoolExecutor
|
25
|
+
|
26
|
+
# Create a new thread pool.
|
27
|
+
#
|
28
|
+
# @param [Integer] num_threads
|
29
|
+
# The number of threads to allocate.
|
30
|
+
#
|
31
|
+
# @param [Hash] opts
|
32
|
+
# The options defining pool behavior.
|
33
|
+
#
|
34
|
+
# @option opts [Symbol] :fallback (`:abort`)
|
35
|
+
# The fallback policy
|
36
|
+
#
|
37
|
+
# @raise [ArgumentError] if `num_threads` is less than or equal to zero
|
38
|
+
#
|
39
|
+
# @raise [ArgumentError] if `fallback` is not a known policy
|
40
|
+
#
|
41
|
+
# @api public
|
42
|
+
def initialize(num_threads, opts = {})
|
43
|
+
fallback = opts.fetch(:fallback, :abort)
|
44
|
+
|
45
|
+
if num_threads < 1
|
46
|
+
raise ArgumentError, 'number of threads must be greater than zero'
|
47
|
+
elsif !FALLBACK_POLICY.include?(fallback)
|
48
|
+
raise ArgumentError, "#{fallback} is not a valid fallback policy"
|
49
|
+
end
|
50
|
+
|
51
|
+
opts = opts.merge(
|
52
|
+
min_threads: num_threads,
|
53
|
+
max_threads: num_threads,
|
54
|
+
fallback: fallback,
|
55
|
+
max_queue: DEFAULT_MAX_QUEUE_SIZE,
|
56
|
+
idletime: DEFAULT_THREAD_IDLETIMEOUT)
|
57
|
+
|
58
|
+
super(opts)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#
|
3
|
+
# Author: Stefano Harding <riddopic@gmail.com>
|
4
|
+
# License: Apache License, Version 2.0
|
5
|
+
# Copyright: (C) 2014-2015 Stefano Harding
|
6
|
+
#
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
8
|
+
# you may not use this file except in compliance with the License.
|
9
|
+
# You may obtain a copy of the License at
|
10
|
+
#
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
12
|
+
#
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
16
|
+
# See the License for the specific language governing permissions and
|
17
|
+
# limitations under the License.
|
18
|
+
#
|
19
|
+
|
20
|
+
require 'thread'
|
21
|
+
require_relative '../monotonic_time'
|
22
|
+
|
23
|
+
module Garcon
|
24
|
+
|
25
|
+
# @!visibility private
|
26
|
+
class ThreadPoolWorker
|
27
|
+
|
28
|
+
# @!visibility private
|
29
|
+
def initialize(queue, parent)
|
30
|
+
@queue = queue
|
31
|
+
@parent = parent
|
32
|
+
@mutex = Mutex.new
|
33
|
+
@last_activity = Garcon.monotonic_time
|
34
|
+
@thread = nil
|
35
|
+
end
|
36
|
+
|
37
|
+
# @!visibility private
|
38
|
+
def dead?
|
39
|
+
return @mutex.synchronize do
|
40
|
+
@thread.nil? ? false : ! @thread.alive?
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# @!visibility private
|
45
|
+
def last_activity
|
46
|
+
@mutex.synchronize { @last_activity }
|
47
|
+
end
|
48
|
+
|
49
|
+
def status
|
50
|
+
@mutex.synchronize do
|
51
|
+
return 'not running' if @thread.nil?
|
52
|
+
@thread.status
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# @!visibility private
|
57
|
+
def kill
|
58
|
+
@mutex.synchronize do
|
59
|
+
Thread.kill(@thread) unless @thread.nil?
|
60
|
+
@thread = nil
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# @!visibility private
|
65
|
+
def run(thread = Thread.current)
|
66
|
+
@mutex.synchronize do
|
67
|
+
raise StandardError, 'already running' unless @thread.nil?
|
68
|
+
@thread = thread
|
69
|
+
end
|
70
|
+
|
71
|
+
loop do
|
72
|
+
task = @queue.pop
|
73
|
+
if task == :stop
|
74
|
+
@thread = nil
|
75
|
+
@parent.on_worker_exit(self)
|
76
|
+
break
|
77
|
+
end
|
78
|
+
|
79
|
+
begin
|
80
|
+
task.last.call(*task.first)
|
81
|
+
rescue => e
|
82
|
+
Chef::Log.debug "Caught exception => #{e}"
|
83
|
+
ensure
|
84
|
+
@last_activity = Garcon.monotonic_time
|
85
|
+
@parent.on_end_task
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#
|
3
|
+
# Author: Stefano Harding <riddopic@gmail.com>
|
4
|
+
# License: Apache License, Version 2.0
|
5
|
+
# Copyright: (C) 2014-2015 Stefano Harding
|
6
|
+
#
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
8
|
+
# you may not use this file except in compliance with the License.
|
9
|
+
# You may obtain a copy of the License at
|
10
|
+
#
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
12
|
+
#
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
16
|
+
# See the License for the specific language governing permissions and
|
17
|
+
# limitations under the License.
|
18
|
+
#
|
19
|
+
|
20
|
+
require 'thread'
|
21
|
+
|
22
|
+
module Garcon
|
23
|
+
|
24
|
+
# Perform the given operation asynchronously after the given number of
|
25
|
+
# seconds.
|
26
|
+
#
|
27
|
+
# @param [Fixnum] seconds
|
28
|
+
# The interval in seconds to wait before executing the task
|
29
|
+
#
|
30
|
+
# @yield the task to execute
|
31
|
+
#
|
32
|
+
# @return [Boolean] true
|
33
|
+
#
|
34
|
+
def timer(seconds, *args, &block)
|
35
|
+
raise ArgumentError, 'no block given' unless block_given?
|
36
|
+
if seconds < 0
|
37
|
+
raise ArgumentError, 'interval must be greater than or equal to zero'
|
38
|
+
end
|
39
|
+
|
40
|
+
Garcon.global_timer_set.post(seconds, *args, &block)
|
41
|
+
true
|
42
|
+
end
|
43
|
+
module_function :timer
|
44
|
+
end
|
@@ -0,0 +1,194 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#
|
3
|
+
# Author: Stefano Harding <riddopic@gmail.com>
|
4
|
+
# License: Apache License, Version 2.0
|
5
|
+
# Copyright: (C) 2014-2015 Stefano Harding
|
6
|
+
#
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
8
|
+
# you may not use this file except in compliance with the License.
|
9
|
+
# You may obtain a copy of the License at
|
10
|
+
#
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
12
|
+
#
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
16
|
+
# See the License for the specific language governing permissions and
|
17
|
+
# limitations under the License.
|
18
|
+
#
|
19
|
+
|
20
|
+
require 'thread'
|
21
|
+
require_relative 'event'
|
22
|
+
require_relative 'priority_queue'
|
23
|
+
require_relative 'executor'
|
24
|
+
require_relative 'single_thread_executor'
|
25
|
+
require_relative 'monotonic_time'
|
26
|
+
require_relative 'executor_options'
|
27
|
+
|
28
|
+
module Garcon
|
29
|
+
|
30
|
+
# Executes a collection of tasks, each after a given delay. A master task
|
31
|
+
# monitors the set and schedules each task for execution at the appropriate
|
32
|
+
# time. Tasks are run on the global task pool or on the supplied executor.
|
33
|
+
#
|
34
|
+
class TimerSet
|
35
|
+
include RubyExecutor
|
36
|
+
include ExecutorOptions
|
37
|
+
|
38
|
+
# Create a new set of timed tasks.
|
39
|
+
#
|
40
|
+
# @!macro [attach] executor_options
|
41
|
+
#
|
42
|
+
# @param [Hash] opts
|
43
|
+
# The options used to specify the executor on which to perform actions.
|
44
|
+
#
|
45
|
+
# @option opts [Executor] :executor
|
46
|
+
# When set use the given `Executor` instance. Three special values are
|
47
|
+
# also supported: `:task` returns the global task pool, `:operation`
|
48
|
+
# returns the global operation pool, and `:immediate` returns a new
|
49
|
+
# `ImmediateExecutor` object.
|
50
|
+
#
|
51
|
+
def initialize(opts = {})
|
52
|
+
@queue = PriorityQueue.new(order: :min)
|
53
|
+
@task_executor = get_executor_from(opts) || Garcon.global_io_executor
|
54
|
+
@timer_executor = SingleThreadExecutor.new
|
55
|
+
@condition = Condition.new
|
56
|
+
init_executor
|
57
|
+
enable_at_exit_handler!(opts)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Post a task to be execute run after a given delay (in seconds). If the
|
61
|
+
# delay is less than 1/100th of a second the task will be immediately post
|
62
|
+
# to the executor.
|
63
|
+
#
|
64
|
+
# @param [Float] delay
|
65
|
+
# The number of seconds to wait for before executing the task.
|
66
|
+
#
|
67
|
+
# @yield the task to be performed.
|
68
|
+
#
|
69
|
+
# @raise [ArgumentError] f the intended execution time is not in the future.
|
70
|
+
#
|
71
|
+
# @raise [ArgumentError] if no block is given.
|
72
|
+
#
|
73
|
+
# @return [Boolean]
|
74
|
+
# True if the message is post, false after shutdown.
|
75
|
+
#
|
76
|
+
def post(delay, *args, &task)
|
77
|
+
raise ArgumentError, 'no block given' unless block_given?
|
78
|
+
delay = TimerSet.calculate_delay!(delay)
|
79
|
+
|
80
|
+
mutex.synchronize do
|
81
|
+
return false unless running?
|
82
|
+
|
83
|
+
if (delay) <= 0.01
|
84
|
+
@task_executor.post(*args, &task)
|
85
|
+
else
|
86
|
+
@queue.push(Task.new(Garcon.monotonic_time + delay, args, task))
|
87
|
+
@timer_executor.post(&method(:process_tasks))
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
@condition.signal
|
92
|
+
true
|
93
|
+
end
|
94
|
+
|
95
|
+
# @!visibility private
|
96
|
+
def <<(task)
|
97
|
+
post(0.0, &task)
|
98
|
+
self
|
99
|
+
end
|
100
|
+
|
101
|
+
# @!macro executor_method_shutdown
|
102
|
+
def kill
|
103
|
+
shutdown
|
104
|
+
end
|
105
|
+
|
106
|
+
# Schedule a task to be executed after a given delay (in seconds).
|
107
|
+
#
|
108
|
+
# @param [Float] delay
|
109
|
+
# The number of seconds to wait for before executing the task.
|
110
|
+
#
|
111
|
+
# @raise [ArgumentError] if the intended execution time is not in the future
|
112
|
+
#
|
113
|
+
# @raise [ArgumentError] if no block is given.
|
114
|
+
#
|
115
|
+
# @return [Float]
|
116
|
+
# The number of seconds to delay.
|
117
|
+
#
|
118
|
+
def self.calculate_delay!(delay)
|
119
|
+
if delay.is_a?(Time)
|
120
|
+
if delay <= now
|
121
|
+
raise ArgumentError, 'schedule time must be in the future'
|
122
|
+
end
|
123
|
+
delay.to_f - now.to_f
|
124
|
+
else
|
125
|
+
if delay.to_f < 0.0
|
126
|
+
raise ArgumentError, 'seconds must be greater than zero'
|
127
|
+
end
|
128
|
+
delay.to_f
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
private # P R O P R I E T À P R I V A T A Vietato L'accesso
|
133
|
+
|
134
|
+
# A struct for encapsulating a task and its intended execution time.
|
135
|
+
# It facilitates proper prioritization by overriding the comparison
|
136
|
+
# (spaceship) operator as a comparison of the intended execution
|
137
|
+
# times.
|
138
|
+
#
|
139
|
+
# @!visibility private
|
140
|
+
Task = Struct.new(:time, :args, :op) do
|
141
|
+
include Comparable
|
142
|
+
|
143
|
+
def <=>(other)
|
144
|
+
self.time <=> other.time
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
private_constant :Task
|
149
|
+
|
150
|
+
# @!visibility private
|
151
|
+
def shutdown_execution
|
152
|
+
@queue.clear
|
153
|
+
@timer_executor.kill
|
154
|
+
stopped_event.set
|
155
|
+
end
|
156
|
+
|
157
|
+
# Run a loop and execute tasks in the scheduled order and at the approximate
|
158
|
+
# scheduled time. If no tasks remain the thread will exit gracefully so that
|
159
|
+
# garbage collection can occur. If there are no ready tasks it will sleep
|
160
|
+
# for up to 60 seconds waiting for the next scheduled task.
|
161
|
+
#
|
162
|
+
# @!visibility private
|
163
|
+
def process_tasks
|
164
|
+
loop do
|
165
|
+
task = mutex.synchronize { @queue.peek }
|
166
|
+
break unless task
|
167
|
+
|
168
|
+
now = Garcon.monotonic_time
|
169
|
+
diff = task.time - now
|
170
|
+
|
171
|
+
if diff <= 0
|
172
|
+
# We need to remove the task from the queue before passing it to the
|
173
|
+
# executor, to avoid race conditions where we pass the peek'ed task
|
174
|
+
# to the executor and then pop a different one that's been added in
|
175
|
+
# the meantime.
|
176
|
+
#
|
177
|
+
# Note that there's no race condition between the peek and this pop -
|
178
|
+
# this pop could retrieve a different task from the peek, but that
|
179
|
+
# task would be due to fire now anyway (because @queue is a priority
|
180
|
+
# queue, and this thread is the only reader, so whatever timer is at
|
181
|
+
# the head of the queue now must have the same pop time, or a closer
|
182
|
+
# one, as when we peeked).
|
183
|
+
#
|
184
|
+
task = mutex.synchronize { @queue.pop }
|
185
|
+
@task_executor.post(*task.args, &task.op)
|
186
|
+
else
|
187
|
+
mutex.synchronize do
|
188
|
+
@condition.wait(mutex, [diff, 60].min)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
@@ -0,0 +1,377 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#
|
3
|
+
# Author: Stefano Harding <riddopic@gmail.com>
|
4
|
+
# License: Apache License, Version 2.0
|
5
|
+
# Copyright: (C) 2014-2015 Stefano Harding
|
6
|
+
#
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
8
|
+
# you may not use this file except in compliance with the License.
|
9
|
+
# You may obtain a copy of the License at
|
10
|
+
#
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
12
|
+
#
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
16
|
+
# See the License for the specific language governing permissions and
|
17
|
+
# limitations under the License.
|
18
|
+
#
|
19
|
+
|
20
|
+
require_relative 'dereferenceable'
|
21
|
+
require_relative 'observable'
|
22
|
+
require_relative 'atomic_boolean'
|
23
|
+
require_relative 'executor'
|
24
|
+
require_relative 'safe_task_executor'
|
25
|
+
|
26
|
+
module Garcon
|
27
|
+
|
28
|
+
# A very common currency pattern is to run a thread that performs a task at
|
29
|
+
# regular intervals. The thread that performs the task sleeps for the given
|
30
|
+
# interval then wakes up and performs the task. Lather, rinse, repeat... This
|
31
|
+
# pattern causes two problems. First, it is difficult to test the business
|
32
|
+
# logic of the task because the task itself is tightly coupled with the
|
33
|
+
# concurrency logic. Second, an exception raised while performing the task can
|
34
|
+
# cause the entire thread to abend. In a long-running application where the
|
35
|
+
# task thread is intended to run for days/weeks/years a crashed task thread
|
36
|
+
# can pose a significant problem. `TimerTask` alleviates both problems.
|
37
|
+
#
|
38
|
+
# When a `TimerTask` is launched it starts a thread for monitoring the
|
39
|
+
# execution interval. The `TimerTask` thread does not perform the task,
|
40
|
+
# however. Instead, the TimerTask launches the task on a separate thread.
|
41
|
+
# Should the task experience an unrecoverable crash only the task thread will
|
42
|
+
# crash. This makes the `TimerTask` very fault tolerant Additionally, the
|
43
|
+
# `TimerTask` thread can respond to the success or failure of the task,
|
44
|
+
# performing logging or ancillary operations. `TimerTask` can also be
|
45
|
+
# configured with a timeout value allowing it to kill a task that runs too
|
46
|
+
# long.
|
47
|
+
#
|
48
|
+
# One other advantage of `TimerTask` is that it forces the business logic to
|
49
|
+
# be completely decoupled from the concurrency logic. The business logic can
|
50
|
+
# be tested separately then passed to the `TimerTask` for scheduling and
|
51
|
+
# running.
|
52
|
+
#
|
53
|
+
# In some cases it may be necessary for a `TimerTask` to affect its own
|
54
|
+
# execution cycle. To facilitate this, a reference to the TimerTask instance
|
55
|
+
# is passed as an argument to the provided block every time the task is
|
56
|
+
# executed.
|
57
|
+
#
|
58
|
+
# The `TimerTask` class includes the `Dereferenceable` mixin module so the
|
59
|
+
# result of the last execution is always available via the `#value` method.
|
60
|
+
# Derefencing options can be passed to the `TimerTask` during construction or
|
61
|
+
# at any later time using the `#set_deref_options` method.
|
62
|
+
#
|
63
|
+
# `TimerTask` supports notification through the Ruby standard library
|
64
|
+
# {http://ruby-doc.org/stdlib-2.0/libdoc/observer/rdoc/Observable.html
|
65
|
+
# Observable} module. On execution the `TimerTask` will notify the observers
|
66
|
+
# with three arguments: time of execution, the result of the block (or nil on
|
67
|
+
# failure), and any raised exceptions (or nil on success). If the timeout
|
68
|
+
# interval is exceeded the observer will receive a `Garcon::TimeoutError`
|
69
|
+
# object as the third argument.
|
70
|
+
#
|
71
|
+
# @example Basic usage
|
72
|
+
# tt = Garcon::TimerTask.new { puts 'Run! Go! Execute! GO! GO! GO!' }
|
73
|
+
# tt.execute
|
74
|
+
#
|
75
|
+
# tt.execution_interval # => 60 (default)
|
76
|
+
# tt.timeout_interval # => 30 (default)
|
77
|
+
#
|
78
|
+
# # wait 60 seconds...
|
79
|
+
# # => 'Run! Go! Execute! GO! GO! GO!'
|
80
|
+
#
|
81
|
+
# tt.shutdown # => true
|
82
|
+
#
|
83
|
+
# @example Configuring `:execution_interval` and `:timeout_interval`
|
84
|
+
# tt = Garcon::TimerTask.new(execution_interval: 5, timeout_interval: 5) do
|
85
|
+
# puts 'Execute! Execute! GO! GO! GO!'
|
86
|
+
# end
|
87
|
+
#
|
88
|
+
# tt.execution_interval # => 5
|
89
|
+
# tt.timeout_interval # => 5
|
90
|
+
#
|
91
|
+
# @example Immediate execution with `:run_now`
|
92
|
+
# tt = Garcon::TimerTask.new(run_now: true) { puts 'GO! GO! GO!' }
|
93
|
+
# tt.execute
|
94
|
+
#
|
95
|
+
# # => 'GO! GO! GO!'
|
96
|
+
#
|
97
|
+
# @example Last `#value` and `Dereferenceable` mixin
|
98
|
+
# tt = Garcon::TimerTask.new(dup_on_deref: true, execution_interval: 5) do
|
99
|
+
# Time.now
|
100
|
+
# end
|
101
|
+
#
|
102
|
+
# tt.execute
|
103
|
+
# Time.now # => 2015-03-21 08:56:50 -0700
|
104
|
+
# sleep(10)
|
105
|
+
# tt.value # => 2015-03-21 08:56:55 -0700
|
106
|
+
#
|
107
|
+
# @example Controlling execution from within the block
|
108
|
+
# timer_task = Garcon::TimerTask.new(execution_interval: 1) do |task|
|
109
|
+
# task.execution_interval.times { print 'Execute! ' }
|
110
|
+
# print "\n"
|
111
|
+
# task.execution_interval += 1
|
112
|
+
# if task.execution_interval > 5
|
113
|
+
# puts 'Executed...'
|
114
|
+
# task.shutdown
|
115
|
+
# end
|
116
|
+
# end
|
117
|
+
#
|
118
|
+
# timer_task.execute # blocking call - this task will stop itself
|
119
|
+
# # => Execute!
|
120
|
+
# # => Execute! Execute!
|
121
|
+
# # => Execute! Execute! Execute!
|
122
|
+
# # => Execute! Execute! Execute! Execute!
|
123
|
+
# # => Execute! Execute! Execute! Execute! Execute!
|
124
|
+
# # => Executed...
|
125
|
+
#
|
126
|
+
# @example Observation
|
127
|
+
# class TaskObserver
|
128
|
+
# def update(time, result, ex)
|
129
|
+
# if result
|
130
|
+
# print "(#{time}) Execution successfully returned #{result}\n"
|
131
|
+
# elsif ex.is_a?(Garcon::TimeoutError)
|
132
|
+
# print "(#{time}) Execution timed out\n"
|
133
|
+
# else
|
134
|
+
# print "(#{time}) Execution failed with error #{ex}\n"
|
135
|
+
# end
|
136
|
+
# end
|
137
|
+
# end
|
138
|
+
#
|
139
|
+
# tt = Garcon::TimerTask.new(execution_interval: 1, timeout_interval: 1) {
|
140
|
+
# 42
|
141
|
+
# }
|
142
|
+
# tt.add_observer(TaskObserver.new)
|
143
|
+
# tt.execute
|
144
|
+
#
|
145
|
+
# # => (2015-03-21 09:06:07 -0700) Execution successfully returned 42
|
146
|
+
# # => (2015-03-21 09:06:08 -0700) Execution successfully returned 42
|
147
|
+
# # => (2015-03-21 09:06:09 -0700) Execution successfully returned 42
|
148
|
+
# tt.shutdown
|
149
|
+
#
|
150
|
+
# tt = Garcon::TimerTask.new(execution_interval: 1, timeout_interval: 1) {
|
151
|
+
# sleep
|
152
|
+
# }
|
153
|
+
# tt.add_observer(TaskObserver.new)
|
154
|
+
# tt.execute
|
155
|
+
#
|
156
|
+
# # => (2015-03-21 09:07:10 -0700) Execution timed out
|
157
|
+
# # => (2015-03-21 09:07:12 -0700) Execution timed out
|
158
|
+
# # => (2015-03-21 09:07:14 -0700) Execution timed out
|
159
|
+
# tt.shutdown
|
160
|
+
#
|
161
|
+
# tt = Garcon::TimerTask.new(execution_interval: 1) { raise StandardError }
|
162
|
+
# tt.add_observer(TaskObserver.new)
|
163
|
+
# tt.execute
|
164
|
+
#
|
165
|
+
# # => (2015-03-21 09:12:11 -0700) Execution failed with error StandardError
|
166
|
+
# # => (2015-03-21 09:12:12 -0700) Execution failed with error StandardError
|
167
|
+
# # => (2015-03-21 09:12:13 -0700) Execution failed with error StandardError
|
168
|
+
# tt.shutdown
|
169
|
+
#
|
170
|
+
class TimerTask
|
171
|
+
include Dereferenceable
|
172
|
+
include RubyExecutor
|
173
|
+
include Observable
|
174
|
+
|
175
|
+
# Default :execution_interval in seconds.
|
176
|
+
EXECUTION_INTERVAL = 60
|
177
|
+
|
178
|
+
# Default :timeout_interval in seconds.
|
179
|
+
TIMEOUT_INTERVAL = 30
|
180
|
+
|
181
|
+
# Create a new TimerTask with the given task and configuration.
|
182
|
+
#
|
183
|
+
# @!macro [attach] timer_task_initialize
|
184
|
+
# @note
|
185
|
+
# Calls Garcon::Dereferenceable# set_deref_options passing opts. All
|
186
|
+
# options supported by Garcon::Dereferenceable can be set during object
|
187
|
+
# initialization.
|
188
|
+
#
|
189
|
+
# @param [Hash] opts
|
190
|
+
# The options defining task execution.
|
191
|
+
# @option opts [Integer] :execution_interval
|
192
|
+
# The number of seconds between task executions (defaults to:
|
193
|
+
# EXECUTION_INTERVAL)
|
194
|
+
# @option opts [Integer] :timeout_interval
|
195
|
+
# The number of seconds a task can run before it is considered to have
|
196
|
+
# failed (default: TIMEOUT_INTERVAL)
|
197
|
+
# @option opts [Boolean] :run_now
|
198
|
+
# Whether to run the task immediately upon instantiation or to wait
|
199
|
+
# until the first execution_interval has passed (default: false)
|
200
|
+
#
|
201
|
+
# @raise ArgumentError
|
202
|
+
# when no block is given.
|
203
|
+
#
|
204
|
+
# @yield to the block after :execution_interval seconds have passed since
|
205
|
+
# the last yield
|
206
|
+
# @yieldparam task a reference to the TimerTask instance so that the
|
207
|
+
# block can control its own lifecycle. Necessary since self will
|
208
|
+
# refer to the execution context of the block rather than the running
|
209
|
+
# TimerTask.
|
210
|
+
#
|
211
|
+
# @return [TimerTask]
|
212
|
+
# the new TimerTask.
|
213
|
+
#
|
214
|
+
# @see Garcon::Dereferenceable# set_deref_options
|
215
|
+
def initialize(opts = {}, &task)
|
216
|
+
raise ArgumentError.new('no block given') unless block_given?
|
217
|
+
|
218
|
+
init_executor
|
219
|
+
set_deref_options(opts)
|
220
|
+
|
221
|
+
self.execution_interval =
|
222
|
+
opts[:execution] || opts[:execution_interval] || EXECUTION_INTERVAL
|
223
|
+
|
224
|
+
self.timeout_interval =
|
225
|
+
opts[:timeout] || opts[:timeout_interval] || TIMEOUT_INTERVAL
|
226
|
+
|
227
|
+
@run_now = opts[:now] || opts[:run_now]
|
228
|
+
@executor = Garcon::SafeTaskExecutor.new(task)
|
229
|
+
@running = Garcon::AtomicBoolean.new(false)
|
230
|
+
|
231
|
+
self.observers = CopyOnNotifyObserverSet.new
|
232
|
+
end
|
233
|
+
|
234
|
+
# Is the executor running?
|
235
|
+
#
|
236
|
+
# @return [Boolean]
|
237
|
+
# True when running, false when shutting down or shutdown
|
238
|
+
def running?
|
239
|
+
@running.true?
|
240
|
+
end
|
241
|
+
|
242
|
+
# Execute a previously created TimerTask.
|
243
|
+
#
|
244
|
+
# @example Instance and execute in separate steps
|
245
|
+
# tt = Garcon::TimerTask.new(execution_interval: 10) { puts 'Sup!' }
|
246
|
+
# tt.running? # => false
|
247
|
+
# tt.execute
|
248
|
+
# tt.running? # => true
|
249
|
+
#
|
250
|
+
# @example Instance and execute in one line
|
251
|
+
# tt = Garcon::TimerTask.new(execution_interval: 10) { puts 'hi' }.execute
|
252
|
+
# tt.running? # => true
|
253
|
+
#
|
254
|
+
# @return [TimerTask]
|
255
|
+
# A reference to self.
|
256
|
+
def execute
|
257
|
+
mutex.synchronize do
|
258
|
+
if @running.false?
|
259
|
+
@running.make_true
|
260
|
+
schedule_next_task(@run_now ? 0 : @execution_interval)
|
261
|
+
end
|
262
|
+
end
|
263
|
+
self
|
264
|
+
end
|
265
|
+
|
266
|
+
# Create and execute a new TimerTask.
|
267
|
+
#
|
268
|
+
# @!macro timer_task_initialize
|
269
|
+
#
|
270
|
+
# @example
|
271
|
+
# task = Garcon::TimerTask.execute(execution_interval: 10) do
|
272
|
+
# puts 'Sappening d00d?'
|
273
|
+
# end
|
274
|
+
# task.running? # => true
|
275
|
+
def self.execute(opts = {}, &task)
|
276
|
+
TimerTask.new(opts, &task).execute
|
277
|
+
end
|
278
|
+
|
279
|
+
# @!attribute [rw] execution_interval
|
280
|
+
# @return [Fixnum]
|
281
|
+
# Number of seconds after the task completes before it is performed again.
|
282
|
+
def execution_interval
|
283
|
+
mutex.lock
|
284
|
+
@execution_interval
|
285
|
+
ensure
|
286
|
+
mutex.unlock
|
287
|
+
end
|
288
|
+
|
289
|
+
# @!attribute [rw] execution_interval
|
290
|
+
# @return [Fixnum]
|
291
|
+
# Number of seconds after the task completes before it is performed again.
|
292
|
+
def execution_interval=(value)
|
293
|
+
if (value = value.to_f) <= 0.0
|
294
|
+
raise ArgumentError.new 'must be greater than zero'
|
295
|
+
else
|
296
|
+
begin
|
297
|
+
mutex.lock
|
298
|
+
@execution_interval = value
|
299
|
+
ensure
|
300
|
+
mutex.unlock
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
# @!attribute [rw] timeout_interval
|
306
|
+
# @return [Fixnum]
|
307
|
+
# Number of seconds the task can run before it is considered failed.
|
308
|
+
def timeout_interval
|
309
|
+
mutex.lock
|
310
|
+
@timeout_interval
|
311
|
+
ensure
|
312
|
+
mutex.unlock
|
313
|
+
end
|
314
|
+
|
315
|
+
# @!attribute [rw] timeout_interval
|
316
|
+
# @return [Fixnum]
|
317
|
+
# Number of seconds the task can run before it is considered failed.
|
318
|
+
def timeout_interval=(value)
|
319
|
+
if (value = value.to_f) <= 0.0
|
320
|
+
raise ArgumentError.new('must be greater than zero')
|
321
|
+
else
|
322
|
+
begin
|
323
|
+
mutex.lock
|
324
|
+
@timeout_interval = value
|
325
|
+
ensure
|
326
|
+
mutex.unlock
|
327
|
+
end
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
private :post, :<<
|
332
|
+
|
333
|
+
protected # A T T E N Z I O N E A R E A P R O T E T T A
|
334
|
+
|
335
|
+
# @!visibility private
|
336
|
+
def shutdown_execution
|
337
|
+
@running.make_false
|
338
|
+
super
|
339
|
+
end
|
340
|
+
|
341
|
+
# @!visibility private
|
342
|
+
def kill_execution
|
343
|
+
@running.make_false
|
344
|
+
super
|
345
|
+
end
|
346
|
+
|
347
|
+
# @!visibility private
|
348
|
+
def schedule_next_task(interval = execution_interval)
|
349
|
+
Garcon::timer(interval, Garcon::Event.new, &method(:execute_task))
|
350
|
+
end
|
351
|
+
|
352
|
+
# @!visibility private
|
353
|
+
def execute_task(completion)
|
354
|
+
return unless @running.true?
|
355
|
+
Garcon::timer(execution_interval, completion, &method(:timeout_task))
|
356
|
+
_success, value, reason = @executor.execute(self)
|
357
|
+
if completion.try?
|
358
|
+
self.value = value
|
359
|
+
schedule_next_task
|
360
|
+
time = Time.now
|
361
|
+
observers.notify_observers do
|
362
|
+
[time, self.value, reason]
|
363
|
+
end
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
# @!visibility private
|
368
|
+
def timeout_task(completion)
|
369
|
+
return unless @running.true?
|
370
|
+
if completion.try?
|
371
|
+
self.value = value
|
372
|
+
schedule_next_task
|
373
|
+
observers.notify_observers(Time.now, nil, Garcon::TimeoutError.new)
|
374
|
+
end
|
375
|
+
end
|
376
|
+
end
|
377
|
+
end
|