polyphony 0.29 → 0.30

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/CHANGELOG.md +14 -0
  4. data/Gemfile.lock +1 -1
  5. data/TODO.md +15 -10
  6. data/docs/getting-started/tutorial.md +3 -3
  7. data/docs/index.md +2 -3
  8. data/docs/{technical-overview → main-concepts}/concurrency.md +62 -15
  9. data/docs/{technical-overview → main-concepts}/design-principles.md +21 -8
  10. data/docs/{technical-overview → main-concepts}/exception-handling.md +80 -38
  11. data/docs/{technical-overview → main-concepts}/extending.md +4 -3
  12. data/docs/{technical-overview → main-concepts}/fiber-scheduling.md +3 -3
  13. data/docs/{technical-overview.md → main-concepts.md} +2 -2
  14. data/examples/core/xx-at_exit.rb +29 -0
  15. data/examples/core/xx-fork-terminate.rb +27 -0
  16. data/examples/core/xx-pingpong.rb +18 -0
  17. data/examples/core/xx-stop.rb +20 -0
  18. data/ext/gyro/async.c +1 -1
  19. data/ext/gyro/extconf.rb +0 -3
  20. data/ext/gyro/gyro.c +7 -8
  21. data/ext/gyro/gyro.h +2 -0
  22. data/ext/gyro/queue.c +6 -6
  23. data/ext/gyro/selector.c +32 -1
  24. data/ext/gyro/thread.c +55 -9
  25. data/ext/gyro/timer.c +1 -0
  26. data/lib/polyphony/core/exceptions.rb +4 -1
  27. data/lib/polyphony/core/global_api.rb +1 -6
  28. data/lib/polyphony/core/thread_pool.rb +3 -3
  29. data/lib/polyphony/extensions/core.rb +7 -1
  30. data/lib/polyphony/extensions/fiber.rb +159 -72
  31. data/lib/polyphony/extensions/io.rb +2 -4
  32. data/lib/polyphony/extensions/openssl.rb +0 -17
  33. data/lib/polyphony/extensions/thread.rb +46 -22
  34. data/lib/polyphony/version.rb +1 -1
  35. data/lib/polyphony.rb +20 -18
  36. data/test/coverage.rb +1 -1
  37. data/test/helper.rb +7 -3
  38. data/test/test_fiber.rb +285 -72
  39. data/test/test_global_api.rb +7 -52
  40. data/test/test_io.rb +8 -0
  41. data/test/test_signal.rb +1 -0
  42. data/test/test_thread.rb +76 -56
  43. data/test/test_thread_pool.rb +27 -5
  44. data/test/test_throttler.rb +1 -0
  45. metadata +12 -12
  46. data/lib/polyphony/core/supervisor.rb +0 -114
  47. data/lib/polyphony/line_reader.rb +0 -82
  48. data/test/test_gyro.rb +0 -25
  49. data/test/test_supervisor.rb +0 -180
data/test/test_fiber.rb CHANGED
@@ -5,7 +5,7 @@ require_relative 'helper'
5
5
  class FiberTest < MiniTest::Test
6
6
  def test_spin_initial_state
7
7
  result = nil
8
- f = Fiber.spin { result = 42 }
8
+ f = Fiber.current.spin { result = 42 }
9
9
  assert_nil result
10
10
  f.await
11
11
  assert_equal 42, result
@@ -15,7 +15,7 @@ class FiberTest < MiniTest::Test
15
15
 
16
16
  def test_await
17
17
  result = nil
18
- f = Fiber.spin do
18
+ f = Fiber.current.spin do
19
19
  snooze
20
20
  result = 42
21
21
  end
@@ -25,6 +25,49 @@ class FiberTest < MiniTest::Test
25
25
  f&.stop
26
26
  end
27
27
 
