polyphony 0.26 → 0.27

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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -3
  3. data/CHANGELOG.md +8 -0
  4. data/Gemfile.lock +1 -1
  5. data/TODO.md +34 -14
  6. data/examples/core/xx-mt-scheduler.rb +349 -0
  7. data/examples/core/xx-queue-async.rb +120 -0
  8. data/examples/core/xx-readpartial.rb +18 -0
  9. data/examples/core/xx-thread-selector-sleep.rb +51 -0
  10. data/examples/core/xx-thread-selector-snooze.rb +46 -0
  11. data/examples/core/xx-thread-sleep.rb +17 -0
  12. data/examples/core/xx-thread-snooze.rb +34 -0
  13. data/examples/performance/snooze.rb +5 -5
  14. data/examples/performance/thread-vs-fiber/polyphony_mt_server.rb +73 -0
  15. data/examples/performance/thread-vs-fiber/polyphony_server.rb +12 -4
  16. data/ext/gyro/async.c +58 -61
  17. data/ext/gyro/child.c +50 -59
  18. data/ext/gyro/gyro.c +84 -192
  19. data/ext/gyro/gyro.h +29 -2
  20. data/ext/gyro/gyro_ext.c +6 -0
  21. data/ext/gyro/io.c +87 -96
  22. data/ext/gyro/queue.c +109 -0
  23. data/ext/gyro/selector.c +117 -0
  24. data/ext/gyro/signal.c +44 -54
  25. data/ext/gyro/socket.c +15 -20
  26. data/ext/gyro/thread.c +203 -0
  27. data/ext/gyro/timer.c +79 -50
  28. data/ext/libev/ev.c +3 -0
  29. data/lib/polyphony.rb +7 -12
  30. data/lib/polyphony/core/global_api.rb +5 -1
  31. data/lib/polyphony/core/throttler.rb +6 -11
  32. data/lib/polyphony/extensions/core.rb +2 -0
  33. data/lib/polyphony/extensions/fiber.rb +11 -13
  34. data/lib/polyphony/extensions/thread.rb +52 -0
  35. data/lib/polyphony/version.rb +1 -1
  36. data/test/helper.rb +8 -3
  37. data/test/test_fiber.rb +2 -2
  38. data/test/test_global_api.rb +4 -5
  39. data/test/test_gyro.rb +3 -2
  40. data/test/test_io.rb +1 -0
  41. data/test/test_supervisor.rb +3 -3
  42. data/test/test_thread.rb +44 -0
  43. data/test/test_throttler.rb +41 -0
  44. data/test/test_timer.rb +13 -7
  45. metadata +17 -6
  46. data/examples/core/xx-thread_cancel.rb +0 -28
  47. data/examples/performance/thread.rb +0 -27
  48. data/lib/polyphony/core/thread.rb +0 -23
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+
6
+ Thread.event_selector = ->(thread) { Gyro::Selector.new(thread) }
7
+
8
+ def bm(sym, x)
9
+ t0 = Time.now
10
+ send(sym, x)
11
+ elapsed = Time.now - t0
12
+ STDOUT.orig_puts "#{sym} #{x / elapsed}"
13
+ end
14
+
15
+ def test_queue(x)
16
+ queue = Queue.new
17
+ async = Gyro::Async.new
18
+ t1 = Thread.new do
19
+ Thread.current.setup_fiber_scheduling
20
+ counter = 0
21
+ loop {
22
+ async.await if queue.empty?
23
+ v = queue.pop
24
+ counter += 1
25
+ break if counter == x
26
+ }
27
+ ensure
28
+ Thread.current.stop_event_selector
29
+ end
30
+ t2 = Thread.new do
31
+ Thread.current.setup_fiber_scheduling
32
+ x.times { |i|
33
+ queue.push i
34
+ async.signal!
35
+ }
36
+ ensure
37
+ Thread.current.stop_event_selector
38
+ end
39
+ t1.join
40
+ t2.join
41
+ end
42
+
43
+ def test_array_mutex(x)
44
+ queue = []
45
+ mutex = Mutex.new
46
+ async = Gyro::Async.new
47
+ t1 = Thread.new {
48
+ Thread.current.setup_fiber_scheduling
49
+ counter = 0
50
+ loop {
51
+ async.await if mutex.synchronize { queue.empty? }
52
+ v = mutex.synchronize { queue.shift }
53
+ counter += 1
54
+ break if counter == x
55
+ }
56
+ }
57
+ t2 = Thread.new {
58
+ Thread.current.setup_fiber_scheduling
59
+ x.times { |i|
60
+ mutex.synchronize { queue.push i }
61
+ async.signal!
62
+ }
63
+ }
64
+ t1.join
65
+ t2.join
66
+ end
67
+
68
+ # class Gyro::Queue
69
+ # def initialize
70
+ # @wait_queue = []
71
+ # @queue = []
72
+ # end
73
+
74
+ # def <<(value)
75
+ # async = @wait_queue.pop
76
+ # if async
77
+ # async.signal! value
78
+ # else
79
+ # @queue.push value
80
+ # end
81
+ # end
82
+
83
+ # def shift
84
+ # if @queue.empty?
85
+ # async = Gyro::Async.new
86
+ # @wait_queue << async
87
+ # async.await
88
+ # else
89
+ # @queue.shift
90
+ # end
91
+ # end
92
+ # end
93
+
94
+ def test_gyro_queue(x)
95
+ queue = Gyro::Queue.new
96
+ x.times { |i| queue << i }
97
+ t1 = Thread.new do
98
+ Thread.current.setup_fiber_scheduling
99
+ x.times { queue.shift }
100
+ ensure
101
+ Thread.current.stop_event_selector
102
+ end
103
+ t2 = Thread.new do
104
+ Thread.current.setup_fiber_scheduling
105
+ x.times { |i| queue << i }
106
+ ensure
107
+ Thread.current.stop_event_selector
108
+ end
109
+ t1.join
110
+ t2.join
111
+ end
112
+
113
+ Thread.current.setup_fiber_scheduling
114
+ # bm(:test_array_mutex, 1000000)
115
+ loop {
116
+ STDOUT.orig_puts "*" * 40
117
+ bm(:test_queue, 1000000)
118
+ bm(:test_gyro_queue, 1000000)
119
+ }
120
+
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+
6
+ Exception.__disable_sanitized_backtrace__ = true
7
+
8
+ buffer = +''
9
+ socket = TCPSocket.new('google.com', 80)
10
+ socket.send("GET /?q=time HTTP/1.1\r\nHost: google.com\r\n\r\n", 0)
11
+ move_on_after(5) {
12
+ while (data = socket.readpartial(8192))
13
+ buffer << data
14
+ end
15
+ }
16
+
17
+ puts "*" * 40
18
+ p buffer
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+
6
+ # Thread.event_selector = Gyro::Selector
7
+ # Thread.current.setup_fiber_scheduling
8
+
9
+ t = Gyro::Timer.new(1, 1)
10
+ s = spin {
11
+ last = Time.now
12
+ loop do
13
+ t.await
14
+ now = Time.now
15
+ puts "elapsed: #{now - last}"
16
+ last = now
17
+ end
18
+ }
19
+ s.await
20
+ exit!
21
+
22
+ p :go_to_sleep
23
+ sleep 1
24
+ p :wake_up
25
+
26
+ puts "*" * 60
27
+
28
+ t = Thread.new {
29
+ Thread.current.setup_fiber_scheduling
30
+
31
+ spin {
32
+ p :go_to_sleep1
33
+ sleep 1
34
+ p :wake_up1
35
+ }
36
+
37
+ spin {
38
+ p :go_to_sleep2
39
+ sleep 2
40
+ p :wake_up2
41
+ }
42
+
43
+ p :waiting
44
+ suspend
45
+ }
46
+
47
+ t.join
48
+
49
+ at_exit {
50
+ p :at_exit
51
+ }
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+
6
+ Exception.__disable_sanitized_backtrace__ = true
7
+ Thread.event_selector = Gyro::Selector
8
+ Thread.current.setup_fiber_scheduling
9
+
10
+ def bm(fibers, iterations)
11
+ count = {}
12
+
13
+ t0 = Time.now
14
+ threads = (1..1).map do |i|
15
+ Thread.new do
16
+ count[i] = 0
17
+ supervise do |s|
18
+ fibers.times do
19
+ s.spin do
20
+ iterations.times do
21
+ snooze
22
+ count[i] += 1
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ threads.each(&:join)
30
+ dt = Time.now - t0
31
+ count = count.values.inject(0, &:+)
32
+ puts "#{[fibers, iterations].inspect} count: #{count} #{count / dt.to_f}/s"
33
+ end
34
+
35
+ # GC.disable
36
+
37
+ loop {
38
+ puts "*" * 60
39
+ bm(1, 1_000_000)
40
+ bm(10, 100_000)
41
+ bm(100, 10_000)
42
+ bm(1_000, 1_000)
43
+ bm(10_000, 100)
44
+ bm(100_000, 10)
45
+ # bm(1_000_000, 1)
46
+ }
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+
6
+ Exception.__disable_sanitized_backtrace__ = true
7
+
8
+ t = Thread.new {
9
+ t0 = Time.now
10
+ puts "sleep"
11
+ sleep 0.01
12
+ puts "wake up #{Time.now - t0}"
13
+ }
14
+
15
+ t0 = Time.now
16
+ t.join
17
+ puts "elapsed: #{Time.now - t0}"
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+
6
+ Exception.__disable_sanitized_backtrace__ = true
7
+
8
+ def work
9
+ puts "creating fibers..."
10
+ 100000.times {
11
+ spin {
12
+ loop { snooze }
13
+ }
14
+ }
15
+
16
+ puts "done"
17
+ suspend
18
+ end
19
+
20
+ def work_thread
21
+ t = Thread.new { work }
22
+ t.join
23
+ end
24
+
25
+ main = Fiber.current
26
+ p [:main, main]
27
+
28
+ # trap('SIGINT') do
29
+ # p [:SIGINT, Fiber.current]
30
+ # p caller
31
+ # exit!
32
+ # end
33
+
34
+ work
@@ -32,8 +32,8 @@ X.times { snooze }
32
32
  dt = Time.now - t0
