eventmachine 0.12.10 → 1.0.0.beta.1

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.
Files changed (69) hide show
  1. data/.gitignore +2 -0
  2. data/Gemfile +1 -0
  3. data/README +1 -2
  4. data/Rakefile +4 -76
  5. data/docs/DEFERRABLES +183 -70
  6. data/docs/KEYBOARD +15 -11
  7. data/docs/LIGHTWEIGHT_CONCURRENCY +84 -24
  8. data/docs/SMTP +3 -1
  9. data/docs/SPAWNED_PROCESSES +84 -25
  10. data/eventmachine.gemspec +19 -26
  11. data/examples/ex_tick_loop_array.rb +15 -0
  12. data/examples/ex_tick_loop_counter.rb +32 -0
  13. data/ext/binder.cpp +0 -1
  14. data/ext/cmain.cpp +36 -11
  15. data/ext/cplusplus.cpp +1 -1
  16. data/ext/ed.cpp +104 -113
  17. data/ext/ed.h +24 -30
  18. data/ext/em.cpp +347 -248
  19. data/ext/em.h +23 -16
  20. data/ext/eventmachine.h +5 -3
  21. data/ext/extconf.rb +5 -3
  22. data/ext/fastfilereader/extconf.rb +5 -3
  23. data/ext/fastfilereader/mapper.cpp +1 -1
  24. data/ext/kb.cpp +1 -3
  25. data/ext/pipe.cpp +9 -11
  26. data/ext/project.h +12 -4
  27. data/ext/rubymain.cpp +138 -89
  28. data/java/src/com/rubyeventmachine/EmReactor.java +1 -0
  29. data/lib/em/channel.rb +1 -1
  30. data/lib/em/connection.rb +6 -1
  31. data/lib/em/deferrable.rb +16 -2
  32. data/lib/em/iterator.rb +270 -0
  33. data/lib/em/protocols.rb +1 -1
  34. data/lib/em/protocols/httpclient.rb +5 -0
  35. data/lib/em/protocols/line_protocol.rb +28 -0
  36. data/lib/em/protocols/smtpserver.rb +101 -8
  37. data/lib/em/protocols/stomp.rb +1 -1
  38. data/lib/{pr_eventmachine.rb → em/pure_ruby.rb} +1 -11
  39. data/lib/em/queue.rb +1 -0
  40. data/lib/em/streamer.rb +1 -1
  41. data/lib/em/tick_loop.rb +85 -0
  42. data/lib/em/timers.rb +2 -1
  43. data/lib/em/version.rb +1 -1
  44. data/lib/eventmachine.rb +38 -84
  45. data/lib/jeventmachine.rb +1 -0
  46. data/tests/test_attach.rb +13 -3
  47. data/tests/test_basic.rb +60 -95
  48. data/tests/test_channel.rb +3 -2
  49. data/tests/test_defer.rb +14 -12
  50. data/tests/test_deferrable.rb +35 -0
  51. data/tests/test_file_watch.rb +1 -1
  52. data/tests/test_futures.rb +1 -1
  53. data/tests/test_hc.rb +40 -68
  54. data/tests/test_httpclient.rb +15 -6
  55. data/tests/test_httpclient2.rb +3 -2
  56. data/tests/test_inactivity_timeout.rb +3 -3
  57. data/tests/test_ltp.rb +13 -5
  58. data/tests/test_next_tick.rb +1 -1
  59. data/tests/test_pending_connect_timeout.rb +2 -2
  60. data/tests/test_process_watch.rb +36 -34
  61. data/tests/test_proxy_connection.rb +52 -0
  62. data/tests/test_pure.rb +10 -1
  63. data/tests/test_sasl.rb +1 -1
  64. data/tests/test_send_file.rb +16 -7
  65. data/tests/test_servers.rb +1 -1
  66. data/tests/test_tick_loop.rb +59 -0
  67. data/tests/test_timers.rb +13 -15
  68. metadata +45 -17
  69. data/web/whatis +0 -7
@@ -30,8 +30,9 @@ class TestEventMachineChannel < Test::Unit::TestCase
30
30
  EM.run do
31
31
  c = EM::Channel.new
32
32
  c.pop{ |v| s = v }
