garcun 0.0.2
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/.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
|