semian_extension 0.11.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 +7 -0
- data/ext/semian/extconf.rb +33 -0
- data/ext/semian/resource.c +394 -0
- data/ext/semian/resource.h +133 -0
- data/ext/semian/semian.c +72 -0
- data/ext/semian/semian.h +14 -0
- data/ext/semian/sysv_semaphores.c +271 -0
- data/ext/semian/sysv_semaphores.h +122 -0
- data/ext/semian/tickets.c +76 -0
- data/ext/semian/tickets.h +13 -0
- data/ext/semian/types.h +41 -0
- data/lib/semian/adapter.rb +75 -0
- data/lib/semian/circuit_breaker.rb +167 -0
- data/lib/semian/grpc.rb +104 -0
- data/lib/semian/instrumentable.rb +28 -0
- data/lib/semian/lru_hash.rb +174 -0
- data/lib/semian/mysql2.rb +135 -0
- data/lib/semian/net_http.rb +117 -0
- data/lib/semian/platform.rb +16 -0
- data/lib/semian/protected_resource.rb +65 -0
- data/lib/semian/rails.rb +7 -0
- data/lib/semian/redis.rb +143 -0
- data/lib/semian/resource.rb +65 -0
- data/lib/semian/simple_integer.rb +38 -0
- data/lib/semian/simple_sliding_window.rb +68 -0
- data/lib/semian/simple_state.rb +50 -0
- data/lib/semian/typhoeus.rb +103 -0
- data/lib/semian/unprotected_resource.rb +73 -0
- data/lib/semian/version.rb +3 -0
- data/lib/semian.rb +310 -0
- metadata +260 -0
@@ -0,0 +1,271 @@
|
|
1
|
+
#include "sysv_semaphores.h"
|
2
|
+
#include <time.h>
|
3
|
+
|
4
|
+
static key_t
|
5
|
+
generate_key(const char *name);
|
6
|
+
|
7
|
+
static void *
|
8
|
+
acquire_semaphore(void *p);
|
9
|
+
|
10
|
+
static int
|
11
|
+
wait_for_new_semaphore_set(key_t key, long permissions);
|
12
|
+
|
13
|
+
static void
|
14
|
+
initialize_new_semaphore_values(int sem_id, long permissions);
|
15
|
+
|
16
|
+
static long
|
17
|
+
diff_timespec_ms(struct timespec *end, struct timespec *begin);
|
18
|
+
|
19
|
+
// Generate string rep for sem indices for debugging puproses
|
20
|
+
static const char *SEMINDEX_STRING[] = {
|
21
|
+
FOREACH_SEMINDEX(GENERATE_STRING)
|
22
|
+
};
|
23
|
+
|
24
|
+
void
|
25
|
+
raise_semian_syscall_error(const char *syscall, int error_num)
|
26
|
+
{
|
27
|
+
rb_raise(eSyscall, "%s failed, errno: %d (%s)", syscall, error_num, strerror(error_num));
|
28
|
+
}
|
29
|
+
|
30
|
+
void
|
31
|
+
initialize_semaphore_set(semian_resource_t* res, const char* id_str, long permissions, int tickets, double quota)
|
32
|
+
{
|
33
|
+
|
34
|
+
res->key = generate_key(id_str);
|
35
|
+
res->strkey = (char*) malloc((2 /*for 0x*/+ sizeof(uint64_t) /*actual key*/+ 1 /*null*/) * sizeof(char));
|
36
|
+
sprintf(res->strkey, "0x%08x", (unsigned int) res->key);
|
37
|
+
res->sem_id = semget(res->key, SI_NUM_SEMAPHORES, IPC_CREAT | IPC_EXCL | permissions);
|
38
|
+
|
39
|
+
/*
|
40
|
+
This approach is based on http://man7.org/tlpi/code/online/dist/svsem/svsem_good_init.c.html
|
41
|
+
which avoids race conditions when initializing semaphore sets.
|
42
|
+
*/
|
43
|
+
if (res->sem_id != -1) {
|
44
|
+
// Happy path - we are the first worker, initialize the semaphore set.
|
45
|
+
initialize_new_semaphore_values(res->sem_id, permissions);
|
46
|
+
} else {
|
47
|
+
// Something went wrong
|
48
|
+
if (errno != EEXIST) {
|
49
|
+
raise_semian_syscall_error("semget() failed to initialize semaphore values", errno);
|
50
|
+
} else {
|
51
|
+
// The semaphore set already exists, ensure it is initialized
|
52
|
+
res->sem_id = wait_for_new_semaphore_set(res->key, permissions);
|
53
|
+
}
|
54
|
+
}
|
55
|
+
|
56
|
+
set_semaphore_permissions(res->sem_id, permissions);
|
57
|
+
|
58
|
+
/*
|
59
|
+
Ensure that a worker for this process is registered.
|
60
|
+
Note that from ruby we ensure that at most one worker may be registered per process.
|
61
|
+
*/
|
62
|
+
if (perform_semop(res->sem_id, SI_SEM_REGISTERED_WORKERS, 1, SEM_UNDO, NULL) == -1) {
|
63
|
+
rb_raise(eInternal, "error incrementing registered workers, errno: %d (%s)", errno, strerror(errno));
|
64
|
+
}
|
65
|
+
|
66
|
+
int state = 0;
|
67
|
+
sem_meta_lock(res->sem_id); // Sets otime for the first time by acquiring the sem lock
|
68
|
+
|
69
|
+
configure_tickets_args_t configure_tickets_args = (configure_tickets_args_t){
|
70
|
+
.sem_id = res->sem_id,
|
71
|
+
.tickets = tickets,
|
72
|
+
.quota = quota,
|
73
|
+
};
|
74
|
+
rb_protect(
|
75
|
+
configure_tickets,
|
76
|
+
(VALUE)&configure_tickets_args,
|
77
|
+
&state);
|
78
|
+
|
79
|
+
sem_meta_unlock(res->sem_id);
|
80
|
+
if (state) {
|
81
|
+
rb_jump_tag(state);
|
82
|
+
}
|
83
|
+
}
|
84
|
+
|
85
|
+
void
|
86
|
+
set_semaphore_permissions(int sem_id, long permissions)
|
87
|
+
{
|
88
|
+
union semun sem_opts;
|
89
|
+
struct semid_ds stat_buf;
|
90
|
+
|
91
|
+
sem_opts.buf = &stat_buf;
|
92
|
+
semctl(sem_id, 0, IPC_STAT, sem_opts);
|
93
|
+
if ((stat_buf.sem_perm.mode & 0xfff) != permissions) {
|
94
|
+
stat_buf.sem_perm.mode &= ~0xfff;
|
95
|
+
stat_buf.sem_perm.mode |= permissions;
|
96
|
+
semctl(sem_id, 0, IPC_SET, sem_opts);
|
97
|
+
}
|
98
|
+
}
|
99
|
+
|
100
|
+
int
|
101
|
+
perform_semop(int sem_id, short index, short op, short flags, struct timespec *ts)
|
102
|
+
{
|
103
|
+
int result;
|
104
|
+
struct sembuf buf = { 0 };
|
105
|
+
|
106
|
+
buf.sem_num = index;
|
107
|
+
buf.sem_op = op;
|
108
|
+
buf.sem_flg = flags;
|
109
|
+
|
110
|
+
int num_retries = 3;
|
111
|
+
|
112
|
+
do {
|
113
|
+
result = semtimedop(sem_id, &buf, 1, ts);
|
114
|
+
} while (result < 0 && errno == EINTR && num_retries-- > 0);
|
115
|
+
|
116
|
+
return result;
|
117
|
+
}
|
118
|
+
|
119
|
+
int
|
120
|
+
get_sem_val(int sem_id, int sem_index)
|
121
|
+
{
|
122
|
+
int ret = semctl(sem_id, sem_index, GETVAL);
|
123
|
+
if (ret == -1) {
|
124
|
+
rb_raise(eInternal, "error getting value of %s for sem %d, errno: %d (%s)", SEMINDEX_STRING[sem_index], sem_id, errno, strerror(errno));
|
125
|
+
}
|
126
|
+
return ret;
|
127
|
+
}
|
128
|
+
|
129
|
+
void
|
130
|
+
sem_meta_lock(int sem_id)
|
131
|
+
{
|
132
|
+
struct timespec ts = { 0 };
|
133
|
+
ts.tv_sec = INTERNAL_TIMEOUT;
|
134
|
+
|
135
|
+
if (perform_semop(sem_id, SI_SEM_LOCK, -1, SEM_UNDO, &ts) == -1) {
|
136
|
+
raise_semian_syscall_error("error acquiring internal semaphore lock, semtimedop()", errno);
|
137
|
+
}
|
138
|
+
}
|
139
|
+
|
140
|
+
void
|
141
|
+
sem_meta_unlock(int sem_id)
|
142
|
+
{
|
143
|
+
if (perform_semop(sem_id, SI_SEM_LOCK, 1, SEM_UNDO, NULL) == -1) {
|
144
|
+
raise_semian_syscall_error("error releasing internal semaphore lock, semop()", errno);
|
145
|
+
}
|
146
|
+
}
|
147
|
+
|
148
|
+
int
|
149
|
+
get_semaphore(int key)
|
150
|
+
{
|
151
|
+
return semget(key, SI_NUM_SEMAPHORES, 0);
|
152
|
+
}
|
153
|
+
|
154
|
+
void *
|
155
|
+
acquire_semaphore_without_gvl(void *p)
|
156
|
+
{
|
157
|
+
WITHOUT_GVL(acquire_semaphore, p, RUBY_UBF_IO, NULL);
|
158
|
+
return NULL;
|
159
|
+
}
|
160
|
+
|
161
|
+
static void *
|
162
|
+
acquire_semaphore(void *p)
|
163
|
+
{
|
164
|
+
semian_resource_t *res = (semian_resource_t *) p;
|
165
|
+
res->error = 0;
|
166
|
+
res->wait_time = -1;
|
167
|
+
#ifdef DEBUG
|
168
|
+
print_sem_vals(res->sem_id);
|
169
|
+
#endif
|
170
|
+
|
171
|
+
struct timespec begin, end;
|
172
|
+
int benchmark_result = clock_gettime(CLOCK_MONOTONIC, &begin);
|
173
|
+
if (perform_semop(res->sem_id, SI_SEM_TICKETS, -1, SEM_UNDO, &res->timeout) == -1) {
|
174
|
+
res->error = errno;
|
175
|
+
}
|
176
|
+
if (benchmark_result == 0) {
|
177
|
+
if (clock_gettime(CLOCK_MONOTONIC, &end) == 0) {
|
178
|
+
res->wait_time = diff_timespec_ms(&end, &begin);
|
179
|
+
}
|
180
|
+
}
|
181
|
+
return NULL;
|
182
|
+
}
|
183
|
+
|
184
|
+
static key_t
|
185
|
+
generate_key(const char *name)
|
186
|
+
{
|
187
|
+
char semset_size_key[20];
|
188
|
+
char *uniq_id_str;
|
189
|
+
|
190
|
+
// It is necessary for the cardinatily of the semaphore set to be part of the key
|
191
|
+
// or else sem_get will complain that we have requested an incorrect number of sems
|
192
|
+
// for the desired key, and have changed the number of semaphores for a given key
|
193
|
+
sprintf(semset_size_key, "_NUM_SEMS_%d", SI_NUM_SEMAPHORES);
|
194
|
+
uniq_id_str = malloc(strlen(name)+strlen(semset_size_key)+1);
|
195
|
+
strcpy(uniq_id_str, name);
|
196
|
+
strcat(uniq_id_str, semset_size_key);
|
197
|
+
|
198
|
+
union {
|
199
|
+
unsigned char str[SHA_DIGEST_LENGTH];
|
200
|
+
key_t key;
|
201
|
+
} digest;
|
202
|
+
SHA1((const unsigned char *) uniq_id_str, strlen(uniq_id_str), digest.str);
|
203
|
+
free(uniq_id_str);
|
204
|
+
/* TODO: compile-time assertion that sizeof(key_t) > SHA_DIGEST_LENGTH */
|
205
|
+
return digest.key;
|
206
|
+
}
|
207
|
+
|
208
|
+
|
209
|
+
static void
|
210
|
+
initialize_new_semaphore_values(int sem_id, long permissions)
|
211
|
+
{
|
212
|
+
unsigned short init_vals[SI_NUM_SEMAPHORES];
|
213
|
+
|
214
|
+
init_vals[SI_SEM_TICKETS] = init_vals[SI_SEM_CONFIGURED_TICKETS] = 0;
|
215
|
+
init_vals[SI_SEM_REGISTERED_WORKERS] = 0;
|
216
|
+
init_vals[SI_SEM_LOCK] = 1;
|
217
|
+
|
218
|
+
if (semctl(sem_id, 0, SETALL, init_vals) == -1) {
|
219
|
+
raise_semian_syscall_error("semctl()", errno);
|
220
|
+
}
|
221
|
+
#ifdef DEBUG
|
222
|
+
print_sem_vals(sem_id);
|
223
|
+
#endif
|
224
|
+
}
|
225
|
+
|
226
|
+
static int
|
227
|
+
wait_for_new_semaphore_set(key_t key, long permissions)
|
228
|
+
{
|
229
|
+
int i;
|
230
|
+
int sem_id = -1;
|
231
|
+
union semun sem_opts;
|
232
|
+
struct semid_ds sem_ds;
|
233
|
+
|
234
|
+
sem_opts.buf = &sem_ds;
|
235
|
+
sem_id = semget(key, 1, permissions);
|
236
|
+
|
237
|
+
if (sem_id == -1){
|
238
|
+
raise_semian_syscall_error("semget()", errno);
|
239
|
+
}
|
240
|
+
|
241
|
+
for (i = 0; i < ((INTERNAL_TIMEOUT * MICROSECONDS_IN_SECOND) / INIT_WAIT); i++) {
|
242
|
+
|
243
|
+
if (semctl(sem_id, 0, IPC_STAT, sem_opts) == -1) {
|
244
|
+
raise_semian_syscall_error("semctl()", errno);
|
245
|
+
}
|
246
|
+
|
247
|
+
// If a semop has been performed by someone else, the values must be initialized
|
248
|
+
if (sem_ds.sem_otime != 0) {
|
249
|
+
break;
|
250
|
+
}
|
251
|
+
|
252
|
+
#ifdef DEBUG
|
253
|
+
printf("Waiting for another process to initialize semaphore values, checked: %d times\n", i);
|
254
|
+
#endif
|
255
|
+
usleep(INIT_WAIT);
|
256
|
+
}
|
257
|
+
|
258
|
+
if (sem_ds.sem_otime == 0) {
|
259
|
+
rb_raise(eTimeout, "error: timeout waiting for semaphore values to initialize after %d checks", INTERNAL_TIMEOUT);
|
260
|
+
}
|
261
|
+
|
262
|
+
return sem_id;
|
263
|
+
}
|
264
|
+
|
265
|
+
static long
|
266
|
+
diff_timespec_ms(struct timespec *end, struct timespec *begin)
|
267
|
+
{
|
268
|
+
long end_ms = (end->tv_sec * 1e3) + (end->tv_nsec / 1e6);
|
269
|
+
long begin_ms = (begin->tv_sec * 1e3) + (begin->tv_nsec / 1e6);
|
270
|
+
return end_ms - begin_ms;
|
271
|
+
}
|
@@ -0,0 +1,122 @@
|
|
1
|
+
/*
|
2
|
+
For manipulating the semian's semaphore set
|
3
|
+
|
4
|
+
Semian semaphore operations and initialization,
|
5
|
+
and functions associated directly weth semops.
|
6
|
+
*/
|
7
|
+
#ifndef SEMIAN_SEMSET_H
|
8
|
+
#define SEMIAN_SEMSET_H
|
9
|
+
|
10
|
+
#include <errno.h>
|
11
|
+
#include <stdio.h>
|
12
|
+
#include <string.h>
|
13
|
+
|
14
|
+
#include <openssl/sha.h>
|
15
|
+
#include <ruby.h>
|
16
|
+
#include <ruby/util.h>
|
17
|
+
#include <ruby/io.h>
|
18
|
+
|
19
|
+
#include "types.h"
|
20
|
+
#include "tickets.h"
|
21
|
+
|
22
|
+
// Defines for ruby threading primitives
|
23
|
+
#if defined(HAVE_RB_THREAD_CALL_WITHOUT_GVL) && defined(HAVE_RUBY_THREAD_H)
|
24
|
+
// 2.0
|
25
|
+
#include <ruby/thread.h>
|
26
|
+
#define WITHOUT_GVL(fn,a,ubf,b) rb_thread_call_without_gvl((fn),(a),(ubf),(b))
|
27
|
+
#elif defined(HAVE_RB_THREAD_BLOCKING_REGION)
|
28
|
+
// 1.9
|
29
|
+
typedef VALUE (*my_blocking_fn_t)(void*);
|
30
|
+
#define WITHOUT_GVL(fn,a,ubf,b) rb_thread_blocking_region((my_blocking_fn_t)(fn),(a),(ubf),(b))
|
31
|
+
#endif
|
32
|
+
|
33
|
+
// Time to wait for timed ops to complete
|
34
|
+
#define INTERNAL_TIMEOUT 5 /* seconds */
|
35
|
+
|
36
|
+
// Time to wait while checking if semaphore has been initialized
|
37
|
+
#define INIT_WAIT 10 /* microseconds */
|
38
|
+
|
39
|
+
// Helper definition to prevent magic number for conversion of microseconds to seconds
|
40
|
+
#define MICROSECONDS_IN_SECOND 1000000
|
41
|
+
|
42
|
+
// Here we define an enum value and string representation of each semaphore
|
43
|
+
// This allows us to key the sem value and string rep in sync easily
|
44
|
+
// utilizing pre-processor macros.
|
45
|
+
// If you're unfamiliar with this pattern, this is using "x macros"
|
46
|
+
// SI_SEM_LOCK metadata lock to act as a mutex, ensuring thread-safety for updating other semaphores
|
47
|
+
// SI_SEM_TICKETS semaphore for the tickets currently issued
|
48
|
+
// SI_SEM_CONFIGURED_TICKETS semaphore to track the desired number of tickets available for issue
|
49
|
+
// SI_SEM_REGISTERED_WORKERS semaphore for the number of workers currently registered
|
50
|
+
// SI_NUM_SEMAPHORES always leave this as last entry for count to be accurate
|
51
|
+
#define FOREACH_SEMINDEX(SEMINDEX) \
|
52
|
+
SEMINDEX(SI_SEM_LOCK) \
|
53
|
+
SEMINDEX(SI_SEM_TICKETS) \
|
54
|
+
SEMINDEX(SI_SEM_CONFIGURED_TICKETS) \
|
55
|
+
SEMINDEX(SI_SEM_REGISTERED_WORKERS) \
|
56
|
+
SEMINDEX(SI_NUM_SEMAPHORES) \
|
57
|
+
|
58
|
+
#define GENERATE_ENUM(ENUM) ENUM,
|
59
|
+
#define GENERATE_STRING(STRING) #STRING,
|
60
|
+
|
61
|
+
// Generate enum for sem indices
|
62
|
+
enum SEMINDEX_ENUM {
|
63
|
+
FOREACH_SEMINDEX(GENERATE_ENUM)
|
64
|
+
};
|
65
|
+
|
66
|
+
extern VALUE eSyscall, eTimeout, eInternal;
|
67
|
+
|
68
|
+
// Helper for syscall verbose debugging
|
69
|
+
void
|
70
|
+
raise_semian_syscall_error(const char *syscall, int error_num);
|
71
|
+
|
72
|
+
// Initialize the sysv semaphore structure
|
73
|
+
void
|
74
|
+
initialize_semaphore_set(semian_resource_t* res, const char* id_str, long permissions, int tickets, double quota);
|
75
|
+
|
76
|
+
// Set semaphore UNIX octal permissions
|
77
|
+
void
|
78
|
+
set_semaphore_permissions(int sem_id, long permissions);
|
79
|
+
|
80
|
+
// Create a new sysV IPC semaphore set
|
81
|
+
int
|
82
|
+
create_semaphore(int key, long permissions, int *created);
|
83
|
+
|
84
|
+
// Wrapper to performs a semop call
|
85
|
+
// The call may be timed or untimed
|
86
|
+
int
|
87
|
+
perform_semop(int sem_id, short index, short op, short flags, struct timespec *ts);
|
88
|
+
|
89
|
+
// Retrieve the current number of tickets in a semaphore by its semaphore index
|
90
|
+
int
|
91
|
+
get_sem_val(int sem_id, int sem_index);
|
92
|
+
|
93
|
+
// Obtain an exclusive lock on the semaphore set critical section
|
94
|
+
void
|
95
|
+
sem_meta_lock(int sem_id);
|
96
|
+
|
97
|
+
// Release an exclusive lock on the semaphore set critical section
|
98
|
+
void
|
99
|
+
sem_meta_unlock(int sem_id);
|
100
|
+
|
101
|
+
// Retrieve a semaphore's ID from its key
|
102
|
+
int
|
103
|
+
get_semaphore(int key);
|
104
|
+
|
105
|
+
// Decrements the ticket semaphore within the semaphore set
|
106
|
+
void *
|
107
|
+
acquire_semaphore_without_gvl(void *p);
|
108
|
+
|
109
|
+
#ifdef DEBUG
|
110
|
+
static inline void
|
111
|
+
print_sem_vals(int sem_id)
|
112
|
+
{
|
113
|
+
printf("lock %d, tickets: %d configured: %d, registered workers %d\n",
|
114
|
+
get_sem_val(sem_id, SI_SEM_LOCK),
|
115
|
+
get_sem_val(sem_id, SI_SEM_TICKETS),
|
116
|
+
get_sem_val(sem_id, SI_SEM_CONFIGURED_TICKETS),
|
117
|
+
get_sem_val(sem_id, SI_SEM_REGISTERED_WORKERS)
|
118
|
+
);
|
119
|
+
}
|
120
|
+
#endif
|
121
|
+
|
122
|
+
#endif // SEMIAN_SEMSET_H
|
@@ -0,0 +1,76 @@
|
|
1
|
+
#include "tickets.h"
|
2
|
+
|
3
|
+
// Update the ticket count for static ticket tracking
|
4
|
+
static VALUE
|
5
|
+
update_ticket_count(int sem_id, int count);
|
6
|
+
|
7
|
+
static int
|
8
|
+
calculate_quota_tickets(int sem_id, double quota);
|
9
|
+
|
10
|
+
// Must be called with the semaphore meta lock already acquired
|
11
|
+
VALUE
|
12
|
+
configure_tickets(VALUE value)
|
13
|
+
{
|
14
|
+
configure_tickets_args_t *args = (configure_tickets_args_t *)value;
|
15
|
+
|
16
|
+
if (args->quota > 0) {
|
17
|
+
args->tickets = calculate_quota_tickets(args->sem_id, args->quota);
|
18
|
+
}
|
19
|
+
|
20
|
+
/*
|
21
|
+
A manually specified ticket count of 0 is special, meaning "don't set"
|
22
|
+
We need to throw an error if we set it to 0 during initialization.
|
23
|
+
Otherwise, we back out of here completely.
|
24
|
+
*/
|
25
|
+
if (get_sem_val(args->sem_id, SI_SEM_CONFIGURED_TICKETS) == 0 && args->tickets == 0) {
|
26
|
+
rb_raise(eSyscall, "More than 0 tickets must be specified when initializing semaphore");
|
27
|
+
} else if (args->tickets == 0) {
|
28
|
+
return Qnil;
|
29
|
+
}
|
30
|
+
|
31
|
+
/*
|
32
|
+
If the current configured ticket count is not the same as the requested ticket
|
33
|
+
count, we need to resize the count. We do this by adding the delta of
|
34
|
+
(tickets - current_configured_tickets) to the semaphore value.
|
35
|
+
*/
|
36
|
+
if (get_sem_val(args->sem_id, SI_SEM_CONFIGURED_TICKETS) != args->tickets) {
|
37
|
+
update_ticket_count(args->sem_id, args->tickets);
|
38
|
+
}
|
39
|
+
|
40
|
+
return Qnil;
|
41
|
+
}
|
42
|
+
|
43
|
+
static VALUE
|
44
|
+
update_ticket_count(int sem_id, int tickets)
|
45
|
+
{
|
46
|
+
short delta;
|
47
|
+
struct timespec ts = { 0 };
|
48
|
+
ts.tv_sec = INTERNAL_TIMEOUT;
|
49
|
+
|
50
|
+
delta = tickets - get_sem_val(sem_id, SI_SEM_CONFIGURED_TICKETS);
|
51
|
+
|
52
|
+
#ifdef DEBUG
|
53
|
+
print_sem_vals(sem_id);
|
54
|
+
#endif
|
55
|
+
if (perform_semop(sem_id, SI_SEM_TICKETS, delta, 0, &ts) == -1) {
|
56
|
+
if (delta < 0 && errno == EAGAIN) {
|
57
|
+
rb_raise(eTimeout, "timeout while trying to update ticket count");
|
58
|
+
} else {
|
59
|
+
rb_raise(eInternal, "error setting ticket count, errno: %d (%s)", errno, strerror(errno));
|
60
|
+
}
|
61
|
+
}
|
62
|
+
|
63
|
+
if (semctl(sem_id, SI_SEM_CONFIGURED_TICKETS, SETVAL, tickets) == -1) {
|
64
|
+
rb_raise(eInternal, "error configuring ticket count, errno: %d (%s)", errno, strerror(errno));
|
65
|
+
}
|
66
|
+
|
67
|
+
return Qnil;
|
68
|
+
}
|
69
|
+
|
70
|
+
static int
|
71
|
+
calculate_quota_tickets (int sem_id, double quota)
|
72
|
+
{
|
73
|
+
int tickets = 0;
|
74
|
+
tickets = (int) ceil(get_sem_val(sem_id, SI_SEM_REGISTERED_WORKERS) * quota);
|
75
|
+
return tickets;
|
76
|
+
}
|
@@ -0,0 +1,13 @@
|
|
1
|
+
/*
|
2
|
+
For logic specific to manipulating semian ticket counts
|
3
|
+
*/
|
4
|
+
#ifndef SEMIAN_TICKETS_H
|
5
|
+
#define SEMIAN_TICKETS_H
|
6
|
+
|
7
|
+
#include "sysv_semaphores.h"
|
8
|
+
|
9
|
+
// Set initial ticket values upon resource creation
|
10
|
+
VALUE
|
11
|
+
configure_tickets(VALUE);
|
12
|
+
|
13
|
+
#endif // SEMIAN_TICKETS_H
|
data/ext/semian/types.h
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
/*
|
2
|
+
For custom type definitions specific to semian
|
3
|
+
*/
|
4
|
+
#ifndef SEMIAN_TYPES_H
|
5
|
+
#define SEMIAN_TYPES_H
|
6
|
+
|
7
|
+
#include <stdint.h>
|
8
|
+
#include <sys/types.h>
|
9
|
+
#include <sys/ipc.h>
|
10
|
+
#include <sys/sem.h>
|
11
|
+
#include <sys/time.h>
|
12
|
+
|
13
|
+
// For sysV semop syscals
|
14
|
+
// see man semop
|
15
|
+
union semun {
|
16
|
+
int val; /* Value for SETVAL */
|
17
|
+
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
|
18
|
+
unsigned short *array; /* Array for GETALL, SETALL */
|
19
|
+
struct seminfo *__buf; /* Buffer for IPC_INFO
|
20
|
+
(Linux-specific) */
|
21
|
+
};
|
22
|
+
|
23
|
+
typedef struct {
|
24
|
+
int sem_id;
|
25
|
+
int tickets;
|
26
|
+
double quota;
|
27
|
+
} configure_tickets_args_t;
|
28
|
+
|
29
|
+
// Internal semaphore structure
|
30
|
+
typedef struct {
|
31
|
+
int sem_id;
|
32
|
+
struct timespec timeout;
|
33
|
+
double quota;
|
34
|
+
int error;
|
35
|
+
uint64_t key;
|
36
|
+
char *strkey;
|
37
|
+
char *name;
|
38
|
+
long wait_time;
|
39
|
+
} semian_resource_t;
|
40
|
+
|
41
|
+
#endif // SEMIAN_TYPES_H
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'semian'
|
2
|
+
|
3
|
+
module Semian
|
4
|
+
module Adapter
|
5
|
+
def semian_identifier
|
6
|
+
raise NotImplementedError.new("Semian adapters must implement a `semian_identifier` method")
|
7
|
+
end
|
8
|
+
|
9
|
+
def semian_resource
|
10
|
+
@semian_resource ||= case semian_options
|
11
|
+
when false
|
12
|
+
UnprotectedResource.new(semian_identifier)
|
13
|
+
when nil
|
14
|
+
Semian.logger.info("Semian is not configured for #{self.class.name}: #{semian_identifier}")
|
15
|
+
UnprotectedResource.new(semian_identifier)
|
16
|
+
else
|
17
|
+
options = semian_options.dup
|
18
|
+
options.delete(:name)
|
19
|
+
options[:consumer] = self
|
20
|
+
options[:exceptions] ||= []
|
21
|
+
options[:exceptions] += resource_exceptions
|
22
|
+
::Semian.retrieve_or_register(semian_identifier, **options)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def clear_semian_resource
|
27
|
+
@semian_resource = nil
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def acquire_semian_resource(scope:, adapter:, &block)
|
33
|
+
return yield if resource_already_acquired?
|
34
|
+
semian_resource.acquire(scope: scope, adapter: adapter, resource: self) do
|
35
|
+
mark_resource_as_acquired(&block)
|
36
|
+
end
|
37
|
+
rescue ::Semian::OpenCircuitError => error
|
38
|
+
last_error = semian_resource.circuit_breaker.last_error
|
39
|
+
message = "#{error.message} caused by #{last_error.message}"
|
40
|
+
last_error = nil unless last_error.is_a?(Exception) # Net::HTTPServerError is not an exception
|
41
|
+
raise self.class::CircuitOpenError.new(semian_identifier, message), cause: last_error
|
42
|
+
rescue ::Semian::BaseError => error
|
43
|
+
raise self.class::ResourceBusyError.new(semian_identifier, error.message)
|
44
|
+
rescue *resource_exceptions => error
|
45
|
+
error.semian_identifier = semian_identifier if error.respond_to?(:semian_identifier=)
|
46
|
+
raise
|
47
|
+
end
|
48
|
+
|
49
|
+
def semian_options
|
50
|
+
return @semian_options if defined? @semian_options
|
51
|
+
options = raw_semian_options
|
52
|
+
@semian_options = options && options.map { |k, v| [k.to_sym, v] }.to_h
|
53
|
+
end
|
54
|
+
|
55
|
+
def raw_semian_options
|
56
|
+
raise NotImplementedError.new("Semian adapters must implement a `raw_semian_options` method")
|
57
|
+
end
|
58
|
+
|
59
|
+
def resource_exceptions
|
60
|
+
[]
|
61
|
+
end
|
62
|
+
|
63
|
+
def resource_already_acquired?
|
64
|
+
@resource_acquired
|
65
|
+
end
|
66
|
+
|
67
|
+
def mark_resource_as_acquired
|
68
|
+
previous = @resource_acquired
|
69
|
+
@resource_acquired = true
|
70
|
+
yield
|
71
|
+
ensure
|
72
|
+
@resource_acquired = previous
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|