33
- c << 1
34
- c << 2
33
+ c.push(1,2,3)
34
+ c << 4
35
+ c << 5
35
36
  EM.next_tick { EM.stop }
36
37
  end
37
38
  assert_equal 1, s
@@ -28,20 +28,22 @@ $:.unshift "../lib"
28
28
  require 'eventmachine'
29
29
  require 'test/unit'
30
30
 
31
- class TestDeferUsage < Test::Unit::TestCase
31
+ unless RUBY_VERSION >= '1.9.0'
32
+ class TestDeferUsage < Test::Unit::TestCase
32
33
 
33
- def test_defers
34
- n = 0
35
- n_times = 20
36
- EM.run {
37
- n_times.times {
38
- work_proc = proc { n += 1 }
39
- callback = proc { EM.stop if n == n_times }
40
- EM.defer work_proc, callback
34
+ def test_defers
35
+ n = 0
36
+ n_times = 20
37
+ EM.run {
38
+ n_times.times {
39
+ work_proc = proc { n += 1 }
40
+ callback = proc { EM.stop if n == n_times }
41
+ EM.defer work_proc, callback
42
+ }
41
43
  }
42
- }
43
- assert_equal( n, n_times )
44
- end unless RUBY_VERSION >= '1.9.0'
44
+ assert_equal( n, n_times )
45
+ end
45
46
 
47
+ end
46
48
  end
47
49
 
@@ -0,0 +1,35 @@
1
+ $:.unshift "../lib"
2
+ require 'eventmachine'
3
+ require 'test/unit'
4
+
5
+ class TestDeferrable < Test::Unit::TestCase
6
+ class Later
7
+ include EM::Deferrable
8
+ end
9
+
10
+ def test_timeout_without_args
11
+ $args = "unset"
12
+
13
+ EM.run {
14
+ df = Later.new
15
+ df.timeout(0.2)
16
+ df.errback { $args = "none" }
17
+ EM.add_timer(0.5) { EM.stop }
18
+ }
19
+
20
+ assert_equal("none", $args)
21
+ end
22
+
23
+ def test_timeout_with_args
24
+ $args = "unset"
25
+
26
+ EM.run {
27
+ df = Later.new
28
+ df.timeout(0.2, :timeout, :foo)
29
+ df.errback { |type, name| $args = [type, name] }
30
+ EM.add_timer(0.5) { EM.stop }
31
+ }
32
+
33
+ assert_equal([:timeout, :foo], $args)
34
+ end
35
+ end
@@ -38,7 +38,7 @@ class TestFileWatch < Test::Unit::TestCase
38
38
  File.open(file.path, 'w'){ |f| f.puts 'hi' }
39
39
 
40
40
  # delete it
41
- EM.add_timer(0.25){ file.close; file.delete }
41
+ EM.add_timer(0.01){ file.close; file.delete }
42
42
  }
43
43
 
44
44
  assert_equal($path, $tmp_path)
@@ -190,7 +190,7 @@ class TestFutures < Test::Unit::TestCase
190
190
  d = EM::DefaultDeferrable.new
191
191
  d.callback {n = 1; EM.stop}
192
192
  d.errback {n = 2; EM.stop}
193
- d.timeout(1)
193
+ d.timeout(0.01)
194
194
  }
195
195
  assert_equal( 2, n )
196
196
  end
@@ -24,7 +24,6 @@
24
24
  #
25
25
  #
26
26
 
27
- # $:.unshift "../lib"
28
27
  require 'eventmachine'
29
28
  require 'test/unit'
30
29
 
@@ -49,27 +48,25 @@ class TestHeaderAndContentProtocol < Test::Unit::TestCase
49
48
  @request << [hdrs, content]
50
49
  end
51
50
  end
51
+
52
+ class StopOnUnbind < EM::Connection
53
+ def unbind
54
+ EM.add_timer(0.1) { EM.stop }
55
+ end
56
+ end
52
57
 
53
58
  def test_no_content
54
59
  the_connection = nil
