polyphony 0.26 → 0.27

Sign up to get free protection for your applications and to get access to all the features.
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
+ }