dynflow 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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
+