farce 0.4.0-x86-linux-gnu → 0.5.0-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 +85 -1
- data/README.md +3 -1
- data/docs/benchmarks.md +37 -21
- data/docs/compatibility.md +13 -0
- data/docs/data-structures.md +37 -0
- data/docs/integrations.md +1 -0
- data/docs/introduction.md +19 -0
- data/docs/modes.md +2 -0
- data/docs/scheduling.md +35 -3
- data/docs/scopes.md +3 -1
- data/docs/shims.md +53 -0
- data/docs/utilities.md +31 -0
- data/docs/variants.md +9 -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 +17 -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 +24 -57
- data/ext/farce/native_wait.c +139 -0
- data/ext/farce/native_wait.h +37 -0
- data/ext/farce/priority_queue.c +62 -59
- data/ext/farce/queue.c +142 -8
- 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 +16 -63
- data/ext/farce/weak_map.c +13 -4
- data/ext/java/org/farce/PriorityQueue.java +18 -2
- data/lib/farce/abstract/concurrent_map.rb +122 -0
- data/lib/farce/abstract/counter.rb +35 -1
- data/lib/farce/abstract/executor.rb +30 -7
- data/lib/farce/abstract/flag.rb +12 -0
- data/lib/farce/abstract/pool.rb +158 -47
- data/lib/farce/abstract/priority_queue.rb +1 -1
- data/lib/farce/abstract/publisher.rb +189 -0
- data/lib/farce/abstract/queue.rb +29 -3
- data/lib/farce/abstract/scheduler.rb +41 -14
- data/lib/farce/abstract/timer_queue.rb +3 -3
- data/lib/farce/abstract/vector.rb +3 -1
- data/lib/farce/atom.rb +9 -2
- data/lib/farce/clock.rb +12 -0
- data/lib/farce/concurrent/count_down_latch.rb +37 -0
- data/lib/farce/concurrent/cyclic_barrier.rb +109 -0
- data/lib/farce/concurrent/event.rb +60 -0
- data/lib/farce/concurrent/fiber_local_var.rb +31 -0
- data/lib/farce/concurrent/local_var.rb +65 -0
- data/lib/farce/concurrent/thread_local_var.rb +28 -0
- data/lib/farce/concurrent/tuple.rb +74 -0
- data/lib/farce/concurrent.rb +50 -0
- data/lib/farce/config.rb +112 -2
- data/lib/farce/counter.rb +0 -16
- data/lib/farce/engine/jruby/map.rb +8 -6
- data/lib/farce/engine/jruby/signal.rb +2 -2
- data/lib/farce/engine/jvm/farce.jar +0 -0
- data/lib/farce/engine/jvm/priority_queue.rb +14 -4
- data/lib/farce/engine/ruby/3.4/farce.so +0 -0
- data/lib/farce/engine/ruby/4.0/farce.so +0 -0
- data/lib/farce/engine/ruby/shared/parallel_scheduler.rb +9 -1
- data/lib/farce/engine/ruby/shared/ractor_selector.rb +1 -4
- data/lib/farce/engine/ruby/shared/weak_map.rb +9 -12
- data/lib/farce/engine/shared/exchanger.rb +2 -2
- data/lib/farce/engine/shared/queue.rb +43 -5
- data/lib/farce/engine/shared/ractor_selector.rb +1 -1
- data/lib/farce/engine/shared/strict_map.rb +5 -0
- data/lib/farce/engine/shared/unshared_atom.rb +1 -1
- data/lib/farce/engine/shared/unshared_vector.rb +8 -7
- data/lib/farce/engine/shared/unshared_weak_map.rb +8 -6
- data/lib/farce/engine/shared/weak_atom/base.rb +5 -10
- data/lib/farce/engine/shared/weak_map/cell.rb +1 -1
- data/lib/farce/engine/truffleruby/map.rb +8 -6
- data/lib/farce/engine/truffleruby/native/priority_queue.rb +18 -4
- data/lib/farce/engine/truffleruby/signal.rb +1 -1
- data/lib/farce/integrations/concurrent.rb +16 -3
- data/lib/farce/internal/blocking_priority_queue.rb +4 -9
- data/lib/farce/internal/flag_waiting.rb +1 -1
- data/lib/farce/internal/lease_initialization.rb +1 -1
- data/lib/farce/internal/lease_pool_state.rb +1 -1
- data/lib/farce/internal/lease_state.rb +2 -2
- data/lib/farce/internal/local_executor_methods.rb +3 -3
- data/lib/farce/internal/map_value_modes.rb +9 -2
- data/lib/farce/internal/pool_settings.rb +9 -0
- data/lib/farce/internal/pool_worker.rb +34 -21
- data/lib/farce/internal/reservation_waiting.rb +1 -1
- data/lib/farce/internal/scheduler_lifecycle.rb +2 -2
- data/lib/farce/internal/weak_map_value_modes.rb +5 -0
- data/lib/farce/internal.rb +8 -8
- data/lib/farce/lazy.rb +3 -0
- data/lib/farce/lazy_ref.rb +3 -0
- data/lib/farce/local/lease.rb +2 -9
- data/lib/farce/local/lease_map.rb +2 -9
- data/lib/farce/local/pool.rb +38 -5
- data/lib/farce/local/publisher.rb +30 -0
- data/lib/farce/local/scheduler.rb +18 -5
- data/lib/farce/local/vector.rb +3 -2
- data/lib/farce/memoize.rb +314 -0
- data/lib/farce/pool.rb +5 -0
- data/lib/farce/priority_queue.rb +1 -1
- data/lib/farce/publisher.rb +57 -0
- data/lib/farce/signal.rb +1 -1
- data/lib/farce/strict/publisher.rb +31 -0
- data/lib/farce/strict/vector.rb +3 -2
- data/lib/farce/subscription/definitions.rb +27 -0
- data/lib/farce/subscription.rb +121 -0
- data/lib/farce/transaction/map.rb +35 -1
- data/lib/farce/unshared/pool.rb +1 -1
- data/lib/farce/unshared/publisher.rb +25 -0
- data/lib/farce/unshared/scheduler.rb +3 -3
- data/lib/farce/unshared/vector.rb +3 -2
- data/lib/farce/vector.rb +12 -9
- data/lib/farce/version.rb +1 -1
- data/lib/farce/walker/definitions.rb +13 -2
- data/lib/farce/weak_atom.rb +5 -2
- data/lib/farce.rb +3 -1
- metadata +23 -1
data/ext/farce/atom.c
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
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 <time.h>
|
|
11
|
-
#include <unistd.h>
|
|
12
10
|
|
|
13
11
|
static VALUE cAtom;
|
|
14
12
|
#ifdef RC_HAVE_NATIVE_WEAK_REFERENCES
|
|
@@ -24,9 +22,7 @@ bool rb_gc_handle_weak_references_alive_p(VALUE object);
|
|
|
24
22
|
typedef struct atom_waiter atom_waiter_t;
|
|
25
23
|
|
|
26
24
|
struct atom_waiter {
|
|
27
|
-
|
|
28
|
-
int write_fd;
|
|
29
|
-
bool notified;
|
|
25
|
+
containers_waiter_t notification;
|
|
30
26
|
atom_waiter_t *next;
|
|
31
27
|
};
|
|
32
28
|
|
|
@@ -105,35 +101,13 @@ atom_parse_timeout(VALUE timeout)
|
|
|
105
101
|
return parsed;
|
|
106
102
|
}
|
|
107
103
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
{
|
|
111
|
-
#ifdef _WIN32
|
|
112
|
-
(void)fd;
|
|
113
|
-
#else
|
|
114
|
-
int flags = fcntl(fd, F_GETFL, 0);
|
|
115
|
-
if (flags >= 0) (void)fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
|
116
|
-
flags = fcntl(fd, F_GETFD, 0);
|
|
117
|
-
if (flags >= 0) (void)fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
|
|
118
|
-
#endif
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/* Called with atom->lock held. Every waiter owns a separate descriptor, so a
|
|
122
|
-
* single state transition can wake all waiters without consuming a shared
|
|
123
|
-
* notification. */
|
|
104
|
+
/* Called with atom->lock held. Every registration owns an independent
|
|
105
|
+
* notification so a transition can wake all waiters. */
|
|
124
106
|
static void
|
|
125
107
|
atom_notify_waiters(atom_t *atom)
|
|
126
108
|
{
|
|
127
|
-
unsigned char byte = 1;
|
|
128
109
|
for (atom_waiter_t *waiter = atom->waiters; waiter; waiter = waiter->next) {
|
|
129
|
-
|
|
130
|
-
ssize_t result;
|
|
131
|
-
do {
|
|
132
|
-
result = write(waiter->write_fd, &byte, 1);
|
|
133
|
-
} while (result < 0 && errno == EINTR);
|
|
134
|
-
if (result == 1 || (result < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))) {
|
|
135
|
-
waiter->notified = true;
|
|
136
|
-
}
|
|
110
|
+
(void)containers_waiter_notify(&waiter->notification);
|
|
137
111
|
}
|
|
138
112
|
}
|
|
139
113
|
|
|
@@ -201,7 +175,7 @@ atom_memsize(const void *pointer)
|
|
|
201
175
|
}
|
|
202
176
|
|
|
203
177
|
static const rb_data_type_t atom_type = {
|
|
204
|
-
.wrap_struct_name = "
|
|
178
|
+
.wrap_struct_name = "Farce::Internal::Atom",
|
|
205
179
|
.function = {
|
|
206
180
|
.dmark = atom_mark,
|
|
207
181
|
.dfree = atom_free,
|
|
@@ -352,23 +326,24 @@ typedef struct {
|
|
|
352
326
|
atom_timeout_t *timeout;
|
|
353
327
|
} atom_wait_context_t;
|
|
354
328
|
|
|
355
|
-
static bool
|
|
356
|
-
atom_wait_for_descriptor(int fd, atom_timeout_t *timeout)
|
|
357
|
-
{
|
|
358
|
-
VALUE wait_timeout = Qnil;
|
|
359
|
-
if (timeout->finite) {
|
|
360
|
-
double remaining = timeout->deadline - atom_monotonic_now();
|
|
361
|
-
if (remaining <= 0) return false;
|
|
362
|
-
wait_timeout = DBL2NUM(remaining);
|
|
363
|
-
}
|
|
364
|
-
return containers_wait_for_readable(fd, wait_timeout);
|
|
365
|
-
}
|
|
366
|
-
|
|
367
329
|
static VALUE
|
|
368
330
|
atom_wait_body(VALUE opaque)
|
|
369
331
|
{
|
|
370
332
|
atom_wait_context_t *context = (atom_wait_context_t *)opaque;
|
|
371
|
-
|
|
333
|
+
atom_timeout_t *timeout = context->timeout;
|
|
334
|
+
int error = 0;
|
|
335
|
+
bool awakened = containers_waiter_wait(
|
|
336
|
+
&context->waiter.notification, &context->atom->lock,
|
|
337
|
+
timeout->finite, timeout->deadline, &error
|
|
338
|
+
);
|
|
339
|
+
if (error) {
|
|
340
|
+
double seconds = timeout->finite ? timeout->deadline - atom_monotonic_now() : 0;
|
|
341
|
+
if ((error == EMFILE || error == ENFILE) && timeout->finite && seconds <= 0) return Qfalse;
|
|
342
|
+
VALUE remaining = timeout->finite ? DBL2NUM(seconds) : Qnil;
|
|
343
|
+
containers_wait_without_descriptor(error, "pipe", &timeout->collected, remaining);
|
|
344
|
+
return Qtrue;
|
|
345
|
+
}
|
|
346
|
+
return awakened ? Qtrue : Qfalse;
|
|
372
347
|
}
|
|
373
348
|
|
|
374
349
|
static VALUE
|
|
@@ -385,8 +360,7 @@ atom_wait_cleanup(VALUE opaque)
|
|
|
385
360
|
}
|
|
386
361
|
}
|
|
387
362
|
pthread_mutex_unlock(&context->atom->lock);
|
|
388
|
-
|
|
389
|
-
close(context->waiter.write_fd);
|
|
363
|
+
containers_waiter_close(&context->waiter.notification);
|
|
390
364
|
return Qnil;
|
|
391
365
|
}
|
|
392
366
|
|
|
@@ -394,29 +368,12 @@ atom_wait_cleanup(VALUE opaque)
|
|
|
394
368
|
static bool
|
|
395
369
|
atom_wait_once(atom_t *atom, atom_timeout_t *timeout)
|
|
396
370
|
{
|
|
397
|
-
int descriptors[2];
|
|
398
|
-
if (pipe(descriptors) != 0) {
|
|
399
|
-
int error = errno;
|
|
400
|
-
pthread_mutex_unlock(&atom->lock);
|
|
401
|
-
double seconds = timeout->finite ? timeout->deadline - atom_monotonic_now() : 0;
|
|
402
|
-
if ((error == EMFILE || error == ENFILE) && timeout->finite && seconds <= 0) return false;
|
|
403
|
-
VALUE remaining = timeout->finite ? DBL2NUM(seconds) : Qnil;
|
|
404
|
-
containers_wait_without_descriptor(error, "pipe", &timeout->collected, remaining);
|
|
405
|
-
return true;
|
|
406
|
-
}
|
|
407
|
-
atom_set_fd_flags(descriptors[0]);
|
|
408
|
-
atom_set_fd_flags(descriptors[1]);
|
|
409
|
-
|
|
410
371
|
atom_wait_context_t context = {
|
|
411
372
|
.atom = atom,
|
|
412
|
-
.waiter = {
|
|
413
|
-
.read_fd = descriptors[0],
|
|
414
|
-
.write_fd = descriptors[1],
|
|
415
|
-
.notified = false,
|
|
416
|
-
.next = atom->waiters,
|
|
417
|
-
},
|
|
373
|
+
.waiter = {.next = atom->waiters},
|
|
418
374
|
.timeout = timeout,
|
|
419
375
|
};
|
|
376
|
+
containers_waiter_init(&context.waiter.notification);
|
|
420
377
|
atom->waiters = &context.waiter;
|
|
421
378
|
pthread_mutex_unlock(&atom->lock);
|
|
422
379
|
return RTEST(rb_ensure(atom_wait_body, (VALUE)&context, atom_wait_cleanup, (VALUE)&context));
|
data/ext/farce/counter.c
CHANGED
|
@@ -55,7 +55,7 @@ RUBY_REFERENCES(counter_references) = {
|
|
|
55
55
|
};
|
|
56
56
|
|
|
57
57
|
static const rb_data_type_t counter_type = {
|
|
58
|
-
.wrap_struct_name = "
|
|
58
|
+
.wrap_struct_name = "Farce::Internal::Counter",
|
|
59
59
|
.function = {
|
|
60
60
|
.dmark = RUBY_REFS_LIST_PTR(counter_references),
|
|
61
61
|
.dfree = counter_free,
|
data/ext/farce/depend
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
bounded_map.o: bounded_map.c containers.h
|
|
2
2
|
dict.o: dict.c dict.h
|
|
3
|
-
farce.o: farce.c containers.h shareable.h
|
|
4
|
-
lock.o: lock.c containers.h
|
|
5
|
-
priority_queue.o: priority_queue.c containers.h dict.h unshared_wait.h
|
|
6
|
-
reactor.o: reactor.c reactor.h
|
|
7
|
-
drivers.o: drivers.c reactor.h
|
|
8
|
-
io.o: io.c reactor.h
|
|
9
|
-
tree_map.o: tree_map.c containers.h
|
|
3
|
+
farce.o: farce.c containers.h shareable.h futex.h
|
|
4
|
+
lock.o: lock.c containers.h native_wait.h futex.h
|
|
5
|
+
priority_queue.o: priority_queue.c containers.h dict.h unshared_wait.h native_wait.h futex.h
|
|
6
|
+
reactor.o: reactor.c reactor.h futex.h
|
|
7
|
+
drivers.o: drivers.c reactor.h futex.h
|
|
8
|
+
io.o: io.c reactor.h futex.h
|
|
9
|
+
tree_map.o: tree_map.c containers.h native_wait.h futex.h
|
|
10
10
|
|
|
11
11
|
queue.o: queue.c containers.h unshared_wait.h
|
|
12
12
|
unshared_signal.o: unshared_signal.c containers.h unshared_wait.h unshared_io_pool.h
|
|
13
|
+
|
|
14
|
+
atom.o: atom.c containers.h transaction.h native_wait.h futex.h
|
|
15
|
+
map.o: map.c containers.h transaction.h native_wait.h futex.h
|
|
16
|
+
vector.o: vector.c containers.h transaction.h native_wait.h futex.h
|
|
17
|
+
signal.o: signal.c containers.h native_wait.h futex.h
|
|
18
|
+
futex.o: futex.c futex.h
|
|
19
|
+
native_wait.o: native_wait.c native_wait.h containers.h futex.h
|
|
20
|
+
unsupported.o: unsupported.c futex.h
|
data/ext/farce/drivers.c
CHANGED
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
#include <limits.h>
|
|
6
6
|
#include <fcntl.h>
|
|
7
7
|
#include <math.h>
|
|
8
|
+
#ifdef FARCE_HAVE_URING_FUTEX
|
|
9
|
+
#include <linux/futex.h>
|
|
10
|
+
#endif
|
|
8
11
|
#ifdef HAVE_SYS_EPOLL_H
|
|
9
12
|
#include <sys/epoll.h>
|
|
10
13
|
#endif
|
|
@@ -12,6 +15,16 @@
|
|
|
12
15
|
#include <sys/event.h>
|
|
13
16
|
#endif
|
|
14
17
|
|
|
18
|
+
// Keep compiled backends identifiable when static dependency symbols are hidden.
|
|
19
|
+
#ifdef FARCE_URING
|
|
20
|
+
RUBY_FUNC_EXPORTED const char farce_backend_io_uring[];
|
|
21
|
+
const char farce_backend_io_uring[] = "io_uring";
|
|
22
|
+
#endif
|
|
23
|
+
#ifdef HAVE_SYS_EVENT_H
|
|
24
|
+
RUBY_FUNC_EXPORTED const char farce_backend_kqueue[];
|
|
25
|
+
const char farce_backend_kqueue[] = "kqueue";
|
|
26
|
+
#endif
|
|
27
|
+
|
|
15
28
|
static struct timespec relative_time(double seconds) {
|
|
16
29
|
struct timespec t;
|
|
17
30
|
t.tv_sec = (time_t)seconds;
|
|
@@ -38,6 +51,13 @@ int fs_driver_init(fs_state *s, int driver) {
|
|
|
38
51
|
int result = io_uring_queue_init(1024, &s->ring, 0);
|
|
39
52
|
if (result < 0) return -result;
|
|
40
53
|
s->ring_live = 1;
|
|
54
|
+
#ifdef FARCE_HAVE_URING_FUTEX
|
|
55
|
+
struct io_uring_probe *probe = io_uring_get_probe_ring(&s->ring);
|
|
56
|
+
if (probe) {
|
|
57
|
+
s->futex_supported = io_uring_opcode_supported(probe, IORING_OP_FUTEX_WAIT);
|
|
58
|
+
io_uring_free_probe(probe);
|
|
59
|
+
}
|
|
60
|
+
#endif
|
|
41
61
|
return 0;
|
|
42
62
|
}
|
|
43
63
|
#endif
|
|
@@ -79,6 +99,28 @@ int fs_driver_receive(fs_state *s, fs_wait *w, int fd, void *buffer, size_t size
|
|
|
79
99
|
#endif
|
|
80
100
|
}
|
|
81
101
|
|
|
102
|
+
// Submit one private 32-bit futex wait. The caller retains the word until the
|
|
103
|
+
// original completion has been consumed, including after timeout or cancellation.
|
|
104
|
+
int fs_driver_futex(fs_state *s, fs_wait *w, containers_futex_t *word) {
|
|
105
|
+
#ifdef FARCE_HAVE_URING_FUTEX
|
|
106
|
+
struct io_uring_sqe *entry = sqe(s);
|
|
107
|
+
if (!entry) return errno;
|
|
108
|
+
io_uring_prep_futex_wait(entry, (uint32_t *)&word->value, 0,
|
|
109
|
+
FUTEX_BITSET_MATCH_ANY, FUTEX_32 | FUTEX_PRIVATE_FLAG, 0);
|
|
110
|
+
entry->user_data = FS_FUTEX | NUM2ULL(w->token);
|
|
111
|
+
w->futex_pending = 1;
|
|
112
|
+
// Submit before yielding. Once queued, cleanup must drain the original CQE
|
|
113
|
+
// even if submission fails after making some progress.
|
|
114
|
+
while (io_uring_sq_ready(&s->ring)) {
|
|
115
|
+
int result = io_uring_submit(&s->ring);
|
|
116
|
+
if (result < 0 && result != -EINTR) return -result;
|
|
117
|
+
}
|
|
118
|
+
return 0;
|
|
119
|
+
#else
|
|
120
|
+
return ENOSYS;
|
|
121
|
+
#endif
|
|
122
|
+
}
|
|
123
|
+
|
|
82
124
|
int fs_driver_update(fs_state *s, fs_reg *reg, int events) {
|
|
83
125
|
uint64_t previous = reg->generation;
|
|
84
126
|
uint64_t generation = ++s->generation;
|
|
@@ -210,13 +252,13 @@ void *fs_driver_poll(void *data) {
|
|
|
210
252
|
consumed++;
|
|
211
253
|
if (cqe->user_data && cqe->user_data != LIBURING_UDATA_TIMEOUT) {
|
|
212
254
|
int e = cqe->res, mask = 0;
|
|
213
|
-
if (!(cqe->user_data &
|
|
255
|
+
if (!(cqe->user_data & FS_OPERATION) && e >= 0) {
|
|
214
256
|
if (e & (POLLIN | POLLHUP | POLLERR)) mask |= FS_READ;
|
|
215
257
|
if (e & (POLLOUT | POLLHUP | POLLERR)) mask |= FS_WRITE;
|
|
216
258
|
if (e & POLLPRI) mask |= FS_PRI;
|
|
217
259
|
}
|
|
218
260
|
s->events[s->event_count++] = (fs_event){cqe->user_data, mask,
|
|
219
|
-
cqe->user_data &
|
|
261
|
+
cqe->user_data & FS_OPERATION ? e : e < 0 && e != -ECANCELED ? -e : 0};
|
|
220
262
|
}
|
|
221
263
|
}
|
|
222
264
|
io_uring_cq_advance(&s->ring, consumed);
|
data/ext/farce/exchanger.c
CHANGED
|
@@ -140,7 +140,7 @@ exchanger_memsize(const void *pointer)
|
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
static const rb_data_type_t exchanger_type = {
|
|
143
|
-
.wrap_struct_name = "
|
|
143
|
+
.wrap_struct_name = "Farce::Internal::Exchanger",
|
|
144
144
|
.function = {
|
|
145
145
|
.dmark = NULL,
|
|
146
146
|
.dfree = exchanger_free,
|
data/ext/farce/extconf.rb
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
# shareable_constant_value: literal
|
|
3
|
+
# warn_indent: true
|
|
2
4
|
|
|
3
5
|
require "mkmf"
|
|
4
6
|
require_relative "../ext_helper"
|
|
@@ -44,19 +46,60 @@ end
|
|
|
44
46
|
|
|
45
47
|
have_func("sysctlbyname", "sys/sysctl.h") if have_header("sys/sysctl.h")
|
|
46
48
|
|
|
49
|
+
futex = false
|
|
50
|
+
if enable_config("futex", true) && RUBY_PLATFORM.include?("linux") &&
|
|
51
|
+
have_header("linux/futex.h") && have_header("sys/syscall.h")
|
|
52
|
+
futex = try_compile(<<~SOURCE)
|
|
53
|
+
#include <linux/futex.h>
|
|
54
|
+
#include <sys/syscall.h>
|
|
55
|
+
#include <stdint.h>
|
|
56
|
+
#include <stdatomic.h>
|
|
57
|
+
#include <time.h>
|
|
58
|
+
#include <unistd.h>
|
|
59
|
+
|
|
60
|
+
_Static_assert(sizeof(_Atomic(uint32_t)) == 4, "32-bit futex word");
|
|
61
|
+
_Static_assert(_Alignof(_Atomic(uint32_t)) >= 4, "aligned futex word");
|
|
62
|
+
int main(void) {
|
|
63
|
+
_Atomic(uint32_t) word = 1;
|
|
64
|
+
struct timespec now;
|
|
65
|
+
(void)clock_gettime(CLOCK_MONOTONIC, &now);
|
|
66
|
+
(void)syscall(SYS_futex, &word, FUTEX_WAIT_PRIVATE, 0, 0, 0, 0);
|
|
67
|
+
(void)syscall(SYS_futex, &word, FUTEX_WAKE_PRIVATE, 1, 0, 0, 0);
|
|
68
|
+
return 0;
|
|
69
|
+
}
|
|
70
|
+
SOURCE
|
|
71
|
+
end
|
|
72
|
+
$defs << "-DFARCE_HAVE_FUTEX" if futex
|
|
73
|
+
|
|
47
74
|
epoll = have_header("sys/epoll.h")
|
|
48
75
|
kqueue = have_header("sys/event.h")
|
|
49
76
|
$srcs = %w[
|
|
50
|
-
atom.c bounded_map.c counter.c darwin.c dict.c exchanger.c farce.c flag.c lock.c map.c
|
|
51
|
-
queue.c signal.c transaction.c tree_map.c trie.c unshareable.c unshared_signal.c vector.c weak_map.c
|
|
77
|
+
atom.c bounded_map.c counter.c darwin.c dict.c exchanger.c farce.c flag.c futex.c lock.c map.c native_wait.c
|
|
78
|
+
priority_queue.c queue.c signal.c transaction.c tree_map.c trie.c unshareable.c unshared_signal.c vector.c weak_map.c
|
|
52
79
|
]
|
|
53
80
|
$srcs.concat(epoll || kqueue ? %w[drivers.c io.c reactor.c] : ["unsupported.c"])
|
|
54
81
|
|
|
55
82
|
if epoll || kqueue
|
|
83
|
+
dir_config("uring")
|
|
56
84
|
have_func("epoll_pwait2", "sys/epoll.h") if epoll
|
|
57
85
|
have_func("rb_io_timeout", "ruby/io.h")
|
|
58
86
|
if enable_config("io-uring", true) && have_header("liburing.h") && have_library("uring", "io_uring_queue_init")
|
|
59
87
|
$defs << "-DFARCE_HAVE_LIBURING"
|
|
88
|
+
$DLDFLAGS << " -Wl,--exclude-libs,liburing.a" if enable_config("uring-static", false)
|
|
89
|
+
if futex && try_compile(<<~SOURCE)
|
|
90
|
+
#include <linux/futex.h>
|
|
91
|
+
#include <liburing.h>
|
|
92
|
+
|
|
93
|
+
int main(void) {
|
|
94
|
+
struct io_uring_sqe sqe;
|
|
95
|
+
uint32_t word = 0;
|
|
96
|
+
io_uring_prep_futex_wait(&sqe, &word, 0, FUTEX_BITSET_MATCH_ANY,
|
|
97
|
+
FUTEX_32 | FUTEX_PRIVATE_FLAG, 0);
|
|
98
|
+
return IORING_OP_FUTEX_WAIT == 0;
|
|
99
|
+
}
|
|
100
|
+
SOURCE
|
|
101
|
+
$defs << "-DFARCE_HAVE_URING_FUTEX"
|
|
102
|
+
end
|
|
60
103
|
end
|
|
61
104
|
end
|
|
62
105
|
|
data/ext/farce/farce.c
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#include "containers.h"
|
|
2
|
+
#include "futex.h"
|
|
2
3
|
#include "transaction.h"
|
|
3
4
|
#include "shareable.h"
|
|
4
5
|
#include "ruby/fiber/scheduler.h"
|
|
@@ -305,6 +306,20 @@ containers_raise_key_error(VALUE receiver, VALUE key)
|
|
|
305
306
|
rb_exc_raise(exception);
|
|
306
307
|
}
|
|
307
308
|
|
|
309
|
+
static VALUE
|
|
310
|
+
farce_value_changed(VALUE namespace, VALUE current, VALUE expected, VALUE identity)
|
|
311
|
+
{
|
|
312
|
+
if (current == expected) return Qfalse;
|
|
313
|
+
if (RTEST(identity)) return identity;
|
|
314
|
+
return RTEST(rb_equal(current, expected)) ? Qfalse : Qtrue;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
static VALUE
|
|
318
|
+
farce_futex_supported(VALUE namespace)
|
|
319
|
+
{
|
|
320
|
+
return containers_futex_available() ? Qtrue : Qfalse;
|
|
321
|
+
}
|
|
322
|
+
|
|
308
323
|
static VALUE
|
|
309
324
|
farce_load_native_fiber_scheduler(VALUE namespace)
|
|
310
325
|
{
|
|
@@ -329,6 +344,7 @@ Init_farce(void)
|
|
|
329
344
|
VALUE mInternal = rb_const_get(mFarce, rb_intern("Internal"));
|
|
330
345
|
VALUE singleton = rb_singleton_class(mInternal);
|
|
331
346
|
eIsolationError = rb_const_get(rb_cRactor, rb_intern("IsolationError"));
|
|
347
|
+
rb_define_singleton_method(mInternal, "value_changed?", farce_value_changed, 3);
|
|
332
348
|
#if RUBY_API_VERSION_CODE == 30400
|
|
333
349
|
rb_define_singleton_method(mInternal, "lock_native_thread", farce_lock_native_thread, 0);
|
|
334
350
|
#endif
|
|
@@ -338,6 +354,7 @@ Init_farce(void)
|
|
|
338
354
|
farce_load_native_fiber_scheduler,
|
|
339
355
|
0
|
|
340
356
|
);
|
|
357
|
+
rb_define_private_method(singleton, "futex_supported?", farce_futex_supported, 0);
|
|
341
358
|
containers_init_transaction(mInternal);
|
|
342
359
|
containers_init_atom(mInternal);
|
|
343
360
|
containers_init_counter(mInternal);
|
data/ext/farce/flag.c
CHANGED
|
@@ -49,7 +49,7 @@ RUBY_REFERENCES(flag_references) = {
|
|
|
49
49
|
};
|
|
50
50
|
|
|
51
51
|
static const rb_data_type_t flag_type = {
|
|
52
|
-
.wrap_struct_name = "
|
|
52
|
+
.wrap_struct_name = "Farce::Internal::Flag",
|
|
53
53
|
.function = {
|
|
54
54
|
.dmark = RUBY_REFS_LIST_PTR(flag_references),
|
|
55
55
|
.dfree = flag_free,
|
data/ext/farce/futex.c
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#include "futex.h"
|
|
2
|
+
|
|
3
|
+
#include <errno.h>
|
|
4
|
+
|
|
5
|
+
#ifdef FARCE_HAVE_FUTEX
|
|
6
|
+
#include <linux/futex.h>
|
|
7
|
+
#include <pthread.h>
|
|
8
|
+
#include <sys/syscall.h>
|
|
9
|
+
#include <unistd.h>
|
|
10
|
+
|
|
11
|
+
enum { FUTEX_WAITING = 0, FUTEX_NOTIFIED = 1 };
|
|
12
|
+
|
|
13
|
+
static pthread_once_t futex_probe_once = PTHREAD_ONCE_INIT;
|
|
14
|
+
static int futex_probe_error;
|
|
15
|
+
static bool futex_supported;
|
|
16
|
+
|
|
17
|
+
static bool
|
|
18
|
+
futex_unsupported_error(int error)
|
|
19
|
+
{
|
|
20
|
+
return error == ENOSYS || error == EPERM || error == EACCES || error == EOPNOTSUPP;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
static void
|
|
24
|
+
futex_probe(void)
|
|
25
|
+
{
|
|
26
|
+
containers_futex_t futex;
|
|
27
|
+
atomic_init(&futex.value, FUTEX_NOTIFIED);
|
|
28
|
+
/* The mismatch never sleeps, including on kernels without futex support. */
|
|
29
|
+
long result = syscall(SYS_futex, &futex.value, FUTEX_WAIT_PRIVATE,
|
|
30
|
+
FUTEX_WAITING, NULL, NULL, 0);
|
|
31
|
+
if (result != -1 || errno != EAGAIN) {
|
|
32
|
+
futex_probe_error = result == -1 ? errno : EIO;
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
result = syscall(SYS_futex, &futex.value, FUTEX_WAKE_PRIVATE, 1, NULL, NULL, 0);
|
|
36
|
+
if (result < 0) {
|
|
37
|
+
futex_probe_error = errno;
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
futex_supported = true;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
#endif
|
|
44
|
+
|
|
45
|
+
bool
|
|
46
|
+
containers_futex_available(void)
|
|
47
|
+
{
|
|
48
|
+
#ifdef FARCE_HAVE_FUTEX
|
|
49
|
+
int error = pthread_once(&futex_probe_once, futex_probe);
|
|
50
|
+
if (error) rb_syserr_fail(error, "pthread_once");
|
|
51
|
+
if (futex_probe_error && !futex_unsupported_error(futex_probe_error)) {
|
|
52
|
+
rb_syserr_fail(futex_probe_error, "futex capability probe");
|
|
53
|
+
}
|
|
54
|
+
return futex_supported;
|
|
55
|
+
#else
|
|
56
|
+
return false;
|
|
57
|
+
#endif
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
void
|
|
61
|
+
containers_futex_init(containers_futex_t *futex)
|
|
62
|
+
{
|
|
63
|
+
atomic_init(&futex->value, 0);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
void
|
|
67
|
+
containers_futex_notify(containers_futex_t *futex)
|
|
68
|
+
{
|
|
69
|
+
atomic_store_explicit(&futex->value, 1, memory_order_release);
|
|
70
|
+
#ifdef FARCE_HAVE_FUTEX
|
|
71
|
+
int saved_errno = errno;
|
|
72
|
+
while (syscall(SYS_futex, &futex->value, FUTEX_WAKE_PRIVATE,
|
|
73
|
+
1, NULL, NULL, 0) < 0 && errno == EINTR) {}
|
|
74
|
+
errno = saved_errno;
|
|
75
|
+
#endif
|
|
76
|
+
}
|
data/ext/farce/futex.h
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#ifndef FARCE_FUTEX_H
|
|
2
|
+
#define FARCE_FUTEX_H
|
|
3
|
+
|
|
4
|
+
#include "ruby.h"
|
|
5
|
+
|
|
6
|
+
#include <stdbool.h>
|
|
7
|
+
#include <stdint.h>
|
|
8
|
+
#include <stdatomic.h>
|
|
9
|
+
|
|
10
|
+
/* Each registered waiter owns this word until all native waits have ended. */
|
|
11
|
+
typedef struct {
|
|
12
|
+
_Atomic(uint32_t) value;
|
|
13
|
+
} containers_futex_t;
|
|
14
|
+
|
|
15
|
+
_Static_assert(sizeof(containers_futex_t) == 4, "a futex word must contain exactly 32 bits");
|
|
16
|
+
_Static_assert(_Alignof(containers_futex_t) >= 4, "a futex word must be four-byte aligned");
|
|
17
|
+
|
|
18
|
+
/* Call without native container locks. Unsupported builds and kernels return false. */
|
|
19
|
+
bool containers_futex_available(void);
|
|
20
|
+
/* These two operations are native-only and may run under a container mutex. */
|
|
21
|
+
void containers_futex_init(containers_futex_t *futex);
|
|
22
|
+
void containers_futex_notify(containers_futex_t *futex);
|
|
23
|
+
|
|
24
|
+
/* Scheduler support is opt-in and never blocks an unsupported event loop. */
|
|
25
|
+
bool containers_futex_scheduler_supported(VALUE scheduler);
|
|
26
|
+
/* True requests a condition/deadline recheck after notification or backend
|
|
27
|
+
* rejection. False means the scheduler timer expired. Every caller still
|
|
28
|
+
* rechecks synchronized state and its original absolute deadline. */
|
|
29
|
+
bool containers_futex_scheduler_wait(VALUE scheduler, containers_futex_t *futex, VALUE timeout);
|
|
30
|
+
|
|
31
|
+
#endif
|
data/ext/farce/lock.c
CHANGED
|
@@ -1,19 +1,13 @@
|
|
|
1
1
|
#include "containers.h"
|
|
2
|
+
#include "native_wait.h"
|
|
2
3
|
#include "ruby/fiber/scheduler.h"
|
|
3
|
-
#include "ruby/io.h"
|
|
4
|
-
|
|
5
|
-
#include <errno.h>
|
|
6
|
-
#include <fcntl.h>
|
|
7
|
-
#include <unistd.h>
|
|
8
4
|
|
|
9
5
|
static VALUE cLock;
|
|
10
6
|
|
|
11
7
|
typedef struct containers_lock_waiter containers_lock_waiter_t;
|
|
12
8
|
|
|
13
9
|
struct containers_lock_waiter {
|
|
14
|
-
|
|
15
|
-
int write_fd;
|
|
16
|
-
bool notified;
|
|
10
|
+
containers_waiter_t notification;
|
|
17
11
|
containers_lock_waiter_t *next;
|
|
18
12
|
};
|
|
19
13
|
|
|
@@ -29,7 +23,6 @@ typedef struct {
|
|
|
29
23
|
typedef struct {
|
|
30
24
|
containers_lock_t *lock;
|
|
31
25
|
containers_lock_waiter_t waiter;
|
|
32
|
-
bool completed;
|
|
33
26
|
} containers_lock_wait_context_t;
|
|
34
27
|
|
|
35
28
|
typedef struct {
|
|
@@ -51,35 +44,14 @@ typedef struct {
|
|
|
51
44
|
VALUE opaque;
|
|
52
45
|
} containers_lock_call_context_t;
|
|
53
46
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
{
|
|
57
|
-
#ifdef _WIN32
|
|
58
|
-
(void)fd;
|
|
59
|
-
#else
|
|
60
|
-
int flags = fcntl(fd, F_GETFL, 0);
|
|
61
|
-
if (flags >= 0) (void)fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
|
62
|
-
flags = fcntl(fd, F_GETFD, 0);
|
|
63
|
-
if (flags >= 0) (void)fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
|
|
64
|
-
#endif
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/* Called with lock->guard held. Mutex wakeups only need to make one waiter
|
|
68
|
-
* runnable; the selected waiter remains linked until its ensure cleanup. */
|
|
47
|
+
/* Called with lock->guard held. The selected waiter remains linked until
|
|
48
|
+
* ensure cleanup, and its notification latches before backend selection. */
|
|
69
49
|
static void
|
|
70
50
|
containers_lock_notify_one_locked(containers_lock_t *lock)
|
|
71
51
|
{
|
|
72
|
-
unsigned char byte = 1;
|
|
73
52
|
for (containers_lock_waiter_t *waiter = lock->waiters; waiter; waiter = waiter->next) {
|
|
74
|
-
if (waiter->notified) continue;
|
|
75
|
-
|
|
76
|
-
do {
|
|
77
|
-
result = write(waiter->write_fd, &byte, 1);
|
|
78
|
-
} while (result < 0 && errno == EINTR);
|
|
79
|
-
if (result == 1 || (result < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))) {
|
|
80
|
-
waiter->notified = true;
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
53
|
+
if (waiter->notification.notified) continue;
|
|
54
|
+
if (containers_waiter_notify(&waiter->notification)) return;
|
|
83
55
|
}
|
|
84
56
|
}
|
|
85
57
|
|
|
@@ -104,7 +76,7 @@ RUBY_REFERENCES(containers_lock_references) = {
|
|
|
104
76
|
};
|
|
105
77
|
|
|
106
78
|
static const rb_data_type_t containers_lock_type = {
|
|
107
|
-
.wrap_struct_name = "
|
|
79
|
+
.wrap_struct_name = "Farce::Internal::Lock",
|
|
108
80
|
.function = {
|
|
109
81
|
.dmark = RUBY_REFS_LIST_PTR(containers_lock_references),
|
|
110
82
|
.dfree = containers_lock_free,
|
|
@@ -188,19 +160,13 @@ containers_lock_initialize_copy(VALUE self, VALUE other)
|
|
|
188
160
|
return containers_publish_native_with_references(self, containers_lock_validate_references);
|
|
189
161
|
}
|
|
190
162
|
|
|
191
|
-
static bool
|
|
192
|
-
containers_lock_wait_for_descriptor(int fd)
|
|
193
|
-
{
|
|
194
|
-
return containers_wait_for_readable(fd, Qnil);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
163
|
static VALUE
|
|
198
164
|
containers_lock_wait_body(VALUE opaque)
|
|
199
165
|
{
|
|
200
166
|
containers_lock_wait_context_t *context = (containers_lock_wait_context_t *)opaque;
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
167
|
+
return containers_waiter_wait(
|
|
168
|
+
&context->waiter.notification, &context->lock->guard, false, 0, NULL
|
|
169
|
+
) ? Qtrue : Qfalse;
|
|
204
170
|
}
|
|
205
171
|
|
|
206
172
|
static VALUE
|
|
@@ -221,12 +187,13 @@ containers_lock_wait_cleanup(VALUE opaque)
|
|
|
221
187
|
}
|
|
222
188
|
previous = *link;
|
|
223
189
|
}
|
|
224
|
-
|
|
190
|
+
/* A wake does not reserve ownership. Pass the baton even on a normal
|
|
191
|
+
* return, since cancellation can arrive before the caller claims the lock. */
|
|
192
|
+
if (context->waiter.notification.notified && NIL_P(context->lock->owner)) {
|
|
225
193
|
containers_lock_notify_one_locked(context->lock);
|
|
226
194
|
}
|
|
227
195
|
pthread_mutex_unlock(&context->lock->guard);
|
|
228
|
-
|
|
229
|
-
close(context->waiter.write_fd);
|
|
196
|
+
containers_waiter_close(&context->waiter.notification);
|
|
230
197
|
return Qnil;
|
|
231
198
|
}
|
|
232
199
|
|
|
@@ -234,24 +201,11 @@ containers_lock_wait_cleanup(VALUE opaque)
|
|
|
234
201
|
static void
|
|
235
202
|
containers_lock_wait_once(containers_lock_t *lock)
|
|
236
203
|
{
|
|
237
|
-
int descriptors[2];
|
|
238
|
-
if (pipe(descriptors) != 0) {
|
|
239
|
-
pthread_mutex_unlock(&lock->guard);
|
|
240
|
-
rb_sys_fail("pipe");
|
|
241
|
-
}
|
|
242
|
-
containers_lock_set_fd_flags(descriptors[0]);
|
|
243
|
-
containers_lock_set_fd_flags(descriptors[1]);
|
|
244
|
-
|
|
245
204
|
containers_lock_wait_context_t context = {
|
|
246
205
|
.lock = lock,
|
|
247
|
-
.waiter = {
|
|
248
|
-
.read_fd = descriptors[0],
|
|
249
|
-
.write_fd = descriptors[1],
|
|
250
|
-
.notified = false,
|
|
251
|
-
.next = NULL,
|
|
252
|
-
},
|
|
253
|
-
.completed = false,
|
|
206
|
+
.waiter = {.next = NULL},
|
|
254
207
|
};
|
|
208
|
+
containers_waiter_init(&context.waiter.notification);
|
|
255
209
|
if (lock->last_waiter) lock->last_waiter->next = &context.waiter;
|
|
256
210
|
else lock->waiters = &context.waiter;
|
|
257
211
|
lock->last_waiter = &context.waiter;
|