fibril 0.0.1 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +20 -3
  3. data/examples/example_async.rb +7 -21
  4. data/examples/example_coop_multi_tasking.rb +33 -0
  5. data/examples/example_enum_tick.rb +9 -0
  6. data/examples/example_execution_order.rb +14 -0
  7. data/examples/example_future_async_await.rb +22 -0
  8. data/examples/example_future_sync_await.rb +23 -0
  9. data/examples/example_guard.rb +9 -7
  10. data/examples/example_guard2.rb +19 -0
  11. data/examples/example_guard3.rb +46 -0
  12. data/examples/example_http.rb +60 -0
  13. data/examples/example_multiple_loops.rb +78 -0
  14. data/examples/example_redis.rb +49 -0
  15. data/examples/{example_loop.rb → example_tick.rb} +1 -1
  16. data/examples/example_tick2.rb +22 -0
  17. data/examples/example_timeout.rb +9 -0
  18. data/fibril.gemspec +1 -1
  19. data/fibril.todo +22 -16
  20. data/lib/fibril.rb +2 -240
  21. data/lib/fibril/async_proxy.rb +28 -0
  22. data/lib/fibril/basic_object.rb +20 -0
  23. data/lib/fibril/control.rb +20 -0
  24. data/lib/fibril/core.rb +299 -0
  25. data/lib/fibril/extras.rb +8 -0
  26. data/lib/fibril/fasync_proxy.rb +36 -0
  27. data/lib/fibril/ffuture.rb +24 -0
  28. data/lib/fibril/fibril_proxy.rb +31 -0
  29. data/lib/fibril/forked_non_blocking_io_wrapper.rb +51 -0
  30. data/lib/fibril/future.rb +24 -0
  31. data/lib/fibril/guard.rb +123 -0
  32. data/lib/fibril/loop.rb +36 -6
  33. data/lib/fibril/non_blocking_io_wrapper.rb +60 -0
  34. data/lib/fibril/tick_proxy.rb +30 -0
  35. data/lib/fibril/version.rb +1 -1
  36. metadata +43 -8
  37. data/examples/example_1.rb +0 -71
  38. data/examples/example_2.rb +0 -80
  39. data/examples/example_3.rb +0 -82
  40. data/examples/example_promise.rb +0 -23
@@ -1,4 +1,4 @@
1
- require_relative "../lib/fibril/loop"
1
+ require 'fibril/loop'
2
2
 