28
+ def test_await_from_multiple_fibers
29
+ buffer = []
30
+ f1 = spin {
31
+ sleep 0.02
32
+ buffer << :foo
33
+ }
34
+ f2 = spin {
35
+ f1.await
36
+ buffer << :bar
37
+ }
38
+ f3 = spin {
39
+ f1.await
40
+ buffer << :baz
41
+ }
42
+ Fiber.await(f2, f3)
43
+ assert_equal [:foo, :bar, :baz], buffer
44
+ assert_equal 0, Fiber.current.children.size
45
+ end
46
+
47
+ def test_await_from_multiple_fibers_with_interruption
48
+ buffer = []
49
+ f1 = spin {
50
+ sleep 0.02
51
+ buffer << :foo
52
+ }
53
+ f2 = spin {
54
+ f1.await
55
+ buffer << :bar
56
+ }
57
+ f3 = spin {
58
+ f1.await
59
+ buffer << :baz
60
+ }
61
+ snooze
62
+ f2.stop
63
+ f3.stop
64
+ snooze
65
+ f1.stop
66
+
67
+ snooze
68
+ assert_equal [], Fiber.current.children
69
+ end
70
+
28
71
  def test_schedule
29
72
  values = []
30
73
  fibers = (0..2).map { |i| spin { suspend; values << i } }
@@ -74,7 +117,7 @@ class FiberTest < MiniTest::Test
74
117
  async.signal!(:foo)
75
118
  end
76
119
 
77
- result = move_on_after(0.05) { async.await }
120
+ result = move_on_after(1) { async.await }
78
121
 
79
122
  assert_equal :foo, result
80
123
  ensure
@@ -86,12 +129,12 @@ class FiberTest < MiniTest::Test
86
129
  Fiber.current.tag = :foo
87
130
  assert_equal :foo, Fiber.current.tag
88
131
 
89
- f = Fiber.spin(:bar) { }
132
+ f = Fiber.current.spin(:bar) { }
90
133
  assert_equal :bar, f.tag
91
134
  end
92
135
 
93
136
  def test_await_return_value
94
- f = Fiber.spin { %i[foo bar] }
137
+ f = Fiber.current.spin { %i[foo bar] }
95
138
  assert_equal %i[foo bar], f.await
96
139
  ensure
97
140
  f&.stop
@@ -99,7 +142,7 @@ class FiberTest < MiniTest::Test
99
142
 
100
143
  def test_await_with_error
101
144
  result = nil
102
- f = Fiber.spin { raise 'foo' }
145
+ f = Fiber.current.spin { raise 'foo' }
103
146
  begin
104
147
  result = f.await
105
148
  rescue Exception => e
@@ -114,7 +157,7 @@ class FiberTest < MiniTest::Test
114
157
  def test_raise
115
158
  result = []
116
159
  error = nil
117
- f = Fiber.spin do
160
+ f = Fiber.current.spin do
118
161
  result << 1
119
162
  2.times { snooze }
120
163
  result << 2
@@ -139,7 +182,7 @@ class FiberTest < MiniTest::Test
139
182
  def test_raise_with_error_class
140
183
  result = []
141
184
  error = nil
142
- f = Fiber.spin do
185
+ f = Fiber.current.spin do
143
186
  result << 1
144
187
  2.times { snooze }
145
188
  result << 2
@@ -161,7 +204,7 @@ class FiberTest < MiniTest::Test
161
204
  def test_raise_with_error_class_and_message
162
205
  result = []
163
206
  error = nil
164
- f = Fiber.spin do
207
+ f = Fiber.current.spin do
165
208
  result << 1
166
209
  2.times { snooze }
167
210
  result << 2
@@ -184,7 +227,7 @@ class FiberTest < MiniTest::Test
184
227
  def test_raise_with_message
185
228
  result = []
186
229
  error = nil
187
- f = Fiber.spin do
230
+ f = Fiber.current.spin do
188
231
  result << 1
189
232
  2.times { snooze }
190
233
  result << 2
@@ -207,7 +250,7 @@ class FiberTest < MiniTest::Test
207
250
  def test_raise_with_exception
208
251
  result = []
209
252
  error = nil
210
- f = Fiber.spin do
253
+ f = Fiber.current.spin do
211
254
  result << 1
212
255
  2.times { snooze }
213
256
  result << 2
@@ -230,7 +273,7 @@ class FiberTest < MiniTest::Test
230
273
  def test_cancel
231
274
  result = []
232
275
  error = nil
233
- f = Fiber.spin do
276
+ f = Fiber.current.spin do
234
277
  result << 1
235
278
  2.times { snooze }
236
279
  result << 2
@@ -252,7 +295,7 @@ class FiberTest < MiniTest::Test
252
295
  def test_interrupt
