polyphony 0.78 → 0.81

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/CHANGELOG.md +19 -0
  3. data/Gemfile.lock +2 -1
  4. data/examples/core/pingpong.rb +7 -4
  5. data/examples/core/zlib_stream.rb +15 -0
  6. data/ext/polyphony/backend_common.c +16 -8
  7. data/ext/polyphony/backend_common.h +9 -3
  8. data/ext/polyphony/backend_io_uring.c +85 -31
  9. data/ext/polyphony/backend_libev.c +33 -17
  10. data/ext/polyphony/fiber.c +27 -27
  11. data/ext/polyphony/polyphony.c +9 -8
  12. data/ext/polyphony/polyphony.h +21 -7
  13. data/ext/polyphony/thread.c +6 -2
  14. data/lib/polyphony/adapters/fs.rb +4 -0
  15. data/lib/polyphony/adapters/process.rb +14 -1
  16. data/lib/polyphony/adapters/redis.rb +28 -0
  17. data/lib/polyphony/adapters/sequel.rb +19 -1
  18. data/lib/polyphony/core/debug.rb +201 -0
  19. data/lib/polyphony/core/exceptions.rb +21 -6
  20. data/lib/polyphony/core/global_api.rb +228 -73
  21. data/lib/polyphony/core/resource_pool.rb +65 -20
  22. data/lib/polyphony/core/sync.rb +57 -12
  23. data/lib/polyphony/core/thread_pool.rb +42 -5
  24. data/lib/polyphony/core/throttler.rb +21 -5
  25. data/lib/polyphony/core/timer.rb +125 -1
  26. data/lib/polyphony/extensions/exception.rb +36 -6
  27. data/lib/polyphony/extensions/fiber.rb +244 -61
  28. data/lib/polyphony/extensions/io.rb +4 -2
  29. data/lib/polyphony/extensions/kernel.rb +9 -4
  30. data/lib/polyphony/extensions/object.rb +8 -0
  31. data/lib/polyphony/extensions/openssl.rb +3 -1
  32. data/lib/polyphony/extensions/socket.rb +458 -39
  33. data/lib/polyphony/extensions/thread.rb +108 -43
  34. data/lib/polyphony/extensions/timeout.rb +12 -1
  35. data/lib/polyphony/extensions.rb +1 -0
  36. data/lib/polyphony/net.rb +66 -7
  37. data/lib/polyphony/version.rb +1 -1
  38. data/lib/polyphony.rb +0 -2
  39. data/test/test_backend.rb +6 -2
  40. data/test/test_global_api.rb +0 -23
  41. data/test/test_io.rb +7 -7
  42. data/test/test_resource_pool.rb +1 -1
  43. data/test/test_signal.rb +15 -15
  44. data/test/test_thread.rb +1 -1
  45. data/test/test_throttler.rb +0 -6
  46. data/test/test_trace.rb +189 -24
  47. metadata +9 -8
  48. data/lib/polyphony/core/channel.rb +0 -15
@@ -61,13 +61,13 @@ extern ID ID_switch_fiber;
61
61
  extern ID ID_to_s;
62
62
  extern ID ID_transfer;
63
63
 
64
- extern VALUE SYM_fiber_create;
65
- extern VALUE SYM_fiber_event_poll_enter;
66
- extern VALUE SYM_fiber_event_poll_leave;
67
- extern VALUE SYM_fiber_run;
68
- extern VALUE SYM_fiber_schedule;
69
- extern VALUE SYM_fiber_switchpoint;
70
- extern VALUE SYM_fiber_terminate;
64
+ extern VALUE SYM_spin;
65
+ extern VALUE SYM_enter_poll;
66
+ extern VALUE SYM_leave_poll;
67
+ extern VALUE SYM_unblock;
68
+ extern VALUE SYM_schedule;
69
+ extern VALUE SYM_block;
70
+ extern VALUE SYM_terminate;
71
71
 
72
72
  VALUE Fiber_auto_watcher(VALUE self);
73
73
  void Fiber_make_runnable(VALUE fiber, VALUE value);
@@ -121,15 +121,29 @@ VALUE Backend_wait_event(VALUE self, VALUE raise_on_exception);
121
121
  VALUE Backend_wakeup(VALUE self);
122
122
  VALUE Backend_run_idle_tasks(VALUE self);
123
123
  VALUE Backend_switch_fiber(VALUE self);
124
+
124
125
  void Backend_schedule_fiber(VALUE thread, VALUE self, VALUE fiber, VALUE value, int prioritize);
125
126
  void Backend_unschedule_fiber(VALUE self, VALUE fiber);
126
127
  void Backend_park_fiber(VALUE self, VALUE fiber);
127
128
  void Backend_unpark_fiber(VALUE self, VALUE fiber);
128
129
 
130
+ VALUE Backend_snooze(VALUE self);
131
+
129
132
  void Thread_schedule_fiber(VALUE thread, VALUE fiber, VALUE value);
130
133
  void Thread_schedule_fiber_with_priority(VALUE thread, VALUE fiber, VALUE value);
131
134
  VALUE Thread_switch_fiber(VALUE thread);