33
33
  puts format('%d/s', (X / dt))
34
34
 
35
- STDOUT << 'Kernel#sleep: '
36
- t0 = Time.now
37
- X.times { sleep(0) }
38
- dt = Time.now - t0
39
- puts "%d/s" % (X / dt)
35
+ # STDOUT << 'Kernel#sleep: '
36
+ # t0 = Time.now
37
+ # X.times { sleep(0) }
38
+ # dt = Time.now - t0
39
+ # puts "%d/s" % (X / dt)
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+ require 'http/parser'
6
+
7
+ class Http::Parser
8
+ def setup_async
9
+ self.on_message_complete = proc { @request_complete = true }
10
+ end
11
+
12
+ def parse(data)
13
+ self << data
14
+ return nil unless @request_complete
15
+
16
+ @request_complete = nil
17
+ self
18
+ end
19
+ end
20
+
21
+ def handle_client(socket)
22
+ parser = Http::Parser.new
23
+ req = nil
24
+ parser.on_message_complete = proc do |env|
25
+ req = parser
26
+ end
27
+ while (data = socket.readpartial(8192)) do
28
+ parser << data
29
+ if req
30
+ handle_request(socket, req)
31
+ req = nil
32
+ snooze
33
+ end
34
+ end
35
+ rescue IOError, SystemCallError => e
36
+ # do nothing
37
+ ensure
38
+ socket.close rescue nil
39
+ parser.reset!
40
+ end
41
+
42
+ def handle_request(client, parser)
43
+ status_code = 200
44
+ data = "Hello world!\n"
45
+ headers = "Content-Length: #{data.bytesize}\r\n"
46
+ client.write "HTTP/1.1 #{status_code}\r\n#{headers}\r\n#{data}"
47
+ end
48
+
49
+ $incoming = Gyro::Queue.new
50
+
51
+ $threads = (1..4).map {
52
+ Thread.new {
53
+ Thread.current.setup_fiber_scheduling
54
+ loop {
55
+ conn = $incoming.pop
56
+ spin { handle_client(conn) }
57
+ }
58
+ }
59
+ }
60
+
61
+ spin do
62
+ server = TCPServer.open('0.0.0.0', 1234)
63
+ puts "listening on port 1234"
64
+
65
+ loop do
66
+ client = server.accept
67
+ $incoming << client
68
+ # spin { handle_client(client) }
69
+ end
70
+ end
71
+
72
+ puts "pid #{Process.pid}"
73
+ suspend
@@ -20,16 +20,16 @@ end
20
20
 
