semian_extension 0.11.4.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 2fbd05996a9e15b84c54519a393a2ce1bf453d38552a7b22e80a831721f285f4
4
+ data.tar.gz: fcd1fa08aa00fd91a7cf0384bafff8c531de237995df5b7b252b2142335afd21
5
+ SHA512:
6
+ metadata.gz: e28e4d4566e503178e0540bf487addc96a7693fd125bbf8d2e1b51edc1d5249caa5b4911c5364e95e4539ee88140f3c20899c8f786f5a2fb1216dde77da6e8d1
7
+ data.tar.gz: 2e366ace3707305ed07f2f61461a7aed93909cafa42500161f3ca356da2f70239202fa27ee32a688e8b3216e72942461a07cdbedc1f7bf3961e0120465df350b
@@ -0,0 +1,33 @@
1
+ $LOAD_PATH.unshift File.expand_path("../../../lib", __FILE__)
2
+
3
+ require 'semian/platform'
4
+
5
+ unless Semian.sysv_semaphores_supported?
6
+ File.write "Makefile", <<MAKEFILE
7
+ all:
8
+ clean:
9
+ install:
10
+ MAKEFILE
11
+ exit
12
+ end
13
+
14
+ require 'mkmf'
15
+
16
+ abort 'openssl is missing. please install openssl.' unless find_header('openssl/sha.h')
17
+ abort 'openssl is missing. please install openssl.' unless find_library('crypto', 'SHA1')
18
+
19
+ have_header 'sys/ipc.h'
20
+ have_header 'sys/sem.h'
21
+ have_header 'sys/types.h'
22
+
23
+ have_func 'rb_thread_blocking_region'
24
+ have_func 'rb_thread_call_without_gvl'
25
+
26
+ $CFLAGS = "-D_GNU_SOURCE -Werror -Wall "
27
+ if ENV.key?('DEBUG')
28
+ $CFLAGS << "-O0 -g -DDEBUG"
29
+ else
30
+ $CFLAGS << "-O3"
31
+ end
32
+
33
+ create_makefile('semian/semian')
@@ -0,0 +1,394 @@
1
+ #include "resource.h"
2
+
3
+ // Ruby variables
4
+ ID id_wait_time;
5
+ ID id_timeout;
6
+ int system_max_semaphore_count;
7
+
8
+ static VALUE
9
+ cleanup_semian_resource_acquire(VALUE self);
10
+
11
+ static void
12
+ check_tickets_xor_quota_arg(VALUE tickets, VALUE quota);
13
+
14
+ static double
15
+ check_quota_arg(VALUE quota);
16
+
17
+ static int
18
+ check_tickets_arg(VALUE tickets);
19
+
20
+ static long
21
+ check_permissions_arg(VALUE permissions);
22
+
23
+ static const
24
+ char *check_id_arg(VALUE id);
25
+
26
+ static double
27
+ check_default_timeout_arg(VALUE default_timeout);
28
+
29
+ static void
30
+ ms_to_timespec(long ms, struct timespec *ts);
31
+
32
+ static const rb_data_type_t
33
+ semian_resource_type;
34
+
35
+ VALUE
36
+ semian_resource_acquire(int argc, VALUE *argv, VALUE self)
37
+ {
38
+ semian_resource_t *self_res = NULL;
39
+ semian_resource_t res = { 0 };
40
+
41
+ if (!rb_block_given_p()) {
42
+ rb_raise(rb_eArgError, "acquire requires a block");
43
+ }
44
+
45
+ TypedData_Get_Struct(self, semian_resource_t, &semian_resource_type, self_res);
46
+ res = *self_res;
47
+
48
+ /* allow the default timeout to be overridden by a "timeout" param */
49
+ if (argc == 1 && TYPE(argv[0]) == T_HASH) {
50
+ VALUE timeout = rb_hash_aref(argv[0], ID2SYM(id_timeout));
51
+ if (TYPE(timeout) != T_NIL) {
52
+ if (TYPE(timeout) != T_FLOAT && TYPE(timeout) != T_FIXNUM) {
53
+ rb_raise(rb_eArgError, "timeout parameter must be numeric");
54
+ }
55
+ ms_to_timespec(NUM2DBL(timeout) * 1000, &res.timeout);
56
+ }
57
+ } else if (argc > 0) {
58
+ rb_raise(rb_eArgError, "invalid arguments");
59
+ }
60
+
61
+ /* release the GVL to acquire the semaphore */
62
+ acquire_semaphore_without_gvl(&res);
63
+ if (res.error != 0) {
64
+ if (res.error == EAGAIN) {
65
+ rb_raise(eTimeout, "timed out waiting for resource '%s'", res.name);
66
+ } else {
67
+ raise_semian_syscall_error("semop()", res.error);
68
+ }
69
+ }
70
+
71
+ VALUE wait_time = Qnil;
72
+ if (res.wait_time >= 0) {
73
+ wait_time = LONG2NUM(res.wait_time);
74
+ }
75
+
76
+ return rb_ensure(rb_yield, wait_time, cleanup_semian_resource_acquire, self);
77
+ }
78
+
79
+ VALUE
80
+ semian_resource_destroy(VALUE self)
81
+ {
82
+ struct timespec ts = { 0 };
83
+ ts.tv_sec = INTERNAL_TIMEOUT;
84
+
85
+ semian_resource_t *res = NULL;
86
+
87
+ TypedData_Get_Struct(self, semian_resource_t, &semian_resource_type, res);
88
+
89
+ // Prevent a race to deletion
90
+ if (perform_semop(res->sem_id, SI_SEM_LOCK, -1, 0, &ts) == -1) {
91
+ if (errno == EINVAL || errno == EIDRM) {
92
+ return Qtrue;
93
+ }
94
+ }
95
+
96
+ if (semctl(res->sem_id, SI_NUM_SEMAPHORES, IPC_RMID) == -1) {
97
+ raise_semian_syscall_error("semctl()", errno);
98
+ }
99
+
100
+ return Qtrue;
101
+ }
102
+
103
+ VALUE
104
+ semian_resource_reset_workers(VALUE self)
105
+ {
106
+ int ret;
107
+ semian_resource_t *res = NULL;
108
+
109
+ TypedData_Get_Struct(self, semian_resource_t, &semian_resource_type, res);
110
+
111
+ sem_meta_lock(res->sem_id);
112
+ // This SETVAL will purge the SEM_UNDO table
113
+ ret = semctl(res->sem_id, SI_SEM_REGISTERED_WORKERS, SETVAL, 0);
114
+ sem_meta_unlock(res->sem_id);
115
+
116
+ if (ret == -1) {
117
+ raise_semian_syscall_error("semctl()", errno);
118
+ }
119
+
120
+ return Qtrue;
121
+ }
122
+
123
+ VALUE
124
+ semian_resource_unregister_worker(VALUE self)
125
+ {
126
+ int ret;
127
+ semian_resource_t *res = NULL;
128
+
129
+ TypedData_Get_Struct(self, semian_resource_t, &semian_resource_type, res);
130
+
131
+ sem_meta_lock(res->sem_id);
132
+ ret = perform_semop(res->sem_id, SI_SEM_REGISTERED_WORKERS, -1, IPC_NOWAIT | SEM_UNDO, NULL);
133
+ sem_meta_unlock(res->sem_id);
134
+
135
+ if ( ret == -1) {
136
+ // Allow EAGAIN with IPC_NOWAIT, as this signals that all workers were unregistered
137
+ // Otherwise, we might block forever or throw an unintended timeout
138
+ if (errno != EAGAIN) {
139
+ rb_raise(eInternal, "error decreasing registered workers, errno: %d (%s)", errno, strerror(errno));
140
+ }
141
+ }
142
+
143
+ return Qtrue;
144
+ }
145
+
146
+ VALUE
147
+ semian_resource_count(VALUE self)
148
+ {
149
+ int ret;
150
+ semian_resource_t *res = NULL;
151
+
152
+ TypedData_Get_Struct(self, semian_resource_t, &semian_resource_type, res);
153
+ ret = semctl(res->sem_id, SI_SEM_TICKETS, GETVAL);
154
+ if (ret == -1) {
155
+ raise_semian_syscall_error("semctl()", errno);
156
+ }
157
+
158
+ return LONG2FIX(ret);
159
+ }
160
+
161
+ VALUE
162
+ semian_resource_tickets(VALUE self)
163
+ {
164
+ int ret;
165
+ semian_resource_t *res = NULL;
166
+
167
+ TypedData_Get_Struct(self, semian_resource_t, &semian_resource_type, res);
168
+ ret = semctl(res->sem_id, SI_SEM_CONFIGURED_TICKETS, GETVAL);
169
+ if (ret == -1) {
170
+ raise_semian_syscall_error("semctl()", errno);
171
+ }
172
+
173
+ return LONG2FIX(ret);
174
+ }
175
+
176
+ VALUE
177
+ semian_resource_workers(VALUE self)
178
+ {
179
+ int ret;
180
+ semian_resource_t *res = NULL;
181
+
182
+ TypedData_Get_Struct(self, semian_resource_t, &semian_resource_type, res);
183
+ ret = semctl(res->sem_id, SI_SEM_REGISTERED_WORKERS, GETVAL);
184
+ if (ret == -1) {
185
+ raise_semian_syscall_error("semctl()", errno);
186
+ }
187
+
188
+ return LONG2FIX(ret);
189
+ }
190
+
191
+ VALUE
192
+ semian_resource_id(VALUE self)
193
+ {
194
+ semian_resource_t *res = NULL;
195
+ TypedData_Get_Struct(self, semian_resource_t, &semian_resource_type, res);
196
+ return LONG2FIX(res->sem_id);
197
+ }
198
+
199
+ VALUE
200
+ semian_resource_key(VALUE self)
201
+ {
202
+ semian_resource_t *res = NULL;
203
+ TypedData_Get_Struct(self, semian_resource_t, &semian_resource_type, res);
204
+ return rb_str_new_cstr(res->strkey);
205
+ }
206
+
207
+ VALUE
208
+ semian_resource_initialize(VALUE self, VALUE id, VALUE tickets, VALUE quota, VALUE permissions, VALUE default_timeout)
209
+ {
210
+ long c_permissions;
211
+ double c_timeout;
212
+ double c_quota;
213
+ int c_tickets;
214
+ semian_resource_t *res = NULL;
215
+ const char *c_id_str = NULL;
216
+
217
+ // Check and cast arguments
218
+ check_tickets_xor_quota_arg(tickets, quota);
219
+ c_quota = check_quota_arg(quota);
220
+ c_tickets = check_tickets_arg(tickets);
221
+ c_permissions = check_permissions_arg(permissions);
222
+ c_id_str = check_id_arg(id);
223
+ c_timeout = check_default_timeout_arg(default_timeout);
224
+
225
+ // Build semian resource structure
226
+ TypedData_Get_Struct(self, semian_resource_t, &semian_resource_type, res);
227
+
228
+ // Populate struct fields
229
+ ms_to_timespec(c_timeout * 1000, &res->timeout);
230
+ res->name = strdup(c_id_str);
231
+ res->quota = c_quota;
232
+ res->wait_time = -1;
233
+
234
+ // Initialize the semaphore set
235
+ initialize_semaphore_set(res, c_id_str, c_permissions, c_tickets, c_quota);
236
+
237
+ return self;
238
+ }
239
+
240
+ VALUE
241
+ semian_resource_alloc(VALUE klass)
242
+ {
243
+ semian_resource_t *res;
244
+ VALUE obj = TypedData_Make_Struct(klass, semian_resource_t, &semian_resource_type, res);
245
+ return obj;
246
+ }
247
+
248
+ VALUE
249
+ semian_resource_in_use(VALUE self)
250
+ {
251
+ return Qtrue;
252
+ }
253
+
254
+ static VALUE
255
+ cleanup_semian_resource_acquire(VALUE self)
256
+ {
257
+ semian_resource_t *res = NULL;
258
+ TypedData_Get_Struct(self, semian_resource_t, &semian_resource_type, res);
259
+ if (perform_semop(res->sem_id, SI_SEM_TICKETS, 1, SEM_UNDO, NULL) == -1) {
260
+ res->error = errno;
261
+ }
262
+ return Qnil;
263
+ }
264
+
265
+ static long
266
+ check_permissions_arg(VALUE permissions)
267
+ {
268
+ Check_Type(permissions, T_FIXNUM);
269
+ return FIX2LONG(permissions);
270
+ }
271
+
272
+ static void
273
+ check_tickets_xor_quota_arg(VALUE tickets, VALUE quota)
274
+ {
275
+ if (tickets == Qnil && quota == Qnil) {
276
+ rb_raise(rb_eArgError, "Semian configuration require either the :ticket or :quota parameter, you provided neither");
277
+ }
278
+ if (tickets != Qnil && quota != Qnil) {
279
+ rb_raise(rb_eArgError, "Semian configuration require either the :ticket or :quota parameter, you provided both");
280
+ }
281
+ }
282
+
283
+ static double
284
+ check_quota_arg(VALUE quota)
285
+ {
286
+ double c_quota;
287
+
288
+ if (TYPE(quota) != T_NIL) {
289
+ if (TYPE(quota) != T_FIXNUM && TYPE(quota) != T_FLOAT) {
290
+ rb_raise(rb_eTypeError, "expected decimal type for quota");
291
+ }
292
+ if (NUM2DBL(quota) <= 0 || NUM2DBL(quota) > 1) {
293
+ rb_raise(rb_eArgError, "quota must be a decimal between 0 and 1");
294
+ }
295
+ c_quota = NUM2DBL(quota);
296
+ } else {
297
+ c_quota = -1.0f;
298
+ }
299
+ return c_quota;
300
+ }
301
+
302
+ static int
303
+ check_tickets_arg(VALUE tickets)
304
+ {
305
+ int c_tickets;
306
+
307
+ if (TYPE(tickets) != T_NIL) {
308
+ if (TYPE(tickets) == T_FLOAT) {
309
+ rb_warn("semian ticket value %f is a float, converting to fixnum", RFLOAT_VALUE(tickets));
310
+ tickets = INT2FIX((int) RFLOAT_VALUE(tickets));
311
+ }
312
+ Check_Type(tickets, T_FIXNUM);
313
+
314
+ if (FIX2LONG(tickets) < 0 || FIX2LONG(tickets) > system_max_semaphore_count) {
315
+ rb_raise(rb_eArgError, "ticket count must be a non-negative value and less than %d", system_max_semaphore_count);
316
+ }
317
+ c_tickets = FIX2LONG(tickets);
318
+ } else {
319
+ c_tickets = -1;
320
+ }
321
+
322
+ return c_tickets;
323
+ }
324
+
325
+ static const char*
326
+ check_id_arg(VALUE id)
327
+ {
328
+ const char *c_id_str = NULL;
329
+
330
+ if (TYPE(id) != T_SYMBOL && TYPE(id) != T_STRING) {
331
+ rb_raise(rb_eTypeError, "id must be a symbol or string");
332
+ }
333
+ if (TYPE(id) == T_SYMBOL) {
334
+ c_id_str = rb_id2name(rb_to_id(id));
335
+ } else if (TYPE(id) == T_STRING) {
336
+ c_id_str = RSTRING_PTR(id);
337
+ }
338
+
339
+ return c_id_str;
340
+ }
341
+
342
+ static double
343
+ check_default_timeout_arg(VALUE default_timeout)
344
+ {
345
+ if (TYPE(default_timeout) != T_FIXNUM && TYPE(default_timeout) != T_FLOAT) {
346
+ rb_raise(rb_eTypeError, "expected numeric type for default_timeout");
347
+ }
348
+
349
+ if (NUM2DBL(default_timeout) < 0) {
350
+ rb_raise(rb_eArgError, "default timeout must be non-negative value");
351
+ }
352
+ return NUM2DBL(default_timeout);
353
+ }
354
+
355
+ static void
356
+ ms_to_timespec(long ms, struct timespec *ts)
357
+ {
358
+ ts->tv_sec = ms / 1000;
359
+ ts->tv_nsec = (ms % 1000) * 1000000;
360
+ }
361
+
362
+ static inline void
363
+ semian_resource_mark(void *ptr)
364
+ {
365
+ /* noop */
366
+ }
367
+
368
+ static inline void
369
+ semian_resource_free(void *ptr)
370
+ {
371
+ semian_resource_t *res = (semian_resource_t *) ptr;
372
+ if (res->name) {
373
+ free(res->name);
374
+ res->name = NULL;
375
+ }
376
+ xfree(res);
377
+ }
378
+
379
+ static inline size_t
380
+ semian_resource_memsize(const void *ptr)
381
+ {
382
+ return sizeof(semian_resource_t);
383
+ }
384
+
385
+ static const rb_data_type_t
386
+ semian_resource_type = {
387
+ "semian_resource",
388
+ {
389
+ semian_resource_mark,
390
+ semian_resource_free,
391
+ semian_resource_memsize
392
+ },
393
+ NULL, NULL, RUBY_TYPED_FREE_IMMEDIATELY
394
+ };
@@ -0,0 +1,133 @@
1
+ /*
2
+ For core semian resource functions exposed directly to ruby.
3
+
4
+ Functions here are associated with rubyland operations.
5
+ */
6
+ #ifndef SEMIAN_RESOURCE_H
7
+ #define SEMIAN_RESOURCE_H
8
+
9
+ #include "types.h"
10
+ #include "sysv_semaphores.h"
11
+
12
+ // Ruby variables
13
+ extern ID id_wait_time;
14
+ extern ID id_timeout;
15
+ extern int system_max_semaphore_count;
16
+
17
+ /*
18
+ * call-seq:
19
+ * Semian::Resource.new(id, tickets, quota, permissions, default_timeout) -> resource
20
+ *
21
+ * Creates a new Resource. Do not create resources directly. Use Semian.register.
22
+ */
23
+ VALUE
24
+ semian_resource_initialize(VALUE self, VALUE id, VALUE tickets, VALUE quota, VALUE permissions, VALUE default_timeout);
25
+
26
+ /*
27
+ * call-seq:
28
+ * resource.acquire(timeout: default_timeout) { ... } -> result of the block
29
+ *
30
+ * Acquires a resource. The call will block for <code>timeout</code> seconds if a ticket
31
+ * is not available. If no ticket is available within the timeout period, Semian::TimeoutError
32
+ * will be raised.
33
+ *
34
+ * If no timeout argument is provided, the default timeout passed to Semian.register will be used.
35
+ *
36
+ */
37
+ VALUE
38
+ semian_resource_acquire(int argc, VALUE *argv, VALUE self);
39
+
40
+ /*
41
+ * call-seq:
42
+ * resource.destroy() -> true
43
+ *
44
+ * Destroys a resource. This method will destroy the underlying SysV semaphore.
45
+ * If there is any code in other threads or processes blocking or using the resource
46
+ * they will likely raise.
47
+ *
48
+ * Use this method very carefully.
49
+ */
50
+ VALUE
51
+ semian_resource_destroy(VALUE self);
52
+
53
+ /*
54
+ * call-seq:
55
+ * resource.reset_registered_workers!() -> true
56
+ *
57
+ * Unregisters all registered workers for a resource, forcefully setting the worker count back to 0.
58
+ * This will purge the SEM_UNDO table.
59
+ *
60
+ * Use this method very carefully.
61
+ */
62
+ VALUE
63
+ semian_resource_reset_workers(VALUE self);
64
+
65
+ /*
66
+ * call-seq:
67
+ * resource.count -> count
68
+ *
69
+ * Returns the current ticket count for a resource.
70
+ */
71
+ VALUE
72
+ semian_resource_count(VALUE self);
73
+
74
+ /*
75
+ * call-seq:
76
+ * resource.tickets -> count
77
+ *
78
+ * Returns the configured number of tickets for a resource.
79
+ */
80
+ VALUE
81
+ semian_resource_tickets(VALUE self);
82
+
83
+ /*
84
+ * call-seq:
85
+ * resource.registered_workers -> count
86
+ *
87
+ * Returns the number of workers (processes) registered to use the resource
88
+ */
89
+ VALUE
90
+ semian_resource_workers(VALUE self);
91
+
92
+ /*
93
+ * call-seq:
94
+ * resource.semid -> id
95
+ *
96
+ * Returns the SysV semaphore id of a resource.
97
+ * Note: This value varies from system to system, and between
98
+ * instances of semian.
99
+ */
100
+ VALUE
101
+ semian_resource_id(VALUE self);
102
+
103
+ /*
104
+ * call-seq:
105
+ * resource.key -> id
106
+ *
107
+ * Returns the hex string representation of SysV semaphore key of a resource.
108
+ * Note: This is a unique identifier that is portable across system
109
+ * and instances of semian.
110
+ */
111
+ VALUE
112
+ semian_resource_key(VALUE self);
113
+
114
+ /*
115
+ * call-seq:
116
+ * resource.unregister_worker() -> true
117
+ *
118
+ * Unregisters a worker, which will affect quota calculations.
119
+ *
120
+ * Be careful to call this only once per process.
121
+ */
122
+ VALUE
123
+ semian_resource_unregister_worker(VALUE self);
124
+
125
+ // Allocate a semian_resource_type struct for ruby memory management
126
+ VALUE
127
+ semian_resource_alloc(VALUE klass);
128
+
129
+ // Returns true if the resource is in use
130
+ VALUE
131
+ semian_resource_in_use(VALUE self);
132
+
133
+ #endif //SEMIAN_RESOURCE_H
@@ -0,0 +1,72 @@
1
+ #include "semian.h"
2
+
3
+ VALUE eSyscall, eTimeout, eInternal;
4
+
5
+ void Init_semian()
6
+ {
7
+ VALUE cSemian, cResource;
8
+ struct seminfo info_buf;
9
+
10
+ cSemian = rb_const_get(rb_cObject, rb_intern("Semian"));
11
+
12
+ /*
13
+ * Document-class: Semian::Resource
14
+ *
15
+ * Resource is the fundamental class of Semian. It is essentially a wrapper around a
16
+ * SystemV semaphore.
17
+ *
18
+ * You should not create this class directly, it will be created indirectly via Semian.register.
19
+ */
20
+ cResource = rb_const_get(cSemian, rb_intern("Resource"));
21
+
22
+ /* Document-class: Semian::SyscallError
23
+ *
24
+ * Represents a Semian error that was caused by an underlying syscall failure.
25
+ */
26
+ eSyscall = rb_const_get(cSemian, rb_intern("SyscallError"));
27
+ rb_global_variable(&eSyscall);
28
+
29
+ /* Document-class: Semian::TimeoutError
30
+ *
31
+ * Raised when a Semian operation timed out.
32
+ */
33
+ eTimeout = rb_const_get(cSemian, rb_intern("TimeoutError"));
34
+ rb_global_variable(&eTimeout);
35
+
36
+ /* Document-class: Semian::InternalError
37
+ *
38
+ * An internal Semian error. These errors should be typically never be raised. If
39
+ * they do, there's a high likelyhood that the underlying SysV semaphore set
40
+ * has been corrupted.
41
+ *
42
+ * If this happens, a strong course of action would be to delete the semaphores
43
+ * using the <code>ipcrm</code> command line tool. Semian will re-initialize
44
+ * the semaphore in this case.
45
+ */
46
+ eInternal = rb_const_get(cSemian, rb_intern("InternalError"));
47
+ rb_global_variable(&eInternal);
48
+
49
+ rb_define_alloc_func(cResource, semian_resource_alloc);
50
+ rb_define_method(cResource, "initialize_semaphore", semian_resource_initialize, 5);
51
+ rb_define_method(cResource, "acquire", semian_resource_acquire, -1);
52
+ rb_define_method(cResource, "count", semian_resource_count, 0);
53
+ rb_define_method(cResource, "semid", semian_resource_id, 0);
54
+ rb_define_method(cResource, "key", semian_resource_key, 0);
55
+ rb_define_method(cResource, "tickets", semian_resource_tickets, 0);
56
+ rb_define_method(cResource, "registered_workers", semian_resource_workers, 0);
57
+ rb_define_method(cResource, "destroy", semian_resource_destroy, 0);
58
+ rb_define_method(cResource, "reset_registered_workers!", semian_resource_reset_workers, 0);
59
+ rb_define_method(cResource, "unregister_worker", semian_resource_unregister_worker, 0);
60
+ rb_define_method(cResource, "in_use?", semian_resource_in_use, 0);
61
+
62
+ id_wait_time = rb_intern("wait_time");
63
+ id_timeout = rb_intern("timeout");
64
+
65
+ if (semctl(0, 0, SEM_INFO, &info_buf) == -1) {
66
+ rb_raise(eInternal, "unable to determine maximum semaphore count - semctl() returned %d: %s ", errno, strerror(errno));
67
+ }
68
+ system_max_semaphore_count = info_buf.semvmx;
69
+
70
+ /* Maximum number of tickets available on this system. */
71
+ rb_define_const(cSemian, "MAX_TICKETS", INT2FIX(system_max_semaphore_count));
72
+ }
@@ -0,0 +1,14 @@
1
+ /*
2
+ System, 3rd party, and project includes
3
+
4
+ Implements Init_semian, which is used as C/Ruby entrypoint.
5
+ */
6
+
7
+ #ifndef SEMIAN_H
8
+ #define SEMIAN_H
9
+
10
+ #include "resource.h"
11
+
12
+ void Init_semian();
13
+
14
+ #endif //SEMIAN_H