dynflow 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. data/.gitignore +6 -0
  2. data/.travis.yml +9 -0
  3. data/Gemfile +0 -10
  4. data/MIT-LICENSE +1 -1
  5. data/README.md +99 -37
  6. data/Rakefile +2 -6
  7. data/doc/images/logo.png +0 -0
  8. data/dynflow.gemspec +10 -1
  9. data/examples/generate_work_for_daemon.rb +24 -0
  10. data/examples/orchestrate.rb +121 -0
  11. data/examples/run_daemon.rb +17 -0
  12. data/examples/web_console.rb +29 -0
  13. data/lib/dynflow.rb +27 -6
  14. data/lib/dynflow/action.rb +185 -77
  15. data/lib/dynflow/action/cancellable_polling.rb +18 -0
  16. data/lib/dynflow/action/finalize_phase.rb +18 -0
  17. data/lib/dynflow/action/flow_phase.rb +44 -0
  18. data/lib/dynflow/action/format.rb +46 -0
  19. data/lib/dynflow/action/missing.rb +26 -0
  20. data/lib/dynflow/action/plan_phase.rb +85 -0
  21. data/lib/dynflow/action/polling.rb +49 -0
  22. data/lib/dynflow/action/presenter.rb +51 -0
  23. data/lib/dynflow/action/progress.rb +62 -0
  24. data/lib/dynflow/action/run_phase.rb +43 -0
  25. data/lib/dynflow/action/suspended.rb +21 -0
  26. data/lib/dynflow/clock.rb +133 -0
  27. data/lib/dynflow/daemon.rb +29 -0
  28. data/lib/dynflow/execution_plan.rb +285 -33
  29. data/lib/dynflow/execution_plan/dependency_graph.rb +29 -0
  30. data/lib/dynflow/execution_plan/output_reference.rb +52 -0
  31. data/lib/dynflow/execution_plan/steps.rb +12 -0
  32. data/lib/dynflow/execution_plan/steps/abstract.rb +121 -0
  33. data/lib/dynflow/execution_plan/steps/abstract_flow_step.rb +52 -0
  34. data/lib/dynflow/execution_plan/steps/error.rb +33 -0
  35. data/lib/dynflow/execution_plan/steps/finalize_step.rb +23 -0
  36. data/lib/dynflow/execution_plan/steps/plan_step.rb +81 -0
  37. data/lib/dynflow/execution_plan/steps/run_step.rb +21 -0
  38. data/lib/dynflow/executors.rb +9 -0
  39. data/lib/dynflow/executors/abstract.rb +32 -0
  40. data/lib/dynflow/executors/parallel.rb +88 -0
  41. data/lib/dynflow/executors/parallel/core.rb +119 -0
  42. data/lib/dynflow/executors/parallel/execution_plan_manager.rb +120 -0
  43. data/lib/dynflow/executors/parallel/flow_manager.rb +48 -0
  44. data/lib/dynflow/executors/parallel/pool.rb +102 -0
  45. data/lib/dynflow/executors/parallel/running_steps_manager.rb +63 -0
  46. data/lib/dynflow/executors/parallel/sequence_cursor.rb +97 -0
  47. data/lib/dynflow/executors/parallel/sequential_manager.rb +81 -0
  48. data/lib/dynflow/executors/parallel/work_queue.rb +44 -0
  49. data/lib/dynflow/executors/parallel/worker.rb +30 -0
  50. data/lib/dynflow/executors/remote_via_socket.rb +38 -0
  51. data/lib/dynflow/executors/remote_via_socket/core.rb +150 -0
  52. data/lib/dynflow/flows.rb +13 -0
  53. data/lib/dynflow/flows/abstract.rb +36 -0
  54. data/lib/dynflow/flows/abstract_composed.rb +104 -0
  55. data/lib/dynflow/flows/atom.rb +36 -0
  56. data/lib/dynflow/flows/concurrence.rb +28 -0
  57. data/lib/dynflow/flows/sequence.rb +13 -0
  58. data/lib/dynflow/future.rb +173 -0
  59. data/lib/dynflow/listeners.rb +7 -0
  60. data/lib/dynflow/listeners/abstract.rb +13 -0
  61. data/lib/dynflow/listeners/serialization.rb +41 -0
  62. data/lib/dynflow/listeners/socket.rb +88 -0
  63. data/lib/dynflow/logger_adapters.rb +8 -0
  64. data/lib/dynflow/logger_adapters/abstract.rb +30 -0
  65. data/lib/dynflow/logger_adapters/delegator.rb +13 -0
  66. data/lib/dynflow/logger_adapters/formatters.rb +8 -0
  67. data/lib/dynflow/logger_adapters/formatters/abstract.rb +33 -0
  68. data/lib/dynflow/logger_adapters/formatters/exception.rb +15 -0
  69. data/lib/dynflow/logger_adapters/simple.rb +59 -0
  70. data/lib/dynflow/micro_actor.rb +102 -0
  71. data/lib/dynflow/persistence.rb +53 -0
  72. data/lib/dynflow/persistence_adapters.rb +6 -0
  73. data/lib/dynflow/persistence_adapters/abstract.rb +56 -0
  74. data/lib/dynflow/persistence_adapters/sequel.rb +160 -0
  75. data/lib/dynflow/persistence_adapters/sequel_migrations/001_initial.rb +52 -0
  76. data/lib/dynflow/serializable.rb +66 -0
  77. data/lib/dynflow/simple_world.rb +18 -0
  78. data/lib/dynflow/stateful.rb +40 -0
  79. data/lib/dynflow/testing.rb +32 -0
  80. data/lib/dynflow/testing/assertions.rb +64 -0
  81. data/lib/dynflow/testing/dummy_execution_plan.rb +40 -0
  82. data/lib/dynflow/testing/dummy_executor.rb +29 -0
  83. data/lib/dynflow/testing/dummy_planned_action.rb +18 -0
  84. data/lib/dynflow/testing/dummy_step.rb +19 -0
  85. data/lib/dynflow/testing/dummy_world.rb +33 -0
  86. data/lib/dynflow/testing/factories.rb +83 -0
  87. data/lib/dynflow/testing/managed_clock.rb +23 -0
  88. data/lib/dynflow/testing/mimic.rb +38 -0
  89. data/lib/dynflow/transaction_adapters.rb +9 -0
  90. data/lib/dynflow/transaction_adapters/abstract.rb +26 -0
  91. data/lib/dynflow/transaction_adapters/active_record.rb +27 -0
  92. data/lib/dynflow/transaction_adapters/none.rb +12 -0
  93. data/lib/dynflow/version.rb +1 -1
  94. data/lib/dynflow/web_console.rb +277 -0
  95. data/lib/dynflow/world.rb +168 -0
  96. data/test/action_test.rb +89 -11
  97. data/test/clock_test.rb +59 -0
  98. data/test/code_workflow_example.rb +382 -0
  99. data/test/execution_plan_test.rb +195 -64
  100. data/test/executor_test.rb +692 -0
  101. data/test/persistance_adapters_test.rb +173 -0
  102. data/test/test_helper.rb +316 -1
  103. data/test/testing_test.rb +148 -0
  104. data/test/web_console_test.rb +38 -0
  105. data/web/assets/javascripts/application.js +25 -0
  106. data/web/assets/stylesheets/application.css +101 -0
  107. data/web/assets/vendor/bootstrap/css/bootstrap-responsive.css +1109 -0
  108. data/web/assets/vendor/bootstrap/css/bootstrap-responsive.min.css +9 -0
  109. data/web/assets/vendor/bootstrap/css/bootstrap.css +6167 -0
  110. data/web/assets/vendor/bootstrap/css/bootstrap.min.css +9 -0
  111. data/web/assets/vendor/bootstrap/img/glyphicons-halflings-white.png +0 -0
  112. data/web/assets/vendor/bootstrap/img/glyphicons-halflings.png +0 -0
  113. data/web/assets/vendor/bootstrap/js/bootstrap.js +2280 -0
  114. data/web/assets/vendor/bootstrap/js/bootstrap.min.js +6 -0
  115. data/web/assets/vendor/google-code-prettify/lang-basic.js +3 -0
  116. data/web/assets/vendor/google-code-prettify/prettify.css +1 -0
  117. data/web/assets/vendor/google-code-prettify/prettify.js +30 -0
  118. data/web/assets/vendor/google-code-prettify/run_prettify.js +34 -0
  119. data/web/assets/vendor/jquery/jquery.js +9807 -0
  120. data/web/views/flow.erb +19 -0
  121. data/web/views/flow_step.erb +31 -0
  122. data/web/views/index.erb +39 -0
  123. data/web/views/layout.erb +20 -0
  124. data/web/views/plan_step.erb +11 -0
  125. data/web/views/show.erb +54 -0
  126. metadata +250 -11
  127. data/examples/events.rb +0 -71
  128. data/examples/workflow.rb +0 -140
  129. data/lib/dynflow/bus.rb +0 -168
  130. data/lib/dynflow/dispatcher.rb +0 -36
  131. data/lib/dynflow/logger.rb +0 -34
  132. data/lib/dynflow/step.rb +0 -234
  133. data/test/bus_test.rb +0 -150
