polyphony 0.43.8
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 +7 -0
- data/.gitbook.yaml +4 -0
- data/.github/workflows/test.yml +29 -0
- data/.gitignore +59 -0
- data/.rubocop.yml +175 -0
- data/CHANGELOG.md +393 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +141 -0
- data/LICENSE +21 -0
- data/README.md +51 -0
- data/Rakefile +26 -0
- data/TODO.md +201 -0
- data/bin/polyphony-debug +87 -0
- data/docs/_config.yml +64 -0
- data/docs/_includes/head.html +40 -0
- data/docs/_includes/title.html +1 -0
- data/docs/_sass/custom/custom.scss +10 -0
- data/docs/_sass/overrides.scss +0 -0
- data/docs/_user-guide/all-about-timers.md +126 -0
- data/docs/_user-guide/index.md +9 -0
- data/docs/_user-guide/web-server.md +136 -0
- data/docs/api-reference/exception.md +27 -0
- data/docs/api-reference/fiber.md +425 -0
- data/docs/api-reference/index.md +9 -0
- data/docs/api-reference/io.md +36 -0
- data/docs/api-reference/object.md +99 -0
- data/docs/api-reference/polyphony-baseexception.md +33 -0
- data/docs/api-reference/polyphony-cancel.md +26 -0
- data/docs/api-reference/polyphony-moveon.md +24 -0
- data/docs/api-reference/polyphony-net.md +20 -0
- data/docs/api-reference/polyphony-process.md +28 -0
- data/docs/api-reference/polyphony-resourcepool.md +59 -0
- data/docs/api-reference/polyphony-restart.md +18 -0
- data/docs/api-reference/polyphony-terminate.md +18 -0
- data/docs/api-reference/polyphony-threadpool.md +67 -0
- data/docs/api-reference/polyphony-throttler.md +77 -0
- data/docs/api-reference/polyphony.md +36 -0
- data/docs/api-reference/thread.md +88 -0
- data/docs/assets/img/echo-fibers.svg +1 -0
- data/docs/assets/img/sleeping-fiber.svg +1 -0
- data/docs/faq.md +195 -0
- data/docs/favicon.ico +0 -0
- data/docs/getting-started/index.md +10 -0
- data/docs/getting-started/installing.md +34 -0
- data/docs/getting-started/overview.md +486 -0
- data/docs/getting-started/tutorial.md +359 -0
- data/docs/index.md +94 -0
- data/docs/main-concepts/concurrency.md +151 -0
- data/docs/main-concepts/design-principles.md +161 -0
- data/docs/main-concepts/exception-handling.md +291 -0
- data/docs/main-concepts/extending.md +89 -0
- data/docs/main-concepts/fiber-scheduling.md +197 -0
- data/docs/main-concepts/index.md +9 -0
- data/docs/polyphony-logo.png +0 -0
- data/examples/adapters/concurrent-ruby.rb +9 -0
- data/examples/adapters/pg_client.rb +36 -0
- data/examples/adapters/pg_notify.rb +35 -0
- data/examples/adapters/pg_pool.rb +43 -0
- data/examples/adapters/pg_transaction.rb +31 -0
- data/examples/adapters/redis_blpop.rb +12 -0
- data/examples/adapters/redis_channels.rb +122 -0
- data/examples/adapters/redis_client.rb +19 -0
- data/examples/adapters/redis_pubsub.rb +26 -0
- data/examples/adapters/redis_pubsub_perf.rb +68 -0
- data/examples/core/01-spinning-up-fibers.rb +18 -0
- data/examples/core/02-awaiting-fibers.rb +20 -0
- data/examples/core/03-interrupting.rb +39 -0
- data/examples/core/04-handling-signals.rb +19 -0
- data/examples/core/xx-agent.rb +102 -0
- data/examples/core/xx-at_exit.rb +29 -0
- data/examples/core/xx-caller.rb +12 -0
- data/examples/core/xx-channels.rb +45 -0
- data/examples/core/xx-daemon.rb +14 -0
- data/examples/core/xx-deadlock.rb +8 -0
- data/examples/core/xx-deferring-an-operation.rb +14 -0
- data/examples/core/xx-erlang-style-genserver.rb +81 -0
- data/examples/core/xx-exception-backtrace.rb +40 -0
- data/examples/core/xx-fork-cleanup.rb +22 -0
- data/examples/core/xx-fork-spin.rb +42 -0
- data/examples/core/xx-fork-terminate.rb +27 -0
- data/examples/core/xx-forking.rb +24 -0
- data/examples/core/xx-move_on.rb +23 -0
- data/examples/core/xx-pingpong.rb +18 -0
- data/examples/core/xx-queue-async.rb +120 -0
- data/examples/core/xx-readpartial.rb +18 -0
- data/examples/core/xx-recurrent-timer.rb +12 -0
- data/examples/core/xx-resource_delegate.rb +31 -0
- data/examples/core/xx-signals.rb +16 -0
- data/examples/core/xx-sleep-forever.rb +9 -0
- data/examples/core/xx-sleeping.rb +25 -0
- data/examples/core/xx-snooze-starve.rb +16 -0
- data/examples/core/xx-spin-fork.rb +49 -0
- data/examples/core/xx-spin_error_backtrace.rb +33 -0
- data/examples/core/xx-state-machine.rb +51 -0
- data/examples/core/xx-stop.rb +20 -0
- data/examples/core/xx-supervise-process.rb +30 -0
- data/examples/core/xx-supervisors.rb +21 -0
- data/examples/core/xx-thread-selector-sleep.rb +51 -0
- data/examples/core/xx-thread-selector-snooze.rb +46 -0
- data/examples/core/xx-thread-sleep.rb +17 -0
- data/examples/core/xx-thread-snooze.rb +34 -0
- data/examples/core/xx-thread_pool.rb +17 -0
- data/examples/core/xx-throttling.rb +18 -0
- data/examples/core/xx-timeout.rb +10 -0
- data/examples/core/xx-timer-gc.rb +17 -0
- data/examples/core/xx-trace.rb +79 -0
- data/examples/core/xx-using-a-mutex.rb +21 -0
- data/examples/core/xx-worker-thread.rb +30 -0
- data/examples/io/tunnel.rb +48 -0
- data/examples/io/xx-backticks.rb +11 -0
- data/examples/io/xx-echo_client.rb +25 -0
- data/examples/io/xx-echo_client_from_stdin.rb +21 -0
- data/examples/io/xx-echo_pipe.rb +16 -0
- data/examples/io/xx-echo_server.rb +17 -0
- data/examples/io/xx-echo_server_with_timeout.rb +34 -0
- data/examples/io/xx-echo_stdin.rb +14 -0
- data/examples/io/xx-happy-eyeballs.rb +36 -0
- data/examples/io/xx-httparty.rb +38 -0
- data/examples/io/xx-irb.rb +17 -0
- data/examples/io/xx-net-http.rb +15 -0
- data/examples/io/xx-open.rb +16 -0
- data/examples/io/xx-switch.rb +15 -0
- data/examples/io/xx-system.rb +11 -0
- data/examples/io/xx-tcpserver.rb +15 -0
- data/examples/io/xx-tcpsocket.rb +18 -0
- data/examples/io/xx-zip.rb +19 -0
- data/examples/performance/fiber_transfer.rb +47 -0
- data/examples/performance/fs_read.rb +38 -0
- data/examples/performance/mem-usage.rb +56 -0
- data/examples/performance/messaging.rb +29 -0
- data/examples/performance/multi_snooze.rb +33 -0
- data/examples/performance/snooze.rb +39 -0
- data/examples/performance/snooze_raw.rb +39 -0
- data/examples/performance/thread-vs-fiber/polyphony_mt_server.rb +74 -0
- data/examples/performance/thread-vs-fiber/polyphony_server.rb +45 -0
- data/examples/performance/thread-vs-fiber/polyphony_server_read_loop.rb +58 -0
- data/examples/performance/thread-vs-fiber/threaded_server.rb +27 -0
- data/examples/performance/thread-vs-fiber/xx-httparty_multi.rb +36 -0
- data/examples/performance/thread-vs-fiber/xx-httparty_threaded.rb +29 -0
- data/examples/performance/thread_pool_perf.rb +63 -0
- data/examples/performance/xx-array.rb +11 -0
- data/examples/performance/xx-fiber-switch.rb +9 -0
- data/examples/performance/xx-snooze.rb +15 -0
- data/examples/xx-spin.rb +32 -0
- data/ext/libev/Changes +548 -0
- data/ext/libev/LICENSE +37 -0
- data/ext/libev/README +59 -0
- data/ext/libev/README.embed +3 -0
- data/ext/libev/ev.c +5279 -0
- data/ext/libev/ev.h +856 -0
- data/ext/libev/ev_epoll.c +296 -0
- data/ext/libev/ev_kqueue.c +224 -0
- data/ext/libev/ev_linuxaio.c +642 -0
- data/ext/libev/ev_poll.c +156 -0
- data/ext/libev/ev_port.c +192 -0
- data/ext/libev/ev_select.c +316 -0
- data/ext/libev/ev_vars.h +215 -0
- data/ext/libev/ev_win32.c +162 -0
- data/ext/libev/ev_wrap.h +216 -0
- data/ext/libev/test_libev_win32.c +123 -0
- data/ext/polyphony/extconf.rb +20 -0
- data/ext/polyphony/fiber.c +109 -0
- data/ext/polyphony/libev.c +2 -0
- data/ext/polyphony/libev.h +9 -0
- data/ext/polyphony/libev_agent.c +882 -0
- data/ext/polyphony/polyphony.c +71 -0
- data/ext/polyphony/polyphony.h +97 -0
- data/ext/polyphony/polyphony_ext.c +21 -0
- data/ext/polyphony/queue.c +168 -0
- data/ext/polyphony/ring_buffer.c +96 -0
- data/ext/polyphony/ring_buffer.h +28 -0
- data/ext/polyphony/thread.c +208 -0
- data/ext/polyphony/tracing.c +11 -0
- data/lib/polyphony.rb +136 -0
- data/lib/polyphony/adapters/fs.rb +19 -0
- data/lib/polyphony/adapters/irb.rb +52 -0
- data/lib/polyphony/adapters/postgres.rb +110 -0
- data/lib/polyphony/adapters/process.rb +33 -0
- data/lib/polyphony/adapters/redis.rb +67 -0
- data/lib/polyphony/adapters/trace.rb +138 -0
- data/lib/polyphony/core/channel.rb +46 -0
- data/lib/polyphony/core/exceptions.rb +36 -0
- data/lib/polyphony/core/global_api.rb +124 -0
- data/lib/polyphony/core/resource_pool.rb +117 -0
- data/lib/polyphony/core/sync.rb +21 -0
- data/lib/polyphony/core/thread_pool.rb +64 -0
- data/lib/polyphony/core/throttler.rb +41 -0
- data/lib/polyphony/event.rb +17 -0
- data/lib/polyphony/extensions/core.rb +174 -0
- data/lib/polyphony/extensions/fiber.rb +379 -0
- data/lib/polyphony/extensions/io.rb +221 -0
- data/lib/polyphony/extensions/openssl.rb +81 -0
- data/lib/polyphony/extensions/socket.rb +150 -0
- data/lib/polyphony/extensions/thread.rb +108 -0
- data/lib/polyphony/net.rb +77 -0
- data/lib/polyphony/version.rb +5 -0
- data/polyphony.gemspec +40 -0
- data/test/coverage.rb +54 -0
- data/test/eg.rb +27 -0
- data/test/helper.rb +56 -0
- data/test/q.rb +24 -0
- data/test/run.rb +5 -0
- data/test/stress.rb +25 -0
- data/test/test_agent.rb +130 -0
- data/test/test_event.rb +59 -0
- data/test/test_ext.rb +196 -0
- data/test/test_fiber.rb +988 -0
- data/test/test_global_api.rb +352 -0
- data/test/test_io.rb +249 -0
- data/test/test_kernel.rb +57 -0
- data/test/test_process_supervision.rb +46 -0
- data/test/test_queue.rb +112 -0
- data/test/test_resource_pool.rb +138 -0
- data/test/test_signal.rb +100 -0
- data/test/test_socket.rb +34 -0
- data/test/test_supervise.rb +103 -0
- data/test/test_thread.rb +170 -0
- data/test/test_thread_pool.rb +101 -0
- data/test/test_throttler.rb +50 -0
- data/test/test_trace.rb +68 -0
- metadata +482 -0
@@ -0,0 +1,123 @@
|
|
1
|
+
// a single header file is required
|
2
|
+
#include <ev.h>
|
3
|
+
#include <stdio.h>
|
4
|
+
#include <io.h>
|
5
|
+
|
6
|
+
// every watcher type has its own typedef'd struct
|
7
|
+
// with the name ev_TYPE
|
8
|
+
ev_io stdin_watcher;
|
9
|
+
ev_timer timeout_watcher;
|
10
|
+
|
11
|
+
// all watcher callbacks have a similar signature
|
12
|
+
// this callback is called when data is readable on stdin
|
13
|
+
static void
|
14
|
+
stdin_cb (EV_P_ ev_io *w, int revents)
|
15
|
+
{
|
16
|
+
puts ("stdin ready or done or something");
|
17
|
+
// for one-shot events, one must manually stop the watcher
|
18
|
+
// with its corresponding stop function.
|
19
|
+
//ev_io_stop (EV_A_ w);
|
20
|
+
|
21
|
+
// this causes all nested ev_loop's to stop iterating
|
22
|
+
//ev_unloop (EV_A_ EVUNLOOP_ALL);
|
23
|
+
}
|
24
|
+
|
25
|
+
// another callback, this time for a time-out
|
26
|
+
static void
|
27
|
+
timeout_cb (EV_P_ ev_timer *w, int revents)
|
28
|
+
{
|
29
|
+
puts ("timeout");
|
30
|
+
// this causes the innermost ev_loop to stop iterating
|
31
|
+
ev_unloop (EV_A_ EVUNLOOP_ONE);
|
32
|
+
}
|
33
|
+
|
34
|
+
|
35
|
+
|
36
|
+
#include <winsock.h>
|
37
|
+
|
38
|
+
#include <stdlib.h>
|
39
|
+
#include <iostream>
|
40
|
+
int get_server_fd()
|
41
|
+
{
|
42
|
+
|
43
|
+
//----------------------
|
44
|
+
// Initialize Winsock.
|
45
|
+
WSADATA wsaData;
|
46
|
+
int iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
|
47
|
+
if (iResult != NO_ERROR) {
|
48
|
+
printf("Error at WSAStartup()\n");
|
49
|
+
return 1;
|
50
|
+
}
|
51
|
+
|
52
|
+
//----------------------
|
53
|
+
// Create a SOCKET for listening for
|
54
|
+
// incoming connection requests.
|
55
|
+
SOCKET ListenSocket;
|
56
|
+
ListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
57
|
+
if (ListenSocket == INVALID_SOCKET) {
|
58
|
+
printf("Error at socket(): %ld\n", WSAGetLastError());
|
59
|
+
WSACleanup();
|
60
|
+
return 1;
|
61
|
+
}
|
62
|
+
printf("socket returned %d\n", ListenSocket);
|
63
|
+
|
64
|
+
//----------------------
|
65
|
+
// The sockaddr_in structure specifies the address family,
|
66
|
+
// IP address, and port for the socket that is being bound.
|
67
|
+
sockaddr_in service;
|
68
|
+
service.sin_family = AF_INET;
|
69
|
+
service.sin_addr.s_addr = inet_addr("127.0.0.1");
|
70
|
+
service.sin_port = htons(4444);
|
71
|
+
|
72
|
+
if (bind( ListenSocket,
|
73
|
+
(SOCKADDR*) &service,
|
74
|
+
sizeof(service)) == SOCKET_ERROR) {
|
75
|
+
printf("bind() failed.\n");
|
76
|
+
closesocket(ListenSocket);
|
77
|
+
WSACleanup();
|
78
|
+
return 1;
|
79
|
+
}
|
80
|
+
|
81
|
+
//----------------------
|
82
|
+
// Listen for incoming connection requests.
|
83
|
+
// on the created socket
|
84
|
+
if (listen( ListenSocket, 1 ) == SOCKET_ERROR) {
|
85
|
+
printf("Error listening on socket.\n");
|
86
|
+
closesocket(ListenSocket);
|
87
|
+
WSACleanup();
|
88
|
+
return 1;
|
89
|
+
}
|
90
|
+
|
91
|
+
|
92
|
+
printf("sock and osf handle are %d %d, error is \n", ListenSocket, _get_osfhandle (ListenSocket)); // -1 is invalid file handle: http://msdn.microsoft.com/en-us/library/ks2530z6.aspx
|
93
|
+
printf("err was %d\n", WSAGetLastError());
|
94
|
+
//----------------------
|
95
|
+
return ListenSocket;
|
96
|
+
}
|
97
|
+
|
98
|
+
|
99
|
+
int
|
100
|
+
main (void)
|
101
|
+
{
|
102
|
+
struct ev_loop *loopy = ev_default_loop(0);
|
103
|
+
int fd = get_server_fd();
|
104
|
+
int fd_real = _open_osfhandle(fd, NULL);
|
105
|
+
int conv = _get_osfhandle(fd_real);
|
106
|
+
printf("got server fd %d, loop %d, fd_real %d, that converted %d\n", fd, loopy, fd_real, conv);
|
107
|
+
// accept(fd, NULL, NULL);
|
108
|
+
// initialise an io watcher, then start it
|
109
|
+
// this one will watch for stdin to become readable
|
110
|
+
ev_io_init (&stdin_watcher, stdin_cb, /*STDIN_FILENO*/ conv, EV_READ);
|
111
|
+
ev_io_start (loopy, &stdin_watcher);
|
112
|
+
|
113
|
+
// initialise a timer watcher, then start it
|
114
|
+
// simple non-repeating 5.5 second timeout
|
115
|
+
//ev_timer_init (&timeout_watcher, timeout_cb, 15.5, 0.);
|
116
|
+
//ev_timer_start (loopy, &timeout_watcher);
|
117
|
+
printf("starting loop\n");
|
118
|
+
// now wait for events to arrive
|
119
|
+
ev_loop (loopy, 0);
|
120
|
+
|
121
|
+
// unloop was called, so exit
|
122
|
+
return 0;
|
123
|
+
}
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rubygems"
|
4
|
+
|
5
|
+
require "mkmf"
|
6
|
+
|
7
|
+
have_header("unistd.h")
|
8
|
+
|
9
|
+
$defs << "-DEV_USE_LINUXAIO" if have_header("linux/aio_abi.h")
|
10
|
+
$defs << "-DEV_USE_SELECT" if have_header("sys/select.h")
|
11
|
+
$defs << "-DEV_USE_POLL" if have_type("port_event_t", "poll.h")
|
12
|
+
$defs << "-DEV_USE_EPOLL" if have_header("sys/epoll.h")
|
13
|
+
$defs << "-DEV_USE_KQUEUE" if have_header("sys/event.h") && have_header("sys/queue.h")
|
14
|
+
$defs << "-DEV_USE_PORT" if have_type("port_event_t", "port.h")
|
15
|
+
$defs << "-DHAVE_SYS_RESOURCE_H" if have_header("sys/resource.h")
|
16
|
+
|
17
|
+
CONFIG["optflags"] << " -fno-strict-aliasing" unless RUBY_PLATFORM =~ /mswin/
|
18
|
+
|
19
|
+
dir_config "polyphony_ext"
|
20
|
+
create_makefile "polyphony_ext"
|
@@ -0,0 +1,109 @@
|
|
1
|
+
#include "polyphony.h"
|
2
|
+
|
3
|
+
ID ID_fiber_trace;
|
4
|
+
ID ID_ivar_auto_watcher;
|
5
|
+
ID ID_trace_ev_loop_enter;
|
6
|
+
ID ID_trace_ev_loop_leave;
|
7
|
+
ID ID_trace_run;
|
8
|
+
ID ID_trace_runnable;
|
9
|
+
ID ID_trace_terminate;
|
10
|
+
ID ID_trace_wait;
|
11
|
+
|
12
|
+
VALUE cEvent = Qnil;
|
13
|
+
|
14
|
+
VALUE SYM_dead;
|
15
|
+
VALUE SYM_running;
|
16
|
+
VALUE SYM_runnable;
|
17
|
+
VALUE SYM_waiting;
|
18
|
+
|
19
|
+
VALUE SYM_fiber_create;
|
20
|
+
VALUE SYM_fiber_ev_loop_enter;
|
21
|
+
VALUE SYM_fiber_ev_loop_leave;
|
22
|
+
VALUE SYM_fiber_run;
|
23
|
+
VALUE SYM_fiber_schedule;
|
24
|
+
VALUE SYM_fiber_switchpoint;
|
25
|
+
VALUE SYM_fiber_terminate;
|
26
|
+
|
27
|
+
static VALUE Fiber_safe_transfer(int argc, VALUE *argv, VALUE self) {
|
28
|
+
VALUE arg = (argc == 0) ? Qnil : argv[0];
|
29
|
+
VALUE ret = rb_funcall(self, ID_transfer, 1, arg);
|
30
|
+
|
31
|
+
TEST_RESUME_EXCEPTION(ret);
|
32
|
+
RB_GC_GUARD(ret);
|
33
|
+
return ret;
|
34
|
+
}
|
35
|
+
|
36
|
+
inline VALUE Fiber_auto_watcher(VALUE self) {
|
37
|
+
VALUE watcher;
|
38
|
+
if (cEvent == Qnil) {
|
39
|
+
cEvent = rb_const_get(mPolyphony, rb_intern("Event"));
|
40
|
+
}
|
41
|
+
|
42
|
+
watcher = rb_ivar_get(self, ID_ivar_auto_watcher);
|
43
|
+
if (watcher == Qnil) {
|
44
|
+
watcher = rb_funcall(cEvent, ID_new, 0);
|
45
|
+
rb_ivar_set(self, ID_ivar_auto_watcher, watcher);
|
46
|
+
}
|
47
|
+
return watcher;
|
48
|
+
}
|
49
|
+
|
50
|
+
static VALUE Fiber_schedule(int argc, VALUE *argv, VALUE self) {
|
51
|
+
VALUE value = (argc == 0) ? Qnil : argv[0];
|
52
|
+
Fiber_make_runnable(self, value);
|
53
|
+
return self;
|
54
|
+
}
|
55
|
+
|
56
|
+
static VALUE Fiber_state(VALUE self) {
|
57
|
+
if (!rb_fiber_alive_p(self) || (rb_ivar_get(self, ID_ivar_running) == Qfalse))
|
58
|
+
return SYM_dead;
|
59
|
+
if (rb_fiber_current() == self) return SYM_running;
|
60
|
+
if (rb_ivar_get(self, ID_runnable) != Qnil) return SYM_runnable;
|
61
|
+
|
62
|
+
return SYM_waiting;
|
63
|
+
}
|
64
|
+
|
65
|
+
void Fiber_make_runnable(VALUE fiber, VALUE value) {
|
66
|
+
VALUE thread = rb_ivar_get(fiber, ID_ivar_thread);
|
67
|
+
if (thread != Qnil) {
|
68
|
+
Thread_schedule_fiber(thread, fiber, value);
|
69
|
+
}
|
70
|
+
else {
|
71
|
+
rb_warn("No thread set for fiber (fiber, value, caller):");
|
72
|
+
}
|
73
|
+
}
|
74
|
+
|
75
|
+
void Init_Fiber() {
|
76
|
+
VALUE cFiber = rb_const_get(rb_cObject, rb_intern("Fiber"));
|
77
|
+
rb_define_method(cFiber, "safe_transfer", Fiber_safe_transfer, -1);
|
78
|
+
rb_define_method(cFiber, "schedule", Fiber_schedule, -1);
|
79
|
+
rb_define_method(cFiber, "state", Fiber_state, 0);
|
80
|
+
rb_define_method(cFiber, "auto_watcher", Fiber_auto_watcher, 0);
|
81
|
+
|
82
|
+
SYM_dead = ID2SYM(rb_intern("dead"));
|
83
|
+
SYM_running = ID2SYM(rb_intern("running"));
|
84
|
+
SYM_runnable = ID2SYM(rb_intern("runnable"));
|
85
|
+
SYM_waiting = ID2SYM(rb_intern("waiting"));
|
86
|
+
rb_global_variable(&SYM_dead);
|
87
|
+
rb_global_variable(&SYM_running);
|
88
|
+
rb_global_variable(&SYM_runnable);
|
89
|
+
rb_global_variable(&SYM_waiting);
|
90
|
+
|
91
|
+
ID_fiber_trace = rb_intern("__fiber_trace__");
|
92
|
+
ID_ivar_auto_watcher = rb_intern("@auto_watcher");
|
93
|
+
|
94
|
+
SYM_fiber_create = ID2SYM(rb_intern("fiber_create"));
|
95
|
+
SYM_fiber_ev_loop_enter = ID2SYM(rb_intern("fiber_ev_loop_enter"));
|
96
|
+
SYM_fiber_ev_loop_leave = ID2SYM(rb_intern("fiber_ev_loop_leave"));
|
97
|
+
SYM_fiber_run = ID2SYM(rb_intern("fiber_run"));
|
98
|
+
SYM_fiber_schedule = ID2SYM(rb_intern("fiber_schedule"));
|
99
|
+
SYM_fiber_switchpoint = ID2SYM(rb_intern("fiber_switchpoint"));
|
100
|
+
SYM_fiber_terminate = ID2SYM(rb_intern("fiber_terminate"));
|
101
|
+
|
102
|
+
rb_global_variable(&SYM_fiber_create);
|
103
|
+
rb_global_variable(&SYM_fiber_ev_loop_enter);
|
104
|
+
rb_global_variable(&SYM_fiber_ev_loop_leave);
|
105
|
+
rb_global_variable(&SYM_fiber_run);
|
106
|
+
rb_global_variable(&SYM_fiber_schedule);
|
107
|
+
rb_global_variable(&SYM_fiber_switchpoint);
|
108
|
+
rb_global_variable(&SYM_fiber_terminate);
|
109
|
+
}
|
@@ -0,0 +1,882 @@
|
|
1
|
+
#include <netdb.h>
|
2
|
+
#include <sys/socket.h>
|
3
|
+
#include <sys/uio.h>
|
4
|
+
#include <unistd.h>
|
5
|
+
#include <fcntl.h>
|
6
|
+
|
7
|
+
#include "polyphony.h"
|
8
|
+
#include "../libev/ev.h"
|
9
|
+
|
10
|
+
VALUE cLibevAgent = Qnil;
|
11
|
+
VALUE cTCPSocket;
|
12
|
+
|
13
|
+
struct LibevAgent_t {
|
14
|
+
struct ev_loop *ev_loop;
|
15
|
+
struct ev_async break_async;
|
16
|
+
int running;
|
17
|
+
int ref_count;
|
18
|
+
int run_no_wait_count;
|
19
|
+
};
|
20
|
+
|
21
|
+
static size_t LibevAgent_size(const void *ptr) {
|
22
|
+
return sizeof(struct LibevAgent_t);
|
23
|
+
}
|
24
|
+
|
25
|
+
static const rb_data_type_t LibevAgent_type = {
|
26
|
+
"Libev",
|
27
|
+
{0, 0, LibevAgent_size,},
|
28
|
+
0, 0, RUBY_TYPED_FREE_IMMEDIATELY
|
29
|
+
};
|
30
|
+
|
31
|
+
static VALUE LibevAgent_allocate(VALUE klass) {
|
32
|
+
struct LibevAgent_t *agent = ALLOC(struct LibevAgent_t);
|
33
|
+
|
34
|
+
return TypedData_Wrap_Struct(klass, &LibevAgent_type, agent);
|
35
|
+
}
|
36
|
+
|
37
|
+
#define GetLibevAgent(obj, agent) \
|
38
|
+
TypedData_Get_Struct((obj), struct LibevAgent_t, &LibevAgent_type, (agent))
|
39
|
+
|
40
|
+
void break_async_callback(struct ev_loop *ev_loop, struct ev_async *ev_async, int revents) {
|
41
|
+
// This callback does nothing, the break async is used solely for breaking out
|
42
|
+
// of a *blocking* event loop (waking it up) in a thread-safe, signal-safe manner
|
43
|
+
}
|
44
|
+
|
45
|
+
static VALUE LibevAgent_initialize(VALUE self) {
|
46
|
+
struct LibevAgent_t *agent;
|
47
|
+
VALUE thread = rb_thread_current();
|
48
|
+
int is_main_thread = (thread == rb_thread_main());
|
49
|
+
|
50
|
+
GetLibevAgent(self, agent);
|
51
|
+
agent->ev_loop = is_main_thread ? EV_DEFAULT : ev_loop_new(EVFLAG_NOSIGMASK);
|
52
|
+
|
53
|
+
ev_async_init(&agent->break_async, break_async_callback);
|
54
|
+
ev_async_start(agent->ev_loop, &agent->break_async);
|
55
|
+
ev_unref(agent->ev_loop); // don't count the break_async watcher
|
56
|
+
|
57
|
+
agent->running = 0;
|
58
|
+
agent->ref_count = 0;
|
59
|
+
agent->run_no_wait_count = 0;
|
60
|
+
|
61
|
+
return Qnil;
|
62
|
+
}
|
63
|
+
|
64
|
+
VALUE LibevAgent_finalize(VALUE self) {
|
65
|
+
struct LibevAgent_t *agent;
|
66
|
+
GetLibevAgent(self, agent);
|
67
|
+
|
68
|
+
ev_async_stop(agent->ev_loop, &agent->break_async);
|
69
|
+
|
70
|
+
if (!ev_is_default_loop(agent->ev_loop)) ev_loop_destroy(agent->ev_loop);
|
71
|
+
|
72
|
+
return self;
|
73
|
+
}
|
74
|
+
|
75
|
+
VALUE LibevAgent_post_fork(VALUE self) {
|
76
|
+
struct LibevAgent_t *agent;
|
77
|
+
GetLibevAgent(self, agent);
|
78
|
+
|
79
|
+
if (!ev_is_default_loop(agent->ev_loop)) {
|
80
|
+
// post_fork is called only for the main thread of the forked process. If
|
81
|
+
// the forked process was forked from a thread other than the main one,
|
82
|
+
// we remove the old non-default ev_loop and use the default one instead.
|
83
|
+
ev_loop_destroy(agent->ev_loop);
|
84
|
+
agent->ev_loop = EV_DEFAULT;
|
85
|
+
}
|
86
|
+
|
87
|
+
ev_loop_fork(agent->ev_loop);
|
88
|
+
|
89
|
+
return self;
|
90
|
+
}
|
91
|
+
|
92
|
+
VALUE LibevAgent_ref(VALUE self) {
|
93
|
+
struct LibevAgent_t *agent;
|
94
|
+
GetLibevAgent(self, agent);
|
95
|
+
|
96
|
+
agent->ref_count++;
|
97
|
+
return self;
|
98
|
+
}
|
99
|
+
|
100
|
+
VALUE LibevAgent_unref(VALUE self) {
|
101
|
+
struct LibevAgent_t *agent;
|
102
|
+
GetLibevAgent(self, agent);
|
103
|
+
|
104
|
+
agent->ref_count--;
|
105
|
+
return self;
|
106
|
+
}
|
107
|
+
|
108
|
+
int LibevAgent_ref_count(VALUE self) {
|
109
|
+
struct LibevAgent_t *agent;
|
110
|
+
GetLibevAgent(self, agent);
|
111
|
+
|
112
|
+
return agent->ref_count;
|
113
|
+
}
|
114
|
+
|
115
|
+
void LibevAgent_reset_ref_count(VALUE self) {
|
116
|
+
struct LibevAgent_t *agent;
|
117
|
+
GetLibevAgent(self, agent);
|
118
|
+
|
119
|
+
agent->ref_count = 0;
|
120
|
+
}
|
121
|
+
|
122
|
+
VALUE LibevAgent_pending_count(VALUE self) {
|
123
|
+
int count;
|
124
|
+
struct LibevAgent_t *agent;
|
125
|
+
GetLibevAgent(self, agent);
|
126
|
+
count = ev_pending_count(agent->ev_loop);
|
127
|
+
return INT2NUM(count);
|
128
|
+
}
|
129
|
+
|
130
|
+
VALUE LibevAgent_poll(VALUE self, VALUE nowait, VALUE current_fiber, VALUE queue) {
|
131
|
+
int is_nowait = nowait == Qtrue;
|
132
|
+
struct LibevAgent_t *agent;
|
133
|
+
GetLibevAgent(self, agent);
|
134
|
+
|
135
|
+
if (is_nowait) {
|
136
|
+
long runnable_count = Queue_len(queue);
|
137
|
+
agent->run_no_wait_count++;
|
138
|
+
if (agent->run_no_wait_count < runnable_count || agent->run_no_wait_count < 10)
|
139
|
+
return self;
|
140
|
+
}
|
141
|
+
|
142
|
+
agent->run_no_wait_count = 0;
|
143
|
+
|
144
|
+
FIBER_TRACE(2, SYM_fiber_ev_loop_enter, current_fiber);
|
145
|
+
agent->running = 1;
|
146
|
+
ev_run(agent->ev_loop, is_nowait ? EVRUN_NOWAIT : EVRUN_ONCE);
|
147
|
+
agent->running = 0;
|
148
|
+
FIBER_TRACE(2, SYM_fiber_ev_loop_leave, current_fiber);
|
149
|
+
|
150
|
+
return self;
|
151
|
+
}
|
152
|
+
|
153
|
+
VALUE LibevAgent_break(VALUE self) {
|
154
|
+
struct LibevAgent_t *agent;
|
155
|
+
GetLibevAgent(self, agent);
|
156
|
+
|
157
|
+
if (agent->running) {
|
158
|
+
// Since the loop will run until at least one event has occurred, we signal
|
159
|
+
// the selector's associated async watcher, which will cause the ev loop to
|
160
|
+
// return. In contrast to using `ev_break` to break out of the loop, which
|
161
|
+
// should be called from the same thread (from within the ev_loop), using an
|
162
|
+
// `ev_async` allows us to interrupt the event loop across threads.
|
163
|
+
ev_async_send(agent->ev_loop, &agent->break_async);
|
164
|
+
return Qtrue;
|
165
|
+
}
|
166
|
+
|
167
|
+
return Qnil;
|
168
|
+
}
|
169
|
+
|
170
|
+
#include "polyphony.h"
|
171
|
+
#include "../libev/ev.h"
|
172
|
+
|
173
|
+
//////////////////////////////////////////////////////////////////////
|
174
|
+
//////////////////////////////////////////////////////////////////////
|
175
|
+
// the following is copied verbatim from the Ruby source code (io.c)
|
176
|
+
struct io_internal_read_struct {
|
177
|
+
int fd;
|
178
|
+
int nonblock;
|
179
|
+
void *buf;
|
180
|
+
size_t capa;
|
181
|
+
};
|
182
|
+
|
183
|
+
#define StringValue(v) rb_string_value(&(v))
|
184
|
+
|
185
|
+
int io_setstrbuf(VALUE *str, long len) {
|
186
|
+
#ifdef _WIN32
|
187
|
+
len = (len + 1) & ~1L; /* round up for wide char */
|
188
|
+
#endif
|
189
|
+
if (NIL_P(*str)) {
|
190
|
+
*str = rb_str_new(0, len);
|
191
|
+
return 1;
|
192
|
+
}
|
193
|
+
else {
|
194
|
+
VALUE s = StringValue(*str);
|
195
|
+
long clen = RSTRING_LEN(s);
|
196
|
+
if (clen >= len) {
|
197
|
+
rb_str_modify(s);
|
198
|
+
return 0;
|
199
|
+
}
|
200
|
+
len -= clen;
|
201
|
+
}
|
202
|
+
rb_str_modify_expand(*str, len);
|
203
|
+
return 0;
|
204
|
+
}
|
205
|
+
|
206
|
+
#define MAX_REALLOC_GAP 4096
|
207
|
+
static void io_shrink_read_string(VALUE str, long n) {
|
208
|
+
if (rb_str_capacity(str) - n > MAX_REALLOC_GAP) {
|
209
|
+
rb_str_resize(str, n);
|
210
|
+
}
|
211
|
+
}
|
212
|
+
|
213
|
+
void io_set_read_length(VALUE str, long n, int shrinkable) {
|
214
|
+
if (RSTRING_LEN(str) != n) {
|
215
|
+
rb_str_modify(str);
|
216
|
+
rb_str_set_len(str, n);
|
217
|
+
if (shrinkable) io_shrink_read_string(str, n);
|
218
|
+
}
|
219
|
+
}
|
220
|
+
|
221
|
+
static rb_encoding* io_read_encoding(rb_io_t *fptr) {
|
222
|
+
if (fptr->encs.enc) {
|
223
|
+
return fptr->encs.enc;
|
224
|
+
}
|
225
|
+
return rb_default_external_encoding();
|
226
|
+
}
|
227
|
+
|
228
|
+
VALUE io_enc_str(VALUE str, rb_io_t *fptr) {
|
229
|
+
OBJ_TAINT(str);
|
230
|
+
rb_enc_associate(str, io_read_encoding(fptr));
|
231
|
+
return str;
|
232
|
+
}
|
233
|
+
|
234
|
+
//////////////////////////////////////////////////////////////////////
|
235
|
+
//////////////////////////////////////////////////////////////////////
|
236
|
+
|
237
|
+
struct libev_io {
|
238
|
+
struct ev_io io;
|
239
|
+
VALUE fiber;
|
240
|
+
};
|
241
|
+
|
242
|
+
void LibevAgent_io_callback(EV_P_ ev_io *w, int revents)
|
243
|
+
{
|
244
|
+
struct libev_io *watcher = (struct libev_io *)w;
|
245
|
+
Fiber_make_runnable(watcher->fiber, Qnil);
|
246
|
+
}
|
247
|
+
|
248
|
+
inline VALUE libev_await(struct LibevAgent_t *agent) {
|
249
|
+
VALUE ret;
|
250
|
+
agent->ref_count++;
|
251
|
+
ret = Thread_switch_fiber(rb_thread_current());
|
252
|
+
agent->ref_count--;
|
253
|
+
RB_GC_GUARD(ret);
|
254
|
+
return ret;
|
255
|
+
}
|
256
|
+
|
257
|
+
VALUE libev_agent_await(VALUE self) {
|
258
|
+
struct LibevAgent_t *agent;
|
259
|
+
GetLibevAgent(self, agent);
|
260
|
+
return libev_await(agent);
|
261
|
+
}
|
262
|
+
|
263
|
+
VALUE libev_io_wait(struct LibevAgent_t *agent, struct libev_io *watcher, rb_io_t *fptr, int flags) {
|
264
|
+
VALUE switchpoint_result;
|
265
|
+
|
266
|
+
if (watcher->fiber == Qnil) {
|
267
|
+
watcher->fiber = rb_fiber_current();
|
268
|
+
ev_io_init(&watcher->io, LibevAgent_io_callback, fptr->fd, flags);
|
269
|
+
}
|
270
|
+
ev_io_start(agent->ev_loop, &watcher->io);
|
271
|
+
switchpoint_result = libev_await(agent);
|
272
|
+
ev_io_stop(agent->ev_loop, &watcher->io);
|
273
|
+
|
274
|
+
RB_GC_GUARD(switchpoint_result);
|
275
|
+
return switchpoint_result;
|
276
|
+
}
|
277
|
+
|
278
|
+
VALUE libev_snooze() {
|
279
|
+
Fiber_make_runnable(rb_fiber_current(), Qnil);
|
280
|
+
return Thread_switch_fiber(rb_thread_current());
|
281
|
+
}
|
282
|
+
|
283
|
+
ID ID_ivar_is_nonblocking;
|
284
|
+
|
285
|
+
// Since we need to ensure that fd's are non-blocking before every I/O
|
286
|
+
// operation, here we improve upon Ruby's rb_io_set_nonblock by caching the
|
287
|
+
// "nonblock" state in an instance variable. Calling rb_ivar_get on every read
|
288
|
+
// is still much cheaper than doing a fcntl syscall on every read! Preliminary
|
289
|
+
// benchmarks (with a "hello world" HTTP server) show throughput is improved
|
290
|
+
// by 10-13%.
|
291
|
+
inline void io_set_nonblock(rb_io_t *fptr, VALUE io) {
|
292
|
+
#ifdef _WIN32
|
293
|
+
return rb_w32_set_nonblock(fptr->fd);
|
294
|
+
#elif defined(F_GETFL)
|
295
|
+
VALUE is_nonblocking = rb_ivar_get(io, ID_ivar_is_nonblocking);
|
296
|
+
if (is_nonblocking == Qnil) {
|
297
|
+
rb_ivar_set(io, ID_ivar_is_nonblocking, Qtrue);
|
298
|
+
int oflags = fcntl(fptr->fd, F_GETFL);
|
299
|
+
if (oflags == -1) return;
|
300
|
+
if (oflags & O_NONBLOCK) return;
|
301
|
+
oflags |= O_NONBLOCK;
|
302
|
+
fcntl(fptr->fd, F_SETFL, oflags);
|
303
|
+
}
|
304
|
+
#endif
|
305
|
+
return;
|
306
|
+
}
|
307
|
+
|
308
|
+
VALUE LibevAgent_read(VALUE self, VALUE io, VALUE str, VALUE length, VALUE to_eof) {
|
309
|
+
struct LibevAgent_t *agent;
|
310
|
+
struct libev_io watcher;
|
311
|
+
rb_io_t *fptr;
|
312
|
+
long dynamic_len = length == Qnil;
|
313
|
+
long len = dynamic_len ? 4096 : NUM2INT(length);
|
314
|
+
int shrinkable = io_setstrbuf(&str, len);
|
315
|
+
char *buf = RSTRING_PTR(str);
|
316
|
+
long total = 0;
|
317
|
+
VALUE switchpoint_result = Qnil;
|
318
|
+
int read_to_eof = RTEST(to_eof);
|
319
|
+
VALUE underlying_io = rb_iv_get(io, "@io");
|
320
|
+
|
321
|
+
GetLibevAgent(self, agent);
|
322
|
+
if (underlying_io != Qnil) io = underlying_io;
|
323
|
+
GetOpenFile(io, fptr);
|
324
|
+
rb_io_check_byte_readable(fptr);
|
325
|
+
io_set_nonblock(fptr, io);
|
326
|
+
watcher.fiber = Qnil;
|
327
|
+
|
328
|
+
OBJ_TAINT(str);
|
329
|
+
|
330
|
+
// Apparently after reopening a closed file, the file position is not reset,
|
331
|
+
// which causes the read to fail. Fortunately we can use fptr->rbuf.len to
|
332
|
+
// find out if that's the case.
|
333
|
+
// See: https://github.com/digital-fabric/polyphony/issues/30
|
334
|
+
if (fptr->rbuf.len > 0) {
|
335
|
+
lseek(fptr->fd, -fptr->rbuf.len, SEEK_CUR);
|
336
|
+
fptr->rbuf.len = 0;
|
337
|
+
}
|
338
|
+
|
339
|
+
while (1) {
|
340
|
+
ssize_t n = read(fptr->fd, buf, len - total);
|
341
|
+
if (n < 0) {
|
342
|
+
int e = errno;
|
343
|
+
if (e != EWOULDBLOCK && e != EAGAIN) rb_syserr_fail(e, strerror(e));
|
344
|
+
|
345
|
+
switchpoint_result = libev_io_wait(agent, &watcher, fptr, EV_READ);
|
346
|
+
if (TEST_EXCEPTION(switchpoint_result)) goto error;
|
347
|
+
}
|
348
|
+
else {
|
349
|
+
switchpoint_result = libev_snooze();
|
350
|
+
if (TEST_EXCEPTION(switchpoint_result)) goto error;
|
351
|
+
|
352
|
+
if (n == 0) break; // EOF
|
353
|
+
total = total + n;
|
354
|
+
if (!read_to_eof) break;
|
355
|
+
|
356
|
+
if (total == len) {
|
357
|
+
if (!dynamic_len) break;
|
358
|
+
|
359
|
+
rb_str_resize(str, total);
|
360
|
+
rb_str_modify_expand(str, len);
|
361
|
+
buf = RSTRING_PTR(str) + total;
|
362
|
+
shrinkable = 0;
|
363
|
+
len += len;
|
364
|
+
}
|
365
|
+
else buf += n;
|
366
|
+
}
|
367
|
+
}
|
368
|
+
if (total == 0) return Qnil;
|
369
|
+
|
370
|
+
io_set_read_length(str, total, shrinkable);
|
371
|
+
io_enc_str(str, fptr);
|
372
|
+
|
373
|
+
RB_GC_GUARD(watcher.fiber);
|
374
|
+
RB_GC_GUARD(switchpoint_result);
|
375
|
+
|
376
|
+
return str;
|
377
|
+
error:
|
378
|
+
return rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
|
379
|
+
}
|
380
|
+
|
381
|
+
VALUE LibevAgent_read_loop(VALUE self, VALUE io) {
|
382
|
+
|
383
|
+
#define PREPARE_STR() { \
|
384
|
+
str = Qnil; \
|
385
|
+
shrinkable = io_setstrbuf(&str, len); \
|
386
|
+
buf = RSTRING_PTR(str); \
|
387
|
+
total = 0; \
|
388
|
+
OBJ_TAINT(str); \
|
389
|
+
}
|
390
|
+
|
391
|
+
#define YIELD_STR() { \
|
392
|
+
io_set_read_length(str, total, shrinkable); \
|
393
|
+
io_enc_str(str, fptr); \
|
394
|
+
rb_yield(str); \
|
395
|
+
PREPARE_STR(); \
|
396
|
+
}
|
397
|
+
|
398
|
+
struct LibevAgent_t *agent;
|
399
|
+
struct libev_io watcher;
|
400
|
+
rb_io_t *fptr;
|
401
|
+
VALUE str;
|
402
|
+
long total;
|
403
|
+
long len = 8192;
|
404
|
+
int shrinkable;
|
405
|
+
char *buf;
|
406
|
+
VALUE switchpoint_result = Qnil;
|
407
|
+
VALUE underlying_io = rb_iv_get(io, "@io");
|
408
|
+
|
409
|
+
PREPARE_STR();
|
410
|
+
|
411
|
+
GetLibevAgent(self, agent);
|
412
|
+
if (underlying_io != Qnil) io = underlying_io;
|
413
|
+
GetOpenFile(io, fptr);
|
414
|
+
rb_io_check_byte_readable(fptr);
|
415
|
+
io_set_nonblock(fptr, io);
|
416
|
+
watcher.fiber = Qnil;
|
417
|
+
|
418
|
+
// Apparently after reopening a closed file, the file position is not reset,
|
419
|
+
// which causes the read to fail. Fortunately we can use fptr->rbuf.len to
|
420
|
+
// find out if that's the case.
|
421
|
+
// See: https://github.com/digital-fabric/polyphony/issues/30
|
422
|
+
if (fptr->rbuf.len > 0) {
|
423
|
+
lseek(fptr->fd, -fptr->rbuf.len, SEEK_CUR);
|
424
|
+
fptr->rbuf.len = 0;
|
425
|
+
}
|
426
|
+
|
427
|
+
while (1) {
|
428
|
+
ssize_t n = read(fptr->fd, buf, len);
|
429
|
+
if (n < 0) {
|
430
|
+
int e = errno;
|
431
|
+
if ((e != EWOULDBLOCK && e != EAGAIN)) rb_syserr_fail(e, strerror(e));
|
432
|
+
|
433
|
+
switchpoint_result = libev_io_wait(agent, &watcher, fptr, EV_READ);
|
434
|
+
if (TEST_EXCEPTION(switchpoint_result)) goto error;
|
435
|
+
}
|
436
|
+
else {
|
437
|
+
switchpoint_result = libev_snooze();
|
438
|
+
if (TEST_EXCEPTION(switchpoint_result)) goto error;
|
439
|
+
|
440
|
+
if (n == 0) break; // EOF
|
441
|
+
|
442
|
+
total = n;
|
443
|
+
YIELD_STR();
|
444
|
+
Fiber_make_runnable(rb_fiber_current(), Qnil);
|
445
|
+
switchpoint_result = Thread_switch_fiber(rb_thread_current());
|
446
|
+
if (TEST_EXCEPTION(switchpoint_result)) {
|
447
|
+
goto error;
|
448
|
+
}
|
449
|
+
}
|
450
|
+
}
|
451
|
+
|
452
|
+
RB_GC_GUARD(str);
|
453
|
+
RB_GC_GUARD(watcher.fiber);
|
454
|
+
RB_GC_GUARD(switchpoint_result);
|
455
|
+
|
456
|
+
return io;
|
457
|
+
error:
|
458
|
+
return rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
|
459
|
+
}
|
460
|
+
|
461
|
+
VALUE LibevAgent_write(VALUE self, VALUE io, VALUE str) {
|
462
|
+
struct LibevAgent_t *agent;
|
463
|
+
struct libev_io watcher;
|
464
|
+
rb_io_t *fptr;
|
465
|
+
VALUE switchpoint_result = Qnil;
|
466
|
+
VALUE underlying_io;
|
467
|
+
char *buf = StringValuePtr(str);
|
468
|
+
long len = RSTRING_LEN(str);
|
469
|
+
long left = len;
|
470
|
+
|
471
|
+
underlying_io = rb_iv_get(io, "@io");
|
472
|
+
if (underlying_io != Qnil) io = underlying_io;
|
473
|
+
GetLibevAgent(self, agent);
|
474
|
+
io = rb_io_get_write_io(io);
|
475
|
+
GetOpenFile(io, fptr);
|
476
|
+
watcher.fiber = Qnil;
|
477
|
+
|
478
|
+
while (left > 0) {
|
479
|
+
ssize_t n = write(fptr->fd, buf, left);
|
480
|
+
if (n < 0) {
|
481
|
+
int e = errno;
|
482
|
+
if ((e != EWOULDBLOCK && e != EAGAIN)) rb_syserr_fail(e, strerror(e));
|
483
|
+
switchpoint_result = libev_io_wait(agent, &watcher, fptr, EV_WRITE);
|
484
|
+
if (TEST_EXCEPTION(switchpoint_result)) goto error;
|
485
|
+
}
|
486
|
+
else {
|
487
|
+
buf += n;
|
488
|
+
left -= n;
|
489
|
+
}
|
490
|
+
}
|
491
|
+
|
492
|
+
if (watcher.fiber == Qnil) {
|
493
|
+
switchpoint_result = libev_snooze();
|
494
|
+
if (TEST_EXCEPTION(switchpoint_result)) goto error;
|
495
|
+
}
|
496
|
+
|
497
|
+
RB_GC_GUARD(watcher.fiber);
|
498
|
+
RB_GC_GUARD(switchpoint_result);
|
499
|
+
|
500
|
+
return INT2NUM(len);
|
501
|
+
error:
|
502
|
+
return rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
|
503
|
+
}
|
504
|
+
|
505
|
+
VALUE LibevAgent_writev(VALUE self, VALUE io, int argc, VALUE *argv) {
|
506
|
+
struct LibevAgent_t *agent;
|
507
|
+
struct libev_io watcher;
|
508
|
+
rb_io_t *fptr;
|
509
|
+
VALUE switchpoint_result = Qnil;
|
510
|
+
VALUE underlying_io;
|
511
|
+
long total_length = 0;
|
512
|
+
long total_written = 0;
|
513
|
+
struct iovec *iov = 0;
|
514
|
+
struct iovec *iov_ptr = 0;
|
515
|
+
int iov_count = argc;
|
516
|
+
|
517
|
+
underlying_io = rb_iv_get(io, "@io");
|
518
|
+
if (underlying_io != Qnil) io = underlying_io;
|
519
|
+
GetLibevAgent(self, agent);
|
520
|
+
io = rb_io_get_write_io(io);
|
521
|
+
GetOpenFile(io, fptr);
|
522
|
+
watcher.fiber = Qnil;
|
523
|
+
|
524
|
+
iov = malloc(iov_count * sizeof(struct iovec));
|
525
|
+
for (int i = 0; i < argc; i++) {
|
526
|
+
VALUE str = argv[i];
|
527
|
+
iov[i].iov_base = StringValuePtr(str);
|
528
|
+
iov[i].iov_len = RSTRING_LEN(str);
|
529
|
+
total_length += iov[i].iov_len;
|
530
|
+
}
|
531
|
+
iov_ptr = iov;
|
532
|
+
|
533
|
+
while (1) {
|
534
|
+
ssize_t n = writev(fptr->fd, iov_ptr, iov_count);
|
535
|
+
if (n < 0) {
|
536
|
+
int e = errno;
|
537
|
+
if ((e != EWOULDBLOCK && e != EAGAIN)) rb_syserr_fail(e, strerror(e));
|
538
|
+
|
539
|
+
switchpoint_result = libev_io_wait(agent, &watcher, fptr, EV_WRITE);
|
540
|
+
if (TEST_EXCEPTION(switchpoint_result)) goto error;
|
541
|
+
}
|
542
|
+
else {
|
543
|
+
total_written += n;
|
544
|
+
if (total_written == total_length) break;
|
545
|
+
|
546
|
+
while (n > 0) {
|
547
|
+
if ((size_t) n < iov_ptr[0].iov_len) {
|
548
|
+
iov_ptr[0].iov_base = (char *) iov_ptr[0].iov_base + n;
|
549
|
+
iov_ptr[0].iov_len -= n;
|
550
|
+
n = 0;
|
551
|
+
}
|
552
|
+
else {
|
553
|
+
n -= iov_ptr[0].iov_len;
|
554
|
+
iov_ptr += 1;
|
555
|
+
iov_count -= 1;
|
556
|
+
}
|
557
|
+
}
|
558
|
+
}
|
559
|
+
}
|
560
|
+
if (watcher.fiber == Qnil) {
|
561
|
+
switchpoint_result = libev_snooze();
|
562
|
+
if (TEST_EXCEPTION(switchpoint_result)) goto error;
|
563
|
+
}
|
564
|
+
|
565
|
+
RB_GC_GUARD(watcher.fiber);
|
566
|
+
RB_GC_GUARD(switchpoint_result);
|
567
|
+
|
568
|
+
free(iov);
|
569
|
+
return INT2NUM(total_written);
|
570
|
+
error:
|
571
|
+
free(iov);
|
572
|
+
return rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
|
573
|
+
}
|
574
|
+
|
575
|
+
VALUE LibevAgent_write_m(int argc, VALUE *argv, VALUE self) {
|
576
|
+
if (argc < 2)
|
577
|
+
// TODO: raise ArgumentError
|
578
|
+
rb_raise(rb_eRuntimeError, "(wrong number of arguments (expected 2 or more))");
|
579
|
+
|
580
|
+
return (argc == 2) ?
|
581
|
+
LibevAgent_write(self, argv[0], argv[1]) :
|
582
|
+
LibevAgent_writev(self, argv[0], argc - 1, argv + 1);
|
583
|
+
}
|
584
|
+
|
585
|
+
///////////////////////////////////////////////////////////////////////////
|
586
|
+
|
587
|
+
VALUE LibevAgent_accept(VALUE self, VALUE sock) {
|
588
|
+
struct LibevAgent_t *agent;
|
589
|
+
struct libev_io watcher;
|
590
|
+
rb_io_t *fptr;
|
591
|
+
int fd;
|
592
|
+
struct sockaddr addr;
|
593
|
+
socklen_t len = (socklen_t)sizeof addr;
|
594
|
+
VALUE switchpoint_result = Qnil;
|
595
|
+
VALUE underlying_sock = rb_iv_get(sock, "@io");
|
596
|
+
if (underlying_sock != Qnil) sock = underlying_sock;
|
597
|
+
|
598
|
+
GetLibevAgent(self, agent);
|
599
|
+
GetOpenFile(sock, fptr);
|
600
|
+
io_set_nonblock(fptr, sock);
|
601
|
+
watcher.fiber = Qnil;
|
602
|
+
while (1) {
|
603
|
+
fd = accept(fptr->fd, &addr, &len);
|
604
|
+
if (fd < 0) {
|
605
|
+
int e = errno;
|
606
|
+
if ((e != EWOULDBLOCK && e != EAGAIN)) rb_syserr_fail(e, strerror(e));
|
607
|
+
|
608
|
+
switchpoint_result = libev_io_wait(agent, &watcher, fptr, EV_READ);
|
609
|
+
if (TEST_EXCEPTION(switchpoint_result)) goto error;
|
610
|
+
}
|
611
|
+
else {
|
612
|
+
VALUE socket;
|
613
|
+
rb_io_t *fp;
|
614
|
+
switchpoint_result = libev_snooze();
|
615
|
+
if (TEST_EXCEPTION(switchpoint_result)) {
|
616
|
+
close(fd); // close fd since we're raising an exception
|
617
|
+
goto error;
|
618
|
+
}
|
619
|
+
|
620
|
+
socket = rb_obj_alloc(cTCPSocket);
|
621
|
+
MakeOpenFile(socket, fp);
|
622
|
+
rb_update_max_fd(fd);
|
623
|
+
fp->fd = fd;
|
624
|
+
fp->mode = FMODE_READWRITE | FMODE_DUPLEX;
|
625
|
+
rb_io_ascii8bit_binmode(socket);
|
626
|
+
io_set_nonblock(fp, socket);
|
627
|
+
rb_io_synchronized(fp);
|
628
|
+
|
629
|
+
// if (rsock_do_not_reverse_lookup) {
|
630
|
+
// fp->mode |= FMODE_NOREVLOOKUP;
|
631
|
+
// }
|
632
|
+
return socket;
|
633
|
+
}
|
634
|
+
}
|
635
|
+
RB_GC_GUARD(switchpoint_result);
|
636
|
+
return Qnil;
|
637
|
+
error:
|
638
|
+
return rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
|
639
|
+
}
|
640
|
+
|
641
|
+
VALUE LibevAgent_accept_loop(VALUE self, VALUE sock) {
|
642
|
+
struct LibevAgent_t *agent;
|
643
|
+
struct libev_io watcher;
|
644
|
+
rb_io_t *fptr;
|
645
|
+
int fd;
|
646
|
+
struct sockaddr addr;
|
647
|
+
socklen_t len = (socklen_t)sizeof addr;
|
648
|
+
VALUE switchpoint_result = Qnil;
|
649
|
+
VALUE socket = Qnil;
|
650
|
+
VALUE underlying_sock = rb_iv_get(sock, "@io");
|
651
|
+
if (underlying_sock != Qnil) sock = underlying_sock;
|
652
|
+
|
653
|
+
GetLibevAgent(self, agent);
|
654
|
+
GetOpenFile(sock, fptr);
|
655
|
+
io_set_nonblock(fptr, sock);
|
656
|
+
watcher.fiber = Qnil;
|
657
|
+
|
658
|
+
while (1) {
|
659
|
+
fd = accept(fptr->fd, &addr, &len);
|
660
|
+
if (fd < 0) {
|
661
|
+
int e = errno;
|
662
|
+
if ((e != EWOULDBLOCK && e != EAGAIN)) rb_syserr_fail(e, strerror(e));
|
663
|
+
|
664
|
+
switchpoint_result = libev_io_wait(agent, &watcher, fptr, EV_READ);
|
665
|
+
if (TEST_EXCEPTION(switchpoint_result)) goto error;
|
666
|
+
}
|
667
|
+
else {
|
668
|
+
rb_io_t *fp;
|
669
|
+
switchpoint_result = libev_snooze();
|
670
|
+
if (TEST_EXCEPTION(switchpoint_result)) {
|
671
|
+
close(fd); // close fd since we're raising an exception
|
672
|
+
goto error;
|
673
|
+
}
|
674
|
+
|
675
|
+
socket = rb_obj_alloc(cTCPSocket);
|
676
|
+
MakeOpenFile(socket, fp);
|
677
|
+
rb_update_max_fd(fd);
|
678
|
+
fp->fd = fd;
|
679
|
+
fp->mode = FMODE_READWRITE | FMODE_DUPLEX;
|
680
|
+
rb_io_ascii8bit_binmode(socket);
|
681
|
+
io_set_nonblock(fp, socket);
|
682
|
+
rb_io_synchronized(fp);
|
683
|
+
|
684
|
+
rb_yield(socket);
|
685
|
+
socket = Qnil;
|
686
|
+
}
|
687
|
+
}
|
688
|
+
|
689
|
+
RB_GC_GUARD(socket);
|
690
|
+
RB_GC_GUARD(watcher.fiber);
|
691
|
+
RB_GC_GUARD(switchpoint_result);
|
692
|
+
return Qnil;
|
693
|
+
error:
|
694
|
+
return rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
|
695
|
+
}
|
696
|
+
|
697
|
+
// VALUE LibevAgent_connect(VALUE self, VALUE sock, VALUE host, VALUE port) {
|
698
|
+
// struct LibevAgent_t *agent;
|
699
|
+
// struct libev_io watcher;
|
700
|
+
// rb_io_t *fptr;
|
701
|
+
// struct sockaddr_in addr;
|
702
|
+
// char *host_buf = StringValueCStr(host);
|
703
|
+
// VALUE switchpoint_result = Qnil;
|
704
|
+
// VALUE underlying_sock = rb_iv_get(sock, "@io");
|
705
|
+
// if (underlying_sock != Qnil) sock = underlying_sock;
|
706
|
+
|
707
|
+
// GetLibevAgent(self, agent);
|
708
|
+
// GetOpenFile(sock, fptr);
|
709
|
+
// io_set_nonblock(fptr, sock);
|
710
|
+
// watcher.fiber = Qnil;
|
711
|
+
|
712
|
+
// addr.sin_family = AF_INET;
|
713
|
+
// addr.sin_addr.s_addr = inet_addr(host_buf);
|
714
|
+
// addr.sin_port = htons(NUM2INT(port));
|
715
|
+
|
716
|
+
// while (1) {
|
717
|
+
// int result = connect(fptr->fd, &addr, sizeof(addr));
|
718
|
+
// if (result < 0) {
|
719
|
+
// int e = errno;
|
720
|
+
// if ((e != EWOULDBLOCK && e != EAGAIN)) rb_syserr_fail(e, strerror(e));
|
721
|
+
|
722
|
+
// switchpoint_result = libev_io_wait(agent, &watcher, fptr, EV_WRITE);
|
723
|
+
// if (TEST_EXCEPTION(switchpoint_result)) goto error;
|
724
|
+
// }
|
725
|
+
// else {
|
726
|
+
// switchpoint_result = libev_snooze();
|
727
|
+
// if (TEST_EXCEPTION(switchpoint_result)) goto error;
|
728
|
+
|
729
|
+
// return sock;
|
730
|
+
// }
|
731
|
+
// }
|
732
|
+
// RB_GC_GUARD(switchpoint_result);
|
733
|
+
// return Qnil;
|
734
|
+
// error:
|
735
|
+
// return rb_funcall(rb_mKernel, ID_raise, 1, switchpoint_result);
|
736
|
+
// }
|
737
|
+
|
738
|
+
VALUE LibevAgent_wait_io(VALUE self, VALUE io, VALUE write) {
|
739
|
+
struct LibevAgent_t *agent;
|
740
|
+
struct libev_io watcher;
|
741
|
+
rb_io_t *fptr;
|
742
|
+
VALUE switchpoint_result = Qnil;
|
743
|
+
int events = RTEST(write) ? EV_WRITE : EV_READ;
|
744
|
+
|
745
|
+
VALUE underlying_io = rb_iv_get(io, "@io");
|
746
|
+
GetLibevAgent(self, agent);
|
747
|
+
if (underlying_io != Qnil) io = underlying_io;
|
748
|
+
GetOpenFile(io, fptr);
|
749
|
+
|
750
|
+
watcher.fiber = rb_fiber_current();
|
751
|
+
ev_io_init(&watcher.io, LibevAgent_io_callback, fptr->fd, events);
|
752
|
+
ev_io_start(agent->ev_loop, &watcher.io);
|
753
|
+
switchpoint_result = libev_await(agent);
|
754
|
+
ev_io_stop(agent->ev_loop, &watcher.io);
|
755
|
+
|
756
|
+
TEST_RESUME_EXCEPTION(switchpoint_result);
|
757
|
+
RB_GC_GUARD(watcher.fiber);
|
758
|
+
RB_GC_GUARD(switchpoint_result);
|
759
|
+
return switchpoint_result;
|
760
|
+
}
|
761
|
+
|
762
|
+
struct libev_timer {
|
763
|
+
struct ev_timer timer;
|
764
|
+
VALUE fiber;
|
765
|
+
};
|
766
|
+
|
767
|
+
void LibevAgent_timer_callback(EV_P_ ev_timer *w, int revents)
|
768
|
+
{
|
769
|
+
struct libev_timer *watcher = (struct libev_timer *)w;
|
770
|
+
Fiber_make_runnable(watcher->fiber, Qnil);
|
771
|
+
}
|
772
|
+
|
773
|
+
VALUE LibevAgent_sleep(VALUE self, VALUE duration) {
|
774
|
+
struct LibevAgent_t *agent;
|
775
|
+
struct libev_timer watcher;
|
776
|
+
VALUE switchpoint_result = Qnil;
|
777
|
+
|
778
|
+
GetLibevAgent(self, agent);
|
779
|
+
watcher.fiber = rb_fiber_current();
|
780
|
+
ev_timer_init(&watcher.timer, LibevAgent_timer_callback, NUM2DBL(duration), 0.);
|
781
|
+
ev_timer_start(agent->ev_loop, &watcher.timer);
|
782
|
+
|
783
|
+
switchpoint_result = libev_await(agent);
|
784
|
+
|
785
|
+
ev_timer_stop(agent->ev_loop, &watcher.timer);
|
786
|
+
|
787
|
+
TEST_RESUME_EXCEPTION(switchpoint_result);
|
788
|
+
RB_GC_GUARD(watcher.fiber);
|
789
|
+
RB_GC_GUARD(switchpoint_result);
|
790
|
+
return switchpoint_result;
|
791
|
+
}
|
792
|
+
|
793
|
+
struct libev_child {
|
794
|
+
struct ev_child child;
|
795
|
+
VALUE fiber;
|
796
|
+
};
|
797
|
+
|
798
|
+
void LibevAgent_child_callback(EV_P_ ev_child *w, int revents)
|
799
|
+
{
|
800
|
+
struct libev_child *watcher = (struct libev_child *)w;
|
801
|
+
int exit_status = w->rstatus >> 8; // weird, why should we do this?
|
802
|
+
VALUE status;
|
803
|
+
|
804
|
+
status = rb_ary_new_from_args(2, INT2NUM(w->rpid), INT2NUM(exit_status));
|
805
|
+
Fiber_make_runnable(watcher->fiber, status);
|
806
|
+
}
|
807
|
+
|
808
|
+
VALUE LibevAgent_waitpid(VALUE self, VALUE pid) {
|
809
|
+
struct LibevAgent_t *agent;
|
810
|
+
struct libev_child watcher;
|
811
|
+
VALUE switchpoint_result = Qnil;
|
812
|
+
GetLibevAgent(self, agent);
|
813
|
+
|
814
|
+
watcher.fiber = rb_fiber_current();
|
815
|
+
ev_child_init(&watcher.child, LibevAgent_child_callback, NUM2INT(pid), 0);
|
816
|
+
ev_child_start(agent->ev_loop, &watcher.child);
|
817
|
+
|
818
|
+
switchpoint_result = libev_await(agent);
|
819
|
+
ev_child_stop(agent->ev_loop, &watcher.child);
|
820
|
+
|
821
|
+
TEST_RESUME_EXCEPTION(switchpoint_result);
|
822
|
+
RB_GC_GUARD(watcher.fiber);
|
823
|
+
RB_GC_GUARD(switchpoint_result);
|
824
|
+
return switchpoint_result;
|
825
|
+
}
|
826
|
+
|
827
|
+
struct ev_loop *LibevAgent_ev_loop(VALUE self) {
|
828
|
+
struct LibevAgent_t *agent;
|
829
|
+
GetLibevAgent(self, agent);
|
830
|
+
return agent->ev_loop;
|
831
|
+
}
|
832
|
+
|
833
|
+
void LibevAgent_async_callback(EV_P_ ev_async *w, int revents) { }
|
834
|
+
|
835
|
+
VALUE LibevAgent_wait_event(VALUE self, VALUE raise) {
|
836
|
+
struct LibevAgent_t *agent;
|
837
|
+
struct ev_async async;
|
838
|
+
VALUE switchpoint_result = Qnil;
|
839
|
+
GetLibevAgent(self, agent);
|
840
|
+
|
841
|
+
ev_async_init(&async, LibevAgent_async_callback);
|
842
|
+
ev_async_start(agent->ev_loop, &async);
|
843
|
+
|
844
|
+
switchpoint_result = libev_await(agent);
|
845
|
+
ev_async_stop(agent->ev_loop, &async);
|
846
|
+
|
847
|
+
if (RTEST(raise)) TEST_RESUME_EXCEPTION(switchpoint_result);
|
848
|
+
RB_GC_GUARD(switchpoint_result);
|
849
|
+
return switchpoint_result;
|
850
|
+
}
|
851
|
+
|
852
|
+
void Init_LibevAgent() {
|
853
|
+
rb_require("socket");
|
854
|
+
cTCPSocket = rb_const_get(rb_cObject, rb_intern("TCPSocket"));
|
855
|
+
|
856
|
+
cLibevAgent = rb_define_class_under(mPolyphony, "LibevAgent", rb_cData);
|
857
|
+
rb_define_alloc_func(cLibevAgent, LibevAgent_allocate);
|
858
|
+
|
859
|
+
rb_define_method(cLibevAgent, "initialize", LibevAgent_initialize, 0);
|
860
|
+
rb_define_method(cLibevAgent, "finalize", LibevAgent_finalize, 0);
|
861
|
+
rb_define_method(cLibevAgent, "post_fork", LibevAgent_post_fork, 0);
|
862
|
+
rb_define_method(cLibevAgent, "pending_count", LibevAgent_pending_count, 0);
|
863
|
+
|
864
|
+
rb_define_method(cLibevAgent, "ref", LibevAgent_ref, 0);
|
865
|
+
rb_define_method(cLibevAgent, "unref", LibevAgent_unref, 0);
|
866
|
+
|
867
|
+
rb_define_method(cLibevAgent, "poll", LibevAgent_poll, 3);
|
868
|
+
rb_define_method(cLibevAgent, "break", LibevAgent_break, 0);
|
869
|
+
|
870
|
+
rb_define_method(cLibevAgent, "read", LibevAgent_read, 4);
|
871
|
+
rb_define_method(cLibevAgent, "read_loop", LibevAgent_read_loop, 1);
|
872
|
+
rb_define_method(cLibevAgent, "write", LibevAgent_write_m, -1);
|
873
|
+
rb_define_method(cLibevAgent, "accept", LibevAgent_accept, 1);
|
874
|
+
rb_define_method(cLibevAgent, "accept_loop", LibevAgent_accept_loop, 1);
|
875
|
+
// rb_define_method(cLibevAgent, "connect", LibevAgent_accept, 3);
|
876
|
+
rb_define_method(cLibevAgent, "wait_io", LibevAgent_wait_io, 2);
|
877
|
+
rb_define_method(cLibevAgent, "sleep", LibevAgent_sleep, 1);
|
878
|
+
rb_define_method(cLibevAgent, "waitpid", LibevAgent_waitpid, 1);
|
879
|
+
rb_define_method(cLibevAgent, "wait_event", LibevAgent_wait_event, 1);
|
880
|
+
|
881
|
+
ID_ivar_is_nonblocking = rb_intern("@is_nonblocking");
|
882
|
+
}
|