polyphony 0.13 → 0.14

Sign up to get free protection for your applications and to get access to all the features.
Files changed (135) hide show
  1. checksums.yaml +4 -4
  2. data/.gitbook.yaml +5 -0
  3. data/.gitignore +55 -0
  4. data/.rubocop.yml +49 -0
  5. data/CHANGELOG.md +13 -2
  6. data/Gemfile +3 -0
  7. data/Gemfile.lock +31 -0
  8. data/LICENSE +21 -0
  9. data/README.md +35 -18
  10. data/Rakefile +20 -0
  11. data/TODO.md +49 -0
  12. data/docs/getting-started/getting-started.md +10 -0
  13. data/docs/getting-started/tutorial.md +2 -0
  14. data/docs/summary.md +9 -0
  15. data/examples/core/cancel.rb +10 -0
  16. data/examples/core/channel_echo.rb +43 -0
  17. data/examples/core/enumerator.rb +14 -0
  18. data/examples/core/fork.rb +22 -0
  19. data/examples/core/genserver.rb +74 -0
  20. data/examples/core/lock.rb +20 -0
  21. data/examples/core/move_on.rb +11 -0
  22. data/examples/core/move_on_twice.rb +17 -0
  23. data/examples/core/move_on_with_ensure.rb +17 -0
  24. data/examples/core/multiple_async.rb +17 -0
  25. data/examples/core/nested_async.rb +18 -0
  26. data/examples/core/nested_cancel.rb +41 -0
  27. data/examples/core/nested_multiple_async.rb +19 -0
  28. data/examples/core/next_tick.rb +13 -0
  29. data/examples/core/pulse.rb +13 -0
  30. data/examples/core/resource.rb +29 -0
  31. data/examples/core/resource_cancel.rb +34 -0
  32. data/examples/core/resource_delegate.rb +32 -0
  33. data/examples/core/sleep.rb +9 -0
  34. data/examples/core/sleep2.rb +13 -0
  35. data/examples/core/spawn.rb +15 -0
  36. data/examples/core/spawn_cancel.rb +19 -0
  37. data/examples/core/spawn_error.rb +28 -0
  38. data/examples/core/supervisor.rb +22 -0
  39. data/examples/core/supervisor_with_cancel_scope.rb +24 -0
  40. data/examples/core/supervisor_with_error.rb +23 -0
  41. data/examples/core/supervisor_with_manual_move_on.rb +25 -0
  42. data/examples/core/thread.rb +30 -0
  43. data/examples/core/thread_cancel.rb +30 -0
  44. data/examples/core/thread_pool.rb +60 -0
  45. data/examples/core/throttle.rb +17 -0
  46. data/examples/fs/read.rb +37 -0
  47. data/examples/interfaces/pg_client.rb +38 -0
  48. data/examples/interfaces/pg_pool.rb +37 -0
  49. data/examples/interfaces/pg_query.rb +32 -0
  50. data/examples/interfaces/redis_channels.rb +119 -0
  51. data/examples/interfaces/redis_client.rb +21 -0
  52. data/examples/interfaces/redis_pubsub.rb +26 -0
  53. data/examples/interfaces/redis_pubsub_perf.rb +65 -0
  54. data/examples/io/config.ru +3 -0
  55. data/examples/io/echo_client.rb +22 -0
  56. data/examples/io/echo_server.rb +14 -0
  57. data/examples/io/echo_server_with_timeout.rb +33 -0
  58. data/examples/io/echo_stdin.rb +15 -0
  59. data/examples/io/happy_eyeballs.rb +32 -0
  60. data/examples/io/http_client.rb +19 -0
  61. data/examples/io/http_server.js +24 -0
  62. data/examples/io/http_server.rb +16 -0
  63. data/examples/io/http_server_forked.rb +27 -0
  64. data/examples/io/http_server_throttled.rb +16 -0
  65. data/examples/io/http_ws_server.rb +42 -0
  66. data/examples/io/https_client.rb +17 -0
  67. data/examples/io/https_server.rb +23 -0
  68. data/examples/io/https_wss_server.rb +46 -0
  69. data/examples/io/rack_server.rb +19 -0
  70. data/examples/io/rack_server_https.rb +24 -0
  71. data/examples/io/rack_server_https_forked.rb +32 -0
  72. data/examples/io/websocket_server.rb +33 -0
  73. data/examples/io/ws_page.html +34 -0
  74. data/examples/io/wss_page.html +34 -0
  75. data/examples/performance/perf_multi_snooze.rb +21 -0
  76. data/examples/performance/perf_snooze.rb +30 -0
  77. data/examples/performance/thread-vs-fiber/polyphony_server.rb +63 -0
  78. data/examples/performance/thread-vs-fiber/threaded_server.rb +27 -0
  79. data/examples/streams/lines.rb +27 -0
  80. data/examples/streams/stdio.rb +18 -0
  81. data/ext/ev/async.c +168 -0
  82. data/ext/ev/child.c +169 -0
  83. data/ext/ev/ev.h +32 -0
  84. data/ext/ev/ev_ext.c +20 -0
  85. data/ext/ev/ev_module.c +222 -0
  86. data/ext/ev/io.c +405 -0
  87. data/ext/ev/libev.h +9 -0
  88. data/ext/ev/signal.c +119 -0
  89. data/ext/ev/timer.c +197 -0
  90. data/ext/libev/Changes +513 -0
  91. data/ext/libev/LICENSE +37 -0
  92. data/ext/libev/README +58 -0
  93. data/ext/libev/README.embed +3 -0
  94. data/ext/libev/ev.c +5214 -0
  95. data/ext/libev/ev.h +849 -0
  96. data/ext/libev/ev_epoll.c +285 -0
  97. data/ext/libev/ev_kqueue.c +218 -0
  98. data/ext/libev/ev_poll.c +151 -0
  99. data/ext/libev/ev_port.c +189 -0
  100. data/ext/libev/ev_select.c +316 -0
  101. data/ext/libev/ev_vars.h +204 -0
  102. data/ext/libev/ev_win32.c +162 -0
  103. data/ext/libev/ev_wrap.h +200 -0
  104. data/ext/libev/test_libev_win32.c +123 -0
  105. data/lib/polyphony.rb +7 -2
  106. data/lib/polyphony/core.rb +1 -1
  107. data/lib/polyphony/core/{coroutine.rb → coprocess.rb} +10 -10
  108. data/lib/polyphony/core/exceptions.rb +5 -5
  109. data/lib/polyphony/core/supervisor.rb +16 -16
  110. data/lib/polyphony/core/thread.rb +1 -1
  111. data/lib/polyphony/extensions/io.rb +43 -42
  112. data/lib/polyphony/extensions/kernel.rb +10 -34
  113. data/lib/polyphony/extensions/postgres.rb +3 -2
  114. data/lib/polyphony/extensions/redis.rb +1 -1
  115. data/lib/polyphony/extensions/socket.rb +8 -4
  116. data/lib/polyphony/extensions/ssl.rb +0 -54
  117. data/lib/polyphony/http/agent.rb +4 -10
  118. data/lib/polyphony/http/http1.rb +25 -25
  119. data/lib/polyphony/http/http1_request.rb +38 -26
  120. data/lib/polyphony/http/http2.rb +4 -5
  121. data/lib/polyphony/http/http2_request.rb +12 -18
  122. data/lib/polyphony/http/rack.rb +1 -3
  123. data/lib/polyphony/http/server.rb +9 -9
  124. data/lib/polyphony/net.rb +2 -2
  125. data/lib/polyphony/resource_pool.rb +5 -1
  126. data/lib/polyphony/version.rb +1 -1
  127. data/lib/polyphony/websocket.rb +52 -0
  128. data/polyphony.gemspec +31 -0
  129. data/test/test_coprocess.rb +131 -0
  130. data/test/test_core.rb +274 -0
  131. data/test/test_ev.rb +117 -0
  132. data/test/test_io.rb +38 -0
  133. metadata +113 -7
  134. data/lib/polyphony/core/async.rb +0 -36
  135. data/lib/polyphony/net_old.rb +0 -299
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'modulation'
4
+
5
+ Polyphony = import('../../lib/polyphony')
6
+
7
+ spawn do
8
+ puts "going to sleep..."
9
+ cancel_after(1) do
10
+ async {
11
+ sleep(2)
12
+ }.await
13
+ end
14
+ rescue Polyphony::Cancel => e
15
+ puts "got error: #{e}"
16
+ ensure
17
+ puts "woke up"
18
+ end
19
+
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'modulation'
4
+
5
+ Polyphony = import('../../lib/polyphony')
6
+ Polyphony.debug = true
7
+
8
+ def error(t)
9
+ raise "hello #{t}"
10
+ end
11
+
12
+ def spawn_with_error
13
+ spawn { error(2) }
14
+ end
15
+
16
+ spawn do
17
+ error(1)
18
+ rescue => e
19
+ e.cleanup_backtrace
20
+ puts "error: #{e.inspect}"
21
+ puts "backtrace:"
22
+ puts e.backtrace.reverse.join("\n")
23
+ puts
24
+ end
25
+
26
+ spawn_with_error
27
+
28
+ puts "done spawning"
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'modulation'
4
+
5
+ Polyphony = import('../../lib/polyphony')
6
+
7
+ async def my_sleep(t)
8
+ puts "#{t} start"
9
+ sleep(t)
10
+ puts "#{t} done"
11
+ end
12
+
13
+ puts "#{Time.now} waiting..."
14
+ supervise do |s|
15
+ s.spawn my_sleep(1)
16
+ s.spawn my_sleep(2)
17
+ s.spawn my_sleep(3)
18
+ s.spawn {
19
+ puts "fiber count: #{Polyphony::FiberPool.size}"
20
+ }
21
+ end
22
+ puts "#{Time.now} done waiting"
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'modulation'
4
+
5
+ Polyphony = import('../../lib/polyphony')
6
+
7
+ async def my_sleep(t)
8
+ puts "start: #{t}"
9
+ r = sleep(t)
10
+ puts "my_sleep result #{r.inspect}"
11
+ puts "done: #{t}"
12
+ end
13
+
14
+ puts "#{Time.now} going to sleep..."
15
+ move_on_after(0.5) do
16
+ supervise do |s|
17
+ puts "supervise block"
18
+ s.spawn my_sleep(1)
19
+ s.spawn my_sleep(2)
20
+ s.spawn my_sleep(3)
21
+ end
22
+ puts "supervisor done"
23
+ end
24
+ puts "#{Time.now} woke up"
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'modulation'
4
+
5
+ Polyphony = import('../../lib/polyphony')
6
+
7
+ async def my_sleep(t)
8
+ sleep(t)
9
+ raise "blah"
10
+ end
11
+
12
+ spawn do
13
+ puts "#{Time.now} going to sleep..."
14
+ supervise do |s|
15
+ s.spawn my_sleep(1)
16
+ s.spawn my_sleep(2)
17
+ s.spawn my_sleep(3)
18
+ end
19
+ rescue => e
20
+ puts "exception from supervisor: #{e}"
21
+ ensure
22
+ puts "#{Time.now} woke up"
23
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'modulation'
4
+
5
+ Polyphony = import('../../lib/polyphony')
6
+
7
+ async def my_sleep(t)
8
+ puts "start: #{t}"
9
+ sleep(t)
10
+ puts "done: #{t}"
11
+ end
12
+
13
+ puts "#{Time.now} going to sleep..."
14
+ result = supervise do |s|
15
+ fiber = Fiber.current
16
+ spawn do
17
+ sleep(0.5)
18
+ puts "stopping supervisor..."
19
+ s.stop!
20
+ end
21
+ s.spawn my_sleep(1)
22
+ s.spawn my_sleep(2)
23
+ s.spawn my_sleep(3)
24
+ end
25
+ puts "#{Time.now} woke up with #{result.inspect}"
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'modulation'
4
+ require 'digest'
5
+ require 'socket'
6
+
7
+ Polyphony = import('../../lib/polyphony')
8
+
9
+ def lengthy_op
10
+ IO.read('../../docs/reality-ui.bmpr')
11
+ end
12
+
13
+ X = 100
14
+
15
+ def blocking
16
+ t0 = Time.now
17
+ data = lengthy_op
18
+ X.times { lengthy_op }
19
+ puts "read blocking #{data.bytesize} bytes (#{Time.now - t0}s)"
20
+ end
21
+
22
+ def threaded
23
+ t0 = Time.now
24
+ data = Polyphony::Thread.spawn { lengthy_op }.await
25
+ X.times { Polyphony::Thread.spawn { lengthy_op }.await }
26
+ puts "read threaded #{data.bytesize} bytes (#{Time.now - t0}s)"
27
+ end
28
+
29
+ blocking
30
+ threaded
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'modulation'
4
+ require 'digest'
5
+ require 'socket'
6
+
7
+ Polyphony = import('../../lib/polyphony')
8
+
9
+ @op_count = 0
10
+
11
+ def lengthy_op
12
+ @op_count += 1
13
+ acc = 0
14
+ count = 0
15
+ 100.times { acc += IO.read('../../docs/reality-ui.bmpr').bytesize; count += 1; p count }
16
+ acc / count
17
+ end
18
+
19
+ spawn do
20
+ t0 = Time.now
21
+ cancel_after(0.01) do
22
+ data = Polyphony::Thread.spawn { lengthy_op }.await
23
+ puts "read #{data.bytesize} bytes (#{Time.now - t0}s)"
24
+ end
25
+ rescue Exception => e
26
+ puts "error: #{e}"
27
+ ensure
28
+ p @op_count
29
+ end
30
+
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'modulation'
4
+ require 'digest'
5
+ require 'socket'
6
+
7
+ Polyphony = import('../../lib/polyphony')
8
+
9
+ def lengthy_op
10
+ data = IO.read('../../docs/reality-ui.bmpr')
11
+ data.clear
12
+ # Socket.getaddrinfo('debian.org', 80)
13
+ #Digest::SHA256.digest(IO.read('doc/Promise.html'))
14
+ end
15
+
16
+ X = 1000
17
+
18
+ def compare_performance
19
+ t0 = Time.now
20
+ X.times { lengthy_op }
21
+ native_perf = X / (Time.now - t0)
22
+ puts "native performance: #{native_perf}"
23
+ # puts "*" * 40
24
+
25
+ begin
26
+ 1.times do
27
+ t0 = Time.now
28
+ X.times do
29
+ Polyphony::ThreadPool.process { lengthy_op }
30
+ end
31
+ async_perf = X / (Time.now - t0)
32
+ puts "seq thread pool performance: %g (X %0.2f)" % [
33
+ async_perf, async_perf / native_perf
34
+ ]
35
+ end
36
+
37
+ acc = 0
38
+ count = 0
39
+ 10.times do |i|
40
+ t0 = Time.now
41
+ supervise do |s|
42
+ X.times do
43
+ s.spawn Polyphony::ThreadPool.process { lengthy_op }
44
+ end
45
+ end
46
+ thread_pool_perf = X / (Time.now - t0)
47
+ acc += thread_pool_perf
48
+ count += 1
49
+ end
50
+ avg_perf = acc / count
51
+ puts "avg thread pool performance: %g (X %0.2f)" % [
52
+ avg_perf, avg_perf / native_perf
53
+ ]
54
+ rescue Exception => e
55
+ p e
56
+ puts e.backtrace.join("\n")
57
+ end
58
+ end
59
+
60
+ spawn { compare_performance }
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'modulation'
4
+
5
+ Polyphony = import('../../lib/polyphony')
6
+
7
+ spawn {
8
+ throttled_loop(3) { STDOUT << '.' }
9
+ }
10
+
11
+ spawn {
12
+ throttled_loop(rate: 2) { STDOUT << '?' }
13
+ }
14
+
15
+ spawn {
16
+ throttled_loop(interval: 1) { STDOUT << '*' }
17
+ }
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'modulation'
4
+
5
+ Polyphony = import('../../lib/polyphony')
6
+
7
+ PATH = File.expand_path('../../../../docs/dev-journal.md', __dir__)
8
+
9
+ def raw_read_file(x)
10
+ t0 = Time.now
11
+ x.times { IO.orig_read(PATH) }
12
+ puts "raw_read_file: #{Time.now - t0}"
13
+ end
14
+
15
+ X = 100
16
+ Y = 10
17
+
18
+ async def async_read_file
19
+ X.times { IO.read(PATH) }
20
+ end
21
+
22
+ def do_read(supervisor, x)
23
+ x.times { nexus << async { read_file } }
24
+ end
25
+
26
+ raw_read_file(X * Y)
27
+
28
+ spawn do
29
+ t0 = Time.now
30
+ supervise do |s|
31
+ 4.times { s.spawn async_read_file }
32
+ end
33
+ puts "thread_pool_read_file: #{Time.now - t0}"
34
+ rescue Exception => e
35
+ p e
36
+ puts e.backtrace.join("\n")
37
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'modulation'
4
+
5
+ Polyphony = import('../../lib/polyphony')
6
+ Postgres = import('../../lib/polyphony/extensions/postgres')
7
+
8
+ def get_records
9
+ res = $db.query("select 1 as test")
10
+ # puts "got #{res.ntuples} records: #{res.to_a}"
11
+ rescue => e
12
+ puts "got error: #{e.inspect}"
13
+ puts e.backtrace.join("\n")
14
+ end
15
+
16
+ time_printer = spawn do
17
+ last = Time.now
18
+ throttled_loop(10) do
19
+ now = Time.now
20
+ puts now - last
21
+ last = now
22
+ end
23
+ end
24
+
25
+ $db = PG.connect(
26
+ host: '/tmp',
27
+ user: 'reality',
28
+ password: nil,
29
+ dbname: 'reality',
30
+ sslmode: 'require'
31
+ )
32
+
33
+ X = 10000
34
+ t0 = Time.now
35
+ X.times { get_records }
36
+ puts "query rate: #{X / (Time.now - t0)} reqs/s"
37
+
38
+ time_printer.stop
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'modulation'
4
+
5
+ Polyphony = import('../../lib/polyphony')
6
+ Postgres = import('../../lib/polyphony/extensions/postgres')
7
+
8
+ PGOPTS = {
9
+ host: '/tmp',
10
+ user: 'reality',
11
+ password: nil,
12
+ dbname: 'reality',
13
+ sslmode: 'require'
14
+ }
15
+
16
+ DBPOOL = Polyphony::ResourcePool.new(limit: 8) { PG.connect(PGOPTS) }
17
+
18
+ def get_records(db)
19
+ res = db.query("select pg_sleep(0.0001) as test")
20
+ # puts "got #{res.ntuples} records: #{res.to_a}"
21
+ rescue => e
22
+ puts "got error: #{e.inspect}"
23
+ puts e.backtrace.join("\n")
24
+ end
25
+
26
+ CONCURRENCY = ARGV.first ? ARGV.first.to_i : 10
27
+ puts "concurrency: #{CONCURRENCY}"
28
+
29
+ DBPOOL.preheat!
30
+ t0 = Time.now
31
+ count = 0
32
+ coprocs = CONCURRENCY.times.map {
33
+ spawn { loop { DBPOOL.acquire { |db| get_records(db); count += 1 } } }
34
+ }
35
+ sleep 3
36
+ puts "count: #{count} query rate: #{count / (Time.now - t0)} queries/s"
37
+ coprocs.each(&:interrupt)
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'modulation'
4
+
5
+ Polyphony = import('../../lib/polyphony')
6
+ Postgres = import('../../lib/polyphony/extensions/postgres')
7
+
8
+ DB = PG.connect(
9
+ host: '/tmp',
10
+ user: 'reality',
11
+ password: nil,
12
+ dbname: 'reality',
13
+ sslmode: 'require'
14
+ )
15
+
16
+ def perform(error)
17
+ puts "*" * 40
18
+ DB.transaction do
19
+ res = DB.query("select 1 as test")
20
+ puts "result: #{res.to_a}"
21
+ raise 'hello' if error
22
+ DB.transaction do
23
+ res = DB.query("select 2 as test")
24
+ puts "result: #{res.to_a}"
25
+ end
26
+ end
27
+ rescue => e
28
+ puts "error: #{e.inspect}"
29
+ end
30
+
31
+ perform(true)
32
+ perform(false)
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'modulation'
4
+
5
+ Polyphony = import('../../lib/polyphony')
6
+ import('../../lib/polyphony/extensions/redis')
7
+
8
+ class RedisChannel < Polyphony::Channel
9
+ def self.publish_connection
10
+ @publish_connection ||= Redis.new
11
+ end
12
+
13
+ def self.subscribe_connection
14
+ @subscribe_connection ||= Redis.new
15
+ end
16
+
17
+ CHANNEL_MASTER_TOPIC = 'channel_master'
18
+
19
+ def self.start_monitor
20
+ @channels = {}
21
+ @monitor = spawn do
22
+ subscribe_connection.subscribe(CHANNEL_MASTER_TOPIC) do |on|
23
+ on.message do |topic, message|
24
+ message = Marshal.load(message)
25
+ topic == CHANNEL_MASTER_TOPIC ? handle_master_message(message) :
26
+ handle_channel_message(topic, message)
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ def self.stop_monitor
33
+ @monitor&.interrupt
34
+ end
35
+
36
+ def self.handle_master_message(message)
37
+ case message[:kind]
38
+ when :subscribe
39
+ subscribe_connection.subscribe(message[:topic])
40
+ when :unsubscribe
41
+ subscribe_connection.unsubscribe(message[:topic])
42
+ end
43
+ end
44
+
45
+ def self.handle_channel_message(topic, message)
46
+ channel = @channels[topic]
47
+ channel&.did_receive(message)
48
+ end
49
+
50
+ def self.watch(channel)
51
+ @channels[channel.topic] = channel
52
+ spawn do
53
+ publish_connection.publish(CHANNEL_MASTER_TOPIC, Marshal.dump({
54
+ kind: :subscribe,
55
+ topic: channel.topic
56
+ }))
57
+ end
58
+ end
59
+
60
+ def self.unwatch(channel)
61
+ @channels.delete(channel.topic)
62
+ spawn do
63
+ publish_connection.publish(CHANNEL_MASTER_TOPIC, Marshal.dump({
64
+ kind: :unsubscribe,
65
+ topic: channel.topic
66
+ }))
67
+ end
68
+ end
69
+
70
+ def self.channel_topic(channel)
71
+ "channel_#{channel.object_id}"
72
+ end
73
+
74
+ attr_reader :topic
75
+
76
+ def initialize(topic)
77
+ @topic = topic
78
+ @waiting_queue = []
79
+ RedisChannel.watch(self)
80
+ end
81
+
82
+ def close
83
+ super
84
+ RedisChannel.unwatch(self)
85
+ end
86
+
87
+ def <<(o)
88
+ RedisChannel.publish_connection.publish(@topic, Marshal.dump(o))
89
+ end
90
+
91
+ def did_receive(o)
92
+ @waiting_queue.shift&.schedule(o)
93
+ end
94
+
95
+ def receive
96
+ @waiting_queue << Fiber.current
97
+ suspend
98
+ end
99
+ end
100
+
101
+ RedisChannel.start_monitor
102
+ channel = RedisChannel.new('channel1')
103
+
104
+ spawn do
105
+ loop do
106
+ message = channel.receive
107
+ puts "got message: #{message}"
108
+ end
109
+ end
110
+
111
+ spawn do
112
+ move_on_after(3) do
113
+ throttled_loop(1) do
114
+ channel << Time.now
115
+ end
116
+ end
117
+ channel.close
118
+ RedisChannel.stop_monitor
119
+ end