55
- EventMachine.run {
56
- EventMachine.start_server( TestHost, TestPort, SimpleTest ) do |conn|
60
+ EM.run {
61
+ EM.start_server( TestHost, TestPort, SimpleTest ) do |conn|
57
62
  the_connection = conn
58
63
  end
59
- EventMachine.add_timer(4) {raise "test timed out"}
64
+ setup_timeout
60
65
 
61
- client = Module.new do
62
- def unbind
63
- EM.add_timer(0.1) { EM.stop }
64
- end
65
-
66
- def post_init
67
- send_data [ "aaa\n", "bbb\r\n", "ccc\n", "\n" ].join
68
- close_connection_after_writing
69
- end
66
+ EM.connect TestHost, TestPort, StopOnUnbind do |c|
67
+ c.send_data [ "aaa\n", "bbb\r\n", "ccc\n", "\n" ].join
68
+ c.close_connection_after_writing
70
69
  end
71
-
72
- EventMachine.connect( TestHost, TestPort, client )
73
70
  }
74
71
  assert_equal( ["aaa"], the_connection.first_header )
75
72
  assert_equal( [%w(aaa bbb ccc)], the_connection.my_headers )
@@ -84,26 +81,14 @@ class TestHeaderAndContentProtocol < Test::Unit::TestCase
84
81
  EventMachine.start_server( TestHost, TestPort, SimpleTest ) do |conn|
85
82
  the_connection = conn
86
83
  end
87
- EventMachine.add_timer(4) { assert(false, 'test timeout'); EM.stop }
88
-
89
- client = Module.new do
90
- define_method(:headers) { headers }
91
- define_method(:content) { content }
92
-
93
- def unbind
94
- EM.add_timer(0.1) { EM.stop }
95
- end
84
+ setup_timeout
96
85
 
97
- def post_init
98
- headers.each { |h| send_data "#{h}\r\n" }
99
- send_data "\n"
100
- send_data content
101
- close_connection_after_writing
102
- end
86
+ EM.connect TestHost, TestPort, StopOnUnbind do |c|
87
+ headers.each { |h| c.send_data "#{h}\r\n" }
88
+ c.send_data "\n"
89
+ c.send_data content
90
+ c.close_connection_after_writing
103
91
  end
104
-
105
- EventMachine.connect( TestHost, TestPort, client )
106
-
107
92
  }
108
93
  assert_equal( ["aaa"], the_connection.first_header )
109
94
  assert_equal( [headers], the_connection.my_headers )
@@ -118,27 +103,16 @@ class TestHeaderAndContentProtocol < Test::Unit::TestCase
118
103
  EventMachine.start_server( TestHost, TestPort, SimpleTest ) do |conn|
119
104
  the_connection = conn
120
105
  end
121
- EventMachine.add_timer(4) { assert(false, 'test timeout'); EM.stop }
122
-
123
- client = Module.new do
124
- define_method(:headers) { headers }
125
- define_method(:content) { content }
126
-
127
- def unbind
128
- EM.add_timer(0.1) { EM.stop }
129
- end
106
+ setup_timeout
130
107
 
131
- def post_init
132
- 5.times do
133
- headers.each { |h| send_data "#{h}\r\n" }
134
- send_data "\n"
135
- send_data content
136
- end
137
- close_connection_after_writing
108
+ EventMachine.connect( TestHost, TestPort, StopOnUnbind ) do |c|
109
+ 5.times do
110
+ headers.each { |h| c.send_data "#{h}\r\n" }
111
+ c.send_data "\n"
112
+ c.send_data content
138
113
  end
114
+ c.close_connection_after_writing
139
115
  end
140
-
141
- EventMachine.connect( TestHost, TestPort, client )
142
116
  }
143
117
  assert_equal( ["aaa"] * 5, the_connection.first_header )
144
118
  assert_equal( [headers] * 5, the_connection.my_headers )
@@ -184,25 +158,14 @@ class TestHeaderAndContentProtocol < Test::Unit::TestCase
184
158
  EventMachine.start_server( TestHost, TestPort, SimpleTest ) do |conn|
185
159
  the_connection = conn
186
160
  end
187
- EventMachine.add_timer(4) {raise "test timed out"}
188
-
189
- client = Module.new do
190
- define_method(:headers) { headers }
191
- define_method(:content) { content }
161
+ setup_timeout
192
162
 