253
296
  # that is, stopped without exception
254
297
  result = []
255
- f = Fiber.spin do
298
+ f = Fiber.current.spin do
256
299
  result << 1
257
300
  2.times { snooze }
258
301
  result << 2
@@ -267,10 +310,39 @@ class FiberTest < MiniTest::Test
267
310
  f&.stop
268
311
  end
269
312
 
313
+ def test_terminate
314
+ buffer = []
315
+ f = spin do
316
+ buffer << :foo
317
+ sleep 1
318
+ buffer << :bar
319
+ rescue Polyphony::Terminate
320
+ buffer << :terminate
321
+ end
322
+ snooze
323
+ f.terminate
324
+ snooze
325
+ assert_equal [:foo, :terminate], buffer
326
+ end
327
+
328
+ def test_interrupt_timer
329
+ result = []
330
+ f = Fiber.current.spin do
331
+ result << :start
332
+ t = Gyro::Timer.new(1, 0)
333
+ result << t.await
334
+ end
335
+ snooze
336
+ f.interrupt
337
+ f.join
338
+
339
+ assert_equal [:start], result
340
+ end
341
+
270
342
  def test_stop
271
343
  # that is, stopped without exception
272
344
  result = []
273
- f = Fiber.spin do
345
+ f = Fiber.current.spin do
274
346
  result << 1
275
347
  2.times { snooze }
276
348
  result << 2
@@ -287,7 +359,7 @@ class FiberTest < MiniTest::Test
287
359
 
288
360
  def test_interrupt_before_start
289
361
  result = []
290
- f = Fiber.spin do
362
+ f = Fiber.current.spin do
291
363
  result << 1
292
364
  end
293
365
  f.interrupt(42)
@@ -336,12 +408,21 @@ class FiberTest < MiniTest::Test
336
408
  snooze while counter < 3
337
409
  assert_equal :waiting, f.state
338
410
  f.stop
411
+ snooze
339
412
  assert_equal :dead, f.state
340
413
  ensure
341
414
  f&.stop
342
415
  end
343
416
 
344
- def test_exception_bubbling
417
+ def test_main?
418
+ f = spin {
419
+ sleep
420
+ }
421
+ assert_nil f.main?
422
+ assert_equal true, Fiber.current.main?
423
+ end
424
+
425
+ def test_exception_propagation
345
426
  # error is propagated to calling fiber
346
427
  raised_error = nil
347
428
  spin do
@@ -358,22 +439,6 @@ class FiberTest < MiniTest::Test
358
439
  assert_equal 'foo', raised_error.message
359
440
  end
360
441
 
361
- def test_exception_bubling_for_orphan_fiber
362
- raised_error = nil
363
- spin do
364
- spin do
365
- snooze
366
- raise 'bar'
367
- end
368
- end
369
- suspend
370
- rescue Exception => e
371
- raised_error = e
372
- ensure
373
- assert raised_error
374
- assert_equal 'bar', raised_error.message
375
- end
376
-
377
442
  def test_await_multiple_fibers
378
443
  f1 = spin { sleep 0.01; :foo }
379
444
  f2 = spin { sleep 0.01; :bar }
@@ -399,7 +464,7 @@ class FiberTest < MiniTest::Test
399
464
  f2 = spin { sleep 0.03; buffer << :bar; :bar }
400
465
  f3 = spin { sleep 0.05; buffer << :baz; :baz }
401
466
 
402
- result, selected = Fiber.select(f1, f2, f3)
467
+ selected, result = Fiber.select(f1, f2, f3)
403
468
  assert_equal :foo, result
404
469
  assert_equal f1, selected
405
470
  assert_equal [:foo], buffer
@@ -446,18 +511,16 @@ class FiberTest < MiniTest::Test
446
511
  assert !f.running?
447
512
  end
448
513
 
449
- def test_list_and_count
450
- assert_equal 1, Fiber.count
451
- assert_equal [Fiber.current], Fiber.list
514
+ def test_children
515
+ assert_equal [], Fiber.current.children
452
516
 
453
517
  f = spin { sleep 1 }
454
518
  snooze
455
- assert_equal 2, Fiber.count
456
- assert_equal f, Fiber.list.last
519
+ assert_equal [f], Fiber.current.children
457
520
 
458
521
  f.stop