3
3
  fibril{
4
4
  puts 1
@@ -0,0 +1,22 @@
1
+ require 'fibril/loop'
2
+
3
+ fibril{
4
+ [1,3,5].each do |i|
5
+ print i.to_s+?:
6
+ tick
7
+ end
8
+ }
9
+
10
+ fibril{
11
+ [2,4,6].each do |i|
12
+ print i.to_s
13
+ tick
14
+ end
15
+ }
16
+
17
+ fibril{
18
+ 3.times{
19
+ print "\n"
20
+ tick
21
+ }
22
+ }
@@ -0,0 +1,9 @@
1
+ require 'fibril/loop'
2
+
3
+ def heartbeat
4
+ puts "•"
5
+ end
6
+
7
+ fibril.puts('ping').loop(20, 0.5)
8
+ fibril{ puts "pong" }.loop(20, 0.5)
9
+ fibril.heartbeat.loop(3, 2)
data/fibril.gemspec CHANGED
@@ -21,11 +21,11 @@ Gem::Specification.new do |spec|
21
21
  spec.bindir = "exe"
22
22
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
23
  spec.require_paths = ["lib"]
24
-
25
24
  spec.add_development_dependency "bundler", "~> 1.10"
26
25
  spec.add_development_dependency "rake", "~> 10.0"
27
26
  spec.add_development_dependency "byebug", "~> 8.2.1"
28
27
  spec.add_development_dependency "pry-byebug", "~> 3.3.0"
29
28
  spec.add_development_dependency 'pry', '~> 0.10.2', '>= 0.10.0'
29
+ spec.add_development_dependency 'redis'
30
30
  spec.add_development_dependency "minitest"
31
31
  end
data/fibril.todo CHANGED
@@ -6,34 +6,40 @@
6
6
  ✔ Create event loop import @done (16-02-18 21:15)
7
7
  ✔ Create continous and counting loop construct @done (16-02-18 21:15)
8
8
  ✔ Create async object @done (16-02-19 07:35)
9
- ✔ Create promises @done (16-02-19 07:49)
9
+ ✔ Create futures @done (16-02-19 07:49)
10
10
  ✔ Turn into gem @done (16-02-19 07:49)
11
+ ✔ Create Fibril proxy @done (16-02-19 19:18)
12
+ ✔ Provide optional timeout value to Fibril looping @done (16-02-23 08:58)
13
+ ✔ Create smarter loop importer. Finds import statement and only evals everything past itself @done (16-02-23 09:01)
14
+ ✔ Add fasync and ffuture @done (16-02-27 11:19)
15
+ ✘ Add keywords syntax highlighting in Sublime @cancelled (16-03-01 18:34)
16
+ ✘ future, async, fibril, await, await_all @cancelled (16-03-01 18:34)
11
17
 
12
- ☐ Add keywords syntax highlighting in Sublime
13
- - promise, async, fibril, await, await_all
14
18
 
15
- Create smarter loop importer. Finds import statement and only evals everything past itself
16
-
17
- Write performance tests
18
- Use efficient ring buffer as queue and block when full
19
+ Use efficient ring buffer as queue and block when full @cancelled (16-03-01 09:05)
20
+ ✔ Split imports into core, and full functionality to prevent conflicts @done (16-03-01 18:41)
21
+ Enumerable ticking with fibril @done (16-03-21 09:03)
22
+ Enumerable concurrency, every nth @done (16-03-21 09:03)
23
+ ☐ Add meaningful errors when common mistakes are made
24
+ ☐ Add option to peek and consume in non blocking IO wrapper
19
25
 
20
26
  ☐ Examples:
21
27
 
22
- Weave with tick
23
- Weave with async IO
24
- Weave external await
25
- Weave with internal await
26
- Promises
28
+ Fibril with tick @done (16-03-21 09:03)
29
+ Fibril with async IO @done (16-03-21 09:03)
30
+ Fibril external await @done (16-03-21 09:03)
31
+ Fibril with internal await @done (16-03-21 09:03)
32
+ Fibril wrapping redis @done (16-03-21 09:03)
33
+ ✔ Futures @done (16-03-21 09:03)
27
34
 
28
35
  ☐ Disk read + write
29
36
  ☐ Database queries using AR
30
- ☐ Network IO
37
+ ☐ Network IO = Https
31
38
  ☐ Asyncifying common functions
32
39
  ☐ Asyncifying class methods
33
40
  ☐ Asyncifying instance methods
34
41
  ☐ Asyncifying module methods
35
42
  ☐ Comparison to thread
36
43
  ☐ Comparison to event machine
37
-
38
- Rename to Weave
39
- ☐ Refactor
44
+ ☐ Write performance tests
45
+ Write tests
data/lib/fibril.rb CHANGED
@@ -1,241 +1,3 @@
1
1
  require "fibril/version"
2
- require 'ostruct'
3
-
4
-
5
- class Fibril < Fiber
6
- class << self
7
- attr_accessor :running, :stopped, :queue, :task_count, :guards, :current, :id_seq
8
- end
9
-
10
- self.queue = []
11
- self.guards = Hash.new{|h,k| }
12
- self.id_seq = 0
13
-
14
- attr_accessor :fiber, :guards, :block, :id
15
-
16
- def self.log(msg)
17
- # puts msg
18
- end
19
-
20
- def variables
21
- @@variables ||= OpenStruct.new
22
- end
23
-
24
- def initialize(&blk)
25
- self.id = Fibril.id_seq += 1
26
- self.block = blk
27
- self.guards = []
28
- define_singleton_method :execute_fibril, self.block
29
- super(&method(:execute))
30
- Fibril.queue << self
31
- end
32
-
33
- def reset(guard)
34
- copy = Fibril.new(&self.block)
35
- copy.guards << guard
36
- return copy
37
- end
38
-
39
- def execute
40
- Fibril.task_count += 1
41
- execute_fibril
42
- self.guards.each(&:visit)
43
- Fibril.task_count -= 1
44
- Fibril.log "Ending #{id}"
45
- end
46
-
47
- def tick
48
- Fibril.enqueue self
49
- self.yield
50
- end
51
-
52
- def self.enqueue(fibril)
53
- Fibril.log "Enqueing fibril #{fibril.id}"
54
- Fibril.queue << fibril
55
- end
56
-
57
- def yield
58
- Fibril.log "Yielding #{id}"
59
- Fiber.yield
60
- end
61
-
62
- def current
63
- self
64
- end
65
-
66
- def self.deplete_guard(guard)
67
- return unless waiters = guards[guard.id]
68
- switches = waiters[:switches]
69
- switches[guard.id] = true
70
- waiters[:block][] if switches.values.all?
71
- end
72
-
73
- def await(*guards, &block)
74
-
75
- if guards.length == 1 && guards[0].kind_of?(Promise)
76
- return await_promise(guards[0])
77
- end
78
-
79
- await_block = {
80
- switches: Hash[guards.map{|guard| [guard.id, false]}],
81
- block: block
82
- }
83
- guards.each do |guard|
84
- Fibril.guards[guard.id] = await_block
85
- end
86
- end
87
-
88
- def promise(&blk)
89
- return Promise.new(&blk)
90
- end
91
-
92
- def await_promise(promise)
93
- promise.await
94
- end
95
-
96
- def await_all(*promises)
97
- promises.map(&:await)
98
- end
99
-
100
- def self.stop
101
- Fibril do
102
- Fibril.stopped = true
103
- end
104
- end
105
-
106
- def resume
107
- Fibril.current = self
108
- Fibril.log "Resuming #{id}"
109
- super
110
- end
111
-
112
- def self.start
113
- self.task_count = 0
114
- self.stopped = false
115
- self.running = true
116
- if queue.any?
117
- queue.shift.resume
118
- self.loop if queue.any? || Fibril.task_count > 0
119
- end
120
- self.running = false
121
- end
122
-
123
- def self.loop
124
- Fibril.log "Starting loop inside #{Fibril.current}"
125
- while ((Fibril.task_count > 0 || queue.any?) && !Fibril.stopped)
126
- Fibril.queue.shift.resume if Fibril.queue.any?
127
- end
128
- end
129
-
130
- def Guard(i, fibril)
131
- return Guard.new(i, fibril)
132
- end
133
-
134
- class AsyncProxy
135
- attr_accessor :target
136
-
137
- def initialize(target)
138
- self.target = target
139
- end
140
-
141
- def method_missing(name, *args, &block)
142
- waiting = Fibril.current
143
- Thread.new do
144
- target.send(name, *args, &block).tap{ Fibril.enqueue waiting }
145
- end.tap{
146
- Fibril.current.yield
147
- }.value
148
- end
149
- end
150
-
151
-
152
- class Guard
153
- class << self
154
- attr_accessor :guard_seq
155
- end
156
-
157
- attr_accessor :fibril, :id, :break_condition, :depleted
158
-
159
- self.guard_seq = 0
160
-
161
- def self.create(fibril, counter=1)
162
- self.guard_seq += 1
163
- guard = Fibril::Guard.new(self.guard_seq, counter, fibril)
164
- fibril.guards << guard
165
- return guard
166
- end
167
-
168
- def await
169
- Fibril.current.tick while !self.depleted
170
- end
171
-
172
- def initialize(id, counter, fibril)
173
- self.id = id
174
- self.fibril = fibril
175
- self.break_condition = 1
176
- end
177
-
178
- def visit
179
- case self.break_condition
180
- when Proc
181
- if self.break_condition[]
182
- self.depleted = true
183
- Fibril.deplete_guard(self)
184
- else
185
- self.fibril = self.fibril.reset(self)
186
- end
187
- else
188
- self.break_condition -= 1
189
- if self.break_condition.zero?
190
- self.depleted = true
191
- Fibril.deplete_guard(self)
192
- else
193
- self.fibril = self.fibril.reset(self)
194
- end
195
- end
196
- end
197
-
198
- def loop(break_condition=-1, &blck)
199
- self.break_condition = block_given? ? blck : break_condition
200
- self
201
- end
202
-
203
- def while(&blk)
204
- loop{ !blk[] }
205
- end
206
-
207
- def until(&blk)
208
- loop{ blk[] }
209
- end
210
- end
211
-
212
- class Promise
213
- attr_accessor :promise_thread
214
- def initialize(&blk)
215
- self.promise_thread = Thread.new(&blk)
216
- end
217
-
218
- def await
219
- self.promise_thread.join.value
220
- end
221
- end
222
-
223
- end
224
-
225
- class ::BasicObject
226
- def async
227
- @async_proxy ||= ::Fibril::AsyncProxy.new(self)
228
- end
229
- end
230
-
231
-
232
- def Fibril(&block)
233
- fibril = Fibril.new(&block).tap do |t|
234
- Fibril.start unless Fibril.running
235
- end
236
- guard = Fibril::Guard.create(fibril)
237
- end
238
-
239
-
240
-
241
- Kernel.send :alias_method, :fibril, :Fibril
2
+ require "fibril/core"
3
+ require "fibril/extras"
@@ -0,0 +1,28 @@
1
+ class Fibril::AsyncProxy
2
+ attr_accessor :target
3
+
4
+ def initialize(target)
5
+ self.target = target
6
+ end
7
+
8
+ ##
9
+ # Execute target method on proxied target. Enqueue the current fibril
10
+ # to be resumed as soon as async task is finished
11
+ ##
12
+ def method_missing(name, *_args, &_block)
13
+ define_singleton_method(name) do |*args, &block|
14
+ waiting = Fibril.current
15
+ Thread.new do
16
+ begin
17
+ target.send(name, *args, &block).tap{ Fibril.enqueue waiting }
18
+ rescue Exception => e
19
+ puts "Exception! #{e}"
20
+ Fibril.enqueue waiting
21
+ end
22
+ end.tap do
23
+ Fibril.current.yield
24
+ end.value
25
+ end
26
+ send(name, *_args, &_block)
27
+ end
28
+ end
@@ -0,0 +1,20 @@
1
+ class ::BasicObject
2
+ ##
3
+ # Expose the async, fasync and fibril methods on all objects
4
+ ##
5
+
6
+ ##
7
+ # An asynchronous proxy. Executes any methods invoked via proxy on target in a separate thread
8
+ ##
9
+ def async
10
+ @async_proxy ||= ::Fibril::AsyncProxy.new(self)
11
+ end
12
+
13
+ ##
14
+ # An asynchronous proxy. Executes any methods invoked via proxy on target in a separate fork
15
+ ##
16
+ def fasync
17
+ @fasync_proxy ||= ::Fibril::FAsyncProxy.new(self)
18
+ end
19
+
20
+ end
@@ -0,0 +1,20 @@
1
+ ##
2
+ # Expose thefuture and ffuture top level functions
3
+ ##
4
+
5
+ ##
6
+ # Create a new future
7
+ #
8
+
9
+ class ::BasicObject
10
+ def future(&blk)
11
+ return ::Fibril::Future.new(&blk)
12
+ end
13
+
14
+ ##
15
+ # Create a new forked future
16
+ ##
17
+ def ffuture(&blk)
18
+ return ::Fibril::FFuture.new(&blk)
19
+ end
20
+ end
@@ -0,0 +1,299 @@
1
+ require 'fibril/guard'
2
+ require 'fibril/future'
3
+ require 'fibril/fibril_proxy'
4
+ require 'fibril/tick_proxy'
5
+ require 'ostruct'
6
+
7
+ class Fibril < Fiber
8
+ class << self
9
+ attr_accessor :running, :stopped, :queue, :task_count, :guards, :current, :id_seq, :loop_thread
10
+ end
11
+
12
+ self.queue = []
13
+ self.guards = Hash.new{|h,k| h[k] = [] }
14
+ self.id_seq = 0
15
+ self.task_count = 0
16
+
17
+ attr_accessor :fiber, :guards, :block, :id
18
+
19
+ def self.log(msg)
20
+ # puts msg
21
+ end
22
+
23
+ def guard
24
+ Fibril.guard
25
+ end
26
+
27
+ def self.guard
28
+ @@guard ||= OpenStruct.new
29
+ end
30
+
31
+ def variables
32
+ Fibril.variables
33
+ end
34
+
35
+ def self.variables
36
+ @@variables ||= OpenStruct.new
37
+ end
38
+
39
+ def initialize(&blk)
40
+ self.id = Fibril.id_seq += 1
41
+ self.block = blk
42
+ self.guards = []
43
+ define_singleton_method :execute_fibril, self.block
44
+ if Fibril.running
45
+ super(&method(:execute))
46
+ Fibril.enqueue self
47
+ else
48
+ Fibril.task_count = 0
49
+ Fibril.stopped = false
50
+ Fibril.running = true
51
+ super(&method(:execute))
52
+ Fibril.enqueue self
53
+ Fibril.start
54
+ end
55
+ end
56
+
57
+ def reset(guard)
58
+ copy = Fibril.new(&self.block)
59
+ copy.guards << guard
60
+ return copy
61
+ end
62
+
63
+ def execute
64
+ Fibril.task_count += 1
65
+ exception = nil
66
+ result = begin
67
+ execute_fibril
68
+ rescue Exception => e
69
+ exception = e
70
+ end
71
+ self.guards.each do |guard|
72
+ guard.visit(result)
73
+ end
74
+ Fibril.task_count -= 1
75
+ Fibril.log "Ending #{id}"
76
+ raise exception if exception
77
+ end
78
+
79
+ def tick
80
+ if Thread.current != Fibril.loop_thread
81
+ Fibril.log "Current thread is #{Thread.current.object_id}"
82
+ Fibril.log "Fibril thread is #{Fibril.loop_thread.object_id}"
83
+ Fibril.log "WARN: Cannot tick inside async code outside of main loop thread. This will be a noop"
84
+ elsif !Fibril.queue.empty?
85
+ Fibril.enqueue self
86
+ self.yield
87
+ end
88
+ end
89
+
90
+ def enqueue
91
+ Fibril.enqueue(self)
92
+ end
93
+
94
+ def self.enqueue(fibril)
95
+ Fibril.log "Enqueing fibril #{fibril.id}"
96
+ Fibril.queue << fibril
97
+ end
98
+
99
+ def yield
100
+ Fibril.log "Yielding #{id}"
101
+ yield(self) if block_given?
102
+ Fiber.yield
103
+ end
104
+
105
+ def current
106
+ self
107
+ end
108
+
109
+ def self.deplete_guard(guard, result)
110
+ return unless waiter_list = guards[guard.id]
111
+ waiter_list.each do |waiters|
112
+ switches = waiters[:switches]
113
+ switches[guard.id] = true
114
+ if waiters.has_key?(:to_fulfill)
115
+ Fibril.enqueue waiters[:to_fulfill] if switches.values.all?
116
+ waiters[:result] ||= []
117
+ waiters[:result] << result
118
+ else
119
+ waiters[:result] ||= []
120
+ waiters[:result] << result
121
+ waiters[:block][*sort_results(waiters[:result], waiters[:guards])] if waiters[:block] && switches.values.all?
122
+ end
123
+ end
124
+ end
125
+
126
+ def await_fibril(guards)
127
+ singular = guards.one?
128
+ return singular ? guards[0].result : guards.map(&:result) if guards.all?(&:result?)
129
+ await_block = {
130
+ switches: Hash[guards.map{|guard| [guard.id, false]}],
131
+ to_fulfill: Fibril.current
132
+ }
133
+ guards.each do |guard|
134
+ Fibril.guards[guard.id] << await_block
135
+ end
136
+ self.yield
137
+ return singular ? await_block[:result][0] : Fibril.sort_results(await_block[:result], guards)
138
+ end
139
+
140
+ def self.sort_results(results, guards)
141
+ by_complete_order = guards.sort_by(&:depleted_at)
142
+ results.zip(by_complete_order).sort do |(_, guard_a), (_, guard_b)|
143
+ guards.index(guard_a) <=> guards.index(guard_b)
144
+ end.map(&:first)
145
+ end
146
+
147
+ def await(*guards, &block)
148
+ guards.map!{|guard| guard.kind_of?(Symbol) ? Fibril.guard.send(guard) : guard}
149
+ raise "Invalid guard given #{guards}" unless guards.all?{|g| g.kind_of?(Guard) || g.kind_of?(Future)}
150
+ if block_given?
151
+ return block[*guards.map(&:result)] if guards.all?(&:result?)
152
+ await_block = {
153
+ switches: Hash[guards.map{|guard| [guard.id, false]}],
154
+ block: block,
155
+ guards: guards
156
+ }
157
+ guards.each do |guard|
158
+ Fibril.guards[guard.id] << await_block
159
+ end
160
+ else
161
+ guard = guards.first
162
+ guard.kind_of?(Future) ? await_future(guard) : await_fibril(guards)
163
+ end
164
+ end
165
+
166
+ def await_future(future)
167
+ tick while future.alive?
168
+ future.await
169
+ end
170
+
171
+ def await_all(*futures)
172
+ futures.map(&:await)
173
+ end
174
+
175
+ def self.stop
176
+ Fibril do
177
+ Fibril.stopped = true
178
+ end
179
+ end
180
+
181
+ def resume
182
+ Fibril.current = self
183
+ Fibril.log "Resuming #{id}"
184
+ super
185
+ end
186
+
187
+ def self.start
188
+ self.start_loop if !queue.empty?
189
+ self.running = false
190
+ end
191
+
192
+ def self.profile(test)
193
+ starts = Time.now
194
+ result = yield
195
+ ends = Time.now
196
+ Fibril.log "#{test} took #{ends - starts}"
197
+ return result
198
+ end
199
+
200
+ def self.start_loop
201
+ Fibril.log "Starting loop inside #{Fibril.current}"
202
+ Fibril.loop_thread = Thread.current
203
+ while pending_tasks?
204
+ Fibril.current = nil
205
+ Fibril.queue.shift.resume while !queue.empty?
206
+ end
207
+ end
208
+
209
+ def self.pending_tasks?
210
+ ((@task_count > 0 || !@queue.empty?) && !@stopped)
211
+ end
212
+
213
+ def Guard(i, fibril)
214
+ return Guard.new(i, fibril)
215
+ end
216
+ end
217
+
218
+
219
+ ##
220
+ # Create a new fibril
221
+ ##
222
+
223
+ def Fibril(*guard_names, &block)
224
+ fibril = Fibril.new(&block)
225
+ return fibril unless Fibril.running
226
+ Fibril::Guard.create(fibril).tap do |guard|
227
+ guard_names.each do |name|
228
+ Fibril.guard.send("#{name}=", guard)
229
+ end
230
+ end
231
+ end
232
+
233
+ class Enumerator
234
+ def fibril(*guard_names, &block)
235
+ context = self
236
+ Kernel.fibril(*guard_names){
237
+ e = Enumerator.new do |enum|
238
+ context.each do |*elm|
239
+ result = enum.yield(*elm)
240
+ tick
241
+ result
242
+ end
243
+ end
244
+ e.each(&block)
245
+ }
246
+ end
247
+
248
+ def n_fibrils(*guard_names, n, &block)
249
+ context = self
250
+
251
+ guards = n.times.map do |i|
252
+ Kernel.fibril{
253
+ e = Enumerator.new do |enum|
254
+ context.each.with_index do |elm, index|
255
+ next unless ((index - i) % n).zero?
256
+ result = enum.yield(*elm)
257
+ tick
258
+ result
259
+ end
260
+ end
261
+ e.each(&block)
262
+ }
263
+ end
264
+ Kernel.fibril(*guard_names){
265
+ all_results = await(*guards)
266
+ length = all_results.max{|x| x.length}.length
267
+ length.times.map do |i|
268
+ all_results.find{|list|
269
+ list[i] != nil
270
+ }[i]
271
+ end
272
+ }
273
+ end
274
+ end
275
+
276
+ class ::BasicObject
277
+ ##
278
+ # This method has two methods of use.
279
+ # Either
280
+ # A. call with block to create a new fibril
281
+ # B. call without block to create a fibril proxy. Any methods invoked on a proxy are executed on the target from
282
+ # within a new Fibril
283
+ ##
284
+ def fibril(*guard_names, &block)
285
+ if block_given?
286
+ Fibril(*guard_names, &block)
287
+ else
288
+ ::Fibril::FibrilProxy.new(self, *guard_names)
289
+ end
290
+ end
291
+
292
+ def tick(*guard_names, **args)
293
+ ::Fibril::TickProxy.new(self, *guard_names, **args)
294
+ end
295
+
296
+ def await(*args, &block)
297
+ ::Fibril.current.await(*args, &block)
298
+ end
299
+ end