farce 0.3.0-x86-linux-gnu → 0.4.1-x86-linux-gnu
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 +50 -0
- data/README.md +117 -1489
- data/docs/compatibility.md +74 -0
- data/docs/concurrency.md +151 -0
- data/docs/data-structures.md +313 -0
- data/docs/installation.md +63 -0
- data/docs/integrations.md +124 -0
- data/docs/introduction.md +266 -0
- data/docs/scheduling.md +176 -0
- data/docs/sharing.md +211 -0
- data/docs/shims.md +144 -0
- data/docs/utilities.md +93 -0
- data/docs/variants-and-scopes.md +92 -0
- data/ext/farce/LIBURING-LICENSE +20 -0
- data/ext/farce/atom.c +23 -66
- data/ext/farce/counter.c +1 -1
- data/ext/farce/depend +15 -7
- data/ext/farce/drivers.c +44 -2
- data/ext/farce/exchanger.c +1 -1
- data/ext/farce/extconf.rb +45 -2
- data/ext/farce/farce.c +8 -0
- data/ext/farce/flag.c +1 -1
- data/ext/farce/futex.c +76 -0
- data/ext/farce/futex.h +31 -0
- data/ext/farce/lock.c +16 -62
- data/ext/farce/map.c +10 -54
- data/ext/farce/native_wait.c +139 -0
- data/ext/farce/native_wait.h +37 -0
- data/ext/farce/priority_queue.c +16 -58
- data/ext/farce/queue.c +1 -1
- data/ext/farce/reactor.c +100 -11
- data/ext/farce/reactor.h +6 -2
- data/ext/farce/signal.c +17 -64
- data/ext/farce/tree_map.c +15 -54
- data/ext/farce/unshareable.c +1 -1
- data/ext/farce/unsupported.c +13 -1
- data/ext/farce/vector.c +13 -60
- data/ext/farce/weak_map.c +1 -1
- data/lib/farce/abstract/flag.rb +6 -0
- data/lib/farce/backports.rb +274 -0
- data/lib/farce/engine/jruby/fiber_scheduler.rb +3 -0
- data/lib/farce/engine/jruby/scheduler_synchronization.rb +87 -0
- data/lib/farce/engine/ruby/3.4/farce.so +0 -0
- data/lib/farce/engine/ruby/3.4/null_port.rb +21 -0
- data/lib/farce/engine/ruby/3.4/patch.rb +67 -0
- data/lib/farce/engine/ruby/3.4/port.rb +13 -12
- data/lib/farce/engine/ruby/3.4/ractor_methods.rb +0 -1
- data/lib/farce/engine/ruby/4.0/farce.so +0 -0
- data/lib/farce/engine/ruby/4.0/null_port.rb +36 -0
- data/lib/farce/engine/ruby/4.0/port.rb +1 -2
- data/lib/farce/engine/ruby/4.1/null_port.rb +5 -0
- data/lib/farce/engine/ruby.rb +7 -0
- data/lib/farce/engine/shared/null_port.rb +18 -0
- data/lib/farce/engine/shared/port.rb +41 -3
- data/lib/farce/engine/shared/queue.rb +4 -4
- data/lib/farce/engine/shared/ractor_methods.rb +2 -2
- data/lib/farce/engine/shared/ractor_selector.rb +73 -0
- data/lib/farce/extension.rb +150 -0
- data/lib/farce/integrations/ractor_tmvar.rb +4 -1
- data/lib/farce/internal/fake_ractor.rb +10 -2
- data/lib/farce/internal/scheduler_lifecycle.rb +7 -4
- data/lib/farce/null_port.rb +12 -0
- data/lib/farce/system.rb +10 -2
- data/lib/farce/version.rb +1 -1
- data/lib/farce.rb +1 -1
- metadata +27 -1
data/ext/farce/map.c
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
#include "containers.h"
|
|
2
|
+
#include "native_wait.h"
|
|
2
3
|
#include "transaction.h"
|
|
3
4
|
#include "ruby/fiber/scheduler.h"
|
|
4
|
-
#include "ruby/io.h"
|
|
5
5
|
|
|
6
6
|
#include <errno.h>
|
|
7
|
-
#include <fcntl.h>
|
|
8
7
|
#include <math.h>
|
|
9
8
|
#include <stdint.h>
|
|
10
9
|
#include <stdatomic.h>
|
|
11
10
|
#include <stdlib.h>
|
|
12
11
|
#include <time.h>
|
|
13
|
-
#include <unistd.h>
|
|
14
12
|
|
|
15
13
|
#define MAP_INITIAL_CAPACITY 16
|
|
16
14
|
|
|
@@ -42,9 +40,7 @@ typedef struct {
|
|
|
42
40
|
} map_slot_t;
|
|
43
41
|
|
|
44
42
|
typedef struct map_waiter {
|
|
45
|
-
|
|
46
|
-
int write_fd;
|
|
47
|
-
bool notified;
|
|
43
|
+
containers_waiter_t notification;
|
|
48
44
|
struct map_waiter *next;
|
|
49
45
|
} map_waiter_t;
|
|
50
46
|
|
|
@@ -141,46 +137,12 @@ map_parse_timeout(VALUE timeout)
|
|
|
141
137
|
return parsed;
|
|
142
138
|
}
|
|
143
139
|
|
|
144
|
-
static void
|
|
145
|
-
map_set_fd_flags(int fd)
|
|
146
|
-
{
|
|
147
|
-
#ifdef _WIN32
|
|
148
|
-
(void)fd;
|
|
149
|
-
#else
|
|
150
|
-
int flags = fcntl(fd, F_GETFL, 0);
|
|
151
|
-
if (flags >= 0) (void)fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
|
152
|
-
flags = fcntl(fd, F_GETFD, 0);
|
|
153
|
-
if (flags >= 0) (void)fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
|
|
154
|
-
#endif
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
static void
|
|
158
|
-
map_waiter_initialize(map_waiter_t *waiter)
|
|
159
|
-
{
|
|
160
|
-
int descriptors[2];
|
|
161
|
-
if (pipe(descriptors) != 0) rb_sys_fail("pipe");
|
|
162
|
-
waiter->read_fd = descriptors[0];
|
|
163
|
-
waiter->write_fd = descriptors[1];
|
|
164
|
-
waiter->notified = false;
|
|
165
|
-
waiter->next = NULL;
|
|
166
|
-
map_set_fd_flags(waiter->read_fd);
|
|
167
|
-
map_set_fd_flags(waiter->write_fd);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
140
|
static void
|
|
171
141
|
map_notify_waiters_locked(map_t *map)
|
|
172
142
|
{
|
|
173
|
-
unsigned char byte = 1;
|
|
174
143
|
map->generation++;
|
|
175
144
|
for (map_waiter_t *waiter = map->waiters; waiter; waiter = waiter->next) {
|
|
176
|
-
|
|
177
|
-
ssize_t result;
|
|
178
|
-
do {
|
|
179
|
-
result = write(waiter->write_fd, &byte, 1);
|
|
180
|
-
} while (result < 0 && errno == EINTR);
|
|
181
|
-
if (result == 1 || (result < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))) {
|
|
182
|
-
waiter->notified = true;
|
|
183
|
-
}
|
|
145
|
+
(void)containers_waiter_notify(&waiter->notification);
|
|
184
146
|
}
|
|
185
147
|
}
|
|
186
148
|
|
|
@@ -260,7 +222,7 @@ map_memsize(const void *pointer)
|
|
|
260
222
|
}
|
|
261
223
|
|
|
262
224
|
static const rb_data_type_t map_type = {
|
|
263
|
-
.wrap_struct_name = "
|
|
225
|
+
.wrap_struct_name = "Farce::Internal::Map",
|
|
264
226
|
.function = {
|
|
265
227
|
.dmark = map_mark,
|
|
266
228
|
.dfree = map_free,
|
|
@@ -430,13 +392,10 @@ static VALUE
|
|
|
430
392
|
map_wait_body(VALUE opaque)
|
|
431
393
|
{
|
|
432
394
|
map_wait_context_t *context = (map_wait_context_t *)opaque;
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
wait_timeout = DBL2NUM(remaining);
|
|
438
|
-
}
|
|
439
|
-
return containers_wait_for_readable(context->waiter->read_fd, wait_timeout) ? Qtrue : Qfalse;
|
|
395
|
+
return containers_waiter_wait(
|
|
396
|
+
&context->waiter->notification, &context->map->lock,
|
|
397
|
+
context->timeout->finite, context->timeout->deadline, NULL
|
|
398
|
+
) ? Qtrue : Qfalse;
|
|
440
399
|
}
|
|
441
400
|
|
|
442
401
|
static VALUE
|
|
@@ -451,8 +410,7 @@ map_wait_cleanup(VALUE opaque)
|
|
|
451
410
|
pthread_mutex_unlock(&context->map->lock);
|
|
452
411
|
context->registered = false;
|
|
453
412
|
}
|
|
454
|
-
|
|
455
|
-
close(context->waiter->write_fd);
|
|
413
|
+
containers_waiter_close(&context->waiter->notification);
|
|
456
414
|
return Qnil;
|
|
457
415
|
}
|
|
458
416
|
|
|
@@ -460,13 +418,11 @@ static bool
|
|
|
460
418
|
map_wait_once(map_t *map, uint64_t generation, map_timeout_t *timeout)
|
|
461
419
|
{
|
|
462
420
|
map_waiter_t waiter;
|
|
463
|
-
|
|
421
|
+
containers_waiter_init(&waiter.notification);
|
|
464
422
|
|
|
465
423
|
pthread_mutex_lock(&map->lock);
|
|
466
424
|
if (map->generation != generation) {
|
|
467
425
|
pthread_mutex_unlock(&map->lock);
|
|
468
|
-
close(waiter.read_fd);
|
|
469
|
-
close(waiter.write_fd);
|
|
470
426
|
return true;
|
|
471
427
|
}
|
|
472
428
|
waiter.next = map->waiters;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
#include "native_wait.h"
|
|
2
|
+
#include "ruby/fiber/scheduler.h"
|
|
3
|
+
|
|
4
|
+
#include <errno.h>
|
|
5
|
+
#include <fcntl.h>
|
|
6
|
+
#include <time.h>
|
|
7
|
+
#include <unistd.h>
|
|
8
|
+
|
|
9
|
+
static double
|
|
10
|
+
waiter_monotonic_now(void)
|
|
11
|
+
{
|
|
12
|
+
struct timespec now;
|
|
13
|
+
#ifdef CLOCK_MONOTONIC
|
|
14
|
+
if (clock_gettime(CLOCK_MONOTONIC, &now) == 0) {
|
|
15
|
+
return (double)now.tv_sec + (double)now.tv_nsec / 1000000000.0;
|
|
16
|
+
}
|
|
17
|
+
#endif
|
|
18
|
+
struct timeval fallback;
|
|
19
|
+
gettimeofday(&fallback, NULL);
|
|
20
|
+
return (double)fallback.tv_sec + (double)fallback.tv_usec / 1000000.0;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
static void
|
|
24
|
+
waiter_set_fd_flags(int fd)
|
|
25
|
+
{
|
|
26
|
+
#ifndef _WIN32
|
|
27
|
+
int flags = fcntl(fd, F_GETFL, 0);
|
|
28
|
+
if (flags >= 0) (void)fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
|
29
|
+
flags = fcntl(fd, F_GETFD, 0);
|
|
30
|
+
if (flags >= 0) (void)fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
|
|
31
|
+
#endif
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
void
|
|
35
|
+
containers_waiter_init(containers_waiter_t *waiter)
|
|
36
|
+
{
|
|
37
|
+
containers_futex_init(&waiter->futex);
|
|
38
|
+
waiter->read_fd = waiter->write_fd = -1;
|
|
39
|
+
waiter->mode = CONTAINERS_WAITER_PENDING;
|
|
40
|
+
waiter->notified = false;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
bool
|
|
44
|
+
containers_waiter_notify(containers_waiter_t *waiter)
|
|
45
|
+
{
|
|
46
|
+
if (waiter->notified) return true;
|
|
47
|
+
if (waiter->mode == CONTAINERS_WAITER_FUTEX) {
|
|
48
|
+
waiter->notified = true;
|
|
49
|
+
containers_futex_notify(&waiter->futex);
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* A pending waiter has not selected its backend yet. Latch its wake so
|
|
54
|
+
* setup can return immediately or a future kernel wait sees a mismatch. */
|
|
55
|
+
atomic_store_explicit(&waiter->futex.value, 1, memory_order_release);
|
|
56
|
+
if (waiter->mode == CONTAINERS_WAITER_PENDING) {
|
|
57
|
+
waiter->notified = true;
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
unsigned char byte = 1;
|
|
62
|
+
ssize_t result;
|
|
63
|
+
do {
|
|
64
|
+
result = write(waiter->write_fd, &byte, 1);
|
|
65
|
+
} while (result < 0 && errno == EINTR);
|
|
66
|
+
if (result == 1 || (result < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))) {
|
|
67
|
+
waiter->notified = true;
|
|
68
|
+
}
|
|
69
|
+
return waiter->notified;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
bool
|
|
73
|
+
containers_waiter_wait(
|
|
74
|
+
containers_waiter_t *waiter,
|
|
75
|
+
pthread_mutex_t *guard,
|
|
76
|
+
bool finite,
|
|
77
|
+
double deadline,
|
|
78
|
+
int *pipe_error
|
|
79
|
+
)
|
|
80
|
+
{
|
|
81
|
+
if (pipe_error) *pipe_error = 0;
|
|
82
|
+
if (atomic_load_explicit(&waiter->futex.value, memory_order_acquire) == 1) return true;
|
|
83
|
+
if (finite && deadline <= waiter_monotonic_now()) return false;
|
|
84
|
+
|
|
85
|
+
/* Selection can enter Ruby or probe the kernel. The registration stays
|
|
86
|
+
* pending, with no native container mutex held, throughout this work. */
|
|
87
|
+
VALUE scheduler = rb_fiber_scheduler_current();
|
|
88
|
+
/* Blocking futex waits failed the cross-core thread handoff performance
|
|
89
|
+
* gate. Keep Ruby's managed IO path for threads and use futexes only when
|
|
90
|
+
* the owning event loop can complete them asynchronously. */
|
|
91
|
+
bool use_futex = !NIL_P(scheduler) && containers_futex_scheduler_supported(scheduler);
|
|
92
|
+
|
|
93
|
+
int descriptors[2] = {-1, -1};
|
|
94
|
+
if (!use_futex) {
|
|
95
|
+
if (pipe(descriptors) != 0) {
|
|
96
|
+
int error = errno;
|
|
97
|
+
if (atomic_load_explicit(&waiter->futex.value, memory_order_acquire) == 1) return true;
|
|
98
|
+
if (pipe_error) {
|
|
99
|
+
*pipe_error = error;
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
rb_syserr_fail(error, "pipe");
|
|
103
|
+
}
|
|
104
|
+
waiter_set_fd_flags(descriptors[0]);
|
|
105
|
+
waiter_set_fd_flags(descriptors[1]);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/* No Ruby calls occur between creating descriptors and recording ownership.
|
|
109
|
+
* A notifier either latches a pending wake or observes the complete backend. */
|
|
110
|
+
if (guard) pthread_mutex_lock(guard);
|
|
111
|
+
waiter->read_fd = descriptors[0];
|
|
112
|
+
waiter->write_fd = descriptors[1];
|
|
113
|
+
waiter->mode = use_futex ? CONTAINERS_WAITER_FUTEX : CONTAINERS_WAITER_IO;
|
|
114
|
+
bool notified = waiter->notified;
|
|
115
|
+
if (guard) pthread_mutex_unlock(guard);
|
|
116
|
+
if (notified) return true;
|
|
117
|
+
|
|
118
|
+
VALUE timeout = Qnil;
|
|
119
|
+
if (finite) {
|
|
120
|
+
double remaining = deadline - waiter_monotonic_now();
|
|
121
|
+
if (remaining <= 0) {
|
|
122
|
+
return atomic_load_explicit(&waiter->futex.value, memory_order_acquire) == 1;
|
|
123
|
+
}
|
|
124
|
+
timeout = DBL2NUM(remaining);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (use_futex) {
|
|
128
|
+
return containers_futex_scheduler_wait(scheduler, &waiter->futex, timeout);
|
|
129
|
+
}
|
|
130
|
+
return containers_wait_for_readable(waiter->read_fd, timeout);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
void
|
|
134
|
+
containers_waiter_close(containers_waiter_t *waiter)
|
|
135
|
+
{
|
|
136
|
+
if (waiter->read_fd >= 0) close(waiter->read_fd);
|
|
137
|
+
if (waiter->write_fd >= 0) close(waiter->write_fd);
|
|
138
|
+
waiter->read_fd = waiter->write_fd = -1;
|
|
139
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#ifndef FARCE_NATIVE_WAIT_H
|
|
2
|
+
#define FARCE_NATIVE_WAIT_H
|
|
3
|
+
|
|
4
|
+
#include "containers.h"
|
|
5
|
+
#include "futex.h"
|
|
6
|
+
|
|
7
|
+
typedef enum {
|
|
8
|
+
CONTAINERS_WAITER_PENDING,
|
|
9
|
+
CONTAINERS_WAITER_FUTEX,
|
|
10
|
+
CONTAINERS_WAITER_IO
|
|
11
|
+
} containers_waiter_mode_t;
|
|
12
|
+
|
|
13
|
+
typedef struct {
|
|
14
|
+
containers_futex_t futex;
|
|
15
|
+
int read_fd, write_fd;
|
|
16
|
+
containers_waiter_mode_t mode;
|
|
17
|
+
bool notified;
|
|
18
|
+
} containers_waiter_t;
|
|
19
|
+
|
|
20
|
+
/* Initialize before linking under the container's guard. Notification is
|
|
21
|
+
* native-only and uses that same guard. A NULL guard denotes owner-local state. */
|
|
22
|
+
void containers_waiter_init(containers_waiter_t *waiter);
|
|
23
|
+
bool containers_waiter_notify(containers_waiter_t *waiter);
|
|
24
|
+
/* Run outside the guard and inside the caller's cleanup ensure. The absolute
|
|
25
|
+
* monotonic deadline includes backend setup. Optional pipe_error lets callers
|
|
26
|
+
* retain their own descriptor-pressure recovery; other errors raise normally. */
|
|
27
|
+
bool containers_waiter_wait(
|
|
28
|
+
containers_waiter_t *waiter,
|
|
29
|
+
pthread_mutex_t *guard,
|
|
30
|
+
bool finite,
|
|
31
|
+
double deadline,
|
|
32
|
+
int *pipe_error
|
|
33
|
+
);
|
|
34
|
+
/* Unlink under the guard first. Waiting has already drained native requests. */
|
|
35
|
+
void containers_waiter_close(containers_waiter_t *waiter);
|
|
36
|
+
|
|
37
|
+
#endif
|
data/ext/farce/priority_queue.c
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#include "containers.h"
|
|
2
|
+
#include "native_wait.h"
|
|
2
3
|
#include "unshared_wait.h"
|
|
3
4
|
#include <ruby/atomic.h>
|
|
4
5
|
#include <ruby/fiber/scheduler.h>
|
|
@@ -348,9 +349,7 @@ priority_queue_bucket_unlink(
|
|
|
348
349
|
}
|
|
349
350
|
|
|
350
351
|
typedef struct priority_queue_lock_waiter {
|
|
351
|
-
|
|
352
|
-
int write_fd;
|
|
353
|
-
bool notified;
|
|
352
|
+
containers_waiter_t notification;
|
|
354
353
|
struct priority_queue_lock_waiter *next;
|
|
355
354
|
} priority_queue_lock_waiter_t;
|
|
356
355
|
|
|
@@ -753,19 +752,6 @@ priority_queue_get(VALUE self)
|
|
|
753
752
|
return queue;
|
|
754
753
|
}
|
|
755
754
|
|
|
756
|
-
static void
|
|
757
|
-
priority_queue_lock_set_fd_flags(int fd)
|
|
758
|
-
{
|
|
759
|
-
#ifdef _WIN32
|
|
760
|
-
(void)fd;
|
|
761
|
-
#else
|
|
762
|
-
int flags = fcntl(fd, F_GETFL, 0);
|
|
763
|
-
if (flags >= 0) (void)fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
|
764
|
-
flags = fcntl(fd, F_GETFD, 0);
|
|
765
|
-
if (flags >= 0) (void)fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
|
|
766
|
-
#endif
|
|
767
|
-
}
|
|
768
|
-
|
|
769
755
|
/* Called with queue->lock held. A logical unlock only needs to make one
|
|
770
756
|
* waiter runnable. The selected waiter remains linked until ensure cleanup. */
|
|
771
757
|
static void
|
|
@@ -777,20 +763,9 @@ priority_queue_lock_notify_one_locked(priority_queue_t *queue)
|
|
|
777
763
|
* next waiter. Reentrant operations can register for a later unlock. */
|
|
778
764
|
farce_unshared_wait_notify_all(&queue->unshared_lock_waiters);
|
|
779
765
|
}
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
for (waiter = queue->waiters; waiter; waiter = waiter->next) {
|
|
784
|
-
ssize_t result;
|
|
785
|
-
if (waiter->notified) continue;
|
|
786
|
-
do {
|
|
787
|
-
result = write(waiter->write_fd, &byte, 1);
|
|
788
|
-
} while (result < 0 && errno == EINTR);
|
|
789
|
-
if (result == 1 ||
|
|
790
|
-
(result < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))) {
|
|
791
|
-
waiter->notified = true;
|
|
792
|
-
return;
|
|
793
|
-
}
|
|
766
|
+
for (priority_queue_lock_waiter_t *waiter = queue->waiters; waiter; waiter = waiter->next) {
|
|
767
|
+
if (waiter->notification.notified) continue;
|
|
768
|
+
if (containers_waiter_notify(&waiter->notification)) return;
|
|
794
769
|
}
|
|
795
770
|
}
|
|
796
771
|
|
|
@@ -799,18 +774,16 @@ typedef struct {
|
|
|
799
774
|
priority_queue_lock_waiter_t waiter;
|
|
800
775
|
} priority_queue_lock_wait_context_t;
|
|
801
776
|
|
|
802
|
-
static bool
|
|
803
|
-
priority_queue_lock_wait_for_descriptor(int fd)
|
|
804
|
-
{
|
|
805
|
-
return containers_wait_for_readable(fd, Qnil);
|
|
806
|
-
}
|
|
807
|
-
|
|
808
777
|
static VALUE
|
|
809
778
|
priority_queue_lock_wait_body(VALUE opaque)
|
|
810
779
|
{
|
|
811
780
|
priority_queue_lock_wait_context_t *context =
|
|
812
781
|
(priority_queue_lock_wait_context_t *)opaque;
|
|
813
|
-
bool awakened =
|
|
782
|
+
bool awakened = containers_waiter_wait(
|
|
783
|
+
&context->waiter.notification,
|
|
784
|
+
context->queue->shared ? &context->queue->lock : NULL,
|
|
785
|
+
false, 0, NULL
|
|
786
|
+
);
|
|
814
787
|
return awakened ? Qtrue : Qfalse;
|
|
815
788
|
}
|
|
816
789
|
|
|
@@ -835,10 +808,10 @@ priority_queue_lock_wait_cleanup(VALUE opaque)
|
|
|
835
808
|
}
|
|
836
809
|
/* A notification only makes this waiter runnable; it does not reserve
|
|
837
810
|
* ownership. Pass the baton whenever a notified waiter leaves while the
|
|
838
|
-
* queue is still ownerless. This covers both interruption
|
|
811
|
+
* queue is still ownerless. This covers both interruption while waiting
|
|
839
812
|
* and cancellation after a normal wake but before the acquisition loop can
|
|
840
813
|
* claim ownership, so later waiters cannot be stranded. */
|
|
841
|
-
if (context->waiter.notified &&
|
|
814
|
+
if (context->waiter.notification.notified &&
|
|
842
815
|
NIL_P(context->queue->owner_fiber) &&
|
|
843
816
|
!priority_queue_publication_busy(
|
|
844
817
|
RUBY_ATOMIC_LOAD(context->queue->publication_state)
|
|
@@ -846,8 +819,7 @@ priority_queue_lock_wait_cleanup(VALUE opaque)
|
|
|
846
819
|
priority_queue_lock_notify_one_locked(context->queue);
|
|
847
820
|
}
|
|
848
821
|
priority_queue_mutex_unlock(context->queue);
|
|
849
|
-
|
|
850
|
-
close(context->waiter.write_fd);
|
|
822
|
+
containers_waiter_close(&context->waiter.notification);
|
|
851
823
|
return Qnil;
|
|
852
824
|
}
|
|
853
825
|
|
|
@@ -859,25 +831,11 @@ priority_queue_lock_wait_once(priority_queue_t *queue)
|
|
|
859
831
|
(void)farce_unshared_wait(&queue->unshared_lock_waiters, queue->unshared_self, false, 0);
|
|
860
832
|
return;
|
|
861
833
|
}
|
|
862
|
-
|
|
863
|
-
priority_queue_lock_wait_context_t context;
|
|
864
|
-
|
|
865
|
-
if (pipe(descriptors) != 0) {
|
|
866
|
-
priority_queue_mutex_unlock(queue);
|
|
867
|
-
rb_sys_fail("pipe");
|
|
868
|
-
}
|
|
869
|
-
priority_queue_lock_set_fd_flags(descriptors[0]);
|
|
870
|
-
priority_queue_lock_set_fd_flags(descriptors[1]);
|
|
871
|
-
|
|
872
|
-
context = (priority_queue_lock_wait_context_t){
|
|
834
|
+
priority_queue_lock_wait_context_t context = {
|
|
873
835
|
.queue = queue,
|
|
874
|
-
.waiter = {
|
|
875
|
-
.read_fd = descriptors[0],
|
|
876
|
-
.write_fd = descriptors[1],
|
|
877
|
-
.notified = false,
|
|
878
|
-
.next = NULL,
|
|
879
|
-
},
|
|
836
|
+
.waiter = {.next = NULL},
|
|
880
837
|
};
|
|
838
|
+
containers_waiter_init(&context.waiter.notification);
|
|
881
839
|
if (queue->last_waiter) queue->last_waiter->next = &context.waiter;
|
|
882
840
|
else queue->waiters = &context.waiter;
|
|
883
841
|
queue->last_waiter = &context.waiter;
|
data/ext/farce/queue.c
CHANGED
|
@@ -224,7 +224,7 @@ queue_memsize(const void *pointer)
|
|
|
224
224
|
}
|
|
225
225
|
|
|
226
226
|
static const rb_data_type_t queue_type = {
|
|
227
|
-
.wrap_struct_name = "
|
|
227
|
+
.wrap_struct_name = "Farce::Internal::Queue",
|
|
228
228
|
.function = {
|
|
229
229
|
.dmark = queue_mark,
|
|
230
230
|
.dfree = queue_free,
|
data/ext/farce/reactor.c
CHANGED
|
@@ -345,6 +345,9 @@ void fs_watch_io(fs_state *s, fs_wait *w, int fd, int events, int group, long in
|
|
|
345
345
|
}
|
|
346
346
|
}
|
|
347
347
|
|
|
348
|
+
// Cancel the outstanding futex operation before its caller releases the word.
|
|
349
|
+
static void fs_cancel_futex(VALUE self, fs_wait *w);
|
|
350
|
+
|
|
348
351
|
// Finish a wait and remove its watches and ready-queue entry.
|
|
349
352
|
// A pending receive must stop using the buffer before cleanup can continue.
|
|
350
353
|
// Clear Ruby references and keep the fiber's slot available for its next wait.
|
|
@@ -352,6 +355,12 @@ VALUE fs_retire(VALUE self, VALUE token) {
|
|
|
352
355
|
fs_state *s = fs_get(self); fs_wait *w = fs_lookup(s, token);
|
|
353
356
|
if (!w) return Qnil;
|
|
354
357
|
if (w->receive_pending) fs_cancel_receive(self, w);
|
|
358
|
+
if (w->futex_pending) {
|
|
359
|
+
// Logical cancellation must not discard the lookup required by the CQE.
|
|
360
|
+
// Suppress resumption first, then consume the original completion.
|
|
361
|
+
w->status = 2;
|
|
362
|
+
fs_cancel_futex(self, w);
|
|
363
|
+
}
|
|
355
364
|
ready_remove(s, w);
|
|
356
365
|
fs_watch *watch = w->watches;
|
|
357
366
|
while (watch) {
|
|
@@ -381,6 +390,56 @@ VALUE fs_retire(VALUE self, VALUE token) {
|
|
|
381
390
|
return Qnil;
|
|
382
391
|
}
|
|
383
392
|
|
|
393
|
+
// Select asynchronous futex parking only for an installed, live native ring.
|
|
394
|
+
// External schedulers require a later integration instead of a duck-typed hook.
|
|
395
|
+
bool containers_futex_scheduler_supported(VALUE scheduler) {
|
|
396
|
+
#ifdef FARCE_HAVE_URING_FUTEX
|
|
397
|
+
if (NIL_P(scheduler) || !rb_typeddata_is_kind_of(scheduler, &fs_type)) return false;
|
|
398
|
+
fs_state *s = fs_get(scheduler);
|
|
399
|
+
return s->driver == FS_URING_DRIVER && s->ring_live && s->futex_supported &&
|
|
400
|
+
!s->closed && !s->stopping && s->owner == rb_thread_current() &&
|
|
401
|
+
s->active == rb_fiber_current() && rb_fiber_scheduler_get() == scheduler &&
|
|
402
|
+
containers_futex_available();
|
|
403
|
+
#else
|
|
404
|
+
return false;
|
|
405
|
+
#endif
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
typedef struct {
|
|
409
|
+
VALUE self, token, timeout;
|
|
410
|
+
containers_futex_t *word;
|
|
411
|
+
} fs_futex_wait;
|
|
412
|
+
|
|
413
|
+
// Submit and park using one suspension. The surrounding ensure retains the word
|
|
414
|
+
// until the kernel request has completed, even if timer setup or parking raises.
|
|
415
|
+
static VALUE futex_wait_body(VALUE data) {
|
|
416
|
+
fs_futex_wait *wait = (fs_futex_wait *)data;
|
|
417
|
+
fs_state *s = fs_get(wait->self);
|
|
418
|
+
fs_wait *w = fs_lookup(s, wait->token);
|
|
419
|
+
int error = fs_driver_futex(s, w, wait->word);
|
|
420
|
+
if (error) rb_syserr_fail(error, "scheduler futex submission");
|
|
421
|
+
return rb_funcall(wait->self, rb_intern("wait_with_timeout"), 3,
|
|
422
|
+
wait->token, wait->timeout, Qfalse);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
static VALUE futex_wait_cleanup(VALUE data) {
|
|
426
|
+
fs_futex_wait *wait = (fs_futex_wait *)data;
|
|
427
|
+
return fs_retire(wait->self, wait->token);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
bool containers_futex_scheduler_wait(VALUE scheduler, containers_futex_t *word, VALUE timeout) {
|
|
431
|
+
if (!containers_futex_scheduler_supported(scheduler)) return true;
|
|
432
|
+
fs_futex_wait wait = {scheduler, fs_arm(scheduler), timeout, word};
|
|
433
|
+
return RTEST(rb_ensure(futex_wait_body, (VALUE)&wait, futex_wait_cleanup, (VALUE)&wait));
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Expose the backend capability privately so native-path validation cannot pass
|
|
437
|
+
// by silently exercising a descriptor fallback.
|
|
438
|
+
static VALUE futex_supported(VALUE self) {
|
|
439
|
+
fs_state *s = fs_get(self);
|
|
440
|
+
return !s->closed && !s->stopping && s->futex_supported && containers_futex_available() ? Qtrue : Qfalse;
|
|
441
|
+
}
|
|
442
|
+
|
|
384
443
|
// Switch from the root to a ready fiber.
|
|
385
444
|
// If a newly started child finishes, return control to its waiting parent
|
|
386
445
|
// before returning to the root dispatcher.
|
|
@@ -600,6 +659,27 @@ static int snapshot_fallback(st_data_t key, st_data_t value, st_data_t arg) {
|
|
|
600
659
|
// Wake-pipe events ask the dispatcher to return to Ruby policy code.
|
|
601
660
|
static void accept_event(VALUE self, fs_event *e) {
|
|
602
661
|
fs_state *s = fs_get(self);
|
|
662
|
+
if (e->generation & FS_FUTEX) {
|
|
663
|
+
fs_wait *w = fs_lookup(s, ULL2NUM(e->generation & ~FS_OPERATION));
|
|
664
|
+
if (!w) return;
|
|
665
|
+
w->futex_pending = 0;
|
|
666
|
+
int error = e->error < 0 ? -e->error : 0;
|
|
667
|
+
if (error == ENOSYS || error == EOPNOTSUPP) s->futex_supported = 0;
|
|
668
|
+
if (!w->status) {
|
|
669
|
+
if (error == ENOSYS || error == EOPNOTSUPP) {
|
|
670
|
+
// Retry with the now-disabled capability, rather than reporting
|
|
671
|
+
// a timeout to consumers that have not reached their deadline.
|
|
672
|
+
fs_resume(self, w->token, Qtrue, Qnil);
|
|
673
|
+
} else {
|
|
674
|
+
// Early notification is reported as EAGAIN. Other interruptions
|
|
675
|
+
// also return to the container's condition/deadline check.
|
|
676
|
+
if (error != EAGAIN && error != EINTR && error != ECANCELED)
|
|
677
|
+
w->native_error = error;
|
|
678
|
+
fs_resume(self, w->token, Qtrue, Qnil);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
603
683
|
if (e->generation & FS_RECEIVE) {
|
|
604
684
|
fs_wait *w = fs_lookup(s, ULL2NUM(e->generation & ~FS_RECEIVE));
|
|
605
685
|
if (!w) return;
|
|
@@ -660,36 +740,44 @@ static void poll_ready(VALUE self) {
|
|
|
660
740
|
if (s->poll_error || s->selected_pending) s->policy_pending = 1;
|
|
661
741
|
}
|
|
662
742
|
|
|
663
|
-
// Cancel an
|
|
664
|
-
//
|
|
665
|
-
//
|
|
666
|
-
void
|
|
743
|
+
// Cancel an operation and consume its original CQE before releasing storage.
|
|
744
|
+
// Cancellation acknowledgements use zero userdata, so a delayed acknowledgement
|
|
745
|
+
// cannot refer to an operation record or a subsequently reused suspension.
|
|
746
|
+
static void cancel_operation(VALUE self, fs_wait *w, uint64_t kind, int *pending) {
|
|
667
747
|
#ifdef FARCE_URING
|
|
668
748
|
fs_state *s = fs_get(self);
|
|
669
749
|
struct io_uring_sqe *entry = io_uring_get_sqe(&s->ring);
|
|
670
750
|
if (!entry) {
|
|
671
751
|
int result;
|
|
672
752
|
do { result = io_uring_submit(&s->ring); } while (result == -EINTR);
|
|
673
|
-
if (result < 0) rb_bug("cannot submit pending scheduler
|
|
753
|
+
if (result < 0) rb_bug("cannot submit pending scheduler operation");
|
|
674
754
|
entry = io_uring_get_sqe(&s->ring);
|
|
675
755
|
if (!entry) rb_bug("cannot reserve scheduler cancellation");
|
|
676
756
|
}
|
|
677
|
-
io_uring_prep_cancel64(entry,
|
|
757
|
+
io_uring_prep_cancel64(entry, kind | NUM2ULL(w->token), 0);
|
|
678
758
|
entry->user_data = 0;
|
|
679
|
-
//
|
|
680
|
-
//
|
|
681
|
-
while (
|
|
759
|
+
// Do not run fibers here. The caller's buffer or futex word stays alive
|
|
760
|
+
// until the original completion, rather than just the cancellation reply.
|
|
761
|
+
while (*pending) {
|
|
682
762
|
int result = io_uring_submit_and_wait(&s->ring, 1);
|
|
683
|
-
if (result < 0 && result != -EINTR) rb_bug("cannot acknowledge scheduler
|
|
763
|
+
if (result < 0 && result != -EINTR) rb_bug("cannot acknowledge scheduler cancellation");
|
|
684
764
|
s->timeout = 0;
|
|
685
765
|
fs_driver_poll(s);
|
|
686
|
-
if (s->poll_error && s->poll_error != EINTR) rb_bug("cannot drain scheduler
|
|
766
|
+
if (s->poll_error && s->poll_error != EINTR) rb_bug("cannot drain scheduler cancellation");
|
|
687
767
|
for (int i = 0; i < s->event_count; i++) accept_event(self, &s->events[i]);
|
|
688
768
|
rearm_events(s);
|
|
689
769
|
}
|
|
690
770
|
#endif
|
|
691
771
|
}
|
|
692
772
|
|
|
773
|
+
void fs_cancel_receive(VALUE self, fs_wait *w) {
|
|
774
|
+
cancel_operation(self, w, FS_RECEIVE, &w->receive_pending);
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
static void fs_cancel_futex(VALUE self, fs_wait *w) {
|
|
778
|
+
cancel_operation(self, w, FS_FUTEX, &w->futex_pending);
|
|
779
|
+
}
|
|
780
|
+
|
|
693
781
|
// Call a helper for every active wait, skipping empty slots and idle fibers.
|
|
694
782
|
static void each_wait(fs_state *s, int (*callback)(st_data_t, st_data_t, st_data_t), st_data_t arg) {
|
|
695
783
|
for (uint32_t i = 0; i < s->slot_count; i++) {
|
|
@@ -836,6 +924,7 @@ void farce_init_fiber_scheduler(VALUE internal) {
|
|
|
836
924
|
rb_define_private_method(klass, "retire_wait", fs_retire, 1);
|
|
837
925
|
rb_define_private_method(klass, "register_select", register_select, 3);
|
|
838
926
|
rb_define_private_method(klass, "ready?", ready, 0);
|
|
927
|
+
rb_define_private_method(klass, "futex_supported?", futex_supported, 0);
|
|
839
928
|
rb_define_private_method(klass, "pending?", pending, 0);
|
|
840
929
|
rb_define_private_method(klass, "pending_count", pending_count, 0);
|
|
841
930
|
rb_define_private_method(klass, "wakeup", wakeup, 0);
|
data/ext/farce/reactor.h
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#ifndef FARCE_FIBER_REACTOR_H
|
|
2
2
|
#define FARCE_FIBER_REACTOR_H
|
|
3
3
|
#include "containers.h"
|
|
4
|
+
#include "futex.h"
|
|
4
5
|
#include <ruby/io.h>
|
|
5
6
|
#include <ruby/io/buffer.h>
|
|
6
7
|
#include <ruby/thread.h>
|
|
@@ -21,6 +22,8 @@
|
|
|
21
22
|
#define FS_WRITE 4
|
|
22
23
|
#define FS_EVENTS 1024
|
|
23
24
|
#define FS_RECEIVE (UINT64_C(1) << 63)
|
|
25
|
+
#define FS_FUTEX (UINT64_C(1) << 62)
|
|
26
|
+
#define FS_OPERATION (FS_RECEIVE | FS_FUTEX)
|
|
24
27
|
#define FS_SLOT_BITS 20
|
|
25
28
|
#define FS_SLOT_MASK ((1U << FS_SLOT_BITS) - 1)
|
|
26
29
|
|
|
@@ -45,7 +48,7 @@ struct fs_reg {
|
|
|
45
48
|
struct fs_wait {
|
|
46
49
|
VALUE token, fiber, value, error, groups, ios;
|
|
47
50
|
uint32_t slot;
|
|
48
|
-
int status, events, selected, queued, native_error, receive_pending;
|
|
51
|
+
int status, events, selected, queued, native_error, receive_pending, futex_pending;
|
|
49
52
|
fs_wait *ready_next, *ready_prev;
|
|
50
53
|
fs_watch *watches;
|
|
51
54
|
fs_watch first_watch;
|
|
@@ -62,7 +65,7 @@ typedef struct {
|
|
|
62
65
|
size_t pending;
|
|
63
66
|
uint64_t serial, generation, suspensions;
|
|
64
67
|
unsigned immediate;
|
|
65
|
-
int selected_pending, receive_disabled;
|
|
68
|
+
int selected_pending, receive_disabled, futex_supported;
|
|
66
69
|
int driver, descriptor, wake[2], closed;
|
|
67
70
|
pthread_mutex_t wake_mutex;
|
|
68
71
|
#ifdef FARCE_URING
|
|
@@ -85,6 +88,7 @@ static inline fs_state *fs_get(VALUE self) {
|
|
|
85
88
|
int fs_driver_init(fs_state *s, int driver);
|
|
86
89
|
int fs_driver_update(fs_state *s, fs_reg *reg, int events);
|
|
87
90
|
int fs_driver_receive(fs_state *s, fs_wait *w, int fd, void *buffer, size_t size);
|
|
91
|
+
int fs_driver_futex(fs_state *s, fs_wait *w, containers_futex_t *word);
|
|
88
92
|
void fs_cancel_receive(VALUE self, fs_wait *w);
|
|
89
93
|
void fs_driver_close(fs_state *s);
|
|
90
94
|
void *fs_driver_poll(void *data);
|