polyphony 1.1.1 → 1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +1 -1
  3. data/.github/workflows/test_io_uring.yml +1 -1
  4. data/.rubocop.yml +16 -8
  5. data/CHANGELOG.md +9 -0
  6. data/README.md +2 -1
  7. data/docs/advanced-io.md +9 -1
  8. data/docs/cancellation.md +213 -0
  9. data/docs/readme.md +2 -1
  10. data/examples/core/enumerator.rb +92 -0
  11. data/examples/io/https_server_sni_2.rb +1 -1
  12. data/ext/polyphony/backend_common.c +11 -0
  13. data/ext/polyphony/backend_common.h +2 -0
  14. data/ext/polyphony/backend_io_uring.c +1 -1
  15. data/ext/polyphony/backend_libev.c +1 -1
  16. data/ext/polyphony/polyphony.h +3 -1
  17. data/lib/polyphony/core/debug.rb +24 -29
  18. data/lib/polyphony/core/exceptions.rb +0 -3
  19. data/lib/polyphony/core/sync.rb +0 -3
  20. data/lib/polyphony/core/thread_pool.rb +1 -5
  21. data/lib/polyphony/core/throttler.rb +0 -1
  22. data/lib/polyphony/core/timer.rb +7 -9
  23. data/lib/polyphony/extensions/exception.rb +0 -1
  24. data/lib/polyphony/extensions/fiber.rb +41 -28
  25. data/lib/polyphony/extensions/io.rb +86 -93
  26. data/lib/polyphony/extensions/kernel.rb +52 -16
  27. data/lib/polyphony/extensions/object.rb +7 -6
  28. data/lib/polyphony/extensions/openssl.rb +6 -8
  29. data/lib/polyphony/extensions/pipe.rb +5 -7
  30. data/lib/polyphony/extensions/socket.rb +28 -37
  31. data/lib/polyphony/extensions/thread.rb +2 -4
  32. data/lib/polyphony/extensions/timeout.rb +0 -1
  33. data/lib/polyphony/version.rb +1 -1
  34. data/lib/polyphony.rb +4 -7
  35. data/polyphony.gemspec +1 -1
  36. data/test/test_fiber.rb +6 -6
  37. data/test/test_global_api.rb +3 -3
  38. data/test/test_io.rb +2 -2
  39. data/test/test_socket.rb +2 -2
  40. data/test/test_supervise.rb +1 -1
  41. metadata +5 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a30f7362ca02a1e3b3fe8a76394d5bca243f8dc774b3a6f3f7e9ffa81aac04f7
4
- data.tar.gz: 6fa0684c3e4ddf3fe62ea6d40d5e49578e36042ff57d20848cff6da165ab6027
3
+ metadata.gz: d8069af9a528319585e1112ca1c1fc97981ae7106da100701691d7caf1478fdc
4
+ data.tar.gz: c5ec274b188332a18f8de7b595fad457f25cfc79bf903c05db0aaf7bf20507dc
5
5
  SHA512:
6
- metadata.gz: 1eb08ca45b2129c25c5a1b023aea14fdbade30323a8f5db824f3c33c9b386f24cd541cfa7d536696c2ecd253dd7b4eeb976e87f53a93a59aa1ff96166e54fe05
7
- data.tar.gz: 2f9145ea40f5d8aeb280cbc70249793805e770735c4758ff9f26f80138a752aacdf347e8e6705604a602ddec3216f33bc1797a00d9521585646dc2d8ccd432c4
6
+ metadata.gz: f4e8fbd79cce5062a1fa054838186c925fa626c29faf86f589889f418838b681923863d07603036c2276bd3644ce99eac1f84cdc57b7c57e97787bceec6f4cd0
7
+ data.tar.gz: 22dbe0e38bc0d46ac1dcd543b0ae2b5e4a282c286f6dd40d823347542cbb37e2f21cf3c7c5503389f80f4b057b644c19c683d346a3500ada6a39e83bdbd1516f
@@ -8,7 +8,7 @@ jobs:
8
8
  fail-fast: false
