polyphony 0.79 → 0.81.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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +19 -0
  3. data/Gemfile.lock +2 -1
  4. data/examples/core/raw_buffer_test.rb +8 -0
  5. data/examples/core/zlib_stream.rb +16 -0
  6. data/ext/polyphony/backend_common.c +2 -1
  7. data/ext/polyphony/backend_common.h +7 -2
  8. data/ext/polyphony/backend_io_uring.c +69 -30
  9. data/ext/polyphony/polyphony.c +8 -0
  10. data/ext/polyphony/polyphony.h +11 -0
  11. data/lib/polyphony/adapters/fs.rb +4 -0
  12. data/lib/polyphony/adapters/process.rb +14 -1
  13. data/lib/polyphony/adapters/redis.rb +28 -0
  14. data/lib/polyphony/adapters/sequel.rb +19 -1
  15. data/lib/polyphony/core/debug.rb +129 -72
  16. data/lib/polyphony/core/exceptions.rb +21 -6
  17. data/lib/polyphony/core/global_api.rb +228 -73
  18. data/lib/polyphony/core/resource_pool.rb +65 -20
  19. data/lib/polyphony/core/sync.rb +57 -12
  20. data/lib/polyphony/core/thread_pool.rb +42 -5
  21. data/lib/polyphony/core/throttler.rb +21 -5
  22. data/lib/polyphony/core/timer.rb +125 -1
  23. data/lib/polyphony/extensions/exception.rb +36 -6
  24. data/lib/polyphony/extensions/fiber.rb +238 -57
  25. data/lib/polyphony/extensions/io.rb +4 -2
  26. data/lib/polyphony/extensions/kernel.rb +9 -4
  27. data/lib/polyphony/extensions/object.rb +8 -0
  28. data/lib/polyphony/extensions/openssl.rb +3 -1
  29. data/lib/polyphony/extensions/socket.rb +458 -39
  30. data/lib/polyphony/extensions/thread.rb +108 -43
  31. data/lib/polyphony/extensions/timeout.rb +12 -1
  32. data/lib/polyphony/extensions.rb +1 -0
  33. data/lib/polyphony/net.rb +66 -7
  34. data/lib/polyphony/version.rb +1 -1
  35. data/lib/polyphony.rb +0 -2
  36. data/test/test_backend.rb +6 -2
  37. data/test/test_global_api.rb +0 -23
  38. data/test/test_resource_pool.rb +1 -1
  39. data/test/test_throttler.rb +0 -6
  40. data/test/test_trace.rb +87 -0
  41. metadata +10 -8
  42. data/lib/polyphony/core/channel.rb +0 -15
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 34ed5a7685a8986f1fe5fd6cd714ee049633952746ecb8bced1aee73553c6c99
4
- data.tar.gz: 03ded408e8849a921d0cfbbe1a232c5cea83cfbd58710efade4900f6240f511c
3
+ metadata.gz: f4584f87975e018fe58887f4c6786bfe8c631a7d2fed3717e6822d72741fcc92
4
+ data.tar.gz: 9353ed759d256a035619d0b18dc53569fba6fdbb0709d79defb585fbf5f63f5a
5
5
  SHA512:
