farce 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -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/version.rb +1 -1
- metadata +6 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: daac1fc9dbb66e9473f1a61dc6fcaf4a4a0875a9c9f98670d47759d450f2b248
|
|
4
|
+
data.tar.gz: 3991bfd67d083fe58cd0e808f810e49809d667c11a005f68e63887cd29038b79
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0cbcf7e5769c5084198c06576d27d919501c5a54eafaacd0dccb8d268db416e784ac3556e773a64a4380a4f5c18cbe65fdedfb040ec5c4b0342c75d68eddaae0
|
|
7
|
+
data.tar.gz: 142f08011fd2704395a1d7ed3f04366e425f9f6a539ef525206b41c14707b03abc4f71d62c53ddcd1a634d11ddf1bcf14f62da034eec94b86ff31905069c78b2
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,12 @@ Farce follows [Semantic Versioning 2.0](http://semver.org/). Any public method o
|
|
|
4
4
|
|
|
5
5
|
## Development Releases
|
|
6
6
|
|
|
7
|
+
### Farce 0.4.1 (2026-10-16)
|
|
8
|
+
|
|
9
|
+
* Make sure prebuilt Linux binaries include liburing support.
|
|
10
|
+
* Use a Futex for efficient fiber synchronization if possible.
|
|
11
|
+
* Consistent `wrap_struct_name` across the native extension for debugging.
|
|
12
|
+
|
|
7
13
|
### Farce 0.4.0 (2026-10-16)
|
|
8
14
|
|
|
9
15
|
#### New Features
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright 2020 Jens Axboe
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a
|
|
4
|
+
copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included
|
|
12
|
+
in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
15
|
+
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
17
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
18
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
19
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
20
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
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,12 @@ containers_raise_key_error(VALUE receiver, VALUE key)
|
|
|
305
306
|
rb_exc_raise(exception);
|
|
306
307
|
}
|
|
307
308
|
|
|
309
|
+
static VALUE
|
|
310
|
+
farce_futex_supported(VALUE namespace)
|
|
311
|
+
{
|
|
312
|
+
return containers_futex_available() ? Qtrue : Qfalse;
|
|
313
|
+
}
|
|
314
|
+
|
|
308
315
|
static VALUE
|
|
309
316
|
farce_load_native_fiber_scheduler(VALUE namespace)
|
|
310
317
|
{
|
|
@@ -338,6 +345,7 @@ Init_farce(void)
|
|
|
338
345
|
farce_load_native_fiber_scheduler,
|
|
339
346
|
0
|
|
340
347
|
);
|
|
348
|
+
rb_define_private_method(singleton, "futex_supported?", farce_futex_supported, 0);
|
|
341
349
|
containers_init_transaction(mInternal);
|
|
342
350
|
containers_init_atom(mInternal);
|
|
343
351
|
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
|