459
522
  snooze
460
- assert_equal 1, Fiber.count
523
+ assert_equal [], Fiber.current.children
461
524
  end
462
525
 
463
526
  def test_inspect
@@ -486,60 +549,109 @@ class FiberTest < MiniTest::Test
486
549
  end
487
550
 
488
551
  def test_system_exit_in_fiber
489
- parent_error = nil
490
- main_fiber_error = nil
491
- f2 = nil
492
- f1 = spin do
493
- f2 = spin { raise SystemExit }
494
- suspend
495
- rescue Exception => parent_error
552
+ error = nil
553
+ spin do
554
+ spin { raise SystemExit }.await
496
555
  end
497
556
 
498
557
  begin
499
558
  suspend
500
- rescue Exception => main_fiber_error
559
+ rescue Exception => error
501
560
  end
502
561
 
503
- assert_nil parent_error
504
- assert_kind_of SystemExit, main_fiber_error
562
+ assert_kind_of SystemExit, error
505
563
  end
506
564
 
507
565
  def test_interrupt_in_fiber
508
- parent_error = nil
509
- main_fiber_error = nil
510
- f2 = nil
511
- f1 = spin do
512
- f2 = spin { raise Interrupt }
513
- suspend
514
- rescue Exception => parent_error
566
+ error = nil
567
+ spin do
568
+ spin { raise Interrupt }.await
515
569
  end
516
570
 
517
571
  begin
518
572
  suspend
519
- rescue Exception => main_fiber_error
573
+ rescue Exception => error
520
574
  end
521
575
 
522
- assert_nil parent_error
523
- assert_kind_of Interrupt, main_fiber_error
576
+ assert_kind_of Interrupt, error
524
577
  end
525
578
 
526
579
  def test_signal_exception_in_fiber
527
- parent_error = nil
528
- main_fiber_error = nil
529
- f2 = nil
530
- f1 = spin do
531
- f2 = spin { raise SignalException.new('HUP') }
532
- suspend
533
- rescue Exception => parent_error
580
+ error = nil
581
+ spin do
582
+ spin { raise SignalException.new('HUP') }.await
534
583
  end
535
584
 
536
585
  begin
537
586
  suspend
538
- rescue Exception => main_fiber_error
587
+ rescue Exception => error
539
588
  end
540
589
 
541
- assert_nil parent_error
542
- assert_kind_of SignalException, main_fiber_error
590
+ assert_kind_of SignalException, error
591
+ end
592
+
593
+ def test_signal_handling_int
594
+ i, o = IO.pipe
595
+ pid = Polyphony.fork do
596
+ f = spin { sleep 100 }
597
+ begin
598
+ i.close
599
+ f.await
600
+ rescue Exception => e
601
+ o << e.class.name
602
+ o.close
603
+ end
604
+ end
605
+ sleep 0.1
606
+ f = spin { Gyro::Child.new(pid).await }
607
+ o.close
608
+ Process.kill('INT', pid)
609
+ f.await
610
+ klass = i.read
611
+ o.close
612
+ assert_equal 'Interrupt', klass
613
+ end
614
+
615
+ def test_signal_handling_term
616
+ i, o = IO.pipe
617
+ pid = Polyphony.fork do
618
+ f = spin { sleep 100 }
619
+ begin
620
+ i.close
621
+ f.await
622
+ rescue Exception => e
623
+ o << e.class.name
624
+ o.close
625
+ end
626
+ end
627
+ sleep 0.1
628
+ f = spin { Gyro::Child.new(pid).await }
629
+ o.close
630
+ Process.kill('TERM', pid)
631
+ f.await
632
+ klass = i.read
633
+ o.close
634
+ assert_equal 'SystemExit', klass
635
+ end
636
+
637
+ def test_main_fiber_child_termination_after_fork
638
+ i, o = IO.pipe
639
+ pid = Polyphony.fork do
640
+ i.close
641
+ f = spin do
642
+ sleep 100
643
+ rescue Exception => e
644
+ o << e.class.to_s
645
+ o.close
646
+ end
647
+ snooze
648
+ ensure
649
+ end
650
+ o.close
651
+ Gyro::Child.new(pid).await
652
+ klass = i.read
653
+ i.close
654
+ assert_equal 'Polyphony::Terminate', klass
543
655
  end
