rjr 0.12.2 → 0.15.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. data/README.md +49 -36
  2. data/Rakefile +2 -0
  3. data/bin/rjr-client +11 -9
  4. data/bin/rjr-server +12 -10
  5. data/examples/amqp.rb +29 -0
  6. data/examples/client.rb +32 -0
  7. data/examples/complete.rb +36 -0
  8. data/examples/local.rb +29 -0
  9. data/examples/server.rb +26 -0
  10. data/examples/tcp.rb +29 -0
  11. data/examples/web.rb +22 -0
  12. data/examples/ws.rb +29 -0
  13. data/lib/rjr/common.rb +7 -12
  14. data/lib/rjr/dispatcher.rb +171 -239
  15. data/lib/rjr/em_adapter.rb +33 -66
  16. data/lib/rjr/message.rb +43 -12
  17. data/lib/rjr/node.rb +197 -103
  18. data/lib/rjr/nodes/amqp.rb +216 -0
  19. data/lib/rjr/nodes/easy.rb +159 -0
  20. data/lib/rjr/nodes/local.rb +118 -0
  21. data/lib/rjr/{missing_node.rb → nodes/missing.rb} +4 -2
  22. data/lib/rjr/nodes/multi.rb +79 -0
  23. data/lib/rjr/nodes/tcp.rb +211 -0
  24. data/lib/rjr/nodes/web.rb +197 -0
  25. data/lib/rjr/nodes/ws.rb +187 -0
  26. data/lib/rjr/stats.rb +70 -0
  27. data/lib/rjr/thread_pool.rb +178 -123
  28. data/site/index.html +45 -0
  29. data/site/jquery-latest.js +9404 -0
  30. data/site/jrw.js +297 -0
  31. data/site/json.js +199 -0
  32. data/specs/dispatcher_spec.rb +244 -198
  33. data/specs/em_adapter_spec.rb +52 -80
  34. data/specs/message_spec.rb +223 -197
  35. data/specs/node_spec.rb +67 -163
  36. data/specs/nodes/amqp_spec.rb +82 -0
  37. data/specs/nodes/easy_spec.rb +13 -0
  38. data/specs/nodes/local_spec.rb +72 -0
  39. data/specs/nodes/multi_spec.rb +65 -0
  40. data/specs/nodes/tcp_spec.rb +75 -0
  41. data/specs/nodes/web_spec.rb +77 -0
  42. data/specs/nodes/ws_spec.rb +78 -0
  43. data/specs/stats_spec.rb +59 -0
  44. data/specs/thread_pool_spec.rb +44 -35
  45. metadata +40 -30
  46. data/lib/rjr/amqp_node.rb +0 -330
  47. data/lib/rjr/inspect.rb +0 -65
  48. data/lib/rjr/local_node.rb +0 -150
  49. data/lib/rjr/multi_node.rb +0 -65
  50. data/lib/rjr/tcp_node.rb +0 -323
  51. data/lib/rjr/thread_pool2.rb +0 -272
  52. data/lib/rjr/util.rb +0 -104
  53. data/lib/rjr/web_node.rb +0 -266
  54. data/lib/rjr/ws_node.rb +0 -289
  55. data/lib/rjr.rb +0 -16
  56. data/specs/amqp_node_spec.rb +0 -31
  57. data/specs/inspect_spec.rb +0 -60
  58. data/specs/local_node_spec.rb +0 -43
  59. data/specs/multi_node_spec.rb +0 -45
  60. data/specs/tcp_node_spec.rb +0 -33
  61. data/specs/util_spec.rb +0 -46
  62. data/specs/web_node_spec.rb +0 -32
  63. data/specs/ws_node_spec.rb +0 -32
  64. /data/lib/rjr/{tcp_node2.rb → nodes/tcp2.rb} +0 -0
  65. /data/lib/rjr/{udp_node.rb → nodes/udp.rb} +0 -0