193
- def unbind
194
- EM.add_timer(0.1) { EM.stop }
195
- end
196
-
197
- def post_init
198
- headers.each { |h| send_data "#{h}\r\n" }
199
- send_data "\n"
200
- send_data content
201
- close_connection_after_writing
202
- end
163
+ EventMachine.connect( TestHost, TestPort, StopOnUnbind ) do |c|
164
+ headers.each { |h| c.send_data "#{h}\r\n" }
165
+ c.send_data "\n"
166
+ c.send_data content
167
+ c.close_connection_after_writing
203
168
  end
204
-
205
- EventMachine.connect( TestHost, TestPort, client )
206
169
  }
207
170
 
208
171
  hsh = the_connection.headers_2_hash( the_connection.my_headers.shift )
@@ -215,4 +178,13 @@ class TestHeaderAndContentProtocol < Test::Unit::TestCase
215
178
  assert_equal(expect, hsh)
216
179
  end
217
180
 
181
+ def setup_timeout(timeout = 4)
182
+ EM.schedule {
183
+ start_time = EM.current_time
184
+ EM.add_periodic_timer(0.01) {
185
+ raise "timeout" if EM.current_time - start_time >= timeout
186
+ }
187
+ }
188
+ end
189
+
218
190
  end
@@ -44,7 +44,7 @@ class TestHttpClient < Test::Unit::TestCase
44
44
  def test_http_client
45
45
  ok = false