6
- metadata.gz: 91fe5a0979c0158315e942bba88f563a47926e78ad3da0ff8e483ee91aff1482af1f2fc3e3aa4cacd49e787531627bfc3295d45439e99d8400b4736b1a749443
7
- data.tar.gz: 3c1eaaccec661c4f3e4c07853fbaaaab19222837d5ac25b4098856bd1b1367833cbc35a58602fb9c30ff8e7cd63d04f79206bf07438a1e842eb86871c8036063
6
+ metadata.gz: c4d2d6e7457e49b4803b44e2125b54465a1104635b3d0a1d127538335401eeca6612095a2dcde9b6e149d939d15c6de2a1cbdf892a114535a5f0bd00ea1936e8
7
+ data.tar.gz: 5f4eb0a66dec6614d542d2c89cad791e2eb7e27ab9ae74f6bf60c8d4bd9831a098f531be6a619d4f13662631e86f1ac6ddc7bf5bf5f005b8926a4dfc3c7e43ef
data/CHANGELOG.md CHANGED
@@ -1,3 +1,22 @@
1
+ ## 0.81.1 2022-03-03
2
+
3
+ - Fix `Backend_recv` regression
4
+
5
+ ## 0.81 2022-03-03
6
+
7
+ - Restore public visibility for `Polyphony::Process.kill_process`
8
+ - Restore public visibility for `Polyphony::Net.setup_alpn`
9
+
10
+ ## 0.80 2022-02-28
11
+
12
+ - Prevent reentry into `trace_proc`
13
+ - Rename `__parser_read_method__` to `__read_method__`
14
+ - Rename `ResourcePool#preheat!` to `#fill`.
15
+ - Remove ability to use `#cancel_after` or `#move_on` without a block
16
+ - Add #move_on alias to `Fiber#interrupt`
17
+ - Allow specifying exception in `Fiber#cancel`
18
+ - Remove deprecated `Polyphony::Channel` class
19
+
1
20
  ## 0.79 2022-02-19
2
21
 