544
656
  end
545
657
 
@@ -552,7 +664,7 @@ class MailboxTest < MiniTest::Test
552
664
 
553
665
  3.times do |i|
554
666
  f << i
555
- snooze
667
+ sleep 0
556
668
  end
557
669
 
558
670
  assert_equal [0, 1, 2], msgs
@@ -568,7 +680,7 @@ class MailboxTest < MiniTest::Test
568
680
 
569
681
  3.times { |i| f << i }
570
682
 
571
- snooze
683
+ sleep 0
572
684
 
573
685
  assert_equal [0, 1, 2], msgs
574
686
  ensure
@@ -588,10 +700,10 @@ class MailboxTest < MiniTest::Test
588
700
  end
589
701
 
590
702
  def test_cross_thread_send_receive
591
- skip "There's currently a race condition in cross-thread send/receive. We're going to rewrite it in C"
592
703
  ping_receive_buffer = []
593
704
  pong_receive_buffer = []
594
705
  pong = Thread.new do
706
+ sleep 0.05
595
707
  loop do
596
708
  peer, data = receive
597
709
  pong_receive_buffer << data
@@ -600,6 +712,7 @@ class MailboxTest < MiniTest::Test
600
712
  end
601
713
 
602
714
  ping = Thread.new do
715
+ sleep 0.05
603
716
  3.times do
604
717
  pong << [Fiber.current, 'ping']
605
718
  data = receive
@@ -613,4 +726,104 @@ class MailboxTest < MiniTest::Test
613
726
  assert_equal %w{pong pong pong}, ping_receive_buffer
614
727
  assert_equal %w{ping ping ping}, pong_receive_buffer
615
728
  end
729
+
730
+ def test_message_queueing
731
+ messages = []
732
+ f = spin do
733
+ loop {
734
+ msg = receive
735
+ break if msg == 'stop'
736
+
737
+ messages << msg
738
+ }
739
+ end
740
+
741
+ 100.times { f << 'foo' }
742
+ f << 'stop'
743
+
744
+ f.await
745
+ assert_equal ['foo'] * 100, messages
746
+ end
616
747
  end
748
+
749
+ class FiberControlTest < MiniTest::Test
750
+ def test_await_multiple
751
+ f1 = spin {
752
+ snooze
753
+ :foo
754
+ }
755
+ f2 = spin {
756
+ snooze
757
+ :bar
758
+ }
759
+ result = Fiber.await(f1, f2)
760
+ assert_equal [:foo, :bar], result
761
+ end
762
+
763
+ def test_await_multiple_with_raised_error
764
+ f1 = spin {
765
+ snooze
766
+ raise 'foo'
767
+ }
768
+ f2 = spin {
769
+ snooze
770
+ :bar
771
+ }
772
+ f3 = spin {
773
+ sleep 3
774
+ }
775
+ error = nil
776
+ begin
777
+ Fiber.await(f1, f2, f3)
778
+ rescue => error
779
+ end
780
+ assert_kind_of RuntimeError, error
781
+ assert_equal 'foo', error.message
782
+
783
+ assert_equal :dead, f1.state
784
+ assert_equal :dead, f2.state
785
+ assert_equal :dead, f3.state
786
+ end
787
+
788
+ def test_await_multiple_with_interruption
789
+ f1 = spin { sleep 0.01; :foo }
790
+ f2 = spin { sleep 1; :bar }
791
+ spin { snooze; f2.interrupt(:baz) }
792
+ result = Fiber.await(f1, f2)
793
+ assert_equal [:foo, :baz], result
794
+ end
795
+
796
+ def test_select
797
+ buffer = []
798
+ f1 = spin { snooze; buffer << :foo; :foo }
799
+ f2 = spin { :bar }
800
+ result = Fiber.select(f1, f2)
801
+ assert_equal [f2, :bar], result
802
+ assert_equal [:foo], buffer
803
+ assert_equal :dead, f1.state
804
+ end
805
+
806
+ def test_select_with_raised_error
807
+ f1 = spin { snooze; raise 'foo' }
808
+ f2 = spin { sleep 3 }
809
+
810
+ result = nil
811
+ begin
812
+ result = Fiber.select(f1, f2)
813
+ rescue => result
814
+ end
815
+
816
+ assert_kind_of RuntimeError, result
817
+ assert_equal 'foo', result.message
818
+ assert_equal :dead, f1.state
819
+ assert_equal :dead, f2.state
820
+ end
821
+
822
+ def test_select_with_interruption
823
+ f1 = spin { sleep 0.01; :foo }
824
+ f2 = spin { sleep 1; :bar }
825
+ spin { snooze; f2.interrupt(:baz) }
826
+ result = Fiber.select(f1, f2)
827
+ assert_equal [f2, :baz], result
828
+ end
829
+ end
@@ -15,7 +15,7 @@ class SpinTest < MiniTest::Test
15
15
 