9
9
  matrix:
10
10
  os: [ubuntu-latest, macos-latest]
11
- ruby: ['3.0', '3.1', '3.2', 'head']
11
+ ruby: ['3.1', '3.2', 'head']
12
12
 
13
13
  name: >-
14
14
  ${{matrix.os}}, ${{matrix.ruby}}
@@ -8,7 +8,7 @@ jobs:
8
8
  fail-fast: false
9
9
  matrix:
10
10
  os: [ubuntu-latest]
11
- ruby: ['3.0', '3.1', '3.2', 'head']
11
+ ruby: ['3.1', '3.2', 'head']
12
12
 
13
13
  name: >-
14
14
  ${{matrix.os}}, ${{matrix.ruby}}
data/.rubocop.yml CHANGED
@@ -64,8 +64,7 @@ Layout/HashAlignment:
64
64
 
65
65
  Naming/AccessorMethodName:
66
66
  Exclude:
67
- - lib/polyphony/http/server/http1.rb
68
- - lib/polyphony/http/server/http2_stream.rb
67
+ - lib/polyphony/extensions/fiber.rb
69
68
  - examples/**/*.rb
70
69
 
71
70
  Naming/MethodName:
@@ -74,16 +73,11 @@ Naming/MethodName:
74
73
 
75
74
  Lint/SuppressedException:
76
75
  Exclude:
77
- - lib/polyphony/http/server/http1.rb
78
- - lib/polyphony/http/server/http2.rb
79
- - lib/polyphony/http/server/http2_stream.rb
80
- - lib/polyphony/http/server.rb
81
76
  - examples/**/*.rb
82
77
 
83
78
  Metrics/MethodLength:
84
79
  Max: 12
85
80
  Exclude:
86
- - lib/polyphony/http/server/rack.rb
87
81
  - lib/polyphony/extensions/io.rb
88
82
  - lib/polyphony/extensions/fiber.rb
89
83
  - test/**/*.rb
@@ -96,7 +90,6 @@ Metrics/ModuleLength:
96
90
 
97
91
  Metrics/ClassLength:
98
92
  Exclude:
99
- - lib/polyphony/http/server/http1.rb
100
93
  - lib/polyphony/extensions/io.rb
101
94
  - lib/polyphony/extensions/fiber.rb
102
95
  - lib/polyphony/extensions/object.rb
@@ -176,6 +169,21 @@ Lint/RaiseException:
176
169
  Lint/StructNewOverride:
177
170
  Enabled: true
178
171
 
172
+ Style/NegatedIf:
173
+ Enabled: false
174
+
175
+ Style/NegatedWhile:
176
+ Enabled: false
177
+
178
+ Style/CombinableLoops:
179
+ Enabled: false
180
+
181
+ Style/InfiniteLoop:
182
+ Enabled: false
183
+
184
+ Style/RedundantReturn:
185
+ Enabled: false
186
+
179
187
  Style/ExponentialNotation:
180
188
  Enabled: true
181
189
 
