celluloid 0.16.0 → 0.17.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of celluloid might be problematic. Click here for more details.

Files changed (167) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +333 -0
  3. data/README.md +1 -1
  4. data/culture/CODE_OF_CONDUCT.md +28 -0
  5. data/culture/Gemfile +9 -0
  6. data/culture/README.md +22 -0
  7. data/culture/Rakefile +5 -0
  8. data/culture/SYNC.md +70 -0
  9. data/culture/celluloid-culture.gemspec +18 -0
  10. data/culture/gems/README.md +39 -0
  11. data/culture/gems/dependencies.yml +78 -0
  12. data/culture/gems/loader.rb +101 -0
  13. data/culture/rubocop/README.md +38 -0
  14. data/culture/rubocop/lint.yml +8 -0
  15. data/culture/rubocop/metrics.yml +15 -0
  16. data/culture/rubocop/rubocop.yml +4 -0
  17. data/culture/rubocop/style.yml +48 -0
  18. data/culture/spec/gems_spec.rb +2 -0
  19. data/culture/spec/spec_helper.rb +0 -0
  20. data/culture/spec/sync_spec.rb +2 -0
  21. data/culture/sync.rb +56 -0
  22. data/culture/tasks/rspec.rake +5 -0
  23. data/culture/tasks/rubocop.rake +2 -0
  24. data/examples/basic_usage.rb +49 -0
  25. data/examples/futures.rb +38 -0
  26. data/examples/ring.rb +61 -0
  27. data/examples/simple_pmap.rb +14 -0
  28. data/examples/timers.rb +72 -0
  29. data/lib/celluloid.rb +142 -127
  30. data/lib/celluloid/actor.rb +47 -41
  31. data/lib/celluloid/actor_system.rb +75 -22
  32. data/lib/celluloid/autostart.rb +1 -1
  33. data/lib/celluloid/backported.rb +2 -0
  34. data/lib/celluloid/call/async.rb +16 -0
  35. data/lib/celluloid/call/block.rb +22 -0
  36. data/lib/celluloid/call/sync.rb +70 -0
  37. data/lib/celluloid/calls.rb +25 -114
  38. data/lib/celluloid/cell.rb +32 -20
  39. data/lib/celluloid/condition.rb +3 -3
  40. data/lib/celluloid/core_ext.rb +1 -1
  41. data/lib/celluloid/current.rb +2 -0
  42. data/lib/celluloid/deprecate.rb +18 -0
  43. data/lib/celluloid/exceptions.rb +1 -1
  44. data/lib/celluloid/fiber.rb +3 -3
  45. data/lib/celluloid/future.rb +7 -6
  46. data/lib/celluloid/group.rb +65 -0
  47. data/lib/celluloid/group/manager.rb +27 -0
  48. data/lib/celluloid/group/pool.rb +125 -0
  49. data/lib/celluloid/group/spawner.rb +71 -0
  50. data/lib/celluloid/logging.rb +5 -5
  51. data/lib/celluloid/mailbox.rb +14 -13
  52. data/lib/celluloid/mailbox/evented.rb +76 -0
  53. data/lib/celluloid/notices.rb +15 -0
  54. data/lib/celluloid/proxies.rb +12 -0
  55. data/lib/celluloid/proxy/abstract.rb +24 -0
  56. data/lib/celluloid/proxy/actor.rb +46 -0
  57. data/lib/celluloid/proxy/async.rb +36 -0
  58. data/lib/celluloid/proxy/block.rb +31 -0
  59. data/lib/celluloid/proxy/cell.rb +76 -0
  60. data/lib/celluloid/proxy/future.rb +40 -0
  61. data/lib/celluloid/proxy/sync.rb +44 -0
  62. data/lib/celluloid/rspec.rb +9 -10
  63. data/lib/celluloid/system_events.rb +16 -15
  64. data/lib/celluloid/{tasks.rb → task.rb} +21 -21
  65. data/lib/celluloid/task/fibered.rb +45 -0
  66. data/lib/celluloid/task/threaded.rb +59 -0
  67. data/lib/celluloid/test.rb +1 -1
  68. data/lib/celluloid/thread.rb +6 -1
  69. data/lib/celluloid/version.rb +3 -0
  70. data/spec/celluloid/actor_spec.rb +2 -2
  71. data/spec/celluloid/actor_system_spec.rb +35 -21
  72. data/spec/celluloid/block_spec.rb +3 -5
  73. data/spec/celluloid/calls_spec.rb +33 -11
  74. data/spec/celluloid/condition_spec.rb +16 -13
  75. data/spec/celluloid/evented_mailbox_spec.rb +1 -31
  76. data/spec/celluloid/future_spec.rb +13 -10
  77. data/spec/celluloid/group/elastic_spec.rb +0 -0
  78. data/spec/celluloid/group/manager_spec.rb +0 -0
  79. data/spec/celluloid/group/pool_spec.rb +8 -0
  80. data/spec/celluloid/group/spawner_spec.rb +8 -0
  81. data/spec/celluloid/mailbox/evented_spec.rb +27 -0
  82. data/spec/celluloid/mailbox_spec.rb +1 -3
  83. data/spec/celluloid/misc/leak_spec.rb +73 -0
  84. data/spec/celluloid/task/fibered_spec.rb +5 -0
  85. data/spec/celluloid/task/threaded_spec.rb +5 -0
  86. data/spec/celluloid/timer_spec.rb +14 -16
  87. data/spec/deprecate/actor_system_spec.rb +72 -0
  88. data/spec/deprecate/block_spec.rb +52 -0
  89. data/spec/deprecate/calls_spec.rb +57 -0
  90. data/spec/deprecate/evented_mailbox_spec.rb +34 -0
  91. data/spec/deprecate/future_spec.rb +32 -0
  92. data/spec/deprecate/internal_pool_spec.rb +4 -0
  93. data/spec/shared/actor_examples.rb +1237 -0
  94. data/spec/shared/group_examples.rb +121 -0
  95. data/{lib/celluloid/rspec → spec/shared}/mailbox_examples.rb +20 -17
  96. data/{lib/celluloid/rspec → spec/shared}/task_examples.rb +9 -8
  97. data/spec/spec_helper.rb +72 -16
  98. data/spec/support/coverage.rb +4 -0
  99. data/spec/support/crash_checking.rb +68 -0
  100. data/spec/support/debugging.rb +31 -0
  101. data/spec/support/env.rb +16 -0
  102. data/{lib/celluloid/rspec/example_actor_class.rb → spec/support/examples/actor_class.rb} +21 -2
  103. data/spec/support/examples/evented_mailbox_class.rb +27 -0
  104. data/spec/support/includer.rb +9 -0
  105. data/spec/support/logging.rb +63 -0
  106. data/spec/support/loose_threads.rb +65 -0
  107. data/spec/support/reset_class_variables.rb +27 -0
  108. data/spec/support/sleep_and_wait.rb +14 -0
  109. data/spec/support/split_logs.rb +1 -0
  110. data/spec/support/stubbing.rb +14 -0
  111. metadata +255 -95
  112. data/lib/celluloid/call_chain.rb +0 -13
  113. data/lib/celluloid/cpu_counter.rb +0 -34
  114. data/lib/celluloid/evented_mailbox.rb +0 -73
  115. data/lib/celluloid/fsm.rb +0 -186
  116. data/lib/celluloid/handlers.rb +0 -41
  117. data/lib/celluloid/internal_pool.rb +0 -159
  118. data/lib/celluloid/legacy.rb +0 -9
  119. data/lib/celluloid/links.rb +0 -36
  120. data/lib/celluloid/logger.rb +0 -93
  121. data/lib/celluloid/logging/incident.rb +0 -21
  122. data/lib/celluloid/logging/incident_logger.rb +0 -129
  123. data/lib/celluloid/logging/incident_reporter.rb +0 -48
  124. data/lib/celluloid/logging/log_event.rb +0 -20
  125. data/lib/celluloid/logging/ring_buffer.rb +0 -65
  126. data/lib/celluloid/method.rb +0 -32
  127. data/lib/celluloid/notifications.rb +0 -83
  128. data/lib/celluloid/pool_manager.rb +0 -146
  129. data/lib/celluloid/probe.rb +0 -73
  130. data/lib/celluloid/properties.rb +0 -24
  131. data/lib/celluloid/proxies/abstract_proxy.rb +0 -20
  132. data/lib/celluloid/proxies/actor_proxy.rb +0 -38
  133. data/lib/celluloid/proxies/async_proxy.rb +0 -31
  134. data/lib/celluloid/proxies/block_proxy.rb +0 -29
  135. data/lib/celluloid/proxies/cell_proxy.rb +0 -68
  136. data/lib/celluloid/proxies/future_proxy.rb +0 -35
  137. data/lib/celluloid/proxies/sync_proxy.rb +0 -36
  138. data/lib/celluloid/receivers.rb +0 -63
  139. data/lib/celluloid/registry.rb +0 -57
  140. data/lib/celluloid/responses.rb +0 -44
  141. data/lib/celluloid/rspec/actor_examples.rb +0 -1054
  142. data/lib/celluloid/signals.rb +0 -23
  143. data/lib/celluloid/stack_dump.rb +0 -133
  144. data/lib/celluloid/supervision_group.rb +0 -169
  145. data/lib/celluloid/supervisor.rb +0 -22
  146. data/lib/celluloid/task_set.rb +0 -49
  147. data/lib/celluloid/tasks/task_fiber.rb +0 -43
  148. data/lib/celluloid/tasks/task_thread.rb +0 -53
  149. data/lib/celluloid/thread_handle.rb +0 -50
  150. data/lib/celluloid/uuid.rb +0 -38
  151. data/spec/celluloid/cpu_counter_spec.rb +0 -82
  152. data/spec/celluloid/fsm_spec.rb +0 -107
  153. data/spec/celluloid/internal_pool_spec.rb +0 -52
  154. data/spec/celluloid/links_spec.rb +0 -45
  155. data/spec/celluloid/logging/ring_buffer_spec.rb +0 -38
  156. data/spec/celluloid/notifications_spec.rb +0 -120
  157. data/spec/celluloid/pool_spec.rb +0 -92
  158. data/spec/celluloid/probe_spec.rb +0 -121
  159. data/spec/celluloid/properties_spec.rb +0 -42
  160. data/spec/celluloid/registry_spec.rb +0 -64
  161. data/spec/celluloid/stack_dump_spec.rb +0 -64
  162. data/spec/celluloid/supervision_group_spec.rb +0 -65
  163. data/spec/celluloid/supervisor_spec.rb +0 -103
  164. data/spec/celluloid/tasks/task_fiber_spec.rb +0 -5
  165. data/spec/celluloid/tasks/task_thread_spec.rb +0 -5
  166. data/spec/celluloid/thread_handle_spec.rb +0 -26
  167. data/spec/celluloid/uuid_spec.rb +0 -11
