polyphony 0.48.0 → 0.50.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +30 -0
- data/Gemfile.lock +1 -1
- data/LICENSE +1 -1
- data/TODO.md +7 -1
- data/examples/core/nested.rb +21 -0
- data/examples/core/suspend.rb +13 -0
- data/examples/core/terminate_main_fiber.rb +12 -0
- data/examples/performance/thread-vs-fiber/polyphony_server.rb +2 -4
- data/ext/polyphony/backend_common.h +22 -2
- data/ext/polyphony/backend_io_uring.c +18 -32
- data/ext/polyphony/backend_libev.c +25 -46
- data/ext/polyphony/fiber.c +2 -1
- data/ext/polyphony/polyphony.h +1 -0
- data/ext/polyphony/runqueue.c +6 -0
- data/ext/polyphony/runqueue_ring_buffer.c +9 -0
- data/ext/polyphony/runqueue_ring_buffer.h +1 -0
- data/ext/polyphony/thread.c +14 -0
- data/lib/polyphony.rb +2 -1
- data/lib/polyphony/adapters/process.rb +2 -0
- data/lib/polyphony/core/exceptions.rb +1 -0
- data/lib/polyphony/core/global_api.rb +1 -1
- data/lib/polyphony/core/timer.rb +115 -0
- data/lib/polyphony/extensions/core.rb +4 -4
- data/lib/polyphony/extensions/fiber.rb +11 -8
- data/lib/polyphony/extensions/thread.rb +1 -2
- data/lib/polyphony/net.rb +3 -6
- data/lib/polyphony/version.rb +1 -1
- data/test/helper.rb +1 -2
- data/test/test_backend.rb +25 -0
- data/test/test_fiber.rb +31 -1
- data/test/test_io.rb +1 -1
- data/test/test_signal.rb +1 -2
- data/test/test_socket.rb +5 -4
- data/test/test_timer.rb +157 -0
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 126431d6f1b6c547d57dd99834c5a2371b4f2b3399df5556f55fdf7ff8465702
|
4
|
+
data.tar.gz: bf4e8d8cbde0a0d25930996f21ca723ffb68c065688ea2423f96b2a1e8946a80
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9b6cb2b64ac16e70941195aa17c019abdb026bac2d440550841f839bae96d248b87609979e6e9d7c57f9905845209714d90f74caa4488d8ba66afa96c35aa425
|
7
|
+
data.tar.gz: '093360cb9c3f5e81d7d23f1e707bf6399608475822b56d0171438748e65752adb2784b1ab36ce850a26840c615f73ce1fcc74ca842018361019269f7d3c31aa9'
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,33 @@
|
|
1
|
+
## 0.50.1
|
2
|
+
|
3
|
+
- Set `IOSQE_ASYNC` flag in io_uring backend
|
4
|
+
- Fix error handling in `Backend#waitpid`
|
5
|
+
- Reimplement libev backend's `#waitpid` by using pidfd_open (in similar manner
|
6
|
+
to the io_uring backend)
|
7
|
+
|
8
|
+
## 0.50.0
|
9
|
+
|
10
|
+
- Use `Process::CLOCK_MONOTONIC` in Timer
|
11
|
+
- Add `Timer#sleep`, `Timer#after`, `Timer#every`
|
12
|
+
- Prevent fiber from being resumed after terminating
|
13
|
+
- Add `Thread#fiber_index_of` method
|
14
|
+
- Use `Backend#wait_event` in `Fiber#await`
|
15
|
+
|
16
|
+
## 0.49.2
|
17
|
+
|
18
|
+
- Fix hang with 100s or more child fibers when terminating
|
19
|
+
- Fix double pending_count increment in io_uring backend
|
20
|
+
|
21
|
+
## 0.49.1
|
22
|
+
|
23
|
+
- Use `TCPSocket` instead of `Socket` in `Net.tcp_connect`
|
24
|
+
- Catch `Errno::ERSCH` in `Process.kill_and_await`
|
25
|
+
- Set io_uring queue size to 2048
|
26
|
+
|
27
|
+
## 0.49.0
|
28
|
+
|
29
|
+
- Implement `Polyphony::Timer` for performant timeouts
|
30
|
+
|
1
31
|
## 0.48.0
|
2
32
|
|
3
33
|
- Implement graceful shutdown
|
data/Gemfile.lock
CHANGED
data/LICENSE
CHANGED
data/TODO.md
CHANGED
@@ -1,4 +1,10 @@
|
|
1
|
-
-
|
1
|
+
- Check segfault when resetting a `cancel_after` timeout lots of times at very high rate
|
2
|
+
- Check why `throttled_loop` inside of `move_on_after` fails to stop
|
3
|
+
|
4
|
+
- Commented out `io_uring_sqe_set_flags(sqe, IOSQE_ASYNC);` in `io_uring_backend_defer_submit_and_await`:
|
5
|
+
- This flag should be set for I/O ops, not for other stuff
|
6
|
+
|
7
|
+
- Override stock `::SizedQueue` impl with Queue with capacity
|
2
8
|
|
3
9
|
- Add support for `break` and `StopIteration` in all loops (with tests)
|
4
10
|
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'polyphony'
|
5
|
+
|
6
|
+
def process
|
7
|
+
p :b_start
|
8
|
+
sleep 1
|
9
|
+
p :b_stop
|
10
|
+
end
|
11
|
+
|
12
|
+
spin do
|
13
|
+
p :a_start
|
14
|
+
spin { process }
|
15
|
+
sleep 60
|
16
|
+
p :a_stop
|
17
|
+
end
|
18
|
+
|
19
|
+
p :main_start
|
20
|
+
sleep 120
|
21
|
+
p :main_stop
|
@@ -26,15 +26,13 @@ def write_response(socket)
|
|
26
26
|
socket.write "HTTP/1.1 #{status_code}\r\n#{headers}\r\n#{data}"
|
27
27
|
end
|
28
28
|
|
29
|
-
server = TCPServer.open('0.0.0.0',
|
30
|
-
puts "pid #{Process.pid} Polyphony (#{Thread.current.backend.kind}) listening on port
|
29
|
+
server = TCPServer.open('0.0.0.0', 4411)
|
30
|
+
puts "pid #{Process.pid} Polyphony (#{Thread.current.backend.kind}) listening on port 4411"
|
31
31
|
|
32
32
|
spin_loop(interval: 10) do
|
33
33
|
p Thread.current.fiber_scheduling_stats
|
34
34
|
end
|
35
35
|
|
36
|
-
GC.disable
|
37
|
-
|
38
36
|
server.accept_loop do |c|
|
39
37
|
spin { handle_client(c) }
|
40
38
|
end
|
@@ -3,6 +3,14 @@
|
|
3
3
|
#include "ruby.h"
|
4
4
|
#include "ruby/io.h"
|
5
5
|
|
6
|
+
#ifndef __NR_pidfd_open
|
7
|
+
#define __NR_pidfd_open 434 /* System call # on most architectures */
|
8
|
+
#endif
|
9
|
+
|
10
|
+
static int pidfd_open(pid_t pid, unsigned int flags) {
|
11
|
+
return syscall(__NR_pidfd_open, pid, flags);
|
12
|
+
}
|
13
|
+
|
6
14
|
//////////////////////////////////////////////////////////////////////
|
7
15
|
//////////////////////////////////////////////////////////////////////
|
8
16
|
// the following is copied verbatim from the Ruby source code (io.c)
|
@@ -114,7 +122,7 @@ inline double current_time() {
|
|
114
122
|
struct timespec ts;
|
115
123
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
116
124
|
long long ns = ts.tv_sec;
|
117
|
-
ns = ns *
|
125
|
+
ns = ns * 1e9 + ts.tv_nsec;
|
118
126
|
double t = ns;
|
119
127
|
return t / 1e9;
|
120
128
|
}
|
@@ -126,4 +134,16 @@ inline VALUE backend_timeout_exception(VALUE exception) {
|
|
126
134
|
return rb_funcall(exception, ID_new, 0);
|
127
135
|
else
|
128
136
|
return rb_funcall(rb_eRuntimeError, ID_new, 1, exception);
|
129
|
-
}
|
137
|
+
}
|
138
|
+
|
139
|
+
VALUE Backend_timeout_safe(VALUE arg) {
|
140
|
+
return rb_yield(arg);
|
141
|
+
}
|
142
|
+
|
143
|
+
VALUE Backend_timeout_rescue(VALUE arg, VALUE exception) {
|
144
|
+
return exception;
|
145
|
+
}
|
146
|
+
|
147
|
+
VALUE Backend_timeout_ensure_safe(VALUE arg) {
|
148
|
+
return rb_rescue2(Backend_timeout_safe, Qnil, Backend_timeout_rescue, Qnil, rb_eException, (VALUE)0);
|
149
|
+
}
|
@@ -19,14 +19,6 @@
|
|
19
19
|
#include "ruby/thread.h"
|
20
20
|
#include "backend_io_uring_context.h"
|
21
21
|
|
22
|
-
#ifndef __NR_pidfd_open
|
23
|
-
#define __NR_pidfd_open 434 /* System call # on most architectures */
|
24
|
-
#endif
|
25
|
-
|
26
|
-
static int pidfd_open(pid_t pid, unsigned int flags) {
|
27
|
-
return syscall(__NR_pidfd_open, pid, flags);
|
28
|
-
}
|
29
|
-
|
30
22
|
VALUE SYM_io_uring;
|
31
23
|
|
32
24
|
typedef struct Backend_t {
|
@@ -72,7 +64,7 @@ static VALUE Backend_initialize(VALUE self) {
|
|
72
64
|
backend->pending_count = 0;
|
73
65
|
backend->poll_no_wait_count = 0;
|
74
66
|
backend->pending_sqes = 0;
|
75
|
-
backend->prepared_limit =
|
67
|
+
backend->prepared_limit = 2048;
|
76
68
|
|
77
69
|
context_store_initialize(&backend->store);
|
78
70
|
io_uring_queue_init(backend->prepared_limit, &backend->ring, 0);
|
@@ -260,12 +252,10 @@ int io_uring_backend_defer_submit_and_await(
|
|
260
252
|
VALUE switchpoint_result = Qnil;
|
261
253
|
|
262
254
|
io_uring_sqe_set_data(sqe, ctx);
|
263
|
-
|
255
|
+
io_uring_sqe_set_flags(sqe, IOSQE_ASYNC);
|
264
256
|
io_uring_backend_defer_submit(backend);
|
265
257
|
|
266
|
-
backend->pending_count++;
|
267
258
|
switchpoint_result = backend_await(backend);
|
268
|
-
backend->pending_count--;
|
269
259
|
|
270
260
|
if (!ctx->completed) {
|
271
261
|
ctx->result = -ECANCELED;
|
@@ -813,18 +803,6 @@ VALUE Backend_timer_loop(VALUE self, VALUE interval) {
|
|
813
803
|
}
|
814
804
|
}
|
815
805
|
|
816
|
-
VALUE Backend_timeout_safe(VALUE arg) {
|
817
|
-
return rb_yield(arg);
|
818
|
-
}
|
819
|
-
|
820
|
-
VALUE Backend_timeout_rescue(VALUE arg, VALUE exception) {
|
821
|
-
return exception;
|
822
|
-
}
|
823
|
-
|
824
|
-
VALUE Backend_timeout_ensure_safe(VALUE arg) {
|
825
|
-
return rb_rescue2(Backend_timeout_safe, Qnil, Backend_timeout_rescue, Qnil, rb_eException, (VALUE)0);
|
826
|
-
}
|
827
|
-
|
828
806
|
struct Backend_timeout_ctx {
|
829
807
|
Backend_t *backend;
|
830
808
|
op_context_t *ctx;
|
@@ -863,7 +841,6 @@ VALUE Backend_timeout(int argc, VALUE *argv, VALUE self) {
|
|
863
841
|
ctx->resume_value = timeout;
|
864
842
|
io_uring_prep_timeout(sqe, &ts, 0, 0);
|
865
843
|
io_uring_sqe_set_data(sqe, ctx);
|
866
|
-
io_uring_sqe_set_flags(sqe, IOSQE_ASYNC);
|
867
844
|
io_uring_backend_defer_submit(backend);
|
868
845
|
|
869
846
|
struct Backend_timeout_ctx timeout_ctx = {backend, ctx};
|
@@ -881,19 +858,28 @@ VALUE Backend_timeout(int argc, VALUE *argv, VALUE self) {
|
|
881
858
|
}
|
882
859
|
|
883
860
|
VALUE Backend_waitpid(VALUE self, VALUE pid) {
|
884
|
-
Backend_t *backend;
|
885
861
|
int pid_int = NUM2INT(pid);
|
886
862
|
int fd = pidfd_open(pid_int, 0);
|
887
|
-
GetBackend(self, backend);
|
888
|
-
|
889
|
-
VALUE resume_value = io_uring_backend_wait_fd(backend, fd, 0);
|
890
|
-
close(fd);
|
891
863
|
|
892
|
-
|
893
|
-
|
864
|
+
if (fd >= 0) {
|
865
|
+
Backend_t *backend;
|
866
|
+
GetBackend(self, backend);
|
894
867
|
|
868
|
+
VALUE resume_value = io_uring_backend_wait_fd(backend, fd, 0);
|
869
|
+
close(fd);
|
870
|
+
RAISE_IF_EXCEPTION(resume_value);
|
871
|
+
RB_GC_GUARD(resume_value);
|
872
|
+
}
|
873
|
+
|
895
874
|
int status;
|
896
875
|
pid_t ret = waitpid(pid_int, &status, WNOHANG);
|
876
|
+
if (ret < 0) {
|
877
|
+
int e = errno;
|
878
|
+
if (e == ECHILD)
|
879
|
+
ret = pid_int;
|
880
|
+
else
|
881
|
+
rb_syserr_fail(e, strerror(e));
|
882
|
+
}
|
897
883
|
return rb_ary_new_from_args(2, INT2NUM(ret), INT2NUM(WEXITSTATUS(status)));
|
898
884
|
}
|
899
885
|
|
@@ -8,6 +8,8 @@
|
|
8
8
|
#include <netinet/in.h>
|
9
9
|
#include <arpa/inet.h>
|
10
10
|
#include <stdnoreturn.h>
|
11
|
+
#include <sys/types.h>
|
12
|
+
#include <sys/wait.h>
|
11
13
|
|
12
14
|
#include "polyphony.h"
|
13
15
|
#include "../libev/ev.h"
|
@@ -685,26 +687,12 @@ noreturn VALUE Backend_timer_loop(VALUE self, VALUE interval) {
|
|
685
687
|
RB_GC_GUARD(switchpoint_result);
|
686
688
|
|
687
689
|
rb_yield(Qnil);
|
688
|
-
|
689
|
-
while (1) {
|
690
|
+
do {
|
690
691
|
next_time += interval_d;
|
691
|
-
|
692
|
-
}
|
692
|
+
} while (next_time <= now);
|
693
693
|
}
|
694
694
|
}
|
695
695
|
|
696
|
-
VALUE Backend_timeout_safe(VALUE arg) {
|
697
|
-
return rb_yield(arg);
|
698
|
-
}
|
699
|
-
|
700
|
-
VALUE Backend_timeout_rescue(VALUE arg, VALUE exception) {
|
701
|
-
return exception;
|
702
|
-
}
|
703
|
-
|
704
|
-
VALUE Backend_timeout_ensure_safe(VALUE arg) {
|
705
|
-
return rb_rescue2(Backend_timeout_safe, Qnil, Backend_timeout_rescue, Qnil, rb_eException, (VALUE)0);
|
706
|
-
}
|
707
|
-
|
708
696
|
struct libev_timeout {
|
709
697
|
struct ev_timer timer;
|
710
698
|
VALUE fiber;
|
@@ -759,38 +747,29 @@ VALUE Backend_timeout(int argc,VALUE *argv, VALUE self) {
|
|
759
747
|
return result;
|
760
748
|
}
|
761
749
|
|
762
|
-
struct libev_child {
|
763
|
-
struct ev_child child;
|
764
|
-
VALUE fiber;
|
765
|
-
};
|
766
|
-
|
767
|
-
void Backend_child_callback(EV_P_ ev_child *w, int revents)
|
768
|
-
{
|
769
|
-
struct libev_child *watcher = (struct libev_child *)w;
|
770
|
-
int exit_status = WEXITSTATUS(w->rstatus);
|
771
|
-
VALUE status;
|
772
|
-
|
773
|
-
status = rb_ary_new_from_args(2, INT2NUM(w->rpid), INT2NUM(exit_status));
|
774
|
-
Fiber_make_runnable(watcher->fiber, status);
|
775
|
-
}
|
776
|
-
|
777
750
|
VALUE Backend_waitpid(VALUE self, VALUE pid) {
|
778
|
-
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
|
751
|
+
int pid_int = NUM2INT(pid);
|
752
|
+
int fd = pidfd_open(pid_int, 0);
|
753
|
+
if (fd >= 0) {
|
754
|
+
Backend_t *backend;
|
755
|
+
GetBackend(self, backend);
|
756
|
+
|
757
|
+
VALUE resume_value = libev_wait_fd(backend, fd, EV_READ, 0);
|
758
|
+
close(fd);
|
759
|
+
RAISE_IF_EXCEPTION(resume_value);
|
760
|
+
RB_GC_GUARD(resume_value);
|
761
|
+
}
|
788
762
|
|
789
|
-
|
790
|
-
|
791
|
-
|
792
|
-
|
793
|
-
|
763
|
+
int status = 0;
|
764
|
+
pid_t ret = waitpid(pid_int, &status, WNOHANG);
|
765
|
+
if (ret < 0) {
|
766
|
+
int e = errno;
|
767
|
+
if (e == ECHILD)
|
768
|
+
ret = pid_int;
|
769
|
+
else
|
770
|
+
rb_syserr_fail(e, strerror(e));
|
771
|
+
}
|
772
|
+
return rb_ary_new_from_args(2, INT2NUM(ret), INT2NUM(WEXITSTATUS(status)));
|
794
773
|
}
|
795
774
|
|
796
775
|
void Backend_async_callback(EV_P_ ev_async *w, int revents) { }
|
data/ext/polyphony/fiber.c
CHANGED
@@ -101,7 +101,8 @@ VALUE Fiber_await(VALUE self) {
|
|
101
101
|
}
|
102
102
|
rb_hash_aset(waiting_fibers, fiber, Qtrue);
|
103
103
|
|
104
|
-
|
104
|
+
VALUE backend = rb_ivar_get(rb_thread_current(), ID_ivar_backend);
|
105
|
+
result = Backend_wait_event(backend, Qnil);
|
105
106
|
|
106
107
|
rb_hash_delete(waiting_fibers, fiber);
|
107
108
|
RAISE_IF_EXCEPTION(result);
|
data/ext/polyphony/polyphony.h
CHANGED
@@ -84,6 +84,7 @@ void Runqueue_push(VALUE self, VALUE fiber, VALUE value, int reschedule);
|
|
84
84
|
void Runqueue_unshift(VALUE self, VALUE fiber, VALUE value, int reschedule);
|
85
85
|
runqueue_entry Runqueue_shift(VALUE self);
|
86
86
|
void Runqueue_delete(VALUE self, VALUE fiber);
|
87
|
+
int Runqueue_index_of(VALUE self, VALUE fiber);
|
87
88
|
void Runqueue_clear(VALUE self);
|
88
89
|
long Runqueue_len(VALUE self);
|
89
90
|
int Runqueue_empty_p(VALUE self);
|
data/ext/polyphony/runqueue.c
CHANGED
@@ -74,6 +74,12 @@ void Runqueue_delete(VALUE self, VALUE fiber) {
|
|
74
74
|
runqueue_ring_buffer_delete(&runqueue->entries, fiber);
|
75
75
|
}
|
76
76
|
|
77
|
+
int Runqueue_index_of(VALUE self, VALUE fiber) {
|
78
|
+
Runqueue_t *runqueue;
|
79
|
+
GetRunqueue(self, runqueue);
|
80
|
+
return runqueue_ring_buffer_index_of(&runqueue->entries, fiber);
|
81
|
+
}
|
82
|
+
|
77
83
|
void Runqueue_clear(VALUE self) {
|
78
84
|
Runqueue_t *runqueue;
|
79
85
|
GetRunqueue(self, runqueue);
|
@@ -80,6 +80,15 @@ void runqueue_ring_buffer_delete(runqueue_ring_buffer *buffer, VALUE fiber) {
|
|
80
80
|
}
|
81
81
|
}
|
82
82
|
|
83
|
+
int runqueue_ring_buffer_index_of(runqueue_ring_buffer *buffer, VALUE fiber) {
|
84
|
+
for (unsigned int i = 0; i < buffer->count; i++) {
|
85
|
+
unsigned int idx = (buffer->head + i) % buffer->size;
|
86
|
+
if (buffer->entries[idx].fiber == fiber)
|
87
|
+
return i;
|
88
|
+
}
|
89
|
+
return -1;
|
90
|
+
}
|
91
|
+
|
83
92
|
void runqueue_ring_buffer_clear(runqueue_ring_buffer *buffer) {
|
84
93
|
buffer->count = buffer->head = buffer->tail = 0;
|
85
94
|
}
|
@@ -27,5 +27,6 @@ void runqueue_ring_buffer_unshift(runqueue_ring_buffer *buffer, VALUE fiber, VAL
|
|
27
27
|
void runqueue_ring_buffer_push(runqueue_ring_buffer *buffer, VALUE fiber, VALUE value);
|
28
28
|
|
29
29
|
void runqueue_ring_buffer_delete(runqueue_ring_buffer *buffer, VALUE fiber);
|
30
|
+
int runqueue_ring_buffer_index_of(runqueue_ring_buffer *buffer, VALUE fiber);
|
30
31
|
|
31
32
|
#endif /* RUNQUEUE_RING_BUFFER_H */
|
data/ext/polyphony/thread.c
CHANGED
@@ -59,6 +59,18 @@ void schedule_fiber(VALUE self, VALUE fiber, VALUE value, int prioritize) {
|
|
59
59
|
}
|
60
60
|
}
|
61
61
|
|
62
|
+
VALUE Thread_fiber_scheduling_index(VALUE self, VALUE fiber) {
|
63
|
+
VALUE runqueue = rb_ivar_get(self, ID_ivar_runqueue);
|
64
|
+
|
65
|
+
return INT2NUM(Runqueue_index_of(runqueue, fiber));
|
66
|
+
}
|
67
|
+
|
68
|
+
VALUE Thread_fiber_unschedule(VALUE self, VALUE fiber) {
|
69
|
+
VALUE runqueue = rb_ivar_get(self, ID_ivar_runqueue);
|
70
|
+
Runqueue_delete(runqueue, fiber);
|
71
|
+
return self;
|
72
|
+
}
|
73
|
+
|
62
74
|
VALUE Thread_schedule_fiber(VALUE self, VALUE fiber, VALUE value) {
|
63
75
|
schedule_fiber(self, fiber, value, 0);
|
64
76
|
return self;
|
@@ -135,6 +147,8 @@ void Init_Thread() {
|
|
135
147
|
rb_define_method(rb_cThread, "schedule_fiber_with_priority",
|
136
148
|
Thread_schedule_fiber_with_priority, 2);
|
137
149
|
rb_define_method(rb_cThread, "switch_fiber", Thread_switch_fiber, 0);
|
150
|
+
rb_define_method(rb_cThread, "fiber_scheduling_index", Thread_fiber_scheduling_index, 1);
|
151
|
+
rb_define_method(rb_cThread, "fiber_unschedule", Thread_fiber_unschedule, 1);
|
138
152
|
|
139
153
|
rb_define_method(rb_cThread, "debug!", Thread_debug, 0);
|
140
154
|
|
data/lib/polyphony.rb
CHANGED
@@ -14,6 +14,7 @@ Thread.current.backend = Polyphony::Backend.new
|
|
14
14
|
require_relative './polyphony/core/global_api'
|
15
15
|
require_relative './polyphony/core/resource_pool'
|
16
16
|
require_relative './polyphony/core/sync'
|
17
|
+
require_relative './polyphony/core/timer'
|
17
18
|
require_relative './polyphony/net'
|
18
19
|
require_relative './polyphony/adapters/process'
|
19
20
|
|
@@ -43,7 +44,7 @@ module Polyphony
|
|
43
44
|
rescue SystemExit
|
44
45
|
# fall through to ensure
|
45
46
|
rescue Exception => e
|
46
|
-
|
47
|
+
STDERR << e.full_message
|
47
48
|
exit!
|
48
49
|
ensure
|
49
50
|
exit_forked_process
|
@@ -0,0 +1,115 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Polyphony
|
4
|
+
# Implements a common timer for running multiple timeouts
|
5
|
+
class Timer
|
6
|
+
def initialize(resolution:)
|
7
|
+
@fiber = spin_loop(interval: resolution) { update }
|
8
|
+
@timeouts = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def stop
|
12
|
+
@fiber.stop
|
13
|
+
end
|
14
|
+
|
15
|
+
def sleep(duration)
|
16
|
+
fiber = Fiber.current
|
17
|
+
@timeouts[fiber] = {
|
18
|
+
interval: duration,
|
19
|
+
target_stamp: now + duration
|
20
|
+
}
|
21
|
+
Thread.current.backend.wait_event(true)
|
22
|
+
ensure
|
23
|
+
@timeouts.delete(fiber)
|
24
|
+
end
|
25
|
+
|
26
|
+
def after(interval, &block)
|
27
|
+
spin do
|
28
|
+
self.sleep interval
|
29
|
+
block.()
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def every(interval)
|
34
|
+
fiber = Fiber.current
|
35
|
+
@timeouts[fiber] = {
|
36
|
+
interval: interval,
|
37
|
+
target_stamp: now + interval,
|
38
|
+
recurring: true
|
39
|
+
}
|
40
|
+
while true
|
41
|
+
Thread.current.backend.wait_event(true)
|
42
|
+
yield
|
43
|
+
end
|
44
|
+
ensure
|
45
|
+
@timeouts.delete(fiber)
|
46
|
+
end
|
47
|
+
|
48
|
+
def cancel_after(interval, with_exception: Polyphony::Cancel)
|
49
|
+
fiber = Fiber.current
|
50
|
+
@timeouts[fiber] = {
|
51
|
+
interval: interval,
|
52
|
+
target_stamp: now + interval,
|
53
|
+
exception: with_exception
|
54
|
+
}
|
55
|
+
yield
|
56
|
+
ensure
|
57
|
+
@timeouts.delete(fiber)
|
58
|
+
end
|
59
|
+
|
60
|
+
def move_on_after(interval, with_value: nil)
|
61
|
+
fiber = Fiber.current
|
62
|
+
@timeouts[fiber] = {
|
63
|
+
interval: interval,
|
64
|
+
target_stamp: now + interval,
|
65
|
+
exception: [Polyphony::MoveOn, with_value]
|
66
|
+
}
|
67
|
+
yield
|
68
|
+
rescue Polyphony::MoveOn => e
|
69
|
+
e.value
|
70
|
+
ensure
|
71
|
+
@timeouts.delete(fiber)
|
72
|
+
end
|
73
|
+
|
74
|
+
def reset
|
75
|
+
record = @timeouts[Fiber.current]
|
76
|
+
return unless record
|
77
|
+
|
78
|
+
record[:target_stamp] = now + record[:interval]
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def now
|
84
|
+
::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
85
|
+
end
|
86
|
+
|
87
|
+
def timeout_exception(record)
|
88
|
+
case (exception = record[:exception])
|
89
|
+
when Array
|
90
|
+
exception[0].new(exception[1])
|
91
|
+
when Class
|
92
|
+
exception.new
|
93
|
+
else
|
94
|
+
RuntimeError.new(exception)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def update
|
99
|
+
return if @timeouts.empty?
|
100
|
+
|
101
|
+
@timeouts.each do |fiber, record|
|
102
|
+
next if record[:target_stamp] > now
|
103
|
+
|
104
|
+
value = record[:exception] ? timeout_exception(record) : record[:value]
|
105
|
+
fiber.schedule value
|
106
|
+
|
107
|
+
next unless record[:recurring]
|
108
|
+
|
109
|
+
while record[:target_stamp] <= now
|
110
|
+
record[:target_stamp] += record[:interval]
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -12,11 +12,11 @@ class ::Exception
|
|
12
12
|
attr_accessor :__disable_sanitized_backtrace__
|
13
13
|
end
|
14
14
|
|
15
|
-
attr_accessor :source_fiber, :
|
15
|
+
attr_accessor :source_fiber, :raising_fiber
|
16
16
|
|
17
17
|
alias_method :orig_initialize, :initialize
|
18
18
|
def initialize(*args)
|
19
|
-
@
|
19
|
+
@raising_fiber = Fiber.current
|
20
20
|
orig_initialize(*args)
|
21
21
|
end
|
22
22
|
|
@@ -31,10 +31,10 @@ class ::Exception
|
|
31
31
|
end
|
32
32
|
|
33
33
|
def sanitized_backtrace
|
34
|
-
return sanitize(orig_backtrace) unless @
|
34
|
+
return sanitize(orig_backtrace) unless @raising_fiber
|
35
35
|
|
36
36
|
backtrace = orig_backtrace || []
|
37
|
-
sanitize(backtrace + @
|
37
|
+
sanitize(backtrace + @raising_fiber.caller)
|
38
38
|
end
|
39
39
|
|
40
40
|
POLYPHONY_DIR = File.expand_path(File.join(__dir__, '..'))
|
@@ -245,8 +245,12 @@ module Polyphony
|
|
245
245
|
end
|
246
246
|
|
247
247
|
def shutdown_all_children(graceful = false)
|
248
|
-
|
249
|
-
|
248
|
+
return unless @children
|
249
|
+
|
250
|
+
@children.keys.each do |c|
|
251
|
+
c.terminate(graceful)
|
252
|
+
c.await
|
253
|
+
end
|
250
254
|
end
|
251
255
|
end
|
252
256
|
|
@@ -312,6 +316,8 @@ module Polyphony
|
|
312
316
|
@running = false
|
313
317
|
inform_dependants(result, uncaught_exception)
|
314
318
|
ensure
|
319
|
+
# Prevent fiber from being resumed after terminating
|
320
|
+
@thread.fiber_unschedule(self)
|
315
321
|
Thread.current.switch_fiber
|
316
322
|
end
|
317
323
|
|
@@ -319,13 +325,10 @@ module Polyphony
|
|
319
325
|
# the children are shut down, it is returned along with the uncaught_exception
|
320
326
|
# flag set. Otherwise, it returns the given arguments.
|
321
327
|
def finalize_children(result, uncaught_exception)
|
322
|
-
|
323
|
-
shutdown_all_children
|
324
|
-
rescue Exception => e
|
325
|
-
result = e
|
326
|
-
uncaught_exception = true
|
327
|
-
end
|
328
|
+
shutdown_all_children
|
328
329
|
[result, uncaught_exception]
|
330
|
+
rescue Exception => e
|
331
|
+
[e, true]
|
329
332
|
end
|
330
333
|
|
331
334
|
def inform_dependants(result, uncaught_exception)
|
@@ -41,8 +41,7 @@ class ::Thread
|
|
41
41
|
|
42
42
|
def finalize(result)
|
43
43
|
unless Fiber.current.children.empty?
|
44
|
-
Fiber.current.
|
45
|
-
Fiber.current.await_all_children
|
44
|
+
Fiber.current.shutdown_all_children
|
46
45
|
end
|
47
46
|
@finalization_mutex.synchronize do
|
48
47
|
@terminated = true
|
data/lib/polyphony/net.rb
CHANGED
@@ -8,10 +8,7 @@ module Polyphony
|
|
8
8
|
module Net
|
9
9
|
class << self
|
10
10
|
def tcp_connect(host, port, opts = {})
|
11
|
-
socket =
|
12
|
-
addr = ::Socket.sockaddr_in(port, host)
|
13
|
-
s.connect(addr)
|
14
|
-
end
|
11
|
+
socket = TCPSocket.new(host, port)
|
15
12
|
if opts[:secure_context] || opts[:secure]
|
16
13
|
secure_socket(socket, opts[:secure_context], opts.merge(host: host))
|
17
14
|
else
|
@@ -23,7 +20,7 @@ module Polyphony
|
|
23
20
|
host ||= '0.0.0.0'
|
24
21
|
raise 'Port number not specified' unless port
|
25
22
|
|
26
|
-
socket =
|
23
|
+
socket = listening_socket_from_options(host, port, opts)
|
27
24
|
if opts[:secure_context] || opts[:secure]
|
28
25
|
secure_server(socket, opts[:secure_context], opts)
|
29
26
|
else
|
@@ -31,7 +28,7 @@ module Polyphony
|
|
31
28
|
end
|
32
29
|
end
|
33
30
|
|
34
|
-
def
|
31
|
+
def listening_socket_from_options(host, port, opts)
|
35
32
|
::Socket.new(:INET, :STREAM).tap do |s|
|
36
33
|
s.reuse_addr if opts[:reuse_addr]
|
37
34
|
s.dont_linger if opts[:dont_linger]
|
data/lib/polyphony/version.rb
CHANGED
data/test/helper.rb
CHANGED
@@ -57,8 +57,7 @@ class MiniTest::Test
|
|
57
57
|
|
58
58
|
def teardown
|
59
59
|
# trace "* teardown #{self.name}"
|
60
|
-
Fiber.current.
|
61
|
-
Fiber.current.await_all_children
|
60
|
+
Fiber.current.shutdown_all_children
|
62
61
|
Fiber.current.instance_variable_set(:@auto_watcher, nil)
|
63
62
|
rescue => e
|
64
63
|
puts e
|
data/test/test_backend.rb
CHANGED
@@ -100,6 +100,31 @@ class BackendTest < MiniTest::Test
|
|
100
100
|
assert_equal [:ready, 'foo', 'bar', :done], buf
|
101
101
|
end
|
102
102
|
|
103
|
+
def test_read_loop_terminate
|
104
|
+
i, o = IO.pipe
|
105
|
+
|
106
|
+
buf = []
|
107
|
+
parent = spin do
|
108
|
+
f = spin do
|
109
|
+
buf << :ready
|
110
|
+
@backend.read_loop(i) { |d| buf << d }
|
111
|
+
buf << :done
|
112
|
+
end
|
113
|
+
suspend
|
114
|
+
end
|
115
|
+
|
116
|
+
# writing always causes snoozing
|
117
|
+
o << 'foo'
|
118
|
+
sleep 0.01
|
119
|
+
o << 'bar'
|
120
|
+
sleep 0.01
|
121
|
+
|
122
|
+
parent.stop
|
123
|
+
|
124
|
+
parent.await
|
125
|
+
assert_equal [:ready, 'foo', 'bar'], buf
|
126
|
+
end
|
127
|
+
|
103
128
|
def test_accept_loop
|
104
129
|
server = TCPServer.new('127.0.0.1', 1234)
|
105
130
|
|
data/test/test_fiber.rb
CHANGED
@@ -349,6 +349,22 @@ class FiberTest < MiniTest::Test
|
|
349
349
|
assert_equal [:foo, :terminate], buffer
|
350
350
|
end
|
351
351
|
|
352
|
+
CMD_TERMINATE_MAIN_FIBER = <<~BASH
|
353
|
+
ruby -rbundler/setup -rpolyphony -e"spin { sleep 0.1; Thread.current.main_fiber.terminate }; begin; sleep; rescue Polyphony::Terminate; STDOUT << 'terminated'; end" 2>&1
|
354
|
+
BASH
|
355
|
+
|
356
|
+
CMD_TERMINATE_CHILD_FIBER = <<~BASH
|
357
|
+
ruby -rbundler/setup -rpolyphony -e"f = spin { sleep }; spin { sleep 0.1; f.terminate }; f.await" 2>&1
|
358
|
+
BASH
|
359
|
+
|
360
|
+
def test_terminate_main_fiber
|
361
|
+
output = `#{CMD_TERMINATE_CHILD_FIBER}`
|
362
|
+
assert_equal '', output
|
363
|
+
|
364
|
+
output = `#{CMD_TERMINATE_MAIN_FIBER}`
|
365
|
+
assert_equal 'terminated', output
|
366
|
+
end
|
367
|
+
|
352
368
|
def test_interrupt_timer
|
353
369
|
result = []
|
354
370
|
f = Fiber.current.spin do
|
@@ -639,7 +655,6 @@ class FiberTest < MiniTest::Test
|
|
639
655
|
i.close
|
640
656
|
f.await
|
641
657
|
rescue Exception => e
|
642
|
-
trace e
|
643
658
|
o << e.class.name
|
644
659
|
o.close
|
645
660
|
end
|
@@ -1039,6 +1054,21 @@ class RestartTest < MiniTest::Test
|
|
1039
1054
|
end
|
1040
1055
|
end
|
1041
1056
|
|
1057
|
+
class ChildrenTerminationTest < MiniTest::Test
|
1058
|
+
def test_shutdown_all_children
|
1059
|
+
f = spin do
|
1060
|
+
1000.times { spin { suspend } }
|
1061
|
+
suspend
|
1062
|
+
end
|
1063
|
+
|
1064
|
+
snooze
|
1065
|
+
assert_equal 1000, f.children.size
|
1066
|
+
|
1067
|
+
f.shutdown_all_children
|
1068
|
+
assert_equal 0, f.children.size
|
1069
|
+
end
|
1070
|
+
end
|
1071
|
+
|
1042
1072
|
class GracefulTerminationTest < MiniTest::Test
|
1043
1073
|
def test_graceful_termination
|
1044
1074
|
buffer = []
|
data/test/test_io.rb
CHANGED
data/test/test_signal.rb
CHANGED
data/test/test_socket.rb
CHANGED
@@ -63,14 +63,15 @@ class HTTPClientTest < MiniTest::Test
|
|
63
63
|
require 'json'
|
64
64
|
|
65
65
|
def test_http
|
66
|
-
res = HTTParty.get('http://
|
66
|
+
res = HTTParty.get('http://ipinfo.io/')
|
67
|
+
|
67
68
|
response = JSON.load(res.body)
|
68
|
-
assert_equal
|
69
|
+
assert_equal 'https://ipinfo.io/missingauth', response['readme']
|
69
70
|
end
|
70
71
|
|
71
72
|
def test_https
|
72
|
-
res = HTTParty.get('https://
|
73
|
+
res = HTTParty.get('https://ipinfo.io/')
|
73
74
|
response = JSON.load(res.body)
|
74
|
-
assert_equal
|
75
|
+
assert_equal 'https://ipinfo.io/missingauth', response['readme']
|
75
76
|
end
|
76
77
|
end
|
data/test/test_timer.rb
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'helper'
|
4
|
+
|
5
|
+
class TimerMoveOnAfterTest < MiniTest::Test
|
6
|
+
def setup
|
7
|
+
@timer = Polyphony::Timer.new(resolution: 0.01)
|
8
|
+
end
|
9
|
+
|
10
|
+
def teardown
|
11
|
+
@timer.stop
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_timer_move_on_after
|
15
|
+
t0 = Time.now
|
16
|
+
v = @timer.move_on_after(0.1) do
|
17
|
+
sleep 1
|
18
|
+
:foo
|
19
|
+
end
|
20
|
+
t1 = Time.now
|
21
|
+
|
22
|
+
assert_in_range 0.1..0.15, t1 - t0
|
23
|
+
assert_nil v
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_timer_move_on_after_with_value
|
27
|
+
t0 = Time.now
|
28
|
+
v = @timer.move_on_after(0.01, with_value: :bar) do
|
29
|
+
sleep 1
|
30
|
+
:foo
|
31
|
+
end
|
32
|
+
t1 = Time.now
|
33
|
+
|
34
|
+
assert_in_range 0.01..0.025, t1 - t0
|
35
|
+
assert_equal :bar, v
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_timer_move_on_after_with_reset
|
39
|
+
t0 = Time.now
|
40
|
+
v = @timer.move_on_after(0.01, with_value: :moved_on) do
|
41
|
+
sleep 0.007
|
42
|
+
@timer.reset
|
43
|
+
sleep 0.007
|
44
|
+
@timer.reset
|
45
|
+
sleep 0.007
|
46
|
+
nil
|
47
|
+
end
|
48
|
+
t1 = Time.now
|
49
|
+
|
50
|
+
assert_nil v
|
51
|
+
assert_in_range 0.015..0.03, t1 - t0
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class TimerCancelAfterTest < MiniTest::Test
|
56
|
+
def setup
|
57
|
+
@timer = Polyphony::Timer.new(resolution: 0.01)
|
58
|
+
end
|
59
|
+
|
60
|
+
def teardown
|
61
|
+
@timer.stop
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_timer_cancel_after
|
65
|
+
t0 = Time.now
|
66
|
+
|
67
|
+
assert_raises Polyphony::Cancel do
|
68
|
+
@timer.cancel_after(0.01) do
|
69
|
+
sleep 1
|
70
|
+
:foo
|
71
|
+
end
|
72
|
+
end
|
73
|
+
t1 = Time.now
|
74
|
+
assert_in_range 0.01..0.03, t1 - t0
|
75
|
+
end
|
76
|
+
|
77
|
+
def test_timer_cancel_after_with_reset
|
78
|
+
t0 = Time.now
|
79
|
+
@timer.cancel_after(0.01) do
|
80
|
+
sleep 0.007
|
81
|
+
@timer.reset
|
82
|
+
sleep 0.007
|
83
|
+
end
|
84
|
+
t1 = Time.now
|
85
|
+
assert_in_range 0.013..0.024, t1 - t0
|
86
|
+
end
|
87
|
+
|
88
|
+
class CustomException < Exception
|
89
|
+
end
|
90
|
+
|
91
|
+
def test_timer_cancel_after_with_custom_exception
|
92
|
+
assert_raises CustomException do
|
93
|
+
@timer.cancel_after(0.01, with_exception: CustomException) do
|
94
|
+
sleep 1
|
95
|
+
:foo
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
begin
|
100
|
+
err = nil
|
101
|
+
@timer.cancel_after(0.01, with_exception: [CustomException, 'custom message']) do
|
102
|
+
sleep 1
|
103
|
+
:foo
|
104
|
+
end
|
105
|
+
rescue Exception => err
|
106
|
+
ensure
|
107
|
+
assert_kind_of CustomException, err
|
108
|
+
assert_equal 'custom message', err.message
|
109
|
+
end
|
110
|
+
|
111
|
+
|
112
|
+
begin
|
113
|
+
e = nil
|
114
|
+
@timer.cancel_after(0.01, with_exception: 'foo') do
|
115
|
+
sleep 1
|
116
|
+
:foo
|
117
|
+
end
|
118
|
+
rescue => e
|
119
|
+
ensure
|
120
|
+
assert_kind_of RuntimeError, e
|
121
|
+
assert_equal 'foo', e.message
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
class TimerMiscTest < MiniTest::Test
|
127
|
+
def setup
|
128
|
+
@timer = Polyphony::Timer.new(resolution: 0.001)
|
129
|
+
sleep 0
|
130
|
+
end
|
131
|
+
|
132
|
+
def teardown
|
133
|
+
@timer.stop
|
134
|
+
end
|
135
|
+
|
136
|
+
def test_timer_after
|
137
|
+
buffer = []
|
138
|
+
f = @timer.after(0.01) { buffer << 2 }
|
139
|
+
assert_kind_of Fiber, f
|
140
|
+
snooze
|
141
|
+
assert_equal [], buffer
|
142
|
+
sleep 0.1
|
143
|
+
p :post_sleep
|
144
|
+
assert_equal [2], buffer
|
145
|
+
end
|
146
|
+
|
147
|
+
def test_timer_every
|
148
|
+
buffer = []
|
149
|
+
t0 = Time.now
|
150
|
+
f = spin do
|
151
|
+
@timer.every(0.01) { buffer << 1 }
|
152
|
+
end
|
153
|
+
sleep 0.05
|
154
|
+
f.stop
|
155
|
+
assert_in_range 4..6, buffer.size
|
156
|
+
end
|
157
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: polyphony
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.50.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sharon Rosner
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-01-
|
11
|
+
date: 2021-01-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake-compiler
|
@@ -353,6 +353,7 @@ files:
|
|
353
353
|
- examples/core/forking.rb
|
354
354
|
- examples/core/handling-signals.rb
|
355
355
|
- examples/core/interrupt.rb
|
356
|
+
- examples/core/nested.rb
|
356
357
|
- examples/core/pingpong.rb
|
357
358
|
- examples/core/recurrent-timer.rb
|
358
359
|
- examples/core/resource_delegate.rb
|
@@ -360,6 +361,8 @@ files:
|
|
360
361
|
- examples/core/spin_error_backtrace.rb
|
361
362
|
- examples/core/supervise-process.rb
|
362
363
|
- examples/core/supervisor.rb
|
364
|
+
- examples/core/suspend.rb
|
365
|
+
- examples/core/terminate_main_fiber.rb
|
363
366
|
- examples/core/thread-sleep.rb
|
364
367
|
- examples/core/thread_pool.rb
|
365
368
|
- examples/core/throttling.rb
|
@@ -476,6 +479,7 @@ files:
|
|
476
479
|
- lib/polyphony/core/sync.rb
|
477
480
|
- lib/polyphony/core/thread_pool.rb
|
478
481
|
- lib/polyphony/core/throttler.rb
|
482
|
+
- lib/polyphony/core/timer.rb
|
479
483
|
- lib/polyphony/extensions/core.rb
|
480
484
|
- lib/polyphony/extensions/debug.rb
|
481
485
|
- lib/polyphony/extensions/fiber.rb
|
@@ -510,6 +514,7 @@ files:
|
|
510
514
|
- test/test_thread.rb
|
511
515
|
- test/test_thread_pool.rb
|
512
516
|
- test/test_throttler.rb
|
517
|
+
- test/test_timer.rb
|
513
518
|
- test/test_trace.rb
|
514
519
|
homepage: https://digital-fabric.github.io/polyphony
|
515
520
|
licenses:
|