farce 0.4.0-x86_64-linux-gnu → 0.5.0-x86_64-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/tree_map.c
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#include "containers.h"
|
|
2
|
+
#include "native_wait.h"
|
|
2
3
|
#include "transaction.h"
|
|
3
4
|
#include <ruby/atomic.h>
|
|
4
5
|
#include <ruby/fiber/scheduler.h>
|
|
@@ -558,9 +559,7 @@ tc_map_store_unlocked(VALUE self, tc_map *map, VALUE key, VALUE value,
|
|
|
558
559
|
typedef struct tc_core_map_waiter tc_core_map_waiter;
|
|
559
560
|
|
|
560
561
|
struct tc_core_map_waiter {
|
|
561
|
-
|
|
562
|
-
int write_fd;
|
|
563
|
-
bool notified;
|
|
562
|
+
containers_waiter_t notification;
|
|
564
563
|
tc_core_map_waiter *next;
|
|
565
564
|
};
|
|
566
565
|
|
|
@@ -787,40 +786,14 @@ typedef struct {
|
|
|
787
786
|
tc_core_map_waiter waiter;
|
|
788
787
|
} tc_core_map_fiber_wait;
|
|
789
788
|
|
|
790
|
-
static void
|
|
791
|
-
tc_core_map_set_fd_flags(int fd)
|
|
792
|
-
{
|
|
793
|
-
#ifdef _WIN32
|
|
794
|
-
(void)fd;
|
|
795
|
-
#else
|
|
796
|
-
int flags = fcntl(fd, F_GETFL, 0);
|
|
797
|
-
if (flags >= 0) (void)fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
|
798
|
-
flags = fcntl(fd, F_GETFD, 0);
|
|
799
|
-
if (flags >= 0) (void)fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
|
|
800
|
-
#endif
|
|
801
|
-
}
|
|
802
|
-
|
|
803
789
|
/* Called with core->lock held. The waiter remains linked until its ensure
|
|
804
790
|
* cleanup so an interrupt cannot leave a dangling stack pointer behind. */
|
|
805
791
|
static void
|
|
806
792
|
tc_core_map_notify_one_locked(tc_core_map *core)
|
|
807
793
|
{
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
waiter != NULL;
|
|
812
|
-
waiter = waiter->next) {
|
|
813
|
-
ssize_t result;
|
|
814
|
-
|
|
815
|
-
if (waiter->notified) continue;
|
|
816
|
-
do {
|
|
817
|
-
result = write(waiter->write_fd, &byte, 1);
|
|
818
|
-
} while (result < 0 && errno == EINTR);
|
|
819
|
-
if (result == 1 ||
|
|
820
|
-
(result < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))) {
|
|
821
|
-
waiter->notified = true;
|
|
822
|
-
return;
|
|
823
|
-
}
|
|
794
|
+
for (tc_core_map_waiter *waiter = core->waiters; waiter; waiter = waiter->next) {
|
|
795
|
+
if (waiter->notification.notified) continue;
|
|
796
|
+
if (containers_waiter_notify(&waiter->notification)) return;
|
|
824
797
|
}
|
|
825
798
|
}
|
|
826
799
|
|
|
@@ -828,7 +801,9 @@ static VALUE
|
|
|
828
801
|
tc_core_map_fiber_wait_body(VALUE opaque)
|
|
829
802
|
{
|
|
830
803
|
tc_core_map_fiber_wait *wait = (tc_core_map_fiber_wait *)opaque;
|
|
831
|
-
return
|
|
804
|
+
return containers_waiter_wait(
|
|
805
|
+
&wait->waiter.notification, &wait->core->lock, false, 0, NULL
|
|
806
|
+
) ? Qtrue : Qfalse;
|
|
832
807
|
}
|
|
833
808
|
|
|
834
809
|
static VALUE
|
|
@@ -851,9 +826,9 @@ tc_core_map_fiber_wait_cleanup(VALUE opaque)
|
|
|
851
826
|
}
|
|
852
827
|
/* Notification makes a waiter runnable, but does not reserve ownership.
|
|
853
828
|
* Always pass the baton if a notified waiter leaves while ownerless,
|
|
854
|
-
* including cancellation after
|
|
829
|
+
* including cancellation after a wait returned but before the lock
|
|
855
830
|
* acquisition loop claimed ownership. */
|
|
856
|
-
if (wait->waiter.notified &&
|
|
831
|
+
if (wait->waiter.notification.notified &&
|
|
857
832
|
NIL_P(wait->core->owner_fiber) &&
|
|
858
833
|
!tc_core_map_publication_busy(
|
|
859
834
|
RUBY_ATOMIC_LOAD(wait->core->publication_state)
|
|
@@ -862,8 +837,7 @@ tc_core_map_fiber_wait_cleanup(VALUE opaque)
|
|
|
862
837
|
}
|
|
863
838
|
pthread_mutex_unlock(&wait->core->lock);
|
|
864
839
|
|
|
865
|
-
|
|
866
|
-
close(wait->waiter.write_fd);
|
|
840
|
+
containers_waiter_close(&wait->waiter.notification);
|
|
867
841
|
return Qnil;
|
|
868
842
|
}
|
|
869
843
|
|
|
@@ -871,24 +845,11 @@ tc_core_map_fiber_wait_cleanup(VALUE opaque)
|
|
|
871
845
|
static void
|
|
872
846
|
tc_core_map_wait_once(tc_core_map *core)
|
|
873
847
|
{
|
|
874
|
-
int descriptors[2];
|
|
875
|
-
|
|
876
|
-
if (pipe(descriptors) != 0) {
|
|
877
|
-
pthread_mutex_unlock(&core->lock);
|
|
878
|
-
rb_sys_fail("pipe");
|
|
879
|
-
}
|
|
880
|
-
tc_core_map_set_fd_flags(descriptors[0]);
|
|
881
|
-
tc_core_map_set_fd_flags(descriptors[1]);
|
|
882
|
-
|
|
883
848
|
tc_core_map_fiber_wait wait = {
|
|
884
849
|
.core = core,
|
|
885
|
-
.waiter = {
|
|
886
|
-
.read_fd = descriptors[0],
|
|
887
|
-
.write_fd = descriptors[1],
|
|
888
|
-
.notified = false,
|
|
889
|
-
.next = NULL,
|
|
890
|
-
},
|
|
850
|
+
.waiter = {.next = NULL},
|
|
891
851
|
};
|
|
852
|
+
containers_waiter_init(&wait.waiter.notification);
|
|
892
853
|
|
|
893
854
|
if (core->last_waiter != NULL) core->last_waiter->next = &wait.waiter;
|
|
894
855
|
else core->waiters = &wait.waiter;
|
|
@@ -904,8 +865,8 @@ tc_core_map_wait_once(tc_core_map *core)
|
|
|
904
865
|
}
|
|
905
866
|
|
|
906
867
|
/* CRuby needs logical Fiber ownership rather than pthread ownership: multiple
|
|
907
|
-
* scheduled Fibers can contend on the same native thread.
|
|
908
|
-
*
|
|
868
|
+
* scheduled Fibers can contend on the same native thread. Native notifications
|
|
869
|
+
* park only the current Fiber while its owner resumes. */
|
|
909
870
|
static void
|
|
910
871
|
tc_core_map_lock(VALUE self, tc_core_map *core)
|
|
911
872
|
{
|
data/ext/farce/unshareable.c
CHANGED
|
@@ -40,7 +40,7 @@ containers_marker_size(const void *pointer)
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
static const rb_data_type_t containers_pin_marker_type = {
|
|
43
|
-
.wrap_struct_name = "
|
|
43
|
+
.wrap_struct_name = "Farce pin marker",
|
|
44
44
|
.function = {
|
|
45
45
|
.dmark = containers_marker_mark,
|
|
46
46
|
.dfree = RUBY_TYPED_DEFAULT_FREE,
|
data/ext/farce/unsupported.c
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
|
-
#include "
|
|
1
|
+
#include "futex.h"
|
|
2
2
|
|
|
3
3
|
void farce_init_fiber_scheduler(VALUE internal) {
|
|
4
4
|
(void)internal;
|
|
5
5
|
rb_raise(rb_eLoadError, "native Farce fiber scheduling requires epoll or kqueue; use the select implementation");
|
|
6
6
|
}
|
|
7
|
+
|
|
8
|
+
bool containers_futex_scheduler_supported(VALUE scheduler) {
|
|
9
|
+
(void)scheduler;
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
bool containers_futex_scheduler_wait(VALUE scheduler, containers_futex_t *word, VALUE timeout) {
|
|
14
|
+
(void)scheduler;
|
|
15
|
+
(void)word;
|
|
16
|
+
(void)timeout;
|
|
17
|
+
return true;
|
|
18
|
+
}
|
data/ext/farce/vector.c
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
#include "containers.h"
|
|
2
|
+
#include "native_wait.h"
|
|
2
3
|
#include "transaction.h"
|
|
3
|
-
#include "ruby/io.h"
|
|
4
4
|
#include "ruby/fiber/scheduler.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 <stdlib.h>
|
|
11
10
|
#include <time.h>
|
|
12
|
-
#include <unistd.h>
|
|
13
11
|
|
|
14
12
|
#define VECTOR_INITIAL_CAPACITY 16
|
|
15
13
|
static VALUE cVector;
|
|
@@ -19,9 +17,7 @@ static VALUE vector_transaction_marker;
|
|
|
19
17
|
typedef struct vector_waiter vector_waiter_t;
|
|
20
18
|
|
|
21
19
|
struct vector_waiter {
|
|
22
|
-
|
|
23
|
-
int write_fd;
|
|
24
|
-
bool notified;
|
|
20
|
+
containers_waiter_t notification;
|
|
25
21
|
vector_waiter_t *next;
|
|
26
22
|
};
|
|
27
23
|
|
|
@@ -85,7 +81,7 @@ vector_memsize(const void *pointer)
|
|
|
85
81
|
}
|
|
86
82
|
|
|
87
83
|
static const rb_data_type_t vector_type = {
|
|
88
|
-
.wrap_struct_name = "
|
|
84
|
+
.wrap_struct_name = "Farce::Internal::Vector",
|
|
89
85
|
.function = {
|
|
90
86
|
.dmark = vector_mark,
|
|
91
87
|
.dfree = vector_free,
|
|
@@ -214,35 +210,14 @@ vector_timeout_keyword(VALUE keywords)
|
|
|
214
210
|
return timeout;
|
|
215
211
|
}
|
|
216
212
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
{
|
|
220
|
-
#ifdef _WIN32
|
|
221
|
-
(void)fd;
|
|
222
|
-
#else
|
|
223
|
-
int flags = fcntl(fd, F_GETFL, 0);
|
|
224
|
-
if (flags >= 0) (void)fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
|
225
|
-
flags = fcntl(fd, F_GETFD, 0);
|
|
226
|
-
if (flags >= 0) (void)fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
|
|
227
|
-
#endif
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/* Called with vector->lock held. Each waiter has an independent pipe, so a
|
|
231
|
-
* transition wakes every waiting thread or scheduled fiber. */
|
|
213
|
+
/* Called with vector->lock held. A transition wakes every registered
|
|
214
|
+
* thread or fiber without sharing a consumable notification. */
|
|
232
215
|
static void
|
|
233
216
|
vector_notify_waiters_locked(vector_t *vector)
|
|
234
217
|
{
|
|
235
|
-
unsigned char byte = 1;
|
|
236
218
|
vector->generation++;
|
|
237
219
|
for (vector_waiter_t *waiter = vector->waiters; waiter; waiter = waiter->next) {
|
|
238
|
-
|
|
239
|
-
ssize_t result;
|
|
240
|
-
do {
|
|
241
|
-
result = write(waiter->write_fd, &byte, 1);
|
|
242
|
-
} while (result < 0 && errno == EINTR);
|
|
243
|
-
if (result == 1 || (result < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))) {
|
|
244
|
-
waiter->notified = true;
|
|
245
|
-
}
|
|
220
|
+
(void)containers_waiter_notify(&waiter->notification);
|
|
246
221
|
}
|
|
247
222
|
}
|
|
248
223
|
|
|
@@ -259,23 +234,14 @@ typedef struct {
|
|
|
259
234
|
vector_timeout_t *timeout;
|
|
260
235
|
} vector_wait_context_t;
|
|
261
236
|
|
|
262
|
-
static bool
|
|
263
|
-
vector_wait_for_descriptor(int fd, vector_timeout_t *timeout)
|
|
264
|
-
{
|
|
265
|
-
VALUE wait_timeout = Qnil;
|
|
266
|
-
if (timeout->finite) {
|
|
267
|
-
double remaining = timeout->deadline - vector_monotonic_now();
|
|
268
|
-
if (remaining <= 0) return false;
|
|
269
|
-
wait_timeout = DBL2NUM(remaining);
|
|
270
|
-
}
|
|
271
|
-
return containers_wait_for_readable(fd, wait_timeout);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
237
|
static VALUE
|
|
275
238
|
vector_wait_body(VALUE opaque)
|
|
276
239
|
{
|
|
277
240
|
vector_wait_context_t *context = (vector_wait_context_t *)opaque;
|
|
278
|
-
return
|
|
241
|
+
return containers_waiter_wait(
|
|
242
|
+
&context->waiter.notification, &context->vector->lock,
|
|
243
|
+
context->timeout->finite, context->timeout->deadline, NULL
|
|
244
|
+
) ? Qtrue : Qfalse;
|
|
279
245
|
}
|
|
280
246
|
|
|
281
247
|
static VALUE
|
|
@@ -292,8 +258,7 @@ vector_wait_cleanup(VALUE opaque)
|
|
|
292
258
|
}
|
|
293
259
|
}
|
|
294
260
|
pthread_mutex_unlock(&context->vector->lock);
|
|
295
|
-
|
|
296
|
-
close(context->waiter.write_fd);
|
|
261
|
+
containers_waiter_close(&context->waiter.notification);
|
|
297
262
|
return Qnil;
|
|
298
263
|
}
|
|
299
264
|
|
|
@@ -303,24 +268,12 @@ vector_wait_cleanup(VALUE opaque)
|
|
|
303
268
|
static bool
|
|
304
269
|
vector_wait_once(vector_t *vector, vector_timeout_t *timeout)
|
|
305
270
|
{
|
|
306
|
-
int descriptors[2];
|
|
307
|
-
if (pipe(descriptors) != 0) {
|
|
308
|
-
pthread_mutex_unlock(&vector->lock);
|
|
309
|
-
rb_sys_fail("pipe");
|
|
310
|
-
}
|
|
311
|
-
vector_set_fd_flags(descriptors[0]);
|
|
312
|
-
vector_set_fd_flags(descriptors[1]);
|
|
313
|
-
|
|
314
271
|
vector_wait_context_t context = {
|
|
315
272
|
.vector = vector,
|
|
316
|
-
.waiter = {
|
|
317
|
-
.read_fd = descriptors[0],
|
|
318
|
-
.write_fd = descriptors[1],
|
|
319
|
-
.notified = false,
|
|
320
|
-
.next = vector->waiters,
|
|
321
|
-
},
|
|
273
|
+
.waiter = {.next = vector->waiters},
|
|
322
274
|
.timeout = timeout,
|
|
323
275
|
};
|
|
276
|
+
containers_waiter_init(&context.waiter.notification);
|
|
324
277
|
vector->waiters = &context.waiter;
|
|
325
278
|
pthread_mutex_unlock(&vector->lock);
|
|
326
279
|
return RTEST(rb_ensure(vector_wait_body, (VALUE)&context, vector_wait_cleanup, (VALUE)&context));
|
|
@@ -1049,7 +1002,7 @@ vector_wait_until_changed(int argc, VALUE *argv, VALUE self)
|
|
|
1049
1002
|
uint64_t generation;
|
|
1050
1003
|
size_t index;
|
|
1051
1004
|
pthread_mutex_lock(&vector->lock);
|
|
1052
|
-
if (!vector_wait_for_reservation_locked(vector, &timeout)) return Qnil;
|
|
1005
|
+
if (!vector_wait_for_reservation_locked(vector, &timeout)) return rb_block_given_p() ? rb_yield(Qnil) : Qnil;
|
|
1053
1006
|
VALUE current = vector_lookup_index(vector, raw, &index) ? vector->values[index] : Qnil;
|
|
1054
1007
|
generation = vector->generation;
|
|
1055
1008
|
pthread_mutex_unlock(&vector->lock);
|
|
@@ -1057,12 +1010,12 @@ vector_wait_until_changed(int argc, VALUE *argv, VALUE self)
|
|
|
1057
1010
|
if (!vector_values_equal(vector, current, expected)) return current;
|
|
1058
1011
|
|
|
1059
1012
|
pthread_mutex_lock(&vector->lock);
|
|
1060
|
-
if (!vector_wait_for_reservation_locked(vector, &timeout)) return Qnil;
|
|
1013
|
+
if (!vector_wait_for_reservation_locked(vector, &timeout)) return rb_block_given_p() ? rb_yield(Qnil) : Qnil;
|
|
1061
1014
|
if (vector->generation != generation) {
|
|
1062
1015
|
pthread_mutex_unlock(&vector->lock);
|
|
1063
1016
|
continue;
|
|
1064
1017
|
}
|
|
1065
|
-
if (!vector_wait_once(vector, &timeout)) return Qnil;
|
|
1018
|
+
if (!vector_wait_once(vector, &timeout)) return rb_block_given_p() ? rb_yield(Qnil) : Qnil;
|
|
1066
1019
|
}
|
|
1067
1020
|
}
|
|
1068
1021
|
|
data/ext/farce/weak_map.c
CHANGED
|
@@ -250,7 +250,7 @@ weak_map_memsize(const void *pointer)
|
|
|
250
250
|
}
|
|
251
251
|
|
|
252
252
|
static const rb_data_type_t weak_map_type = {
|
|
253
|
-
.wrap_struct_name = "
|
|
253
|
+
.wrap_struct_name = "Farce::Internal::WeakMap",
|
|
254
254
|
.function = {
|
|
255
255
|
.dmark = weak_map_mark,
|
|
256
256
|
.dfree = weak_map_free,
|
|
@@ -1173,9 +1173,15 @@ weak_map_modify_body(VALUE opaque)
|
|
|
1173
1173
|
}
|
|
1174
1174
|
|
|
1175
1175
|
static VALUE
|
|
1176
|
-
weak_map_modify(VALUE
|
|
1176
|
+
weak_map_modify(int argc, VALUE *argv, VALUE self)
|
|
1177
1177
|
{
|
|
1178
1178
|
rb_check_frozen(self);
|
|
1179
|
+
VALUE key;
|
|
1180
|
+
VALUE keywords = Qnil;
|
|
1181
|
+
VALUE keyword_values[1] = {Qundef};
|
|
1182
|
+
ID keyword_ids[] = {rb_intern("timeout")};
|
|
1183
|
+
rb_scan_args(argc, argv, "1:", &key, &keywords);
|
|
1184
|
+
if (!NIL_P(keywords)) rb_get_kwargs(keywords, keyword_ids, 0, 1, keyword_values);
|
|
1179
1185
|
rb_need_block();
|
|
1180
1186
|
weak_map_t *map = get_weak_map(self);
|
|
1181
1187
|
key = weak_map_prepare_key(map, key);
|
|
@@ -1187,8 +1193,11 @@ weak_map_modify(VALUE self, VALUE key)
|
|
|
1187
1193
|
};
|
|
1188
1194
|
operation.hash = weak_map_key_hash(map, key);
|
|
1189
1195
|
rb_check_frozen(self);
|
|
1196
|
+
weak_map_timeout_t timeout = weak_map_parse_timeout(
|
|
1197
|
+
keyword_values[0] == Qundef ? Qnil : keyword_values[0]
|
|
1198
|
+
);
|
|
1190
1199
|
weak_map_execution_context_t execution = {.self = self};
|
|
1191
|
-
|
|
1200
|
+
if (!weak_map_lock_for_key_with_timeout(map, key, operation.hash, &timeout, &execution)) return Qfalse;
|
|
1192
1201
|
bool found;
|
|
1193
1202
|
size_t index = weak_map_find_slot(map, key, operation.hash, &found, &execution);
|
|
1194
1203
|
if (RB_OBJ_FROZEN(self)) {
|
|
@@ -1675,7 +1684,7 @@ define_weak_map_methods(VALUE klass)
|
|
|
1675
1684
|
rb_define_method(klass, "store", weak_map_store_with_timeout, -1);
|
|
1676
1685
|
rb_define_method(klass, "swap", weak_map_swap, -1);
|
|
1677
1686
|
rb_define_method(klass, "update", weak_map_update, -1);
|
|
1678
|
-
rb_define_method(klass, "modify", weak_map_modify, 1);
|
|
1687
|
+
rb_define_method(klass, "modify", weak_map_modify, -1);
|
|
1679
1688
|
rb_define_method(klass, "wait_until_changed", weak_map_wait_until_changed, -1);
|
|
1680
1689
|
rb_define_method(klass, "wait_until_non_nil", weak_map_wait_until_non_nil, -1);
|
|
1681
1690
|
rb_define_method(klass, "store_if_absent", weak_map_store_if_absent, -1);
|
|
@@ -54,7 +54,8 @@ public final class PriorityQueue {
|
|
|
54
54
|
private final Consumer<Runnable> committer;
|
|
55
55
|
private final QueueSignal signal;
|
|
56
56
|
private final BooleanSupplier scheduled;
|
|
57
|
-
private
|
|
57
|
+
private long capacity;
|
|
58
|
+
private Object rubyCapacity;
|
|
58
59
|
private final boolean trackAge;
|
|
59
60
|
private long size;
|
|
60
61
|
private long generation;
|
|
@@ -70,7 +71,7 @@ public final class PriorityQueue {
|
|
|
70
71
|
BiPredicate<Object, Object> identical,
|
|
71
72
|
BiPredicate<Object, Object> match,
|
|
72
73
|
Consumer<Runnable> committer,
|
|
73
|
-
BooleanSupplier scheduled, long capacity, QueueSignal signal,
|
|
74
|
+
BooleanSupplier scheduled, long capacity, Object rubyCapacity, QueueSignal signal,
|
|
74
75
|
boolean trackAge) {
|
|
75
76
|
this.tree = new TreeMap<>(comparator);
|
|
76
77
|
this.snapshot = snapshot;
|
|
@@ -82,6 +83,7 @@ public final class PriorityQueue {
|
|
|
82
83
|
this.signal = signal;
|
|
83
84
|
this.scheduled = scheduled;
|
|
84
85
|
this.capacity = capacity;
|
|
86
|
+
this.rubyCapacity = rubyCapacity;
|
|
85
87
|
this.trackAge = trackAge;
|
|
86
88
|
}
|
|
87
89
|
|
|
@@ -302,6 +304,20 @@ public final class PriorityQueue {
|
|
|
302
304
|
}
|
|
303
305
|
}
|
|
304
306
|
|
|
307
|
+
public Object getCapacity() {
|
|
308
|
+
enter();
|
|
309
|
+
try { return rubyCapacity; } finally { lock.unlock(); }
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
public void setCapacity(long capacity, Object rubyCapacity) {
|
|
313
|
+
enter();
|
|
314
|
+
try { commit(() -> {
|
|
315
|
+
this.capacity = capacity;
|
|
316
|
+
this.rubyCapacity = rubyCapacity;
|
|
317
|
+
}); }
|
|
318
|
+
finally { lock.unlock(); }
|
|
319
|
+
}
|
|
320
|
+
|
|
305
321
|
public long size() {
|
|
306
322
|
enter();
|
|
307
323
|
try { return size; } finally { lock.unlock(); }
|
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
module Farce
|
|
6
6
|
module Abstract
|
|
7
7
|
# @abstract Super class for {Map maps} with added concurrency features.
|
|
8
|
+
#
|
|
9
|
+
# Maps implement most of the interface provided by Hash,
|
|
10
|
+
# as well as `Concurrent::Map` from the `concurrent-ruby` gem.
|
|
8
11
|
class ConcurrentMap < Map
|
|
9
12
|
include DuplicableMap
|
|
10
13
|
|
|
@@ -34,6 +37,24 @@ module Farce
|
|
|
34
37
|
end
|
|
35
38
|
end
|
|
36
39
|
|
|
40
|
+
# Return an existing value or store a default for an absent key.
|
|
41
|
+
# A block takes precedence over the default and receives the original key.
|
|
42
|
+
# The lookup and store are separate operations. Concurrent writes can be overwritten.
|
|
43
|
+
# @param key [BasicObject] The key to look up or store.
|
|
44
|
+
# @param defaults [Array<BasicObject>] At most one default value to store when the key is absent.
|
|
45
|
+
# @see #store For supported transfer and timeout options.
|
|
46
|
+
# @yieldparam key [BasicObject] The original key.
|
|
47
|
+
# @yieldreturn [BasicObject] The value to store.
|
|
48
|
+
# @return [BasicObject, nil] The existing or newly stored value.
|
|
49
|
+
# @raise [KeyError] If the key is absent and neither a default nor a block is provided.
|
|
50
|
+
def fetch_or_store(key, *defaults, **)
|
|
51
|
+
if defaults.size > 1
|
|
52
|
+
raise ArgumentError, "wrong number of arguments (given #{defaults.size + 1}, expected 1..2)"
|
|
53
|
+
end
|
|
54
|
+
return fetch(key) if defaults.empty? && !block_given?
|
|
55
|
+
fetch(key) { store(key, block_given? ? yield(key) : defaults.first, **) }
|
|
56
|
+
end
|
|
57
|
+
|
|
37
58
|
# Read the value associated with a key, waiting for atomic-update access if necessary.
|
|
38
59
|
# @param key [BasicObject] The key to look up.
|
|
39
60
|
# @param timeout [Numeric, nil] The maximum number of seconds to wait for access.
|
|
@@ -43,6 +64,12 @@ module Farce
|
|
|
43
64
|
# The associated value, nil if the key is absent, or the fallback result if the operation times out.
|
|
44
65
|
def get(key, timeout: nil, &) = internal_map.get(key, timeout:, &)
|
|
45
66
|
|
|
67
|
+
# Sets a value and returns the previous value.
|
|
68
|
+
# @param key [BasicObject] The key to update.
|
|
69
|
+
# @param value [BasicObject] The value to store.
|
|
70
|
+
# @return [BasicObject, nil] The previous value, or nil if the key was absent.
|
|
71
|
+
def get_and_set(...) = swap(...)
|
|
72
|
+
|
|
46
73
|
# Associate a value with a key, waiting for atomic-update access if necessary.
|
|
47
74
|
# @param key [BasicObject] The key to store.
|
|
48
75
|
# @param value [BasicObject] The value to store.
|
|
@@ -52,6 +79,9 @@ module Farce
|
|
|
52
79
|
# @return [BasicObject, nil] `value` on success, or the fallback result or nil on timeout.
|
|
53
80
|
def store(key, value, timeout: nil, &) = internal_map.store(key, value, timeout:, &)
|
|
54
81
|
|
|
82
|
+
# (see #store)
|
|
83
|
+
def put(...) = store(...)
|
|
84
|
+
|
|
55
85
|
# Replace the value associated with a key and return its previous value.
|
|
56
86
|
# @param key [BasicObject] The key to update.
|
|
57
87
|
# @param replacement [BasicObject] The value to store.
|
|
@@ -71,6 +101,69 @@ module Farce
|
|
|
71
101
|
# @raise [LocalJumpError] If no block is given.
|
|
72
102
|
def store_if_absent(key, timeout: nil, &) = internal_map.store_if_absent(key, timeout:, &)
|
|
73
103
|
|
|
104
|
+
# (see #store_if_absent)
|
|
105
|
+
def compute_if_absent(...) = store_if_absent(...)
|
|
106
|
+
|
|
107
|
+
# Atomically store a value only when the key is absent.
|
|
108
|
+
# Existing nil and false values are preserved.
|
|
109
|
+
# @param key [BasicObject] The key to look up or store.
|
|
110
|
+
# @param value [BasicObject] The value to store when the key is absent.
|
|
111
|
+
# @see #store_if_absent For supported transfer and timeout options.
|
|
112
|
+
# @return [BasicObject, nil] The existing value, or nil when a value was inserted or access timed out.
|
|
113
|
+
def put_if_absent(key, value, **)
|
|
114
|
+
inserted = false
|
|
115
|
+
previous = store_if_absent(key, **) do
|
|
116
|
+
inserted = true
|
|
117
|
+
value
|
|
118
|
+
end
|
|
119
|
+
inserted ? nil : previous
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Atomically replace an existing value and return its previous value.
|
|
123
|
+
# An absent key is left unchanged. Existing nil and false values can be replaced.
|
|
124
|
+
# @param key [BasicObject] The key to update.
|
|
125
|
+
# @param replacement [BasicObject] The value to store.
|
|
126
|
+
# @param timeout [Numeric, nil] The maximum number of seconds to wait for access.
|
|
127
|
+
# @see #store For supported transfer options.
|
|
128
|
+
# @return [BasicObject, nil] The previous value, or nil when the key is absent or access timed out.
|
|
129
|
+
def replace_if_exists(key, replacement, timeout: nil, **)
|
|
130
|
+
map = internal_map
|
|
131
|
+
previous = nil
|
|
132
|
+
changed = map.modify(key, timeout:) do |present, value|
|
|
133
|
+
next Internal::MAP_KEEP unless present
|
|
134
|
+
previous = value
|
|
135
|
+
map.check_mutation
|
|
136
|
+
wrap_value(replacement, **)
|
|
137
|
+
end
|
|
138
|
+
unwrap_value(previous) if changed
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Atomically replace the value associated with a key if it is present.
|
|
142
|
+
# The block receives the current value and should return the new value to store.
|
|
143
|
+
# The block is not called for an absent key. A stored nil is passed to the block.
|
|
144
|
+
# @param key [BasicObject] The key to update.
|
|
145
|
+
# @param timeout [Numeric, nil] The maximum number of seconds to wait for access.
|
|
146
|
+
# @yield [value] Called with the current value if the key is present.
|
|
147
|
+
# @yieldparam value [BasicObject] The current value.
|
|
148
|
+
# @yieldreturn [BasicObject] The new value to store.
|
|
149
|
+
# @return [BasicObject, nil] The newly stored value, or nil if the key was absent or the operation timed out.
|
|
150
|
+
# @raise [LocalJumpError] If no block is given.
|
|
151
|
+
def store_if_present(key, timeout: nil)
|
|
152
|
+
raise LocalJumpError, "no block given" unless block_given?
|
|
153
|
+
map = internal_map
|
|
154
|
+
result = nil
|
|
155
|
+
changed = map.modify(key, timeout:) do |present, value|
|
|
156
|
+
next Internal::MAP_KEEP unless present
|
|
157
|
+
replacement = yield(unwrap_value(value))
|
|
158
|
+
map.check_mutation
|
|
159
|
+
result = wrap_value(replacement)
|
|
160
|
+
end
|
|
161
|
+
unwrap_value(result) if changed
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# (see #store_if_present)
|
|
165
|
+
def compute_if_present(...) = store_if_present(...)
|
|
166
|
+
|
|
74
167
|
# Atomically replace a value if the key is present and its current value matches `expected`.
|
|
75
168
|
# The configured value-comparison mode determines whether matching uses equality or identity.
|
|
76
169
|
# @param key [BasicObject] The key to update.
|
|
@@ -82,6 +175,23 @@ module Farce
|
|
|
82
175
|
internal_map.compare_and_set(key, expected, replacement, timeout:)
|
|
83
176
|
end
|
|
84
177
|
|
|
178
|
+
# (see #compare_and_set)
|
|
179
|
+
def replace_pair(...) = compare_and_set(...)
|
|
180
|
+
|
|
181
|
+
# Atomically remove a key if its current value matches `expected`.
|
|
182
|
+
# The configured value-comparison mode determines whether matching uses equality or identity.
|
|
183
|
+
# Missing keys and mismatched values are left unchanged.
|
|
184
|
+
# @param key [BasicObject] The key to remove.
|
|
185
|
+
# @param expected [BasicObject] The expected current value.
|
|
186
|
+
# @param timeout [Numeric, nil] The maximum number of seconds to wait for access.
|
|
187
|
+
# @return [Boolean] Whether the entry was removed. Returns false on timeout.
|
|
188
|
+
def delete_pair(key, expected, timeout: nil)
|
|
189
|
+
expected = wrap_comparison(expected)
|
|
190
|
+
internal_map.modify(key, timeout:) do |present, value|
|
|
191
|
+
present && values_equal?(value, expected) ? Internal::MAP_DELETE : Internal::MAP_KEEP
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
85
195
|
# Atomically replace the value associated with a key with the block result.
|
|
86
196
|
# The block receives nil when the key is absent or its current value is nil.
|
|
87
197
|
# @param key [BasicObject] The key to update.
|
|
@@ -93,6 +203,9 @@ module Farce
|
|
|
93
203
|
# @raise [LocalJumpError] If no block is given.
|
|
94
204
|
def update(key, timeout: nil, &) = internal_map.update(key, timeout:, &)
|
|
95
205
|
|
|
206
|
+
# (see #update)
|
|
207
|
+
def compute(...) = update(...)
|
|
208
|
+
|
|
96
209
|
# Atomically insert `initial` for an absent key or replace an existing value with the block result.
|
|
97
210
|
# The block is not called when the key is absent.
|
|
98
211
|
# @param key [BasicObject] The key to insert or update.
|
|
@@ -105,6 +218,9 @@ module Farce
|
|
|
105
218
|
# @raise [LocalJumpError] If no block is given.
|
|
106
219
|
def upsert(key, initial, timeout: nil, &) = internal_map.upsert(key, initial, timeout:, &)
|
|
107
220
|
|
|
221
|
+
# (see #upsert)
|
|
222
|
+
def merge_pair(...) = upsert(...)
|
|
223
|
+
|
|
108
224
|
# Wait until a block condition matches the current value.
|
|
109
225
|
# One timeout budget covers all checks and waits. The block is not interrupted.
|
|
110
226
|
# @yieldparam value [BasicObject, nil] the current value
|
|
@@ -344,6 +460,12 @@ module Farce
|
|
|
344
460
|
|
|
345
461
|
private
|
|
346
462
|
|
|
463
|
+
def wrap_comparison(value) = value
|
|
464
|
+
|
|
465
|
+
def values_equal?(left, right)
|
|
466
|
+
compare_values_by_identity? ? left.equal?(right) : left == right
|
|
467
|
+
end
|
|
468
|
+
|
|
347
469
|
def modify_entry(key, canonical: false)
|
|
348
470
|
map = internal_map
|
|
349
471
|
map = map.instance_variable_get(:@map) if canonical && map.is_a?(Internal::KeyNormalizer::ConcurrentMap)
|
|
@@ -7,6 +7,22 @@ module Farce
|
|
|
7
7
|
# @abstract Common numeric interface and atomic operations for counters.
|
|
8
8
|
# @note This is a module rather than a class so {Farce::Counter} can inherit
|
|
9
9
|
# directly from the native counter and avoid delegation overhead.
|
|
10
|
+
#
|
|
11
|
+
# @!method initial
|
|
12
|
+
# @return [Integer] The initial value retained by reset and copies.
|
|
13
|
+
#
|
|
14
|
+
# @!method value
|
|
15
|
+
# @return [Integer] The current value of the counter.
|
|
16
|
+
#
|
|
17
|
+
# @!method increment(by = 1)
|
|
18
|
+
# Increment the counter by the given amount (default is 1).
|
|
19
|
+
# @param [Numeric, String, #to_int] by The amount, converted to an Integer.
|
|
20
|
+
# @return [self] Returns self for chaining.
|
|
21
|
+
#
|
|
22
|
+
# @!method decrement(by = 1)
|
|
23
|
+
# Decrement the counter by the given amount (default is 1).
|
|
24
|
+
# @param [Numeric, String, #to_int] by The amount, converted to an Integer.
|
|
25
|
+
# @return [self] Returns self for chaining.
|
|
10
26
|
module Counter
|
|
11
27
|
include Internal::MarshalSupport::Counter
|
|
12
28
|
include Internal::Copyable
|
|
@@ -24,6 +40,24 @@ module Farce
|
|
|
24
40
|
include Value
|
|
25
41
|
include Internal::ValueSerialization
|
|
26
42
|
|
|
43
|
+
# Pass the current value to the given block, replacing it with the block's result.
|
|
44
|
+
# May retry if the value changes during the block's execution.
|
|
45
|
+
#
|
|
46
|
+
# Provided for compatibility with `Concurrent::AtomicFixnum`.
|
|
47
|
+
#
|
|
48
|
+
# @yield [count] Calculate the new value based on the current value.
|
|
49
|
+
# @yieldparam count [Integer] the current value of the atomic fixnum.
|
|
50
|
+
# @yieldreturn [Integer] the new value to replace the current value.
|
|
51
|
+
# @return [Integer] the new value after the block has been applied.
|
|
52
|
+
def update
|
|
53
|
+
raise LocalJumpError, "no block given" unless block_given?
|
|
54
|
+
while true
|
|
55
|
+
current = value
|
|
56
|
+
new_value = yield(current)
|
|
57
|
+
return new_value if compare_and_set(current, new_value)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
27
61
|
# Wait until the current value differs from the expected value.
|
|
28
62
|
# @param expected [Integer] the value to wait to change from
|
|
29
63
|
# @param timeout [Numeric, nil] the total seconds available
|
|
@@ -35,7 +69,7 @@ module Farce
|
|
|
35
69
|
observed = signal.generation
|
|
36
70
|
current = value
|
|
37
71
|
return current unless expected == current
|
|
38
|
-
break unless signal.wait(observed, timeout:
|
|
72
|
+
break unless signal.wait(observed, timeout: Clock.remaining(deadline))
|
|
39
73
|
end
|
|
40
74
|
yield if block_given?
|
|
41
75
|
end
|