@@ -0,0 +1,82 @@
1
+ require 'rjr/nodes/amqp'
2
+ require 'rjr/nodes/missing'
3
+
4
+ if RJR::Nodes::AMQP == RJR::Nodes::Missing
5
+ puts "Missing AMQP node dependencies, skipping amqp tests"
6
+
7
+ else
8
+ module RJR::Nodes
9
+ describe AMQP do
10
+ describe "#send_msg" do
11
+ it "should send message to the specified queue"
12
+ end
13
+
14
+ describe "#listen" do
15
+ it "should listen for messages" do
16
+ ci = cp = rn = rni = rnt = p = invoked = nil
17
+ node = AMQP.new :node_id => 'server', :broker => 'localhost'
18
+ node.dispatcher.handle('test') do |param|
19
+ ci = @rjr_client_ip
20
+ cp = @rjr_client_port
21
+ rn = @rjr_node
22
+ rni = @rjr_node_id
23
+ rnt = @rjr_node_type
24
+ p = param
25
+ invoked = true
26
+ end
27
+ node.listen
28
+
29
+ # issue request
30
+ AMQP.new(:node_id => 'client',
31
+ :broker => 'localhost').invoke 'server-queue',
32
+ 'test',
33
+ 'myparam'
34
+ node.halt.join
35
+ invoked.should be_true
36
+ ci.should be_nil
37
+ cp.should be_nil
38
+ rn.should == node
39
+ rni.should == 'server'
40
+ rnt.should == :amqp
41
+ p.should == 'myparam'
42
+ end
43
+ end
44
+
45
+ describe "#invoke" do
46
+ it "should invoke request" do
47
+ server = AMQP.new :node_id => 'server', :broker => 'localhost'
48
+ server.dispatcher.handle('test') do |p|
49
+ 'retval'
50
+ end
51
+ server.listen
52
+
53
+ client = AMQP.new :node_id => 'client', :broker => 'localhost'
54
+ res = client.invoke 'server-queue', 'test', 'myparam'
55
+
56
+ server.halt.join
57
+ res.should == 'retval'
58
+ end
59
+ end
60
+
61
+ describe "#notify" do
62
+ it "should send notification" do
63
+ server = AMQP.new :node_id => 'server', :broker => 'localhost'
64
+ server.dispatcher.handle('test') do |p|
65
+ 'retval'
66
+ end
67
+ server.listen
68
+
69
+ client = AMQP.new :node_id => 'client', :broker => 'localhost'
70
+ res = client.notify 'server-queue', 'test', 'myparam'
71
+
72
+ server.halt.join
73
+ res.should == nil
74
+ end
75
+ end
76
+
77
+ # TODO test callbacks over amqp interface
78
+ # TODO ensure closed / error event handlers are invoked
79
+
80
+ end # describe AMQP
81
+ end # module RJR::Nodes
82
+ end # (!missing)
@@ -0,0 +1,13 @@
1
+ require 'rjr/nodes/easy'
2
+
3
+ module RJR::Nodes
4
+ describe Easy do
5
+ describe "#invoke" do
6
+ it "should invoke request"
7
+ end
8
+
9
+ describe "#notify" do
10
+ it "should send notification"
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,72 @@
1
+ require 'rjr/nodes/local'
2
+
3
+ module RJR::Nodes
4
+ describe Local do
5
+ describe "#send_msg" do
6
+ it "should dispatch local notification"
7
+ end
8
+
9
+ describe "#invoke" do
10
+ it "should dispatch local request" do
11
+ invoked = rn = rni = rnt = p = nil
12
+ node = Local.new :node_id => 'aaa'
13
+ node.dispatcher.handle('foobar') { |param|
14
+ rn = @rjr_node
15
+ rni = @rjr_node_id
16
+ rnt = @rjr_node_type
17
+ p = param
18
+ invoked = true
19
+ 'retval'
20
+ }
21
+
22
+ res = node.invoke 'foobar', 'myparam'
23
+
24
+ invoked.should == true
25
+ res.should == 'retval'
26
+ rn.should == node
27
+ rni.should == 'aaa'
28
+ rnt.should == :local
29
+ p.should == 'myparam'
30
+ end
31
+ end
32
+
33
+ describe "#notify" do
34
+ it "should dispatch local notification" do
35
+ invoked = nil
36
+ node = Local.new :node_id => 'aaa'
37
+ node.dispatcher.handle('foobar') { |param|
38
+ invoked = true
39
+ 'retval'
40
+ }
41
+
42
+ res = node.notify 'foobar', 'myparam'
43
+ invoked.should == true
44
+ res.should == nil
45
+ end
46
+ end
47
+
48
+ it "should invoke callbacks" do
49
+ node = Local.new
50
+ cbp = nil
51
+ foobar_invoked = false
52
+ callback_invoked = false
53
+ node.dispatcher.handle('foobar') {
54
+ foobar_invoked = true
55
+ @rjr_callback.notify('callback', 'cp')
56
+ }
57
+ node.dispatcher.handle('callback') { |param|
58
+ callback_invoked = true
59
+ cbp = param
60
+ }
61
+
62
+ node.invoke 'foobar', 'myparam'
63
+ foobar_invoked.should be_true
64
+ callback_invoked.should be_true
65
+ cbp.should == 'cp'
66
+ end
67
+
68
+ # TODO make sure local parameters are not modified if altered
69
+ # on remote end of invoke/notify
70
+
71
+ end # desribe Local
72
+ end # module RJR::Nodes
@@ -0,0 +1,65 @@
1
+ require 'rjr/nodes/multi'
2
+ require 'rjr/nodes/amqp'
3
+ require 'rjr/nodes/web'
4
+ require 'rjr/nodes/missing'
5
+
6
+ if RJR::Nodes::AMQP == RJR::Nodes::Missing ||
7
+ RJR::Nodes::Web == RJR::Nodes::Missing
8
+ puts "Missing AMQP and/or web node dependencies, skipping multi tests"
9
+
10
+ else
11
+ module RJR::Nodes
12
+ describe Multi do
13
+ describe "#listen" do
14
+ it "should listen for messages" do
15
+ invoked1 = invoked2 = false
16
+ rni1 = rni2 = nil
17
+ rnt1 = rnt2 = nil
18
+ p1 = p2 = nil
19
+ amqp = AMQP.new :node_id => 'amqp',
20
+ :broker => 'localhost'
21
+ web = Web.new :node_id => 'web',
22
+ :host => 'localhost', :port => 9876
23
+ multi = Multi.new :node_id => 'multi',
24
+ :nodes => [amqp, web]
25
+
26
+ multi.dispatcher.handle('method1') { |param|
27
+ rni1 = @rjr_node_id
28
+ rnt1 = @rjr_node_type
29
+ p1 = param
30
+ invoked1 = true
31
+ 'retval1'
32
+ }
33
+ multi.dispatcher.handle('method2') { |param|
34
+ rni2 = @rjr_node_id
35
+ rnt2 = @rjr_node_type
36
+ p2 = param
37
+ invoked2 = true
38
+ 'retval2'
39
+ }
40
+ multi.listen
41
+ # TODO should wait until we know server is listening
42
+
43
+ web_client = Web.new
44
+ res = web_client.invoke 'http://localhost:9876', 'method2', 'myparam2'
45
+ res.should == 'retval2'
46
+ rni2.should == 'web'
47
+ rnt2.should == :web
48
+ p2.should == 'myparam2'
49
+
50
+ amqp_client = AMQP.new :node_id => 'client',
51
+ :broker => 'localhost'
52
+ res = amqp_client.invoke 'amqp-queue', 'method1', 'myparam1'
53
+ res.should == 'retval1'
54
+ invoked1.should be_true
55
+ rni1.should == 'amqp'
56
+ rnt1.should == :amqp
57
+ p1.should == 'myparam1'
58
+
59
+ multi.halt.join
60
+ end
61
+ end
62
+ end # describe Multi
63
+
64
+ end # module RJR::Nodes
65
+ end # (!missing)
@@ -0,0 +1,75 @@
1
+ require 'rjr/nodes/tcp'
2
+
3
+ module RJR::Nodes
4
+ describe TCP do
5
+ describe "#send_msg" do
6
+ it "should send message using the specifed connection"
7
+ end
8
+
9
+ describe "#listen" do
10
+ it "should listen for messages" do
11
+ ci = cp = rn = rni = rnt = p = invoked = nil
12
+ server = TCP.new :node_id => 'tcp',
13
+ :host => 'localhost', :port => 9987
14
+ server.dispatcher.handle('foobar') { |param|
15
+ ci = @rjr_client_ip
16
+ cp = @rjr_client_port
17
+ rn = @rjr_node
18
+ rni = @rjr_node_id
19
+ rnt = @rjr_node_type
20
+ p = param
21
+ invoked = true
22
+ }
23
+ server.listen
24
+
25
+ # issue request
26
+ TCP.new.invoke 'jsonrpc://localhost:9987', 'foobar', 'myparam'
27
+ server.halt.join
28
+ ci.should == "127.0.0.1"
29
+ #cp.should == 9987
30
+ rn.should == server
31
+ rni.should == 'tcp'
32
+ rnt.should == :tcp
33
+ p.should == 'myparam'
34
+ invoked.should == true
35
+ end
36
+ end
37
+
38
+ describe "#invoke" do
39
+ it "should invoke request" do
40
+ server = TCP.new :node_id => 'tcp',
41
+ :host => 'localhost', :port => 9987
42
+ server.dispatcher.handle('foobar') { |param|
43
+ 'retval'
44
+ }
45
+ server.listen
46
+
47
+ client = TCP.new
48
+ res = client.invoke 'jsonrpc://localhost:9987', 'foobar', 'myparam'
49
+ server.halt.join
50
+ res.should == 'retval'
51
+ end
52
+ end
53
+
54
+ describe "#notify" do
55
+ it "should send notification" do
56
+ server = TCP.new :node_id => 'tcp',
57
+ :host => 'localhost', :port => 9987
58
+ server.dispatcher.handle('foobar') { |param|
59
+ 'retval'
60
+ }
61
+ server.listen
62
+
63
+ client = TCP.new
64
+ res = client.notify 'jsonrpc://localhost:9987', 'foobar', 'myparam'
65
+ server.halt.join
66
+ res.should == nil
67
+ end
68
+ end
69
+
70
+ # TODO test callbacks over tcp interface
71
+ # TODO ensure closed / error event handlers are invoked
72
+ end
73
+ end
74
+
75
+
@@ -0,0 +1,77 @@
1
+ require 'rjr/nodes/web'
2
+ require 'rjr/nodes/missing'
3
+
4
+ if RJR::Nodes::Web == RJR::Nodes::Missing
5
+ puts "Missing Web node dependencies, skipping web tests"
6
+
7
+ else
8
+ module RJR::Nodes
9
+ describe Web do
10
+ describe "#send_msg" do
11
+ it "should send response using the specified connection"
12
+ end
13
+
14
+ describe "#listen" do
15
+ it "should listen for messages" do
16
+ ci = cp = rn = rni = rnt = p = invoked = nil
17
+ node = Web.new :node_id => 'www', :host => 'localhost', :port => 9678
18
+ node.dispatcher.handle('test') do |param|
19
+ ci = @rjr_client_ip
20
+ cp = @rjr_client_port
21
+ rn = @rjr_node
22
+ rni = @rjr_node_id
23
+ rnt = @rjr_node_type
24
+ p = param
25
+ invoked = true
26
+ end
27
+ node.listen
28
+
29
+ # issue request
30
+ Web.new.invoke 'http://localhost:9678', 'test', 'myparam'
31
+ node.halt.join
32
+ invoked.should be_true
33
+ ci.should == '127.0.0.1'
34
+ #cp.should
35
+ rn.should == node
36
+ rni.should == 'www'
37
+ rnt.should == :web
38
+ p.should == 'myparam'
39
+ end
40
+ end
41
+
42
+ describe "#invoke" do
43
+ it "should invoke request" do
44
+ server = Web.new :node_id => 'www', :host => 'localhost', :port => 9678
45
+ server.dispatcher.handle('test') do |p|
46
+ 'retval'
47
+ end
48
+ server.listen
49
+
50
+ client = Web.new
51
+ res = client.invoke 'http://localhost:9678', 'test', 'myparam'
52
+
53
+ server.halt.join
54
+ res.should == 'retval'
55
+ end
56
+ end
57
+
58
+ describe "#notify" do
59
+ it "should send notification" do
60
+ server = Web.new :node_id => 'www', :host => 'localhost', :port => 9678
61
+ server.dispatcher.handle('test') do |p|
62
+ 'retval'
63
+ end
64
+ server.listen
65
+
66
+ client = Web.new
67
+ res = client.notify 'http://localhost:9678', 'test', 'myparam'
68
+
69
+ server.halt.join
70
+ res.should == nil
71
+ end
72
+ end
73
+
74
+ # TODO ensure closed / error event handlers are invoked
75
+ end # describe Web
76
+ end # module RJR::Nodes
77
+ end # (!missing)
@@ -0,0 +1,78 @@
1
+ require 'rjr/nodes/ws'
2
+ require 'rjr/nodes/missing'
3
+
4
+ if RJR::Nodes::WS == RJR::Nodes::Missing
5
+ puts "Missing Ws node dependencies, skipping ws tests"
6
+
7
+ else
8
+ module RJR::Nodes
9
+ describe WS do
10
+ describe "#send_msg" do
11
+ it "should send response using the specified connection"
12
+ end
13
+
14
+ describe "#listen" do
15
+ it "should listen for messages" do
16
+ ci = cp = rn = rni = rnt = p = invoked = nil
17
+ node = WS.new :node_id => 'ws', :host => 'localhost', :port => 9678
18
+ node.dispatcher.handle('test') do |param|
19
+ ci = @rjr_client_ip
20
+ cp = @rjr_client_port
21
+ rn = @rjr_node
22
+ rni = @rjr_node_id
23
+ rnt = @rjr_node_type
24
+ p = param
25
+ invoked = true
26
+ end
27
+ node.listen
28
+
29
+ # issue request
30
+ WS.new.invoke 'http://localhost:9678', 'test', 'myparam'
31
+ node.halt.join
32
+ invoked.should be_true
33
+ ci.should == '127.0.0.1'
34
+ #cp.should
35
+ rn.should == node
36
+ rni.should == 'ws'
37
+ rnt.should == :ws
38
+ p.should == 'myparam'
39
+ end
40
+ end
41
+
42
+ describe "#invoke" do
43
+ it "should invoke request" do
44
+ server = WS.new :node_id => 'ws', :host => 'localhost', :port => 9678
45
+ server.dispatcher.handle('test') do |p|
46
+ 'retval'
47
+ end
48
+ server.listen
49
+
50
+ client = WS.new
51
+ res = client.invoke 'http://localhost:9678', 'test', 'myparam'
52
+
53
+ server.halt.join
54
+ res.should == 'retval'
55
+ end
56
+ end
57
+
58
+ describe "#notify" do
59
+ it "should send notification" do
60
+ server = WS.new :node_id => 'ws', :host => 'localhost', :port => 9678
61
+ server.dispatcher.handle('test') do |p|
62
+ 'retval'
63
+ end
64
+ server.listen
65
+
66
+ client = WS.new
67
+ res = client.notify 'http://localhost:9678', 'test', 'myparam'
68
+
69
+ server.halt.join
70
+ res.should == nil
71
+ end
72
+ end
73
+
74
+ # TODO test callbacks over ws interface
75
+ # TODO ensure closed / error event handlers are invoked
76
+ end # describe Ws
77
+ end # module RJR::Nodes
78
+ end # (!missing)
@@ -0,0 +1,59 @@
1
+ require 'rjr/dispatcher'
2
+ require 'rjr/stats'
3
+
4
+ describe "#select_stats" do
5
+ before(:each) do
6
+ @d = RJR::Dispatcher.new
7
+
8
+ @req1 = RJR::Request.new :rjr_node_type => :local, :rjr_method => 'foobar'
9
+ @req1.result = RJR::Result.new :result => true
10
+
11
+ @req2 = RJR::Request.new :rjr_node_type => :tcp, :rjr_method => 'barfoo'
12
+ @req2.result = RJR::Result.new :error_code => 123
13
+
14
+ @req3 = RJR::Request.new :rjr_node_type => :amqp, :rjr_method => 'foobar'
15
+ @req3.result = RJR::Result.new :error_code => 123
16
+
17
+ # XXX not prettiest but works for now
18
+ @d.instance_variable_set(:@requests, [@req1, @req2, @req3])
19
+ end
20
+
21
+ it "should get all requests" do
22
+ requests = select_stats(@d)
23
+ requests.size.should == 3
24
+ requests[0].should == @req1
25
+ requests[1].should == @req2
26
+ requests[2].should == @req3
27
+ end
28
+
29
+ it "should get all requests for a node" do
30
+ requests = select_stats @d, 'on_node', 'local'
31
+ requests.size.should == 1
32
+ requests.first.should == @req1
33
+ end
34
+
35
+ it "should get all requests for a method" do
36
+ requests = select_stats @d, 'for_method', 'foobar'
37
+ requests.size.should == 2
38
+ requests.first.should == @req1
39
+ requests.last.should == @req3
40
+ end
41
+
42
+ it "should get all successfull/failed requests" do
43
+ requests = select_stats @d, 'successful'
44
+ requests.size.should == 1
45
+ requests.first.should == @req1
46
+
47
+ requests = select_stats @d, 'failed'
48
+ requests.size.should == 2
49
+ requests.first.should == @req2
50
+ requests.last.should == @req3
51
+ end
52
+
53
+ it "should get dispatcher stats meeting multiple criteria" do
54
+ requests = select_stats @d, 'for_method', 'foobar', 'successful'
55
+ requests.size.should == 1
56
+ requests.first.should == @req1
57
+ end
58
+
59
+ end
@@ -1,41 +1,50 @@
1
- require 'rjr/dispatcher'
2
- require 'rjr/thread_pool2'
1
+ require 'thread'
2
+ require 'rjr/thread_pool'
3
3
 