16
16
  def test_that_spin_accepts_fiber_argument
17
17
  result = nil
18
- fiber = Fiber.spin { result = 42 }
18
+ fiber = Fiber.current.spin { result = 42 }
19
19
 
20
20
  assert_nil result
21
21
  suspend
@@ -128,51 +128,6 @@ class CancelScopeTest < Minitest::Test
128
128
  # end
129
129
  end
130
130
 
131
- class SupervisorTest < MiniTest::Test
132
- def sleep_and_set(ctx, idx)
133
- proc do
134
- sleep(0.001 * idx)
135
- ctx[idx] = true
136
- end
137
- end
138
-
139
- def parallel_sleep(ctx)
140
- supervise do |s|
141
- (1..3).each { |idx| s.spin(&sleep_and_set(ctx, idx)) }
142
- end
143
- end
144
-
145
- def test_that_supervisor_waits_for_all_nested_fibers_to_complete
146
- ctx = {}
147
- spin do
148
- parallel_sleep(ctx)
149
- end
150
- suspend
151
- assert ctx[1]
152
- assert ctx[2]
153
- assert ctx[3]
154
- end
155
-
156
- def test_that_supervisor_can_add_fibers_after_having_started
157
- result = []
158
- spin do
159
- supervisor = Polyphony::Supervisor.new
160
- 3.times do |i|
161
- spin do
162
- sleep(0.001)
163
- supervisor.spin do
164
- sleep(0.001)
165
- result << i
166
- end
167
- end
168
- end
169
- supervisor.await
170
- end.await
171
-
172
- assert_equal [0, 1, 2], result.sort
173
- end
174
- end
175
-
176
131
  class ExceptionTest < MiniTest::Test
177
132
  def test_cross_fiber_backtrace
178
133
  error = nil
@@ -191,8 +146,8 @@ class ExceptionTest < MiniTest::Test
191
146
  rescue Exception => e
192
147
  frames << 3
193
148
  raise e
194
- end
195
- 5.times { |i| snooze }
149
+ end#.await
150
+ 5.times { snooze }
196
151
  rescue Exception => e
197
152
  error = e
198
153
  ensure
@@ -206,10 +161,9 @@ class ExceptionTest < MiniTest::Test
206
161
  spin do
207
162
  spin do
208
163
  raise 'foo'
209
- end
210
- end
211
- end
212
- 4.times { snooze }
164
+ end.await
165
+ end.await
166
+ end.await
213
167
  rescue Exception => e
214
168
  error = e
215
169
  ensure
@@ -319,6 +273,7 @@ class MoveOnAfterTest < MiniTest::Test
319
273
  snooze
320
274
  assert f.running?
321
275
  f.stop
276
+ snooze
322
277
  assert !f.running?
323
278
  end
324
279
 
data/test/test_io.rb CHANGED
@@ -194,4 +194,12 @@ class IOClassMethodsTest < MiniTest::Test
194
194
  ensure
195
195
  $stdout = orig_stdout
196
196
  end
197
+
198
+ def test_read_large_file
199
+ fn = '/tmp/test.txt'
200
+ File.open(fn, 'w') { |f| f << ('*' * 1e6) }
201
+ s = IO.read(fn)
202
+ assert_equal 1e6, s.bytesize
203
+ assert s == IO.orig_read(fn)
204
+ end
197
205
  end
data/test/test_signal.rb CHANGED
@@ -32,6 +32,7 @@ class SignalTest < MiniTest::Test
32
32
 
33
33
  snooze
34
34
  Process.kill(:HUP, Process.pid)
35
+ snooze
35
36
  assert_equal 1, count
36
37
  end
37
38
  end