132
135
 
133
136
  VALUE Polyphony_snooze(VALUE self);
134
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
+
135
149
  #endif /* POLYPHONY_H */
@@ -23,11 +23,15 @@ VALUE Thread_fiber_unschedule(VALUE self, VALUE fiber) {
23
23
  }
24
24
 
25
25
  inline void Thread_schedule_fiber(VALUE self, VALUE fiber, VALUE value) {
26
- schedule_fiber(self, fiber, value, 0);
26
+ Backend_schedule_fiber(self, rb_ivar_get(self, ID_ivar_backend), fiber, value, 0);
27
+
28
+ // schedule_fiber(self, fiber, value, 0);
27
29
  }
28
30
 
29
31
  inline void Thread_schedule_fiber_with_priority(VALUE self, VALUE fiber, VALUE value) {
30
- schedule_fiber(self, fiber, value, 1);
32
+ Backend_schedule_fiber(self, rb_ivar_get(self, ID_ivar_backend), fiber, value, 1);
33
+
34
+ // schedule_fiber(self, fiber, value, 1);
31
35
  }
32
36
 
33
37
  VALUE Thread_switch_fiber(VALUE self) {
@@ -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
 
@@ -1,8 +1,15 @@
1
+ # Kernel extensions
1
2
  module ::Kernel
3
+ # Prints a trace message to `STDOUT`, bypassing the Polyphony backend.
4
+ #
5
+ # @return [void]
2
6
  def trace(*args)
3
7
  STDOUT.orig_write(format_trace(args))
4
8
  end
5
9
 
10
+ # Formats a trace message.
11
+ #
12
+ # @return [String] trace message
6
13
  def format_trace(args)
7
14
  if args.size > 1 && args.first.is_a?(String)
8
15
  format("%s: %p\n", args.shift, args.size == 1 ? args.first : args)
@@ -13,3 +20,197 @@ module ::Kernel
13
20
  end
14
21
  end
15
22
  end
23
+
24
+ module Polyphony
25
+
26
+ # Trace provides tools for tracing the activity of the current thread's
27
+ # backend.
28
+ module Trace
29
+ class << self
30
+
31
+ # Starts tracing, emitting events converted to hashes to the given block.
32
+ # If an IO instance is given, events are dumped to it instead.
33
+ #
34
+ # @param io [IO, nil] IO instance
35
+ # @param &block [Proc] event handler block
36
+ # @return [void]
37
+ def start_event_firehose(io = nil, &block)
38
+ Thread.backend.trace_proc = firehose_proc(io, block)
39
+ end
40
+
41
+ private
42
+
43
+ # Returns a firehose proc for the given io and block.
44
+ #
45
+ # @param io [IO, nil] IO instance
46
+ # @param block [Proc] event handler block
47
+ # @return [Proc] firehose proc
48
+ def firehose_proc(io, block)
49
+ if io
50
+ ->(*e) { io.orig_write("#{trace_event_info(e).inspect}\n") }
51
+ elsif block
52
+ ->(*e) { block.(trace_event_info(e)) }
53
+ else
54
+ raise "Please provide an io or a block"
55
+ end
56
+ end
57
+
58
+ # Converts an event (expressed as an array) to a hash.
59
+ #
60
+ # @param e [Array] event as emitted by the backend
61
+ # @return [Hash] event hash
62
+ def trace_event_info(e)
63
+ {
64
+ stamp: Time.now,
65
+ event: e[0]
66
+ }.merge(
67
+ send(:"event_props_#{e[0]}", e)
68
+ )
69
+ end
70
+
71
+ # Returns an event hash for a `:block` event.
72
+ #
73
+ # @param e [Array] event array
74
+ # @return [Hash] event hash
75
+ def event_props_block(e)
76
+ {
77
+ fiber: e[1],
78
+ caller: e[2]
79
+ }
80
+ end
81
+
82
+ # Returns an event hash for a `:enter_poll` event.
83
+ #
84
+ # @param e [Array] event array
85
+ # @return [Hash] event hash
86
+ def event_props_enter_poll(e)
87
+ {}
88
+ end
89
+
90
+ # Returns an event hash for a `:leave_poll` event.
91
+ #
92
+ # @param e [Array] event array
93
+ # @return [Hash] event hash
94
+ def event_props_leave_poll(e)
95
+ {}
96
+ end
97
+
98
+ # Returns an event hash for a `:schedule` event.
99
+ #
100
+ # @param e [Array] event array
101
+ # @return [Hash] event hash
102
+ def event_props_schedule(e)
103
+ {
104
+ fiber: e[1],
105
+ value: e[2],
106
+ caller: e[4],
107
+ source_fiber: Fiber.current
108
+ }
109
+ end
110
+
111
+ # Returns an event hash for a `:spin` event.
112
+ #
113
+ # @param e [Array] event array
114
+ # @return [Hash] event hash
115
+ def event_props_spin(e)
116
+ {
117
+ fiber: e[1],
118
+ caller: e[2],
119
+ source_fiber: Fiber.current
120
+ }
121
+ end
122
+
123
+ # Returns an event hash for a `:terminate` event.
124
+ #
125
+ # @param e [Array] event array
126
+ # @return [Hash] event hash
127
+ def event_props_terminate(e)
128
+ {
129
+ fiber: e[1],
130
+ value: e[2],
131
+ }
132
+ end
133
+
134
+ # Returns an event hash for a `:unblock` event.
135
+ #
136
+ # @param e [Array] event array
137
+ # @return [Hash] event hash
138
+ def event_props_unblock(e)
139
+ {
140
+ fiber: e[1],
141
+ value: e[2],
142
+ caller: e[3],
143
+ }
144
+ end
145
+
146
+ # TODO: work on text formatting of events
147
+ # def format_trace_event_message(e)
148
+ # props = send(:"event_props_#{e[0]}", e).merge(
149
+ # timestamp: format_current_time,
150
+ # event: e[0]
151
+ # )
152
+ # templ = send(:"event_format_#{e[0]}", e)
153
+ # msg = format("%<timestamp>s #{templ}\n", **props)
154
+ # end
155
+
156
+ # def format_current_time
157
+ # Time.now.strftime('%Y-%m-%d %H:%M:%S')
158
+ # end
159
+
160
+ # def generic_event_format
161
+ # '%<event>-12.12s'
162
+ # end
163
+
164
+ # def fiber_event_format
165
+ # "#{generic_event_format} %<fiber>-44.44s"
166
+ # end
167
+
168
+ # def event_format_enter_poll(e)
169
+ # generic_event_format
170
+ # end
171
+
172
+ # def event_format_leave_poll(e)
173
+ # generic_event_format
174
+ # end
175
+
176
+
177
+ # def event_format_schedule(e)
178
+ # "#{fiber_event_format} %<value>-24.24p %<caller>-120.120s <= %<origin_fiber>s"
179
+ # end
180
+
181
+
182
+ # def event_format_unblock(e)
183
+ # "#{fiber_event_format} %<value>-24.24p %<caller>-120.120s"
184
+ # end
185
+
186
+ # def event_format_terminate(e)
187
+ # "#{fiber_event_format} %<value>-24.24p"
188
+ # end
189
+
190
+ # def event_format_block(e)
191
+ # "#{fiber_event_format} #{' ' * 24} %<caller>-120.120s"
192
+ # end
193
+
194
+
195
+ # def event_format_spin(e)
196
+ # "#{fiber_event_format} #{' ' * 24} %<caller>-120.120s <= %<origin_fiber>s"
197
+ # end
198
+
199
+ # def fibe_repr(fiber)
200
+ # format("%-6x %-20.20s %-10.10s", fiber.object_id, fiber.tag, "(#{fiber.state})")
201
+ # end
202
+
203
+ # def fiber_compact_repr(fiber)
204
+ # if fiber.tag
205
+ # format("%-6x %-.20s %-.10s", fiber.object_id, fiber.tag, "(#{fiber.state})")
206
+ # else
207
+ # format("%-6x %-.10s", fiber.object_id, "(#{fiber.state})")
208
+ # end
209
+ # end
210
+
211
+ # def caller_repr(c)
212
+ # c.map { |i| i.gsub('/home/sharon/repo/polyphony/lib/polyphony', '') }.join(' ')
213
+ # end
214
+ end
215
+ end
216
+ end
@@ -1,15 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Polyphony
4
- # Common exception class for interrupting fibers. These exceptions allow
5
- # control of fibers. BaseException exceptions can encapsulate a value and thus
6
- # provide a way to interrupt long-running blocking operations while still
7
- # passing a value back to the call site. BaseException exceptions can also
8
- # references a cancel scope in order to allow correct bubbling of exceptions
9
- # through nested cancel scopes.
4
+
5
+ # Base exception class for interrupting fibers. These exceptions allow control
6
+ # of fibers. BaseException exceptions can encapsulate a value and thus provide
7
+ # a way to interrupt long-running blocking operations while still passing a
8
+ # value back to the call site. BaseException exceptions can also references a
9
+ # cancel scope in order to allow correct bubbling of exceptions through nested
10
+ # cancel scopes.
10
11
  class BaseException < ::Exception
12
+
13
+ # Exception value, used mainly for `MoveOn` exceptions.
11
14
  attr_reader :value
12
15
 
16
+ # Initializes the exception, setting the caller and the value.
17
+ #
18
+ # @param value [any] Exception value
19
+ # @return [void]
13
20
  def initialize(value = nil)
14
21
  @caller_backtrace = caller
15
22
  @value = value
@@ -33,10 +40,18 @@ module Polyphony
33
40
 
34
41
  # Interjection is used to run arbitrary code on arbitrary fibers at any point
35
42
  class Interjection < BaseException
43
+
44
+ # Initializes an Interjection with the given proc.
45
+ #
46
+ # @param proc [Proc] interjection proc
47
+ # @return [void]
36
48
  def initialize(proc)
37
49
  @proc = proc
38
50
  end
39
51
 
52
+ # Invokes the exception by calling the associated proc.
53
+ #
54
+ # @return [void]
40
55
  def invoke
41
56
  @proc.call
42
57
  end