4
4
  # TODO ? test ThreadPoolJob being_executed?, completed?, exec, handle_timeout!
5
5
 
6
- describe ThreadPool2 do
7
- it "should start and stop successfully" do
8
- tp = ThreadPool2.new 10, :timeout => 10
9
- tp.running?.should be_false
10
-
11
- tp.start
12
- tp.running?.should be_true
13
- tp.instance_variable_get(:@worker_threads).size.should == 10
14
- tp.instance_variable_get(:@manager_thread).should_not be_nil
15
- ['run', 'sleep'].should include(tp.instance_variable_get(:@manager_thread).status)
16
- sleep 0.5
17
-
18
- tp.stop
19
- tp.running?.should be_false
20
- tp.instance_variable_get(:@manager_thread).should be_nil
21
- tp.instance_variable_get(:@worker_threads).size.should == 0
22
- end
23
-
24
- it "should accept and run work" do
25
- tp = ThreadPool2.new 10, :timeout => 10
26
- tp.start
27
-
28
- tp.instance_variable_get(:@work_queue).size.should == 0
29
- jobs_executed = []
30
- tp << ThreadPool2Job.new { jobs_executed << 1 }
31
- tp << ThreadPool2Job.new { jobs_executed << 2 }
32
- tp.instance_variable_get(:@work_queue).size.should == 2
33
-
34
- sleep 0.5
35
- jobs_executed.should include(1)
36
- jobs_executed.should include(2)
37
- tp.instance_variable_get(:@work_queue).size.should == 0
6
+ module RJR
7
+ describe ThreadPool do
8
+ after(:each) do
9
+ ThreadPool.instance.stop
10
+ ThreadPool.instance.join
11
+ end
12
+
13
+ it "should be a singleton" do
14
+ tp = ThreadPool.instance
15
+ ThreadPool.instance.should == tp
16
+ end
17
+
18
+ it "should start the thread pool" do
19
+ tp = ThreadPool.instance
20
+ tp.start
21
+ tp.instance_variable_get(:@worker_threads).size.should == ThreadPool.num_threads
22
+ tp.should be_running
23
+ end
24
+
25
+ it "should stop the thread pool" do
26
+ tp = ThreadPool.instance
27
+ tp.start
28
+ tp.stop
29
+ tp.join
30
+ tp.instance_variable_get(:@worker_threads).size.should == 0
31
+ tp.should_not be_running
32
+ end
33
+
34
+ it "should run work" do
35
+ tp = ThreadPool.instance
36
+ tp.start
37
+
38
+ jobs_executed = []
39
+ m,c = Mutex.new, ConditionVariable.new
40
+ tp << ThreadPoolJob.new { jobs_executed << 1 ; m.synchronize { c.signal } }
41
+ tp << ThreadPoolJob.new { jobs_executed << 2 ; m.synchronize { c.signal } }
42
+
43
+ m.synchronize { c.wait m, 0.1 } unless jobs_executed.include?(1)
44
+ m.synchronize { c.wait m, 0.1 } unless jobs_executed.include?(2)
45
+ jobs_executed.should include(1)
46
+ jobs_executed.should include(2)
47
+ end
38
48
 
39
- tp.stop
40
49
  end
41
50
  end