@@ -1,23 +0,0 @@
1
- module Celluloid
2
- # Event signaling between methods of the same object
3
- class Signals
4
- def initialize
5
- @conditions = {}
6
- end
7
-
8
- # Wait for the given signal and return the associated value
9
- def wait(name)
10
- raise "cannot wait for signals while exclusive" if Celluloid.exclusive?
11
-
12
- @conditions[name] ||= Condition.new
13
- @conditions[name].wait
14
- end
15
-
16
- # Send a signal to all method calls waiting for the given name
17
- def broadcast(name, value = nil)
18
- if condition = @conditions.delete(name)
19
- condition.broadcast(value)
20
- end
21
- end
22
- end
23
- end
@@ -1,133 +0,0 @@
1
- module Celluloid
2
- class StackDump
3
- module DisplayBacktrace
4
- def display_backtrace(backtrace, output, indent = nil)
5
- backtrace ||= ["EMPTY BACKTRACE"]
6
- backtrace.each do |line|
7
- output << indent if indent
8
- output << "\t" << line << "\n"
9
- end
10
- output << "\n\n"
11
- end
12
- end
13
-
14
- class TaskState < Struct.new(:task_class, :type, :meta, :status, :backtrace)
15
- end
16
-
17
- class ActorState
18
- include DisplayBacktrace
19
- attr_accessor :name, :id, :cell
20
- attr_accessor :status, :tasks
21
- attr_accessor :backtrace
22
-
23
- def dump
24
- string = ""
25
- string << "Celluloid::Actor 0x#{id.to_s(16)}"
26
- string << " [#{name}]" if name
27
- string << "\n"
28
-
29
- if cell
30
- string << cell.dump
31
- string << "\n"
32
- end
33
-
34
- if status == :idle
35
- string << "State: Idle (waiting for messages)\n"
36
- display_backtrace backtrace, string
37
- else
38
- string << "State: Running (executing tasks)\n"
39
- display_backtrace backtrace, string
40
- string << "\tTasks:\n"
41
-
42
- tasks.each_with_index do |task, i|
43
- string << "\t #{i+1}) #{task.task_class}[#{task.type}]: #{task.status}\n"
44
- string << "\t #{task.meta.inspect}\n"
45
- display_backtrace task.backtrace, string, "\t"
46
- end
47
- end
48
-
49
- string
50
- end
51
- end
52
-
53
- class CellState < Struct.new(:subject_id, :subject_class)
54
- def dump
55
- "Celluloid::Cell 0x#{subject_id.to_s(16)}: #{subject_class}"
56
- end
57
- end
58
-
59
- class ThreadState < Struct.new(:thread_id, :backtrace, :role)
60
- include DisplayBacktrace
61
-
62
- def dump
63
- string = ""
64
- string << "Thread 0x#{thread_id.to_s(16)} (#{role}):\n"
65
- display_backtrace backtrace, string
66
- string
67
- end
68
- end
69
-
70
- attr_accessor :actors, :threads
71
-
72
- def initialize(internal_pool)
73
- @internal_pool = internal_pool
74
-
75
- @actors = []
76
- @threads = []
77
-
78
- snapshot
79
- end
80
-
81
- def snapshot
82
- @internal_pool.each do |thread|
83
- if thread.role == :actor
84
- @actors << snapshot_actor(thread.actor) if thread.actor
85
- else
86
- @threads << snapshot_thread(thread)
87
- end
88
- end
89
- end
90
-
91
- def snapshot_actor(actor)
92
- state = ActorState.new
93
- state.id = actor.object_id
94
-
95
- # TODO: delegate to the behavior
96
- if actor.behavior.is_a?(Cell)
97
- state.cell = snapshot_cell(actor.behavior)
98
- end
99
-
100
- tasks = actor.tasks
101
- if tasks.empty?
102
- state.status = :idle
103
- else
104
- state.status = :running
105
- state.tasks = tasks.to_a.map { |t| TaskState.new(t.class, t.type, t.meta, t.status, t.backtrace) }
106
- end
107
-
108
- state.backtrace = actor.thread.backtrace if actor.thread
109
- state
110
- end
111
-
112
- def snapshot_cell(behavior)
113
- state = CellState.new
114
- state.subject_id = behavior.subject.object_id
115
- state.subject_class = behavior.subject.class
116
- state
117
- end
118
-
119
- def snapshot_thread(thread)
120
- ThreadState.new(thread.object_id, thread.backtrace, thread.role)
121
- end
122
-
123
- def print(output = STDERR)
124
- @actors.each do |actor|
125
- output.print actor.dump
126
- end
127
-
128
- @threads.each do |thread|
129
- output.print thread.dump
130
- end
131
- end
132
- end
133
- end
@@ -1,169 +0,0 @@
1
- module Celluloid
2
- # Supervise collections of actors as a group
3
- class SupervisionGroup
4
- include Celluloid
5
- trap_exit :restart_actor
6
-
7
- class << self
8
-
9
- # Actors or sub-applications to be supervised
10
- def blocks
11
- @blocks ||= []
12
- end
13
-
14
- # Start this application (and watch it with a supervisor)
15
- def run!(registry = nil)
16
- group = new(registry) do |_group|
17
- blocks.each do |block|
18
- block.call(_group)
19
- end
20
- end
21
- group
22
- end
23
-
24
- # Run the application in the foreground with a simple watchdog
25
- def run(registry = nil)
26
- loop do
27
- supervisor = run!(registry)
28
-
29
- # Take five, toplevel supervisor
30
- sleep 5 while supervisor.alive?
31
-
32
- Logger.error "!!! Celluloid::SupervisionGroup #{self} crashed. Restarting..."
33
- end
34
- end
35
-
36
- # Register an actor class or a sub-group to be launched and supervised
37
- # Available options are:
38
- #
39
- # * as: register this application in the Celluloid::Actor[] directory
40
- # * args: start the actor with the given arguments
41
- def supervise(klass, options = {})
42
- blocks << lambda do |group|
43
- group.add klass, options
44
- end
45
- end
46
-
47
- # Register a pool of actors to be launched on group startup
48
- # Available options are:
49
- #
50
- # * as: register this application in the Celluloid::Actor[] directory
51
- # * args: start the actor pool with the given arguments
52
- def pool(klass, options = {})
53
- blocks << lambda do |group|
54
- group.pool klass, options
55
- end
56
- end
57
- end
58
-
59
- finalizer :finalize
60
-
61
- # Start the group
62
- def initialize(registry = nil)
63
- @members = []
64
- @registry = registry || Celluloid.actor_system.registry
65
-
66
- yield current_actor if block_given?
67
- end
68
-
69
- execute_block_on_receiver :initialize, :supervise, :supervise_as
70
-
71
- def supervise(klass, *args, &block)
72
- add(klass, :args => args, :block => block)
73
- end
74
-
75
- def supervise_as(name, klass, *args, &block)
76
- add(klass, :args => args, :block => block, :as => name)
77
- end
78
-
79
- def pool(klass, options = {})
80
- options[:method] = 'pool_link'
81
- add(klass, options)
82
- end
83
-
84
- def add(klass, options)
85
- member = Member.new(@registry, klass, options)
86
- @members << member
87
- member.actor
88
- end
89
-
90
- def actors
91
- @members.map(&:actor)
92
- end
93
-
94
- def [](actor_name)
95
- @registry[actor_name]
96
- end
97
-
98
- # Restart a crashed actor
99
- def restart_actor(actor, reason)
100
- member = @members.find do |_member|
101
- _member.actor == actor
102
- end
103
- raise "a group member went missing. This shouldn't be!" unless member
104
-
105
- if reason
106
- member.restart
107
- else
108
- member.cleanup
109
- @members.delete(member)
110
- end
111
- end
112
-
113
- # A member of the group
114
- class Member
115
- def initialize(registry, klass, options = {})
116
- @registry = registry
117
- @klass = klass
118
-
119
- # Stringify keys :/
120
- options = options.inject({}) { |h,(k,v)| h[k.to_s] = v; h }
121
-
122
- @name = options['as']
123
- @block = options['block']
124
- @args = options['args'] ? Array(options['args']) : []
125
- @method = options['method'] || 'new_link'
126
- @pool = @method == 'pool_link'
127
- @pool_size = options['size'] if @pool
128
-
129
- start
130
- end
131
- attr_reader :name, :actor
132
-
133
- def start
134
- # when it is a pool, then we don't splat the args
135
- # and we need to extract the pool size if set
136
- if @pool
137
- options = {:args => @args}
138
- options[:size] = @pool_size if @pool_size
139
- @args = [options]
140
- end
141
- @actor = @klass.send(@method, *@args, &@block)
142
- @registry[@name] = @actor if @name
143
- end
144
-
145
- def restart
146
- @actor = nil
147
- cleanup
148
-
149
- start
150
- end
151
-
152
- def terminate
153
- cleanup
154
- @actor.terminate if @actor
155
- rescue DeadActorError
156
- end
157
-
158
- def cleanup
159
- @registry.delete(@name) if @name
160
- end
161
- end
162
-
163
- private
164
-
165
- def finalize
166
- @members.reverse_each(&:terminate) if @members
167
- end
168
- end
169
- end
@@ -1,22 +0,0 @@
1
- module Celluloid
2
- # Supervisors are actors that watch over other actors and restart them if
3
- # they crash
4
- class Supervisor
5
- class << self
6
- # Define the root of the supervision tree
7
- attr_accessor :root
8
-
9
- def supervise(klass, *args, &block)
10
- SupervisionGroup.new do |group|
11
- group.supervise klass, *args, &block
12
- end
13
- end
14
-
15
- def supervise_as(name, klass, *args, &block)
16
- SupervisionGroup.new do |group|
17
- group.supervise_as name, klass, *args, &block
18
- end
19
- end
20
- end
21
- end
22
- end
@@ -1,49 +0,0 @@
1
- require 'set'
2
- require 'forwardable'
3
-
4
- module Celluloid
5
- if defined? JRUBY_VERSION
6
- require 'jruby/synchronized'
7
-
8
- class TaskSet
9
- extend Forwardable
10
- include JRuby::Synchronized
11
-
12
- def_delegators :@tasks, :<<, :delete, :first, :empty?, :to_a
13
-
14
- def initialize
15
- @tasks = Set.new
16
- end
17
- end
18
- elsif defined? Rubinius
19
- class TaskSet
20
- def initialize
21
- @tasks = Set.new
22
- end
23
-
24
- def <<(task)
25
- Rubinius.synchronize(self) { @tasks << task }
26
- end
27
-
28
- def delete(task)
29
- Rubinius.synchronize(self) { @tasks.delete task }
30
- end
31
-
32
- def first
33
- Rubinius.synchronize(self) { @tasks.first }
34
- end
35
-
36
- def empty?
37
- Rubinius.synchronize(self) { @tasks.empty? }
38
- end
39
-
40
- def to_a
41
- Rubinius.synchronize(self) { @tasks.to_a }
42
- end
43
- end
44
- else
45
- # Assume we're on MRI, where we have the GIL. But what about IronRuby?
46
- # Or MacRuby. Do people care? This will break Celluloid::StackDumps
47
- TaskSet = Set
48
- end
49
- end
@@ -1,43 +0,0 @@
1
- module Celluloid
2
- class FiberStackError < Celluloid::Error; end
3
-
4
- # Tasks with a Fiber backend
5
- class TaskFiber < Task
6
-
7
- def create
8
- queue = Thread.current[:celluloid_queue]
9
- actor_system = Thread.current[:celluloid_actor_system]
10
- @fiber = Fiber.new do
11
- # FIXME: cannot use the writer as specs run inside normal Threads
12
- Thread.current[:celluloid_role] = :actor
13
- Thread.current[:celluloid_queue] = queue
14
- Thread.current[:celluloid_actor_system] = actor_system
15
- yield
16
- end
17
- end
18
-
19
- def signal
20
- Fiber.yield
21
- end
22
-
23
- # Resume a suspended task, giving it a value to return if needed
24
- def deliver(value)
25
- @fiber.resume value
26
- rescue SystemStackError => ex
27
- raise FiberStackError, "#{ex} (please see https://github.com/celluloid/celluloid/wiki/Fiber-stack-errors)"
28
- rescue FiberError => ex
29
- raise DeadTaskError, "cannot resume a dead task (#{ex})"
30
- end
31
-
32
- # Terminate this task
33
- def terminate
34
- super
35
- rescue FiberError
36
- # If we're getting this the task should already be dead
37
- end
38
-
39
- def backtrace
40
- ["#{self.class} backtrace unavailable. Please try `Celluloid.task_class = Celluloid::TaskThread` if you need backtraces here."]
41
- end
42
- end
43
- end
@@ -1,53 +0,0 @@
1
- module Celluloid
2
- # Tasks with a Thread backend
3
- class TaskThread < Task
4
- # Run the given block within a task
5
- def initialize(type, meta)
6
- @resume_queue = Queue.new
7
- @exception_queue = Queue.new
8
- @yield_mutex = Mutex.new
9
- @yield_cond = ConditionVariable.new
10
-
11
- super
12
- end
13
-
14
- def create
15
- # TODO: move this to ActorSystem#get_thread (ThreadHandle inside InternalPool)
16
- @thread = ThreadHandle.new(Thread.current[:celluloid_actor_system], :task) do
17
- begin
18
- ex = @resume_queue.pop
19
- raise ex if ex.is_a?(Task::TerminatedError)
20
-
21
- yield
22
- rescue Exception => ex
23
- @exception_queue << ex
24
- ensure
25
- @yield_cond.signal
26
- end
27
- end
28
- end
29
-
30
- def signal
31
- @yield_cond.signal
32
- @resume_queue.pop
33
- end
34
-
35
- def deliver(value)
36
- raise DeadTaskError, "cannot resume a dead task" unless @thread.alive?
37
-
38
- @yield_mutex.synchronize do
39
- @resume_queue.push(value)
40
- @yield_cond.wait(@yield_mutex)
41
- while @exception_queue.size > 0
42
- raise @exception_queue.pop
43
- end
44
- end
45
- rescue ThreadError
46
- raise DeadTaskError, "cannot resume a dead task"
47
- end
48
-
49
- def backtrace
50
- @thread.backtrace
51
- end
52
- end
53
- end