3
22
  - Overhaul trace events system (#73)
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- polyphony (0.79)
4
+ polyphony (0.81.1)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -60,6 +60,7 @@ GEM
60
60
  unicode-display_width (1.8.0)
61
61
 
62
62
  PLATFORMS
63
+ ruby
63
64
  universal-darwin
64
65
  universal-freebsd
65
66
  universal-linux
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+
6
+ puts '* pre'
7
+ Polyphony.backend_test(STDOUT, "Hello, world!\n")
8
+ puts '* post'
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+ require 'polyphony'
5
+ require 'zlib'
6
+
7
+ r, w = IO.pipe
8
+ writer = Zlib::GzipWriter.new(w)
9
+
10
+ writer << 'chunk'
11
+ writer.flush
12
+ p pos: writer.pos
13
+ # w.close
14
+ writer.close
15
+
16
+ p r.read
@@ -17,6 +17,7 @@ inline void backend_base_initialize(struct Backend_base *base) {
17
17
  base->idle_gc_last_time = 0;
18
18
  base->idle_proc = Qnil;
19
19
  base->trace_proc = Qnil;
20
+ base->in_trace_proc = 0;
20
21
  }
21
22
 
22
23
  inline void backend_base_finalize(struct Backend_base *base) {
@@ -137,7 +138,7 @@ inline void backend_base_unpark_fiber(struct Backend_base *base, VALUE fiber) {
137
138
  }
138
139
 
139
140
  inline void backend_trace(struct Backend_base *base, int argc, VALUE *argv) {
140
- if (base->trace_proc == Qnil) return;
141
+ if (base->trace_proc == Qnil || base->in_trace_proc) return;
141
142
 
142
143
  rb_funcallv(base->trace_proc, ID_call, argc, argv);
143
144
  }
@@ -32,6 +32,7 @@ struct Backend_base {
32
32
  double idle_gc_last_time;
33
33
  VALUE idle_proc;
34
34
  VALUE trace_proc;
35
+ unsigned int in_trace_proc;
35
36
  };
36
37
 
37
38
  void backend_base_initialize(struct Backend_base *base);
@@ -46,8 +47,12 @@ void backend_trace(struct Backend_base *base, int argc, VALUE *argv);
46
47
  struct backend_stats backend_base_stats(struct Backend_base *base);
47
48
 
48
49
  // tracing
49
- #define SHOULD_TRACE(base) ((base)->trace_proc != Qnil)
50
- #define TRACE(base, ...) rb_funcall((base)->trace_proc, ID_call, __VA_ARGS__)
50
+ #define SHOULD_TRACE(base) ((base)->trace_proc != Qnil && !(base)->in_trace_proc)
51
+ #define TRACE(base, ...) { \
52
+ (base)->in_trace_proc = 1; \
53
+ rb_funcall((base)->trace_proc, ID_call, __VA_ARGS__); \
54
+ (base)->in_trace_proc = 0; \
55
+ }
51
56
  #define COND_TRACE(base, ...) if (SHOULD_TRACE(base)) { TRACE(base, __VA_ARGS__); }
52
57
 
53
58
 
@@ -102,6 +102,24 @@ static VALUE Backend_initialize(VALUE self) {
102
102
  return self;
103
103
  }
104
104
 
105
+ static inline struct io_buffer get_io_buffer(VALUE in) {
106
+ if (FIXNUM_P(in)) {
107
+ struct raw_buffer *raw = (struct raw_buffer *)(FIX2LONG(in));
108
+ return (struct io_buffer){ raw->base, raw->size, 1 };
109
+ }
110
+ return (struct io_buffer){ RSTRING_PTR(in), RSTRING_LEN(in), 0 };
111
+ }
112
+
113
+ static inline VALUE coerce_io_string_or_buffer(VALUE buf) {
114
+ switch (TYPE(buf)) {
115
+ case T_STRING:
116
+ case T_FIXNUM:
117
+ return buf;
118
+ default:
119
+ return StringValue(buf);
120
+ }
121
+ }
122
+
105
123
  VALUE Backend_finalize(VALUE self) {
106
124
  Backend_t *backend;
107
125
  GetBackend(self, backend);
@@ -332,23 +350,37 @@ VALUE io_uring_backend_wait_fd(Backend_t *backend, int fd, int write) {
332
350
  VALUE Backend_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof, VALUE pos) {
333
351
  Backend_t *backend;
334
352
  rb_io_t *fptr;
335
- long dynamic_len = length == Qnil;
336
- long buffer_size = dynamic_len ? 4096 : NUM2INT(length);
353
+ struct io_buffer buffer = get_io_buffer(str);
337
354
  long buf_pos = NUM2INT(pos);
338
- int shrinkable;
339
- char *buf;
355
+ int shrinkable_string = 0;
356
+ int expandable_buffer = 0;
340
357
  long total = 0;
341
358
  int read_to_eof = RTEST(to_eof);
342
359
  VALUE underlying_io = rb_ivar_get(io, ID_ivar_io);
343
360
 
344
-
345
- if (str != Qnil) {
346
- int current_len = RSTRING_LEN(str);
347
- if (buf_pos < 0 || buf_pos > current_len) buf_pos = current_len;
361
+ if (buffer.raw) {
362
+ if (buf_pos < 0 || buf_pos > buffer.size) buf_pos = buffer.size;
363
+ buffer.base += buf_pos;
364
+ buffer.size -= buf_pos;
365
+ }
366
+ else {
367
+ expandable_buffer = length == Qnil;
368
+ long expected_read_length = expandable_buffer ? 4096 : FIX2INT(length);
369
+ long string_cap = rb_str_capacity(str);
370
+ if (buf_pos < 0 || buf_pos > buffer.size) buf_pos = buffer.size;
371
+
372
+ if (string_cap < expected_read_length + buf_pos) {
373
+ shrinkable_string = io_setstrbuf(&str, expected_read_length + buf_pos);
374
+ buffer.base = RSTRING_PTR(str) + buf_pos;
375
+ buffer.size = expected_read_length;
376
+ }
377
+ else {
378
+ buffer.base += buf_pos;
379
+ buffer.size = string_cap - buf_pos;
380
+ if (buffer.size > expected_read_length)
381
+ buffer.size = expected_read_length;
382
+ }
348
383
  }
349
- else buf_pos = 0;
350
- shrinkable = io_setstrbuf(&str, buf_pos + buffer_size);
351
- buf = RSTRING_PTR(str) + buf_pos;
352
384
 
353
385
  GetBackend(self, backend);
354
386
  if (underlying_io != Qnil) io = underlying_io;
@@ -364,7 +396,7 @@ VALUE Backend_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof,
364
396
  int result;
365
397
  int completed;
366
398
 
367
- io_uring_prep_read(sqe, fptr->fd, buf, buffer_size - total, -1);
399
+ io_uring_prep_read(sqe, fptr->fd, buffer.base, buffer.size, -1);
368
400
 
369
401
  result = io_uring_backend_defer_submit_and_await(backend, sqe, ctx, &resume_value);
370
402
  completed = context_store_release(&backend->store, ctx);
@@ -383,26 +415,32 @@ VALUE Backend_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof,
383
415
  total += result;
384
416
  if (!read_to_eof) break;
385
417
 
386
- if (total == buffer_size) {
387
- if (!dynamic_len) break;
418
+ if (result == buffer.size) {
419
+ if (!expandable_buffer) break;
388
420
 
389
- // resize buffer
390
- rb_str_resize(str, buf_pos + total);
391
- rb_str_modify_expand(str, buffer_size);
392
- buf = RSTRING_PTR(str) + buf_pos + total;
393
- shrinkable = 0;
394
- buffer_size += buffer_size;
421
+ // resize buffer to double its capacity
422
+ rb_str_resize(str, total + buf_pos);
423
+ rb_str_modify_expand(str, rb_str_capacity(str));
424
+ shrinkable_string = 0;
425
+ buffer.base = RSTRING_PTR(str) + total + buf_pos;
426
+ buffer.size = rb_str_capacity(str) - total - buf_pos;
427
+ }
428
+ else {
429
+ buffer.base += result;
430
+ buffer.size -= result;
431
+ if (!buffer.size) break;
395
432
  }
396
- else buf += result;
397
433
  }
398
434
  }
399
435
 
400
- io_set_read_length(str, buf_pos + total, shrinkable);
401
- io_enc_str(str, fptr);
436
+ if (!buffer.raw) {
437
+ io_set_read_length(str, buf_pos + total, shrinkable_string);
438
+ io_enc_str(str, fptr);
439
+ }
402
440
 
403
441
  if (!total) return Qnil;
404
442
 
405
- return str;
443
+ return buffer.raw ? INT2FIX(total) : str;
406
444
  }
407
445
 
408
446
  VALUE Backend_read_loop(VALUE self, VALUE io, VALUE maxlen) {
@@ -514,9 +552,9 @@ VALUE Backend_write(VALUE self, VALUE io, VALUE str) {
514
552
  Backend_t *backend;
515
553
  rb_io_t *fptr;
516
554
  VALUE underlying_io;
517
- char *buf = StringValuePtr(str);
518
- long len = RSTRING_LEN(str);
519
- long left = len;
555
+
556
+ struct io_buffer buffer = get_io_buffer(str);
557
+ long left = buffer.size;
520
558
 
521
559
  underlying_io = rb_ivar_get(io, ID_ivar_io);
522
560
  if (underlying_io != Qnil) io = underlying_io;
@@ -532,7 +570,7 @@ VALUE Backend_write(VALUE self, VALUE io, VALUE str) {
532
570
  int result;
533
571
  int completed;
534
572
 
535
- io_uring_prep_write(sqe, fptr->fd, buf, left, 0);
573
+ io_uring_prep_write(sqe, fptr->fd, buffer.base, left, 0);
536
574
 
537
575
  result = io_uring_backend_defer_submit_and_await(backend, sqe, ctx, &resume_value);
538
576
  completed = context_store_release(&backend->store, ctx);
@@ -546,12 +584,13 @@ VALUE Backend_write(VALUE self, VALUE io, VALUE str) {
546
584
  if (result < 0)
547
585
  rb_syserr_fail(-result, strerror(-result));
548
586
  else {
549
- buf += result;
587
+ buffer.base += result;
588
+ buffer.size -= result;
550
589
  left -= result;
551
590
  }
552
591
  }
553
592
 
554
- return INT2NUM(len);
593
+ return INT2NUM(buffer.size);
555
594
  }
556
595
 
557
596
  VALUE Backend_writev(VALUE self, VALUE io, int argc, VALUE *argv) {
@@ -118,6 +118,12 @@ VALUE Polyphony_backend_write(int argc, VALUE *argv, VALUE self) {
118
118
  return Backend_write_m(argc, argv, BACKEND());
119
119
  }
120
120
 
121
+ VALUE Polyphony_backend_test(VALUE self, VALUE io, VALUE str) {
122
+ struct raw_buffer buffer = { RSTRING_PTR(str), RSTRING_LEN(str) };
123
+ VALUE args[2] = { io, LONG2FIX((long)&buffer) };
124
+ return Polyphony_backend_write(2, args, self);
125
+ }
126
+
121
127
  // VALUE Polyphony_backend_close(VALUE self, VALUE io) {
122
128
  // return Backend_close(BACKEND(), io);
123
129
  // }
@@ -149,6 +155,8 @@ void Init_Polyphony() {
149
155
  // rb_define_singleton_method(mPolyphony, "backend_close", Polyphony_backend_close, 1);
150
156
  rb_define_singleton_method(mPolyphony, "backend_verify_blocking_mode", Backend_verify_blocking_mode, 2);
151
157
 
158
+ rb_define_singleton_method(mPolyphony, "backend_test", Polyphony_backend_test, 2);
159
+
152
160
  rb_define_global_function("snooze", Polyphony_snooze, 0);
153
161
  rb_define_global_function("suspend", Polyphony_suspend, 0);
154
162
 
@@ -135,4 +135,15 @@ VALUE Thread_switch_fiber(VALUE thread);
135
135
 
136
136
  VALUE Polyphony_snooze(VALUE self);
137
137
 
138
+ struct raw_buffer {
139
+ char *base;
140
+ int size;
141
+ };
142
+
143
+ struct io_buffer {
144
+ char *base;
145
+ int size;
146
+ int raw;
147
+ };
148
+
138
149
  #endif /* POLYPHONY_H */
@@ -6,6 +6,8 @@ require_relative '../core/thread_pool'
6
6
 
7
7
  ::File.singleton_class.instance_eval do
8
8
  alias_method :orig_stat, :stat
9
+
10
+ # Offloads `File.stat` to the default thread pool.
9
11
  def stat(path)
10
12
  ThreadPool.process { orig_stat(path) }
11
13
  end
@@ -13,6 +15,8 @@ end
13
15
 
14
16
  ::IO.singleton_class.instance_eval do
15
17
  alias_method :orig_read, :read
18
+
19
+ # Offloads `IO.read` to the default thread pool.
16
20
  def read(path)
17
21
  ThreadPool.process { orig_read(path) }
18
22
  end
@@ -1,9 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Polyphony
4
- # Process patches
4
+ # Process extensions
5
5
  module Process
6
6
  class << self
7
+
8
+ # Watches a forked or spawned process, waiting for it to terminate. If
9
+ # `cmd` is given it is spawned, otherwise the process is forked with the
10
+ # given block.
11
+ #
12
+ # If the operation is interrupted for any reason, the spawned or forked
13
+ # process is killed.
14
+ #
15
+ # @param cmd [String, nil] command to spawn
16
+ # @param &block [Proc] block to fork
17
+ # @return [void]
7
18
  def watch(cmd = nil, &block)
8
19
  terminated = nil
9
20
  pid = cmd ? Kernel.spawn(cmd) : Polyphony.fork(&block)
@@ -21,6 +32,8 @@ module Polyphony
21
32
  kill_and_await(-9, pid)
22
33
  end
23
34
 
35
+ private
36
+
24
37
  def kill_and_await(sig, pid)
25
38
  ::Process.kill(sig, pid)
26
39
  Polyphony.backend_waitpid(pid)
@@ -7,6 +7,10 @@ require 'hiredis/reader'
7
7
 
8
8
  # Polyphony-based Redis driver
9
9
  class Polyphony::RedisDriver
10
+
11
+ # Connects to a Redis server using the given config.
12
+ #
13
+ # @return [TCPSocket, UNIXSocket, SSLSocket] client connectio
10
14
  def self.connect(config)
11
15
  raise 'unix sockets not supported' if config[:scheme] == 'unix'
12
16
 
@@ -20,28 +24,49 @@ class Polyphony::RedisDriver
20
24
  # connection.connect(config[:host], config[:port], connect_timeout)
21
25
  end
22
26
 
27
+ # Initializes a Redis client connection.
28
+ #
29
+ # @param host [String] hostname
30
+ # @param port [Integer] port number
23
31
  def initialize(host, port)
24
32
  @connection = Polyphony::Net.tcp_connect(host, port)
25
33
  @reader = ::Hiredis::Reader.new
26
34
  end
27
35
 
36
+ # Returns true if connected to server.
37
+ #
38
+ # @return [bool] is connected to server
28
39
  def connected?
29
40
  @connection && !@connection.closed?
30
41
  end
31
42
 
43
+ # Sets a timeout for the connection.
44
+ #
45
+ # @return [void]
32
46
  def timeout=(timeout)
33
47
  # ignore timeout for now
34
48
  end
35
49
 
50
+ # Disconnects from the server.
51
+ #
52
+ # @return [void]
36
53
  def disconnect
37
54
  @connection.close
38
55
  @connection = nil
39
56
  end
40
57
 
58
+ # Sends a command to the server.
59
+ #
60
+ # @param command [Array] Redis command
61
+ # @return [void]
41
62
  def write(command)
42
63
  @connection.write(format_command(command))
43
64
  end
44
65
 
66
+ # Formats a command for sending to server.
67
+ #
68
+ # @param args [Array] command
69
+ # @return [String] formatted command
45
70
  def format_command(args)
46
71
  args = args.flatten
47
72
  (+"*#{args.size}\r\n").tap do |s|
@@ -52,6 +77,9 @@ class Polyphony::RedisDriver
52
77
  end
53
78
  end
54
79
 
80
+ # Reads from the connection, feeding incoming data to the parser.
81
+ #
82
+ # @return [void]
55
83
  def read
56
84
  reply = @reader.gets
57
85
  return reply if reply
@@ -4,14 +4,23 @@ require_relative '../../polyphony'
4
4
  require 'sequel'
5
5
 
6
6
  module Polyphony
7
+
7
8
  # Sequel ConnectionPool that delegates to Polyphony::ResourcePool.
8
9
  class FiberConnectionPool < Sequel::ConnectionPool
10
+
11
+ # Initializes the connection pool.
12
+ #
13
+ # @param db [any] db to connect to
14
+ # @opts [Hash] connection pool options
9
15
  def initialize(db, opts = OPTS)
10
16
  super
11
17
  max_size = Integer(opts[:max_connections] || 4)
12
18
  @pool = Polyphony::ResourcePool.new(limit: max_size) { make_new(:default) }
13
19
  end
14
20
 
21
+ # Holds a connection from the pool, passing it to the given block.
22
+ #
23
+ # @return [any] block's return value
15
24
  def hold(_server = nil)
16
25
  @pool.acquire do |conn|
17
26
  yield conn
@@ -23,16 +32,25 @@ module Polyphony
23
32
  end
24
33
  end
25
34
 
35
+ # Returns the pool's size.
36
+ #
37
+ # @return [Integer] size of pool
26
38
  def size
27
39
  @pool.size
28
40
  end
29
41
 
42
+ # Returns the pool's maximal size.
43
+ #
44
+ # @return [Integer] maximum pool size
30
45
  def max_size
31
46
  @pool.limit
32
47
  end
33
48
 
49
+ # Fills pool and preconnects all db instances in pool.
50
+ #
51
+ # @return [void]
34
52
  def preconnect(_concurrent = false)
35
- @pool.preheat!
53
+ @pool.fill!
36
54
  end
37
55
  end
38
56