@@ -0,0 +1,46 @@
1
+ module Dynflow
2
+
3
+ # Input/output format validation logic calling
4
+ # input_format/output_format with block acts as a setter for
5
+ # specifying the format. Without a block it acts as a getter
6
+ module Action::Format
7
+
8
+ # we don't evaluate tbe block immediatelly, but postpone it till all the
9
+ # action classes are loaded, because we can use them to reference output format
10
+ def input_format(&block)
11
+ case
12
+ when block && !@input_format_block
13
+ @input_format_block = block
14
+ when !block && @input_format_block
15
+ return @input_format ||= Apipie::Params::Description.define(&@input_format_block)
16
+ when block && @input_format_block
17
+ raise "The input_format has already been defined in #{self.action_class}"
18
+ when !block && !@input_format_block
19
+ if superclass.respond_to? :input_format
20
+ superclass.input_format
21
+ else
22
+ raise "The input_format has not been defined yet in #{self.action_class}"
23
+ end
24
+ end
25
+ end
26
+
27
+ def output_format(&block)
28
+ case
29
+ when block && !@output_format_block
30
+ @output_format_block = block
31
+ when !block && @output_format_block
32
+ return @output_format ||= Apipie::Params::Description.define(&@output_format_block)
33
+ when block && @output_format_block
34
+ raise "The output_format has already been defined in #{self.action_class}"
35
+ when !block && !@output_format_block
36
+ if superclass.respond_to? :output_format
37
+ superclass.output_format
38
+ else
39
+ raise "The output_format has not been defined yet in #{self.action_class}"
40
+ end
41
+ end
42
+ end
43
+
44
+ end
45
+ end
46
+
@@ -0,0 +1,26 @@
1
+ module Dynflow
2
+ # for cases the serialized action was renamed and it's not available
3
+ # in the code base anymore.
4
+ class Action::Missing < Dynflow::Action
5
+
6
+ def self.generate(action_name)
7
+ Class.new(self).tap do |klass|
8
+ klass.singleton_class.send(:define_method, :name) do
9
+ action_name
10
+ end
11
+ end
12
+ end
13
+
14
+ def plan(*args)
15
+ raise StandardError, "This action is not meant to be planned"
16
+ end
17
+
18
+ def run
19
+ raise StandardError, "This action is not meant to be run"
20
+ end
21
+
22
+ def finalize
23
+ raise StandardError, "This action is not meant to be finalized"
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,85 @@
1
+ module Dynflow
2
+ module Action::PlanPhase
3
+ attr_reader :execution_plan, :trigger
4
+
5
+ def self.included(base)
6
+ base.attr_indifferent_access_hash :input
7
+ end
8
+
9
+ def initialize(attributes, execution_plan, trigger)
10
+ super attributes, execution_plan.world
11
+ plan_step_id || raise(ArgumentError, 'missing plan_step_id')
12
+
13
+ self.input = attributes[:input] || {}
14
+ @execution_plan = Type! execution_plan, ExecutionPlan
15
+ @plan_step_id = plan_step_id
16
+ @trigger = Type! trigger, Action, NilClass
17
+ end
18
+
19
+ def execute(*args)
20
+ self.state = :running
21
+ save_state
22
+ with_error_handling do
23
+ execution_plan.switch_flow(Flows::Concurrence.new([])) do
24
+ plan(*args)
25
+ end
26
+
27
+ subscribed_actions = world.subscribed_actions(self.action_class)
28
+ if subscribed_actions.any?
29
+ # we encapsulate the flow for this action into a concurrence and
30
+ # add the subscribed flows to it as well.
31
+ trigger_flow = execution_plan.current_run_flow.sub_flows.pop
32
+ execution_plan.switch_flow(Flows::Concurrence.new([trigger_flow].compact)) do
33
+ subscribed_actions.each do |action_class|
34
+ new_plan_step = execution_plan.add_plan_step(action_class, self)
35
+ new_plan_step.execute(execution_plan, self, *args)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ def to_hash
43
+ super.merge recursive_to_hash(input: input)
44
+ end
45
+
46
+ # DSL for plan method
47
+
48
+ def concurrence(&block)
49
+ execution_plan.switch_flow(Flows::Concurrence.new([]), &block)
50
+ end
51
+
52
+ def sequence(&block)
53
+ execution_plan.switch_flow(Flows::Sequence.new([]), &block)
54
+ end
55
+
56
+ def plan_self(input)
57
+ @input = input.with_indifferent_access
58
+ if self.respond_to?(:run)
59
+ run_step = execution_plan.add_run_step(self)
60
+ @run_step_id = run_step.id
61
+ @output_reference = ExecutionPlan::OutputReference.new(run_step.id, id)
62
+ end
63
+
64
+ if self.respond_to?(:finalize)
65
+ finalize_step = execution_plan.add_finalize_step(self)
66
+ @finalize_step_id = finalize_step.id
67
+ end
68
+
69
+ return self # to stay consistent with plan_action
70
+ end
71
+
72
+ def plan_action(action_class, *args)
73
+ execution_plan.add_plan_step(action_class, self).execute(execution_plan, nil, *args)
74
+ end
75
+
76
+ def output
77
+ unless @output_reference
78
+ raise 'plan_self has to be invoked before being able to reference the output'
79
+ end
80
+
81
+ return @output_reference
82
+ end
83
+
84
+ end
85
+ end
@@ -0,0 +1,49 @@
1
+ module Dynflow
2
+ module Action::Polling
3
+
4
+ Poll = Algebrick.atom
5
+
6
+ def run(event = nil)
7
+ case event
8
+ when nil
9
+ self.external_task = invoke_external_task
10
+ suspend_and_ping
11
+ when Poll
12
+ self.external_task = poll_external_task
13
+ suspend_and_ping unless done?
14
+ else
15
+ raise "unrecognized event #{event}"
16
+ end
17
+ end
18
+
19
+ def external_task
20
+ raise NotImplementedError
21
+ end
22
+
23
+ def done?
24
+ raise NotImplementedError
25
+ end
26
+
27
+ private
28
+
29
+ def invoke_external_task
30
+ raise NotImplementedError
31
+ end
32
+
33
+ def external_task=(external_task_data)
34
+ raise NotImplementedError
35
+ end
36
+
37
+ def poll_external_task
38
+ raise NotImplementedError
39
+ end
40
+
41
+ def suspend_and_ping
42
+ suspend { |suspended_action| world.clock.ping suspended_action, poll_interval, Poll }
43
+ end
44
+
45
+ def poll_interval
46
+ 0.5
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,51 @@
1
+ module Dynflow
2
+
3
+ # This module is used for providing access to the results of the
4
+ # action. It's used in ExecutionPlan#actions to provide access to
5
+ # data of the actions in the execution plan.
6
+ #
7
+ # It also defines helper methods to extract usable data from the action itself,
8
+ # as well as other actions involved in the execution plan. One action (usually the
9
+ # main trigger, can use them to collect data across the whole execution_plan)
10
+ module Action::Presenter
11
+
12
+ def self.included(base)
13
+ base.send(:attr_reader, :input)
14
+ base.send(:attr_reader, :output)
15
+ base.send(:attr_reader, :all_actions)
16
+ end
17
+
18
+ def self.load(execution_plan, action_id, involved_steps, all_actions)
19
+ persistence_adapter = execution_plan.world.persistence.adapter
20
+ attributes = persistence_adapter.load_action(execution_plan.id,
21
+ action_id)
22
+ raise ArgumentError, 'missing :class' unless attributes[:class]
23
+ Action.constantize(attributes[:class]).presenter.new(attributes,
24
+ involved_steps,
25
+ all_actions)
26
+ end
27
+
28
+ def to_hash
29
+ recursive_to_hash(action: action_class,
30
+ input: input,
31
+ output: output)
32
+ end
33
+
34
+ # @param [Hash] attributes - the action attributes, usually loaded form persistence layer
35
+ # @param [Array<ExecutionPlan::Steps::AbstractStep> - steps that operate on top of the action
36
+ # @param [Array<Action::Presenter>] - array of all the actions involved in the execution plan
37
+ # with this action. Allows to access data from other actions
38
+ def initialize(attributes, involved_steps, all_actions)
39
+ @execution_plan_id = attributes[:execution_plan_id] || raise(ArgumentError, 'missing execution_plan_id')
40
+ @id = attributes[:id] || raise(ArgumentError, 'missing id')
41
+
42
+ # TODO: use the involved_steps to provide summary state and error for the action
43
+ @involved_steps = involved_steps
44
+ @all_actions = all_actions
45
+
46
+ indifferent_access_hash_variable_set :input, attributes[:input]
47
+ indifferent_access_hash_variable_set :output, attributes[:output] || {}
48
+ end
49
+
50
+ end
51
+ end
@@ -0,0 +1,62 @@
1
+ module Dynflow
2
+
3
+ # Methods for specifying the progress of the action
4
+ # the +*_progress+ methods should return number in 0..1.
5
+ # The weight is there to increase/decrease the portion of this task
6
+ # in the context of other tasks in execution plan. Normal action has
7
+ # weight 1.
8
+ #
9
+ # The +*_progress+ is run only when the action is in running/suspend state. Otherwise
10
+ # the progress is 1 for success/skipped actions and 0 for errorneous ones.
11
+ module Action::Progress
12
+
13
+ def run_progress
14
+ 0.5
15
+ end
16
+
17
+ def run_progress_weight
18
+ 1
19
+ end
20
+
21
+ def finalize_progress
22
+ 0.5
23
+ end
24
+
25
+ def finalize_progress_weight
26
+ 1
27
+ end
28
+
29
+ # this method is not intended to be overriden. Use +{run, finalize}_progress+
30
+ # variants instead
31
+ def progress_done
32
+ case self.state
33
+ when :success, :skipped
34
+ 1
35
+ when :running, :suspended
36
+ case self
37
+ when Action::RunPhase
38
+ run_progress
39
+ when Action::FinalizePhase
40
+ finalize_progress
41
+ else
42
+ raise "Calculating progress for this phase is not supported"
43
+ end
44
+ else
45
+ 0
46
+ end
47
+ end
48
+
49
+ def progress_weight
50
+ case self
51
+ when Action::RunPhase
52
+ run_progress_weight
53
+ when Action::FinalizePhase
54
+ finalize_progress_weight
55
+ else
56
+ raise "Calculating progress for this phase is not supported"
57
+ end
58
+ end
59
+
60
+ end
61
+ end
62
+
@@ -0,0 +1,43 @@
1
+ module Dynflow
2
+ module Action::RunPhase
3
+
4
+ def self.included(base)
5
+ base.send(:include, Action::FlowPhase)
6
+ base.attr_indifferent_access_hash :output
7
+ end
8
+
9
+ SUSPEND = Object.new
10
+
11
+ def execute(event)
12
+ @world.logger.debug "step #{execution_plan_id}:#{@step.id} got event #{event}" if event
13
+ case
14
+ when state == :running
15
+ raise NotImplementedError, 'recovery after restart is not implemented'
16
+
17
+ when [:pending, :error, :suspended].include?(state)
18
+ if [:pending, :error].include?(state) && event
19
+ raise 'event can be processed only when in suspended state'
20
+ end
21
+
22
+ self.state = :running
23
+ save_state
24
+ with_error_handling do
25
+ result = catch(SUSPEND) { event ? run(event) : run }
26
+ if result == SUSPEND
27
+ self.state = :suspended
28
+ end
29
+ end
30
+
31
+ else
32
+ raise "wrong state #{state} when event:#{event}"
33
+ end
34
+ end
35
+
36
+ # DSL for run
37
+
38
+ def suspend(&block)
39
+ block.call Action::Suspended.new self if block
40
+ throw SUSPEND, SUSPEND
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,21 @@
1
+ module Dynflow
2
+ class Action::Suspended
3
+ attr_reader :execution_plan_id, :step_id
4
+
5
+ def initialize(action)
6
+ @world = action.world
7
+ @execution_plan_id = action.execution_plan_id
8
+ @step_id = action.run_step_id
9
+ end
10
+
11
+ def event(event, future = Future.new)
12
+ @world.event execution_plan_id, step_id, event, future
13
+ end
14
+
15
+ def <<(event)
16
+ event event
17
+ end
18
+
19
+ alias_method :ask, :event
20
+ end
21
+ end
@@ -0,0 +1,133 @@
1
+ module Dynflow
2
+ require 'set'
3
+
4
+ class Clock < MicroActor
5
+
6
+ include Algebrick::Types
7
+
8
+ Tick = Algebrick.atom
9
+ Timer = Algebrick.type do
10
+ fields! who: Object, # to ping back
11
+ when: Time, # to deliver
12
+ what: Maybe[Object], # to send
13
+ where: Symbol # it should be delivered, which method
14
+ end
15
+
16
+ module Timer
17
+ def self.[](*fields)
18
+ super(*fields).tap { |v| Match! v.who, -> who { who.respond_to? v.where } }
19
+ end
20
+
21
+ include Comparable
22
+
23
+ def <=>(other)
24
+ Type! other, self.class
25
+ self.when <=> other.when
26
+ end
27
+
28
+ def apply
29
+ if Algebrick::Some[Object] === what
30
+ who.send where, what.value
31
+ else
32
+ who.send where
33
+ end
34
+ end
35
+ end
36
+
37
+ Pills = Algebrick.type do
38
+ variants None = atom,
39
+ Took = atom,
40
+ Pill = type { fields Float }
41
+ end
42
+
43
+ def ping(who, time, with_what = nil, where = :<<)
44
+ Type! time, Time, Numeric
45
+ time = Time.now + time if time.is_a? Numeric
46
+ timer = Timer[who, time, with_what.nil? ? None : Some[Object][with_what], where]
47
+ if terminated?
48
+ Thread.new do
49
+ sleep [timer.when - Time.now, 0].max
50
+ timer.apply
51
+ end
52
+ else
53
+ self << timer
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ def delayed_initialize
60
+ @timers = SortedSet.new
61
+ @sleeping_pill = None
62
+ @sleep_barrier = Mutex.new
63
+ @sleeper = Thread.new { sleeping }
64
+ Thread.pass until @sleep_barrier.locked? || @sleeper.status == 'sleep'
65
+ end
66
+
67
+ def termination
68
+ @sleeper.kill
69
+ super
70
+ end
71
+
72
+ def on_message(message)
73
+ match message,
74
+ Tick >-> do
75
+ run_ready_timers
76
+ sleep_to first_timer
77
+ end,
78
+ ~Timer >-> timer do
79
+ @timers.add timer
80
+ if @timers.size == 1
81
+ sleep_to timer
82
+ else
83
+ wakeup if timer == first_timer
84
+ end
85
+ end
86
+ end
87
+
88
+ def run_ready_timers
89
+ while first_timer && first_timer.when <= Time.now
90
+ first_timer.apply
91
+ @timers.delete(first_timer)
92
+ end
93
+ end
94
+
95
+ def first_timer
96
+ @timers.first
97
+ end
98
+
99
+ def wakeup
100
+ while @sleep_barrier.synchronize { Pill === @sleeping_pill }
101
+ Thread.pass
102
+ end
103
+ @sleep_barrier.synchronize do
104
+ @sleeper.wakeup if Took === @sleeping_pill
105
+ end
106
+ end
107
+
108
+ def sleep_to(timer)
109
+ return unless timer
110
+ sec = [timer.when - Time.now, 0.0].max
111
+ @sleep_barrier.synchronize do
112
+ @sleeping_pill = Pill[sec]
113
+ @sleeper.wakeup
114
+ end
115
+ end
116
+
117
+ def sleeping
118
+ @sleep_barrier.synchronize do
119
+ loop do
120
+ @sleeping_pill = None
121
+ @sleep_barrier.sleep
122
+ pill = @sleeping_pill
123
+ @sleeping_pill = Took
124
+ @sleep_barrier.sleep pill.value
125
+ self << Tick
126
+ end
127
+ end
128
+ end
129
+
130
+ end
131
+ end
132
+
133
+