21
21
  def handle_client(socket)
22
22
  parser = Http::Parser.new
23
- req = nil
23
+ reqs = []
24
24
  parser.on_message_complete = proc do |env|
25
- req = parser
25
+ reqs << Object.new # parser
26
26
  end
27
27
  while (data = socket.readpartial(8192)) do
28
28
  parser << data
29
- if req
29
+ while (req = reqs.shift)
30
30
  handle_request(socket, req)
31
31
  req = nil
32
- snooze
32
+ # snooze
33
33
  end
34
34
  end
35
35
  rescue IOError, SystemCallError => e
@@ -53,6 +53,14 @@ spin do
53
53
  loop do
54
54
  client = server.accept
55
55
  spin { handle_client(client) }
56
+ # snooze
57
+ end
58
+ end
59
+
60
+ spin do
61
+ loop do
62
+ sleep 1
63
+ puts "#{Time.now} #{Thread.current.fiber_scheduling_stats}"
56
64
  end
57
65
  end
58
66
 
@@ -2,35 +2,34 @@
2
2
 
3
3
  struct Gyro_Async {
4
4
  struct ev_async ev_async;
5
+ struct ev_loop *ev_loop;
5
6
  int active;
6
7
  VALUE fiber;
7
8
  VALUE value;
8
9
  };