data/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ ## 1.2 2023-06-17
2
+
3
+ - Require Ruby 3.1 or newer
4
+ - Add cancellation doc page
5
+ - Cleanup code
6
+ - Accept array of fiber in `Fiber.await` (in addition to accepting multiple fibers)
7
+ - Automatically create backend for thread if not already created (#100)
8
+ - Fix trap API when used with debug gem (#100)
9
+
1
10
  ## 1.1.1 2023-06-08
2
11
 
3
12
  - Minor improvements to documentation
data/README.md CHANGED
@@ -51,7 +51,7 @@ the hood, Polyphony uses
51
51
  In order to use Polyphony you need to have:
52
52
 
53
53
  - Linux or MacOS (support for Windows will come at a later stage)
54
- - Ruby (MRI) 3.0 or newer
54
+ - Ruby (MRI) 3.1 or newer
55
55
 
56
56
  ### Installing the Polyphony Gem
57
57
 
@@ -77,6 +77,7 @@ $ gem install polyphony
77
77
 
78
78
  - [Overview](docs/overview.md)
79
79
  - [Tutorial](docs/tutorial.md)
80
+ - [All About Cancellation: How to Stop Concurrent Operations](docs/cancellation.md)
80
81
  - [Advanced I/O with Polyphony](docs/advanced-io.md)
81
82
  - [Cheat-Sheet](docs/cheat-sheet.md)
82
83
  - [FAQ](docs/faq.md)
data/docs/advanced-io.md CHANGED
@@ -72,6 +72,10 @@ def send_file_using_splice(src, dest)
72
72
  end
73
73
  IO.splice(pipe, dest, -2**14)
74
74
  end
75
+
76
+ # +----+ IO.splice() +------+ IO.splice() +--------+
77
+ # | io |-------------->| pipe |-------------->| socket |
78
+ # +----+ +------+ +--------+
75
79
  ```
76
80
 
77
81
  There are a few things to notice here: While we have two concurrent operations
@@ -293,11 +297,15 @@ def send_compressed_chunked_response_from_io(socket, io)
293
297
  spin { IO.gzip(io, pipe) }
294
298
  IO.http1_splice_chunked(pipe, socket, MAX_CHUNK_SIZE)
295
299
  end
300
+
301
+ # +----+ IO.gzip() +------+ IO.http1_splice_chunked() +--------+
302
+ # | io |------------>| pipe |---------------------------->| socket |
303
+ # +----+ +------+ +--------+
296
304
  ```
297
305
 
298
306
  The code above looks simple enough, but it actually packs a lot of power in just
299
307
  3 lines of code: we create a pipe, then spin up a fiber that compresses data
300
- data `io` into the pipe. We then serve data from the pipe to the socket using
308
+ from `io` into the pipe. We then splice data from the pipe to the socket using
301
309
  chunked transfer encoding. As discussed above, we do this without actually
302
310
  allocating any Ruby strings for holding the data, we take maximum advantage of
303
311
  kernel buffers (a.k.a. pipes) and we perform the two operations - compressing
@@ -0,0 +1,213 @@
1
+ # @title All About Cancellation: How to Stop Concurrent Operations
2
+
3
+ # All About Cancellation: How to Stop Concurrent Operations
4
+
5
+ ## The Problem of Cancellation
6
+
7
+ Being able to cancel an operation is an important aspect of concurrent
8
+ programming. When you have multiple operations going on at the same time, you
9
+ want to be able to stop an operation in certain circumstances. Imagine sending a
10
+ an HTTP request to some server, and waiting for it to respond. We can wait
11
+ forever, or we can use some kind of mechanism for stopping the operation and
12
+ declaring it a failure. This mechanism, which is generally called cancellation,
13
+ plays a crucial part in how Polyphony works. Let's examine how operations are
14
+ cancelled in Polyphony.
15
+
16
+ ## Cancellation in Polyphony
17
+
18
+ In Polyphony, every operation can be cancelled in the same way, using the same
19
+ APIs. Polyphony provides multiple APIs that can be used to stop an ongoing
20
+ operation, but the underlying mechanism is always the same: the fiber running
21
+ the ongoing operation is scheduled with an exception.
22
+
23
+ Let's revisit how fibers are run in Polyphony (this is covered in more detail in
24
+ the overview document). When a waiting fiber is ready to continue, it is
25
+ scheduled with the result of the operation which it was waiting for. If the
26
+ waiting fiber is scheduled with an exception *before* the operation it is
27
+ waiting for is completed, the operation is stopped, and the exception is raised
28
+ in the context of the fiber once it is switched to. What this means is that any
29
+ fiber waiting for a long-running operation to complete can be stopped at any
30
+ moment, with Polyphony taking care of actually stopping the operation, whether
31
+ it is reading from a file, or from a socket, or waiting for a timer to elapse.
32
+
33
+ On top of this general mechanism of cancellation, Polyphony provides
34
+ cancellation APIs with differing semantics that can be employed by the
35
+ developer. For example, `move_on_after` can be used to stop an operation after a
36
+ timeout without raising an exception, while `cancel_after` can be used to raise
37
+ an exception that must be handled. There's also the `Fiber#restart` API which,
38
+ as its name suggests, allows one to restart any fiber, which might be very
39
+ useful for retrying complex operations.
40
+
41
+ Let's examine how a concurrent operation is stopped in Polyphony:
42
+
43
+ ```ruby
44
+ sleeper = spin { sleep 1 }
45
+ sleep 0.5
46
+ sleeper.raise 'Foo'
47
+ ```
48
+
49
+ In the example above, we spin up a fiber that sleeps for 1 second, we then sleep
50
+ for half a second, and cancel `sleeper` by raising an exception in its context.
51
+ This causes the sleep operation to be cancelled and the fiber to be stopped. The
52
+ exception is further propagated to the context of the main fiber, and the
53
+ program finally exits with an exception message.
54
+
55
+ Another way to stop a concurrent operation is to use the `Fiber#move_on` method,
56
+ which causes the fiber to stop, but without raising an exception:
57
+
58
+ ```ruby
59
+ sleeper = spin { sleep 1; :foo }
60
+ sleep 0.5
61
+ sleeper.move_on :bar
62
+ result = sleeper.await #=> :bar
63
+ ```
64
+
65
+ Using `Fiber#move_on`, we avoid raising an exception which then needs to be
66
+ rescued, and instead cause the fiber to stop, with its return value being the
67
+ value given to `Fiber#move_on`. In the code above, the fiber's result will be
68
+ set to `:bar` instead of `:foo`.
69
+
70
+ ## Using Timeouts
71
+
72
+ Timeouts are probably the most common reason for cancelling an operation. While
73
+ different Ruby gems provide their own APIs and mechanisms for setting timeouts
74
+ (core Ruby has also recently introduced timeout settings for IO operations),
75
+ Polyphony provides a uniform interface for stopping *any* long-running operation
76
+ based on a timeout, using either the core ruby `Timeout` class, or the
77
+ `move_on_after` and `cancel_after` that Polyphony provides.
78
+
79
+ Before we discuss the different timeout APIs, we can first explore how to create
80
+ a timeout mechanism from scratch in Polyphony:
81
+
82
+ ```ruby
83
+ class MyTimeoutError < RuntimeError
84
+ end
85
+
86
+ def with_timeout(duration)
87
+ timeout_fiber = spin do
88
+ sleep duration
89
+ raise MyTimeoutError
90
+ end
91
+ yield
92
+ ensure
93
+ timeout_fiber.stop # this is the same as timeout_fiber.move_on
94
+ end
95
+
96
+ # Usage example:
97
+ with_timeout(5) { sleep 1; :foo } #=> :foo
98
+ with_timeout(5) { sleep 10; :bar } #=> MyTimeoutError raised!
99
+ ```
100
+
101
+ In the code above, we create a `with_timeout` method that takes a duration
102
+ argument. It starts by spinning up a fiber that will sleep for the given
103
+ duration, then raise a custom exception. It then runs the given block by calling
104
+ `yield`. If the given block stops running before the timeout, it exists
105
+ normally, not before making sure to stop the timeout fiber. If the given block
106
+ runs longer than the timeout, the exception raised by the timeout fiber will be
107
+ propagated to the fiber running the block, causing it to be stopped.
108
+
109
+ Now that we have an idea of how we can construct timeouts, let's look at the
110
+ different timeout APIs included in Polyphony:
111
+
112
+ ```ruby
113
+ # Timeout without raising an exception
114
+ move_on_after(5) { ... }
115
+
116
+ # Timeout without raising an exception, returning an arbitrary value
117
+ move_on_after(5, with_value: :foo) { ... } #=> :foo (in case of a timeout)
118
+
119
+ # Timeout raising an exception
120
+ cancel_after(5) { ... } #=> raises a Polyphony::Cancel exception
121
+
122
+ # Timeout raising a custom exception
123
+ cancel_after(5, with_exception: MyExceptionClass) { ... } #=> raises the given exception
124
+
125
+ # Timeout using the Timeout API
126
+ Timeout.timeout(5) { ... } #=> raises Timeout::Error
127
+ ```
128
+
129
+ ## Resetting Ongoing Operations
130
+
131
+ In addition to offering a uniform API for cancelling operations and setting
132
+ timeouts, Polyphony also allows you to reset, or restart, ongoing operations.
133
+ Let's imagine an active search feature that shows the user search results while
134
+ they're typing their search term. How we go about implementing this? We would
135
+ like to show the user search results, but if the user hits another key before
136
+ the results are received from the database, we'd like to cancel the operation
137
+ and relaunch the search. Let's see how Polyphony let's us do this:
138
+
139
+ ```ruby
140
+ searcher = spin do
141
+ peer, term = receive
142
+ results = get_search_results_from_db(term)
143
+ peer << results
144
+ end
145
+
146
+ def search_term_updated(term)
147
+ spin do
148
+ searcher.restart
149
+ searcher << [Fiber.current, term]
150
+ results = receive
151
+ update_search_results(results)
152
+ end
153
+ end
154
+ ```
155
+
156
+ In the example above we use fiber message passing in order to communicate
157
+ between two concurrent operations. Each time `search_term_updated` is called, we
158
+ *restart* the `searcher` fiber, send the term to it, wait for the results and
159
+ them update them in the UI.
160
+
161
+ ## Resettable Timeouts
162
+
163
+ Here's another example of restarting: we have a TCP server that accepts
164
+ connection but would like to close connections after one minute of inactivity.
165
+ We can use a timeout for that, but each time we receive data from the client, we
166
+ need to reset the timeout. Here's how we can do this:
167
+
168
+ ```ruby
169
+ def handle_connection(conn)
170
+ timeout = spin do
171
+ sleep 60
172
+ raise Polyphony::Cancel
173
+ end
174
+ conn.recv_loop do |msg|
175
+ timeout.reset # same as timeout.restart
176
+ handle_message(msg)
177
+ end
178
+ rescue Polyphony::Cancel
179
+ puts 'Closing connection due to inactivity!'
180
+ ensure
181
+ timeout.stop
182
+ end
183
+
184
+ server.accept_loop { |conn| handle_connection(conn) }
185
+ ```
186
+
187
+ In the code above, we create a timeout fiber that sleeps for one minute, then
188
+ raises an exception. We then run a loop waiting for messages from the client,
189
+ and each time a message arrives we reset the timeout. In fact, the standard
190
+ `#move_on_after` and `#cancel_after` APIs also propose a way to reset timeouts.
191
+ Let's examine how to do just that:
192
+
193
+ ```ruby
194
+ def handle_connection(conn)
195
+ cancel_after(60) do |timeout|
196
+ conn.recv_loop do |msg|
197
+ timeout.reset
198
+ handle_message(msg)
199
+ end
200
+ end
201
+ rescue Polyphony::Cancel
202
+ puts 'Closing connection due to inactivity!'
203
+ end
204
+
205
+ server.accept_loop { |conn| handle_connection(conn) }
206
+ ```
207
+
208
+ Here, instead of hand-rolling our own timeout mechanism, we use `#cancel_after`
209
+ but give it a block that takes an argument. When the block is called, this
210
+ argument is actually the timeout fiber that `#cancel_after` spins up, which lets
211
+ us reset it just like in the example before. Also notice how we don't need to
212
+ cleanup the timeout in the ensure block, as `#cancel_after` takes care of it by
213
+ itself.
data/docs/readme.md CHANGED
@@ -53,7 +53,7 @@ the hood, Polyphony uses
53
53
  In order to use Polyphony you need to have:
54
54
 
55
55
  - Linux or MacOS (support for Windows will come at a later stage)
56
- - Ruby (MRI) 3.0 or newer
56
+ - Ruby (MRI) 3.1 or newer
57
57
 
58
58
  ### Installing the Polyphony Gem
59
59
 
@@ -79,6 +79,7 @@ $ gem install polyphony
79
79
 
80
80
  - {file:/docs/overview.md Overview}
81
81
  - {file:/docs/tutorial.md Tutorial}
82
+ - {file:/docs/cancellation.md All About Cancellation: How to Stop Concurrent Operations}
82
83
  - {file:/docs/advanced-io.md Advanced I/O with Polyphony}
83
84
  - {file:/docs/cheat-sheet.md Cheat-Sheet}
84
85
  - {file:/docs/faq.md FAQ}
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+
6
+ class ::Enumerator
7
+ def spin
8
+ map { |i| Object.spin { yield i } }
9
+ end
10
+
11
+ def concurrently(max_fibers: nil, &block)
12
+ return each_concurrently_with_fiber_pool(max_fibers, &block) if max_fibers
13
+
14
+ results = []
15
+ fibers = []
16
+ each_with_index do |i, idx|
17
+ fibers << Object.spin { results[idx] = block.(i) }
18
+ end
19
+ Fiber.await(fibers)
20
+ results
21
+ end
22
+
23
+ private
24
+
25
+ def each_concurrently_with_fiber_pool(max_fibers, &block)
26
+ fiber_count = 0
27
+ results = []
28
+ workers = []
29
+
30
+ each_with_index do |i, idx|
31
+ if fiber_count < max_fibers
32
+ workers << Object.spin do
33
+ loop do
34
+ item, idx = receive
35
+ break if item == :__stop__
36
+ results[idx] = block.(item)
37
+ end
38
+ end
39
+ end
40
+
41
+ fiber = workers.shift
42
+ fiber << [i, idx]
43
+ workers << fiber
44
+ end
45
+ workers.each { |f| f << :__stop__ }
46
+ Fiber.current.await_all_children
47
+ results
48
+ end
49
+ end
50
+
51
+ a = [1, 2, 3]
52
+
53
+ # ff = a.map do |i|
54
+ # spin do
55
+ # puts "#{Fiber.current.inspect} #{i} >>"
56
+ # sleep rand(0.1..0.2)
57
+ # puts "#{Fiber.current.inspect} #{i} <<"
58
+ # end
59
+ # end
60
+
61
+ # Fiber.await(*ff)
62
+
63
+ # puts; puts '*' * 40; puts
64
+
65
+ # ff = a.each.spin do |i|
66
+ # puts "#{Fiber.current.inspect} #{i} >>"
67
+ # sleep 0.1
68
+ # puts "#{Fiber.current.inspect} #{i} <<"
69
+ # end
70
+
71
+ # Fiber.await(*ff)
72
+
73
+ # puts; puts '*' * 40; puts
74
+
75
+ # ff = a.each.concurrently do |i|
76
+ # puts "#{Fiber.current.inspect} #{i} >>"
77
+ # sleep 0.1
78
+ # puts "#{Fiber.current.inspect} #{i} <<"
79
+ # i * 10
80
+ # end
81
+ # p ff: ff
82
+
83
+ puts; puts '*' * 40; puts
84
+
85
+ ff = a.each.concurrently(max_fibers: 2) do |i|
86
+ puts "#{Fiber.current.inspect} #{i} >>"
87
+ sleep i
88
+ puts "#{Fiber.current.inspect} #{i} <<"
89
+ i * 10
90
+ end
91
+
92
+ p ff: ff
@@ -28,7 +28,7 @@ server = Polyphony::Net.tcp_listen('localhost', 1234, opts)
28
28
  puts 'Serving HTTPS on port 1234'
29
29
 
30
30
  begin
31
- server.accept_loop(false) do |socket|
31
+ server.accept_loop(ignore_errors: false) do |socket|
32
32
  spin do
33
33
  while (data = socket.gets("\n", 8192))
34
34
  if data.chomp.empty?
@@ -8,6 +8,8 @@
8
8
  #include "ruby/io/buffer.h"
9
9
  #endif
10
10
 
11
+ VALUE cBackend;
12
+
11
13
  inline void backend_base_initialize(struct Backend_base *base) {
12
14
  runqueue_initialize(&base->runqueue);
13
15
  runqueue_initialize(&base->parked_runqueue);
@@ -604,3 +606,12 @@ VALUE coerce_io_string_or_buffer(VALUE buf) {
604
606
  return StringValue(buf);
605
607
  }
606
608
  }
609
+
610
+ inline VALUE Backend_for_current_thread(void) {
611
+ VALUE backend = rb_ivar_get(rb_thread_current(), ID_ivar_backend);
612
+ if (backend == Qnil) {
613
+ backend = rb_funcall(cBackend, ID_new, 0);
614
+ rb_ivar_set(rb_thread_current(), ID_ivar_backend, backend);
615
+ }
616
+ return backend;
617
+ }
@@ -10,6 +10,8 @@
10
10
  #include "ruby/io.h"
11
11
  #include "runqueue.h"
12
12
 
13
+ extern VALUE cBackend;
14
+
13
15
  #ifndef HAVE_RB_IO_DESCRIPTOR
14
16
  static int rb_io_descriptor_fallback(VALUE io) {
15
17
  rb_io_t *fptr;
@@ -1965,7 +1965,7 @@ void Backend_unpark_fiber(VALUE self, VALUE fiber) {
1965
1965
  }
1966
1966
 
1967
1967
  void Init_Backend(void) {
1968
- VALUE cBackend = rb_define_class_under(mPolyphony, "Backend", rb_cObject);
1968
+ cBackend = rb_define_class_under(mPolyphony, "Backend", rb_cObject);
1969
1969
  rb_define_alloc_func(cBackend, Backend_allocate);
1970
1970
 
1971
1971
  rb_define_method(cBackend, "initialize", Backend_initialize, 0);
@@ -1610,7 +1610,7 @@ void Backend_unpark_fiber(VALUE self, VALUE fiber) {
1610
1610
  void Init_Backend(void) {
1611
1611
  ev_set_allocator(xrealloc);
1612
1612
 
1613
- VALUE cBackend = rb_define_class_under(mPolyphony, "Backend", rb_cObject);
1613
+ cBackend = rb_define_class_under(mPolyphony, "Backend", rb_cObject);
1614
1614
  rb_define_alloc_func(cBackend, Backend_allocate);
1615
1615
 
1616
1616
  rb_define_method(cBackend, "initialize", Backend_initialize, 0);
@@ -25,7 +25,7 @@
25
25
  #define FIBER_TRANSFER(fiber, value) rb_funcall(fiber, ID_transfer, 1, value)
26
26
  #endif
27
27
 
28
- #define BACKEND() (rb_ivar_get(rb_thread_current(), ID_ivar_backend))
28
+ #define BACKEND() Backend_for_current_thread()
29
29
 
30
30
  // SAFE is used to cast functions used in rb_ensure
31
31
  #define SAFE(f) (VALUE (*)(VALUE))(f)
@@ -95,6 +95,8 @@ VALUE Pipe_close(VALUE self);
95
95
 
96
96
  // Backend public interface
97
97
 
98
+ VALUE Backend_for_current_thread();
99
+
98
100
  VALUE Backend_accept(VALUE self, VALUE server_socket, VALUE socket_class);
99
101
  VALUE Backend_accept_loop(VALUE self, VALUE server_socket, VALUE socket_class);
100
102
  VALUE Backend_connect(VALUE self, VALUE io, VALUE addr, VALUE port);