46
46
  EventMachine.run {
47
- c = EventMachine::Protocols::HttpClient.send :request, :host => "www.bayshorenetworks.com", :port => 80
47
+ c = EventMachine::Protocols::HttpClient.send :request, :host => "www.google.com", :port => 80
48
48
  c.callback {
49
49
  ok = true
50
50
  EventMachine.stop
@@ -59,7 +59,7 @@ class TestHttpClient < Test::Unit::TestCase
59
59
  def test_http_client_1
60
60
  ok = false
61
61
  EventMachine.run {
62
- c = EventMachine::Protocols::HttpClient.send :request, :host => "www.bayshorenetworks.com", :port => 80
62
+ c = EventMachine::Protocols::HttpClient.send :request, :host => "www.google.com", :port => 80
63
63
  c.callback {ok = true; EventMachine.stop}
64
64
  c.errback {EventMachine.stop}
65
65
  }
@@ -71,7 +71,7 @@ class TestHttpClient < Test::Unit::TestCase
71
71
  def test_http_client_2
72
72
  ok = false
73
73
  EventMachine.run {
74
- c = EventMachine::Protocols::HttpClient.send :request, :host => "www.bayshorenetworks.com", :port => 80
74
+ c = EventMachine::Protocols::HttpClient.send :request, :host => "www.google.com", :port => 80
75
75
  c.callback {|result|
76
76
  ok = true;
77
77
  EventMachine.stop
@@ -154,6 +154,15 @@ class TestHttpClient < Test::Unit::TestCase
154
154
  close_connection_after_writing
155
155
  end
156
156
  end
157
+
158
+ def setup_timeout(timeout = 4)
159
+ EM.schedule {
160
+ start_time = EM.current_time
161
+ EM.add_periodic_timer(0.01) {
162
+ raise "timeout" if EM.current_time - start_time >= timeout
163
+ }
164
+ }
165
+ end
157
166
 
158
167
  # TODO, this is WRONG. The handler is asserting an HTTP 1.1 request, but the client
159
168
  # is sending a 1.0 request. Gotta fix the client
@@ -161,7 +170,7 @@ class TestHttpClient < Test::Unit::TestCase
161
170
  response = nil
162
171
  EventMachine.run {
163
172
  EventMachine.start_server Localhost, Localport, PostContent
164
- EventMachine.add_timer(2) {raise "timed out"}
173
+ setup_timeout(2)
165
174
  c = EventMachine::Protocols::HttpClient.request(
166
175
  :host=>Localhost,
167
176
  :port=>Localport,
@@ -186,7 +195,7 @@ class TestHttpClient < Test::Unit::TestCase
186
195
  def test_cookie
187
196
  ok = false
188
197
  EM.run {
189
- c = EM::Protocols::HttpClient.send :request, :host => "www.bayshorenetworks.com", :port => 80, :cookie=>"aaa=bbb"
198
+ c = EM::Protocols::HttpClient.send :request, :host => "www.google.com", :port => 80, :cookie=>"aaa=bbb"
190
199
  c.callback {|result|
191
200
  ok = true;
192
201
  EventMachine.stop
@@ -202,7 +211,7 @@ class TestHttpClient < Test::Unit::TestCase
202
211
  ok = false
203
212
  EM.run {
204
213
  c = EM::P::HttpClient.request(
205
- :host => "www.bayshorenetworks.com",
214
+ :host => "www.google.com",
206
215
  :port => 80,
207
216
  :version => "1.0"
208
217
  )
@@ -121,7 +121,8 @@ class TestHttpClient2 < Test::Unit::TestCase
121
121
  e.callback {
122
122
  headers2 = e.headers
123
123
  }
124
- EM::Timer.new(1) {EM.stop}
124
+ EM.tick_loop { EM.stop if headers && headers2 }
125
+ EM.add_timer(1) { EM.stop }
125
126
  }
126
127
  assert(headers)
127
128
  assert(headers2)
@@ -141,7 +142,7 @@ class TestHttpClient2 < Test::Unit::TestCase
141
142
  def test_https_get
142
143
  d = nil
143
144
  EM.run {
144
- http = EM::P::HttpClient2.connect :host => 'www.amazon.com', :port => 443, :ssl => true
145
+ http = EM::P::HttpClient2.connect :host => 'www.apple.com', :port => 443, :ssl => true
145
146
  d = http.get "/"
146
147
  d.callback {
147
148
  EM.stop
@@ -37,14 +37,14 @@ class TestInactivityTimeout < Test::Unit::TestCase
37
37
  EM.run {
38
38
  EM.heartbeat_interval = 0.1
39
39
  EM.start_server("127.0.0.1", 12345)
40
- EM.add_timer(0.2) {
40
+ EM.add_timer(0.1) {
41
41
  $start = Time.now
42
42
  c = EM.connect("127.0.0.1", 12345, TimeoutHandler)
43
- c.comm_inactivity_timeout = 2.5
43
+ c.comm_inactivity_timeout = 0.2
44
44
  }
45
45
  }
46
46
 
47
- assert_in_delta(2.5, (Time.now - $start), 0.3)
47
+ assert_in_delta(0.2, (Time.now - $start), 0.1)
48
48
  end
49
49
 
50
50
  end
@@ -55,7 +55,15 @@ class TestLineAndTextProtocol < Test::Unit::TestCase
55
55
  EM.add_timer(0.1) { EM.stop }
56
56
  end
57
57
  end
58
-
58
+
59
+ def setup_timeout(timeout = 4)
60
+ EM.schedule {
61
+ start_time = EM.current_time
62
+ EM.add_periodic_timer(0.01) {
63
+ raise "timeout" if EM.current_time - start_time >= timeout
64
+ }
65
+ }
66
+ end
59
67
 
60
68
  def test_simple_lines
61
69
  lines_received = []
@@ -63,7 +71,7 @@ class TestLineAndTextProtocol < Test::Unit::TestCase
63
71
  EventMachine.start_server( TestHost, TestPort, SimpleLineTest ) do |conn|
64
72
  conn.instance_eval "@line_buffer = lines_received"
65
73
  end
66
- EventMachine.add_timer(4) {assert(false, "test timed out")}
74
+ setup_timeout
67
75
 
68
76
  EventMachine.connect TestHost, TestPort, StopClient do |c|
69
77
  c.send_data "aaa\nbbb\r\nccc\n"
@@ -87,7 +95,7 @@ class TestLineAndTextProtocol < Test::Unit::TestCase
87
95
  EventMachine.start_server( TestHost, TestPort, SimpleLineTest ) do |conn|
88
96
  conn.instance_eval "@error_message = lines_received"
89
97
  end
90
- EventMachine.add_timer(4) {assert(false, "test timed out")}
98
+ setup_timeout
91
99
 
92
100
  EventMachine.connect TestHost, TestPort, StopClient do |c|
93
101
  c.send_data "a" * (16*1024 + 1)
@@ -126,7 +134,7 @@ class TestLineAndTextProtocol < Test::Unit::TestCase
126
134
  EventMachine.start_server( TestHost, TestPort, LineAndTextTest ) do |conn|
127
135
  conn.instance_eval "@lines = lines_received; @text = text_received"
128
136
  end
129
- EventMachine.add_timer(4) {assert(false, "test timed out")}
137
+ setup_timeout
130
138
 
131
139
  EventMachine.connect TestHost, TestPort, StopClient do |c|
132
140
  c.set_receive_data { |data| output << data }
@@ -166,7 +174,7 @@ class TestLineAndTextProtocol < Test::Unit::TestCase
166
174
  EventMachine.start_server( TestHost, TestPort, BinaryTextTest ) do |conn|
167
175
  conn.instance_eval "@lines = lines_received; @text = text_received"
168
176
  end
169
- EventMachine.add_timer(4) {assert(false, "test timed out")}
177
+ setup_timeout
170
178
 
171
179
  EventMachine.connect TestHost, TestPort, StopClient do |c|
172
180
  c.set_receive_data { |data| output << data }
@@ -61,7 +61,7 @@ class TestNextTick < Test::Unit::TestCase
61
61
  def test_pre_run_queue
62
62
  x = false
63
63
  EM.next_tick { EM.stop; x = true }
64
- EM.run { EM.add_timer(0.2) { EM.stop } }
64
+ EM.run { EM.add_timer(0.01) { EM.stop } }
65
65
  assert x
66
66
  end
67
67
 
@@ -39,10 +39,10 @@ class TestPendingConnectTimeout < Test::Unit::TestCase
39
39
  EM.heartbeat_interval = 0.1
40
40
  $start = Time.now
41
41
  c = EM.connect("1.2.3.4", 54321, TimeoutHandler)
42
- c.pending_connect_timeout = 5
42
+ c.pending_connect_timeout = 0.2
43
43
  }
44
44
 
45
- assert_in_delta(5, (Time.now - $start), 0.3)
45
+ assert_in_delta(0.2, (Time.now - $start), 0.1)
46
46
  end
47
47
 
48
48
  end
@@ -2,47 +2,49 @@ $:.unshift "../lib"
2
2
  require 'eventmachine'
3
3
  require 'test/unit'
4
4
 
5
- class TestProcessWatch < Test::Unit::TestCase
6
- module ParentProcessWatcher
7
- def process_forked
8
- $forked = true
5
+ if EM.kqueue?
6
+ class TestProcessWatch < Test::Unit::TestCase
7
+ module ParentProcessWatcher
8
+ def process_forked
9
+ $forked = true
10
+ end
9
11
  end
10
- end
11
12
 
12
- module ChildProcessWatcher
13
- def process_exited
14
- $exited = true
15
- end
16
- def unbind
17
- $unbind = true
18
- EM.stop
13
+ module ChildProcessWatcher
14
+ def process_exited
15
+ $exited = true
16
+ end
17
+ def unbind
18
+ $unbind = true
19
+ EM.stop
20
+ end
19
21
  end
20
- end
21
22
 
22
- def setup
23
- EM.kqueue = true if EM.kqueue?
24
- end
23
+ def setup
24
+ EM.kqueue = true
25
+ end
25
26
 
26
- def teardown
27
- EM.kqueue = false if EM.kqueue?
28
- end
27
+ def teardown
28
+ EM.kqueue = false
29
+ end
29
30
 
30
- def test_events
31
- EM.run{
32
- # watch ourselves for a fork notification
33
- EM.watch_process(Process.pid, ParentProcessWatcher)
34
- $fork_pid = fork{ sleep }
35
- child = EM.watch_process($fork_pid, ChildProcessWatcher)
36
- $pid = child.pid
31
+ def test_events
32
+ EM.run{
33
+ # watch ourselves for a fork notification
34
+ EM.watch_process(Process.pid, ParentProcessWatcher)
35
+ $fork_pid = fork{ sleep }
36
+ child = EM.watch_process($fork_pid, ChildProcessWatcher)
37
+ $pid = child.pid
37
38
 
38
- EM.add_timer(0.5){
39
- Process.kill('TERM', $fork_pid)
39
+ EM.add_timer(0.2){
40
+ Process.kill('TERM', $fork_pid)
41
+ }
40
42
  }
41
- }
42
43
 
43
- assert_equal($pid, $fork_pid)
44
- assert($forked)
45
- assert($exited)
46
- assert($unbind)
44
+ assert_equal($pid, $fork_pid)
45
+ assert($forked)
46
+ assert($exited)
47
+ assert($unbind)
48
+ end
47
49
  end
48
- end
50
+ end