9
10
 
10
- static VALUE cGyro_Async = Qnil;
11
+ VALUE cGyro_Async = Qnil;
11
12
 
12
- /* Allocator/deallocator */
13
- static VALUE Gyro_Async_allocate(VALUE klass);
14
- static void Gyro_Async_mark(void *ptr);
15
- static void Gyro_Async_free(void *ptr);
16
- static size_t Gyro_Async_size(const void *ptr);
17
-
18
- /* Methods */
19
- static VALUE Gyro_Async_initialize(VALUE self);
20
-
21
- static VALUE Gyro_Async_signal(int argc, VALUE *argv, VALUE self);
22
- static VALUE Gyro_Async_await(VALUE self);
23
-
24
- void Gyro_Async_callback(struct ev_loop *ev_loop, struct ev_async *async, int revents);
13
+ static void Gyro_Async_mark(void *ptr) {
14
+ struct Gyro_Async *async = ptr;
15
+ if (async->fiber != Qnil) {
16
+ rb_gc_mark(async->fiber);
17
+ }
18
+ if (async->value != Qnil) {
19
+ rb_gc_mark(async->value);
20
+ }
21
+ }
25
22
 
26
- /* async encapsulates an async watcher */
27
- void Init_Gyro_Async() {
28
- cGyro_Async = rb_define_class_under(mGyro, "Async", rb_cData);
29
- rb_define_alloc_func(cGyro_Async, Gyro_Async_allocate);
23
+ static void Gyro_Async_free(void *ptr) {
24
+ struct Gyro_Async *async = ptr;
25
+ if (async->active) {
26
+ printf("Async watcher garbage collected while still active!\n");
27
+ }
28
+ xfree(async);
29
+ }
30
30
 
31
- rb_define_method(cGyro_Async, "initialize", Gyro_Async_initialize, 0);
32
- rb_define_method(cGyro_Async, "signal!", Gyro_Async_signal, -1);
33
- rb_define_method(cGyro_Async, "await", Gyro_Async_await, 0);
31
+ static size_t Gyro_Async_size(const void *ptr) {
32
+ return sizeof(struct Gyro_Async);
34
33
  }
35
34
 
36
35
  static const rb_data_type_t Gyro_Async_type = {
@@ -45,24 +44,17 @@ static VALUE Gyro_Async_allocate(VALUE klass) {
45
44
  return TypedData_Wrap_Struct(klass, &Gyro_Async_type, async);
46
45
  }
47
46
 
48
- static void Gyro_Async_mark(void *ptr) {
49
- struct Gyro_Async *async = ptr;
50
- if (async->fiber != Qnil) {
51
- rb_gc_mark(async->fiber);
52
- }
53
- if (async->value != Qnil) {
54
- rb_gc_mark(async->value);
55
- }
56
- }
47
+ void Gyro_Async_callback(struct ev_loop *ev_loop, struct ev_async *ev_async, int revents) {
48
+ struct Gyro_Async *async = (struct Gyro_Async*)ev_async;
57
49
 
58
- static void Gyro_Async_free(void *ptr) {
59
- struct Gyro_Async *async = ptr;
60
- ev_async_stop(EV_DEFAULT, &async->ev_async);
61
- xfree(async);
62
- }
50
+ ev_async_stop(async->ev_loop, ev_async);
51
+ async->active = 0;
63
52
 
64
- static size_t Gyro_Async_size(const void *ptr) {
65
- return sizeof(struct Gyro_Async);
53
+ if (async->fiber != Qnil) {
54
+ Gyro_schedule_fiber(async->fiber, async->value);
55
+ async->fiber = Qnil;
56
+ async->value = Qnil;
57
+ }
66
58
  }
67
59
 
68
60
  #define GetGyro_Async(obj, async) \
@@ -77,34 +69,27 @@ static VALUE Gyro_Async_initialize(VALUE self) {
77
69
  async->active = 0;
78
70
 
79
71
  ev_async_init(&async->ev_async, Gyro_Async_callback);
72
+ async->ev_loop = 0;
80
73
 
81
74
  return Qnil;
82
75
  }
83
76
 
84
- void Gyro_Async_callback(struct ev_loop *ev_loop, struct ev_async *ev_async, int revents) {
85
- struct Gyro_Async *async = (struct Gyro_Async*)ev_async;
86
-
87
- ev_async_stop(EV_DEFAULT, ev_async);
88
- async->active = 0;
89
-
90
- if (async->fiber != Qnil) {
91
- Gyro_schedule_fiber(async->fiber, async->value);
92
- async->fiber = Qnil;
93
- async->value = Qnil;
94
- }
95
- }
96
-
97
77
  static VALUE Gyro_Async_signal(int argc, VALUE *argv, VALUE self) {
98
78
  struct Gyro_Async *async;
99
79
  GetGyro_Async(self, async);
100
80
 
81
+ if (!async->ev_loop) {
82
+ // printf("signal! called before await\n");
83
+ return Qnil;
84
+ }
85
+
101
86
  async->value = (argc == 1) ? argv[0] : Qnil;
102
- ev_async_send(EV_DEFAULT, &async->ev_async);
87
+ ev_async_send(async->ev_loop, &async->ev_async);
103
88
 
104
89
  return Qnil;
105
90
  }
106
91
 
107
- static VALUE Gyro_Async_await(VALUE self) {
92
+ VALUE Gyro_Async_await(VALUE self) {
108
93
  struct Gyro_Async *async;
109
94
  VALUE ret;
110
95
 
@@ -113,22 +98,34 @@ static VALUE Gyro_Async_await(VALUE self) {
113
98
  async->fiber = rb_fiber_current();
114
99
  if (!async->active) {
115
100
  async->active = 1;
116
- ev_async_start(EV_DEFAULT, &async->ev_async);
101
+ async->ev_loop = Gyro_Selector_current_thread_ev_loop();
102
+ ev_async_start(async->ev_loop, &async->ev_async);
117
103
  }
118
104
 
119
- ret = Gyro_await();
105
+ ret = Fiber_await();
106
+ RB_GC_GUARD(ret);
107
+
108
+ if (async->active) {
109
+ async->active = 0;
110
+ async->fiber = Qnil;
111
+ ev_async_stop(async->ev_loop, &async->ev_async);
112
+ async->value = Qnil;
113
+ }
120
114
 
121
115
  // fiber is resumed
122
116
  if (RTEST(rb_obj_is_kind_of(ret, rb_eException))) {
123
- if (async->active) {
124
- async->active = 0;
125
- ev_async_stop(EV_DEFAULT, &async->ev_async);
126
- async->fiber = Qnil;
127
- async->value = Qnil;
128
- }
129
117
  return rb_funcall(rb_mKernel, ID_raise, 1, ret);
130
118
  }
131
119
  else {
132
120
  return ret;
133
121
  }
134
- }
122
+ }
123
+
124
+ void Init_Gyro_Async() {
125
+ cGyro_Async = rb_define_class_under(mGyro, "Async", rb_cData);
126
+ rb_define_alloc_func(cGyro_Async, Gyro_Async_allocate);
127
+
128
+ rb_define_method(cGyro_Async, "initialize", Gyro_Async_initialize, 0);
129
+ rb_define_method(cGyro_Async, "signal!", Gyro_Async_signal, -1);
130
+ rb_define_method(cGyro_Async, "await", Gyro_Async_await, 0);
131
+ }