em-synchrony 1.0.1 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +173 -173
- data/em-synchrony.gemspec +1 -1
- data/lib/active_record/connection_adapters/em_mysql2_adapter.rb +47 -47
- data/lib/em-synchrony.rb +1 -1
- data/lib/em-synchrony/activerecord.rb +102 -102
- data/lib/em-synchrony/amqp.rb +180 -180
- data/lib/em-synchrony/em-hiredis.rb +103 -103
- data/lib/em-synchrony/tcpsocket.rb +157 -28
- data/spec/activerecord_spec.rb +108 -108
- data/spec/amqp_spec.rb +146 -146
- data/spec/tcpsocket_spec.rb +401 -18
- data/spec/timer_spec.rb +7 -0
- metadata +10 -5
data/spec/activerecord_spec.rb
CHANGED
@@ -1,109 +1,109 @@
|
|
1
|
-
require "spec/helper/all"
|
2
|
-
require "em-synchrony/activerecord"
|
3
|
-
require "em-synchrony/fiber_iterator"
|
4
|
-
|
5
|
-
# create database widgets;
|
6
|
-
# use widgets;
|
7
|
-
# create table widgets (
|
8
|
-
# id INT NOT NULL AUTO_INCREMENT,
|
9
|
-
# title varchar(255),
|
10
|
-
# PRIMARY KEY (`id`)
|
11
|
-
# );
|
12
|
-
|
13
|
-
class Widget < ActiveRecord::Base; end;
|
14
|
-
|
15
|
-
describe "Fiberized ActiveRecord driver for mysql2" do
|
16
|
-
DELAY = 0.25
|
17
|
-
QUERY = "SELECT sleep(#{DELAY})"
|
18
|
-
|
19
|
-
def establish_connection
|
20
|
-
ActiveRecord::Base.establish_connection(
|
21
|
-
:adapter => 'em_mysql2',
|
22
|
-
:database => 'widgets',
|
23
|
-
:username => 'root',
|
24
|
-
:pool => 10
|
25
|
-
)
|
26
|
-
Widget.delete_all
|
27
|
-
end
|
28
|
-
|
29
|
-
it "should establish AR connection" do
|
30
|
-
EventMachine.synchrony do
|
31
|
-
establish_connection
|
32
|
-
|
33
|
-
result = Widget.find_by_sql(QUERY)
|
34
|
-
result.size.should eql(1)
|
35
|
-
EventMachine.stop
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
it "should fire sequential, synchronous requests within single fiber" do
|
40
|
-
EventMachine.synchrony do
|
41
|
-
establish_connection
|
42
|
-
|
43
|
-
start = now
|
44
|
-
res = []
|
45
|
-
|
46
|
-
res.push Widget.find_by_sql(QUERY)
|
47
|
-
res.push Widget.find_by_sql(QUERY)
|
48
|
-
|
49
|
-
(now - start.to_f).should be_within(DELAY * res.size * 0.15).of(DELAY * res.size)
|
50
|
-
res.size.should eql(2)
|
51
|
-
|
52
|
-
EventMachine.stop
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
it "should fire 100 requests in fibers" do
|
57
|
-
EM.synchrony do
|
58
|
-
establish_connection
|
59
|
-
EM::Synchrony::FiberIterator.new(1..100, 40).each do |i|
|
60
|
-
widget = Widget.create title: 'hi'
|
61
|
-
widget.update_attributes title: 'hello'
|
62
|
-
end
|
63
|
-
EM.stop
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
it "should create widget" do
|
68
|
-
EM.synchrony do
|
69
|
-
establish_connection
|
70
|
-
Widget.create
|
71
|
-
Widget.create
|
72
|
-
Widget.count.should eql(2)
|
73
|
-
EM.stop
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
it "should update widget" do
|
78
|
-
EM.synchrony do
|
79
|
-
establish_connection
|
80
|
-
ActiveRecord::Base.connection.execute("TRUNCATE TABLE widgets;")
|
81
|
-
widget = Widget.create title: 'hi'
|
82
|
-
widget.update_attributes title: 'hello'
|
83
|
-
Widget.find(widget.id).title.should eql('hello')
|
84
|
-
EM.stop
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
describe "transactions" do
|
89
|
-
it "should work properly" do
|
90
|
-
EM.synchrony do
|
91
|
-
establish_connection
|
92
|
-
EM::Synchrony::FiberIterator.new(1..50, 30).each do |i|
|
93
|
-
widget = Widget.create title: "hi#{i}"
|
94
|
-
ActiveRecord::Base.transaction do
|
95
|
-
widget.update_attributes title: "hello"
|
96
|
-
end
|
97
|
-
ActiveRecord::Base.transaction do
|
98
|
-
raise ActiveRecord::Rollback
|
99
|
-
end
|
100
|
-
end
|
101
|
-
Widget.all.each do |widget|
|
102
|
-
widget.title.should eq('hello')
|
103
|
-
end
|
104
|
-
EM.stop
|
105
|
-
end
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
1
|
+
require "spec/helper/all"
|
2
|
+
require "em-synchrony/activerecord"
|
3
|
+
require "em-synchrony/fiber_iterator"
|
4
|
+
|
5
|
+
# create database widgets;
|
6
|
+
# use widgets;
|
7
|
+
# create table widgets (
|
8
|
+
# id INT NOT NULL AUTO_INCREMENT,
|
9
|
+
# title varchar(255),
|
10
|
+
# PRIMARY KEY (`id`)
|
11
|
+
# );
|
12
|
+
|
13
|
+
class Widget < ActiveRecord::Base; end;
|
14
|
+
|
15
|
+
describe "Fiberized ActiveRecord driver for mysql2" do
|
16
|
+
DELAY = 0.25
|
17
|
+
QUERY = "SELECT sleep(#{DELAY})"
|
18
|
+
|
19
|
+
def establish_connection
|
20
|
+
ActiveRecord::Base.establish_connection(
|
21
|
+
:adapter => 'em_mysql2',
|
22
|
+
:database => 'widgets',
|
23
|
+
:username => 'root',
|
24
|
+
:pool => 10
|
25
|
+
)
|
26
|
+
Widget.delete_all
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should establish AR connection" do
|
30
|
+
EventMachine.synchrony do
|
31
|
+
establish_connection
|
32
|
+
|
33
|
+
result = Widget.find_by_sql(QUERY)
|
34
|
+
result.size.should eql(1)
|
35
|
+
EventMachine.stop
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should fire sequential, synchronous requests within single fiber" do
|
40
|
+
EventMachine.synchrony do
|
41
|
+
establish_connection
|
42
|
+
|
43
|
+
start = now
|
44
|
+
res = []
|
45
|
+
|
46
|
+
res.push Widget.find_by_sql(QUERY)
|
47
|
+
res.push Widget.find_by_sql(QUERY)
|
48
|
+
|
49
|
+
(now - start.to_f).should be_within(DELAY * res.size * 0.15).of(DELAY * res.size)
|
50
|
+
res.size.should eql(2)
|
51
|
+
|
52
|
+
EventMachine.stop
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should fire 100 requests in fibers" do
|
57
|
+
EM.synchrony do
|
58
|
+
establish_connection
|
59
|
+
EM::Synchrony::FiberIterator.new(1..100, 40).each do |i|
|
60
|
+
widget = Widget.create title: 'hi'
|
61
|
+
widget.update_attributes title: 'hello'
|
62
|
+
end
|
63
|
+
EM.stop
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should create widget" do
|
68
|
+
EM.synchrony do
|
69
|
+
establish_connection
|
70
|
+
Widget.create
|
71
|
+
Widget.create
|
72
|
+
Widget.count.should eql(2)
|
73
|
+
EM.stop
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should update widget" do
|
78
|
+
EM.synchrony do
|
79
|
+
establish_connection
|
80
|
+
ActiveRecord::Base.connection.execute("TRUNCATE TABLE widgets;")
|
81
|
+
widget = Widget.create title: 'hi'
|
82
|
+
widget.update_attributes title: 'hello'
|
83
|
+
Widget.find(widget.id).title.should eql('hello')
|
84
|
+
EM.stop
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe "transactions" do
|
89
|
+
it "should work properly" do
|
90
|
+
EM.synchrony do
|
91
|
+
establish_connection
|
92
|
+
EM::Synchrony::FiberIterator.new(1..50, 30).each do |i|
|
93
|
+
widget = Widget.create title: "hi#{i}"
|
94
|
+
ActiveRecord::Base.transaction do
|
95
|
+
widget.update_attributes title: "hello"
|
96
|
+
end
|
97
|
+
ActiveRecord::Base.transaction do
|
98
|
+
raise ActiveRecord::Rollback
|
99
|
+
end
|
100
|
+
end
|
101
|
+
Widget.all.each do |widget|
|
102
|
+
widget.title.should eq('hello')
|
103
|
+
end
|
104
|
+
EM.stop
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
109
|
end
|
data/spec/amqp_spec.rb
CHANGED
@@ -1,146 +1,146 @@
|
|
1
|
-
require "spec/helper/all"
|
2
|
-
|
3
|
-
describe EM::Synchrony::AMQP do
|
4
|
-
|
5
|
-
it "should yield until connection is ready" do
|
6
|
-
EM.synchrony do
|
7
|
-
connection = EM::Synchrony::AMQP.connect
|
8
|
-
connection.connected?.should be_true
|
9
|
-
EM.stop
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
it "should yield until disconnection is complete" do
|
14
|
-
EM.synchrony do
|
15
|
-
connection = EM::Synchrony::AMQP.connect
|
16
|
-
connection.disconnect
|
17
|
-
connection.connected?.should be_false
|
18
|
-
EM.stop
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
it "should yield until the channel is created" do
|
23
|
-
EM.synchrony do
|
24
|
-
connection = EM::Synchrony::AMQP.connect
|
25
|
-
channel = EM::Synchrony::AMQP::Channel.new(connection)
|
26
|
-
channel.should be_kind_of(EM::Synchrony::AMQP::Channel)
|
27
|
-
EM.stop
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
it "should yield until the queue is created" do
|
32
|
-
EM.synchrony do
|
33
|
-
connection = EM::Synchrony::AMQP.connect
|
34
|
-
channel = EM::Synchrony::AMQP::Channel.new(connection)
|
35
|
-
queue = EM::Synchrony::AMQP::Queue.new(channel, "test.em-synchrony.queue1", :auto_delete => true)
|
36
|
-
EM.stop
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
it "should yield when a queue is created from a channel" do
|
41
|
-
EM.synchrony do
|
42
|
-
connection = EM::Synchrony::AMQP.connect
|
43
|
-
channel = EM::Synchrony::AMQP::Channel.new(connection)
|
44
|
-
queue = channel.queue("test.em-synchrony.queue1", :auto_delete => true)
|
45
|
-
EM.stop
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
it "should yield until the exchange is created" do
|
50
|
-
EM.synchrony do
|
51
|
-
connection = EM::Synchrony::AMQP.connect
|
52
|
-
channel = EM::Synchrony::AMQP::Channel.new(connection)
|
53
|
-
|
54
|
-
exchange = EM::Synchrony::AMQP::Exchange.new(channel, :fanout, "test.em-synchrony.exchange")
|
55
|
-
exchange.should be_kind_of(EventMachine::Synchrony::AMQP::Exchange)
|
56
|
-
|
57
|
-
direct = channel.direct("test.em-synchrony.direct")
|
58
|
-
fanout = channel.fanout("test.em-synchrony.fanout")
|
59
|
-
topic = channel.topic("test.em-synchrony.topic")
|
60
|
-
headers = channel.headers("test.em-synchrony.headers")
|
61
|
-
|
62
|
-
direct.should be_kind_of(EventMachine::Synchrony::AMQP::Exchange)
|
63
|
-
fanout.should be_kind_of(EventMachine::Synchrony::AMQP::Exchange)
|
64
|
-
topic.should be_kind_of(EventMachine::Synchrony::AMQP::Exchange)
|
65
|
-
headers.should be_kind_of(EventMachine::Synchrony::AMQP::Exchange)
|
66
|
-
EM.stop
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
it "should publish and receive messages" do
|
71
|
-
nb_msg = 10
|
72
|
-
EM.synchrony do
|
73
|
-
connection = EM::Synchrony::AMQP.connect
|
74
|
-
channel = EM::Synchrony::AMQP::Channel.new(connection)
|
75
|
-
ex = EM::Synchrony::AMQP::Exchange.new(channel, :fanout, "test.em-synchrony.fanout")
|
76
|
-
|
77
|
-
q1 = channel.queue("test.em-synchrony.queues.1", :auto_delete => true)
|
78
|
-
q2 = channel.queue("test.em-synchrony.queues.2", :auto_delete => true)
|
79
|
-
|
80
|
-
q1.bind(ex)
|
81
|
-
q2.bind(ex)
|
82
|
-
|
83
|
-
nb_q1, nb_q2 = 0, 0
|
84
|
-
stop_cb = proc { EM.stop if nb_q1 + nb_q2 == 2 * nb_msg }
|
85
|
-
|
86
|
-
q1.subscribe do |meta, msg|
|
87
|
-
msg.should match(/^Bonjour [0-9]+/)
|
88
|
-
nb_q1 += 1
|
89
|
-
stop_cb.call
|
90
|
-
end
|
91
|
-
|
92
|
-
q2.subscribe do |meta, msg|
|
93
|
-
msg.should match(/^Bonjour [0-9]+/)
|
94
|
-
nb_q2 += 1
|
95
|
-
stop_cb.call
|
96
|
-
end
|
97
|
-
|
98
|
-
Fiber.new do
|
99
|
-
nb_msg.times do |n|
|
100
|
-
ex.publish("Bonjour #{n}")
|
101
|
-
EM::Synchrony.sleep(0.1)
|
102
|
-
end
|
103
|
-
end.resume
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
it "should handle several consumers" do
|
108
|
-
nb_msg = 10
|
109
|
-
EM.synchrony do
|
110
|
-
connection = EM::Synchrony::AMQP.connect
|
111
|
-
channel = EM::Synchrony::AMQP::Channel.new(connection)
|
112
|
-
exchange = EM::Synchrony::AMQP::Exchange.new(channel, :fanout, "test.em-synchrony.consumers.fanout")
|
113
|
-
|
114
|
-
queue = channel.queue("test.em-synchrony.consumers.queue", :auto_delete => true)
|
115
|
-
queue.bind(exchange)
|
116
|
-
|
117
|
-
cons1 = EM::Synchrony::AMQP::Consumer.new(channel, queue)
|
118
|
-
cons2 = EM::Synchrony::AMQP::Consumer.new(channel, queue)
|
119
|
-
|
120
|
-
nb_cons1, nb_cons2 = 0, 0
|
121
|
-
stop_cb = Proc.new do
|
122
|
-
if nb_cons1 + nb_cons2 == nb_msg
|
123
|
-
nb_cons1.should eq(nb_cons2)
|
124
|
-
EM.stop
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
cons1.on_delivery do |meta, msg|
|
129
|
-
msg.should match(/^Bonjour [0-9]+/)
|
130
|
-
nb_cons1 += 1
|
131
|
-
stop_cb.call
|
132
|
-
end.consume
|
133
|
-
|
134
|
-
cons2.on_delivery do |meta, msg|
|
135
|
-
msg.should match(/^Bonjour [0-9]+/)
|
136
|
-
nb_cons2 += 1
|
137
|
-
stop_cb.call
|
138
|
-
end.consume
|
139
|
-
|
140
|
-
10.times do |n|
|
141
|
-
exchange.publish("Bonjour #{n}")
|
142
|
-
EM::Synchrony.sleep(0.1)
|
143
|
-
end
|
144
|
-
end
|
145
|
-
end
|
146
|
-
end
|
1
|
+
require "spec/helper/all"
|
2
|
+
|
3
|
+
describe EM::Synchrony::AMQP do
|
4
|
+
|
5
|
+
it "should yield until connection is ready" do
|
6
|
+
EM.synchrony do
|
7
|
+
connection = EM::Synchrony::AMQP.connect
|
8
|
+
connection.connected?.should be_true
|
9
|
+
EM.stop
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should yield until disconnection is complete" do
|
14
|
+
EM.synchrony do
|
15
|
+
connection = EM::Synchrony::AMQP.connect
|
16
|
+
connection.disconnect
|
17
|
+
connection.connected?.should be_false
|
18
|
+
EM.stop
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should yield until the channel is created" do
|
23
|
+
EM.synchrony do
|
24
|
+
connection = EM::Synchrony::AMQP.connect
|
25
|
+
channel = EM::Synchrony::AMQP::Channel.new(connection)
|
26
|
+
channel.should be_kind_of(EM::Synchrony::AMQP::Channel)
|
27
|
+
EM.stop
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should yield until the queue is created" do
|
32
|
+
EM.synchrony do
|
33
|
+
connection = EM::Synchrony::AMQP.connect
|
34
|
+
channel = EM::Synchrony::AMQP::Channel.new(connection)
|
35
|
+
queue = EM::Synchrony::AMQP::Queue.new(channel, "test.em-synchrony.queue1", :auto_delete => true)
|
36
|
+
EM.stop
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should yield when a queue is created from a channel" do
|
41
|
+
EM.synchrony do
|
42
|
+
connection = EM::Synchrony::AMQP.connect
|
43
|
+
channel = EM::Synchrony::AMQP::Channel.new(connection)
|
44
|
+
queue = channel.queue("test.em-synchrony.queue1", :auto_delete => true)
|
45
|
+
EM.stop
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should yield until the exchange is created" do
|
50
|
+
EM.synchrony do
|
51
|
+
connection = EM::Synchrony::AMQP.connect
|
52
|
+
channel = EM::Synchrony::AMQP::Channel.new(connection)
|
53
|
+
|
54
|
+
exchange = EM::Synchrony::AMQP::Exchange.new(channel, :fanout, "test.em-synchrony.exchange")
|
55
|
+
exchange.should be_kind_of(EventMachine::Synchrony::AMQP::Exchange)
|
56
|
+
|
57
|
+
direct = channel.direct("test.em-synchrony.direct")
|
58
|
+
fanout = channel.fanout("test.em-synchrony.fanout")
|
59
|
+
topic = channel.topic("test.em-synchrony.topic")
|
60
|
+
headers = channel.headers("test.em-synchrony.headers")
|
61
|
+
|
62
|
+
direct.should be_kind_of(EventMachine::Synchrony::AMQP::Exchange)
|
63
|
+
fanout.should be_kind_of(EventMachine::Synchrony::AMQP::Exchange)
|
64
|
+
topic.should be_kind_of(EventMachine::Synchrony::AMQP::Exchange)
|
65
|
+
headers.should be_kind_of(EventMachine::Synchrony::AMQP::Exchange)
|
66
|
+
EM.stop
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should publish and receive messages" do
|
71
|
+
nb_msg = 10
|
72
|
+
EM.synchrony do
|
73
|
+
connection = EM::Synchrony::AMQP.connect
|
74
|
+
channel = EM::Synchrony::AMQP::Channel.new(connection)
|
75
|
+
ex = EM::Synchrony::AMQP::Exchange.new(channel, :fanout, "test.em-synchrony.fanout")
|
76
|
+
|
77
|
+
q1 = channel.queue("test.em-synchrony.queues.1", :auto_delete => true)
|
78
|
+
q2 = channel.queue("test.em-synchrony.queues.2", :auto_delete => true)
|
79
|
+
|
80
|
+
q1.bind(ex)
|
81
|
+
q2.bind(ex)
|
82
|
+
|
83
|
+
nb_q1, nb_q2 = 0, 0
|
84
|
+
stop_cb = proc { EM.stop if nb_q1 + nb_q2 == 2 * nb_msg }
|
85
|
+
|
86
|
+
q1.subscribe do |meta, msg|
|
87
|
+
msg.should match(/^Bonjour [0-9]+/)
|
88
|
+
nb_q1 += 1
|
89
|
+
stop_cb.call
|
90
|
+
end
|
91
|
+
|
92
|
+
q2.subscribe do |meta, msg|
|
93
|
+
msg.should match(/^Bonjour [0-9]+/)
|
94
|
+
nb_q2 += 1
|
95
|
+
stop_cb.call
|
96
|
+
end
|
97
|
+
|
98
|
+
Fiber.new do
|
99
|
+
nb_msg.times do |n|
|
100
|
+
ex.publish("Bonjour #{n}")
|
101
|
+
EM::Synchrony.sleep(0.1)
|
102
|
+
end
|
103
|
+
end.resume
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
it "should handle several consumers" do
|
108
|
+
nb_msg = 10
|
109
|
+
EM.synchrony do
|
110
|
+
connection = EM::Synchrony::AMQP.connect
|
111
|
+
channel = EM::Synchrony::AMQP::Channel.new(connection)
|
112
|
+
exchange = EM::Synchrony::AMQP::Exchange.new(channel, :fanout, "test.em-synchrony.consumers.fanout")
|
113
|
+
|
114
|
+
queue = channel.queue("test.em-synchrony.consumers.queue", :auto_delete => true)
|
115
|
+
queue.bind(exchange)
|
116
|
+
|
117
|
+
cons1 = EM::Synchrony::AMQP::Consumer.new(channel, queue)
|
118
|
+
cons2 = EM::Synchrony::AMQP::Consumer.new(channel, queue)
|
119
|
+
|
120
|
+
nb_cons1, nb_cons2 = 0, 0
|
121
|
+
stop_cb = Proc.new do
|
122
|
+
if nb_cons1 + nb_cons2 == nb_msg
|
123
|
+
nb_cons1.should eq(nb_cons2)
|
124
|
+
EM.stop
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
cons1.on_delivery do |meta, msg|
|
129
|
+
msg.should match(/^Bonjour [0-9]+/)
|
130
|
+
nb_cons1 += 1
|
131
|
+
stop_cb.call
|
132
|
+
end.consume
|
133
|
+
|
134
|
+
cons2.on_delivery do |meta, msg|
|
135
|
+
msg.should match(/^Bonjour [0-9]+/)
|
136
|
+
nb_cons2 += 1
|
137
|
+
stop_cb.call
|
138
|
+
end.consume
|
139
|
+
|
140
|
+
10.times do |n|
|
141
|
+
exchange.publish("Bonjour #{n}")
|
142
|
+
EM::Synchrony.sleep(0.1)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|