semian 0.6.2 → 0.7.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5952a089360c3f99f037dd49e4bfdec80e010d3b
4
- data.tar.gz: e1862220b675cba619fdddbdde1cccce6c24cd87
3
+ metadata.gz: '084b4a21e0290cba65079b0490496da902f49b4c'
4
+ data.tar.gz: 07b2fda101604725efad2e5b9c6d478af5792b6b
5
5
  SHA512:
6
- metadata.gz: 4d6024b8df7dd42b1a6eb517cb8619c8138f20074529a0ffb1f7181dd5d81f9cdeeb9e2b7752ca7d234f400de2880a1d84ff592affc8f92806e2ce15f6d72f28
7
- data.tar.gz: c7f64858fb8fc83a51b6f43a729cdc2dab9f24633c2a9016b50f3be945921de2893f9bea0eec4ae8419577eb715f108e70bae45ab44134be96b7161e75b6743b
6
+ metadata.gz: 3c351bf24c90d6ccd7b4b15e055451543d218608f663dc229b99fd533a06f731606ecabf93a101f206938b7f39113d447dbb35b234a5ba6b31d603154b1e3c06
7
+ data.tar.gz: cc23e70d1c25cb10ed075a35b1f184caf07d5c5939e98ad5412cfd4da5a36bb5364811e46e020fbe2a56d93036cf3c0606e14d975519aca3bc391d5d6195a43f
@@ -25,7 +25,7 @@ have_func 'rb_thread_call_without_gvl'
25
25
 
26
26
  $CFLAGS = "-D_GNU_SOURCE -Werror -Wall "
27
27
  if ENV.key?('DEBUG')
28
- $CFLAGS << "-O0 -g"
28
+ $CFLAGS << "-O0 -g -DDEBUG"
29
29
  else
30
30
  $CFLAGS << "-O3"
31
31
  end
@@ -3,6 +3,12 @@
3
3
  static VALUE
4
4
  cleanup_semian_resource_acquire(VALUE self);
5
5
 
6
+ static void
7
+ check_tickets_xor_quota_arg(VALUE tickets, VALUE quota);
8
+
9
+ static double
10
+ check_quota_arg(VALUE quota);
11
+
6
12
  static int
7
13
  check_tickets_arg(VALUE tickets);
8
14
 
@@ -48,7 +54,7 @@ semian_resource_acquire(int argc, VALUE *argv, VALUE self)
48
54
  }
49
55
 
50
56
  /* release the GVL to acquire the semaphore */
51
- WITHOUT_GVL(acquire_semaphore_without_gvl, &res, RUBY_UBF_IO, NULL); // FIXME - replace with wrapped version
57
+ acquire_semaphore_without_gvl(&res);
52
58
  if (res.error != 0) {
53
59
  if (res.error == EAGAIN) {
54
60
  rb_raise(eTimeout, "timed out waiting for resource '%s'", res.name);
@@ -63,9 +69,20 @@ semian_resource_acquire(int argc, VALUE *argv, VALUE self)
63
69
  VALUE
64
70
  semian_resource_destroy(VALUE self)
65
71
  {
72
+ struct timespec ts = { 0 };
73
+ ts.tv_sec = INTERNAL_TIMEOUT;
74
+
66
75
  semian_resource_t *res = NULL;
67
76
 
68
77
  TypedData_Get_Struct(self, semian_resource_t, &semian_resource_type, res);
78
+
79
+ // Prevent a race to deletion
80
+ if (perform_semop(res->sem_id, SI_SEM_LOCK, -1, 0, &ts) == -1) {
81
+ if (errno == EINVAL || errno == EIDRM) {
82
+ return Qtrue;
83
+ }
84
+ }
85
+
69
86
  if (semctl(res->sem_id, SI_NUM_SEMAPHORES, IPC_RMID) == -1) {
70
87
  raise_semian_syscall_error("semctl()", errno);
71
88
  }
@@ -73,6 +90,49 @@ semian_resource_destroy(VALUE self)
73
90
  return Qtrue;
74
91
  }
75
92
 
93
+ VALUE
94
+ semian_resource_reset_workers(VALUE self)
95
+ {
96
+ int ret;
97
+ semian_resource_t *res = NULL;
98
+
99
+ TypedData_Get_Struct(self, semian_resource_t, &semian_resource_type, res);
100
+
101
+ sem_meta_lock(res->sem_id);
102
+ // This SETVAL will purge the SEM_UNDO table
103
+ ret = semctl(res->sem_id, SI_SEM_REGISTERED_WORKERS, SETVAL, 0);
104
+ sem_meta_unlock(res->sem_id);
105
+
106
+ if (ret == -1) {
107
+ raise_semian_syscall_error("semctl()", errno);
108
+ }
109
+
110
+ return Qtrue;
111
+ }
112
+
113
+ VALUE
114
+ semian_resource_unregister_worker(VALUE self)
115
+ {
116
+ int ret;
117
+ semian_resource_t *res = NULL;
118
+
119
+ TypedData_Get_Struct(self, semian_resource_t, &semian_resource_type, res);
120
+
121
+ sem_meta_lock(res->sem_id);
122
+ ret = perform_semop(res->sem_id, SI_SEM_REGISTERED_WORKERS, -1, IPC_NOWAIT | SEM_UNDO, NULL);
123
+ sem_meta_unlock(res->sem_id);
124
+
125
+ if ( ret == -1) {
126
+ // Allow EAGAIN with IPC_NOWAIT, as this signals that all workers were unregistered
127
+ // Otherwise, we might block forever or throw an unintended timeout
128
+ if (errno != EAGAIN) {
129
+ rb_raise(eInternal, "error decreasing registered workers, errno: %d (%s)", errno, strerror(errno));
130
+ }
131
+ }
132
+
133
+ return Qtrue;
134
+ }
135
+
76
136
  VALUE
77
137
  semian_resource_count(VALUE self)
78
138
  {
@@ -88,6 +148,36 @@ semian_resource_count(VALUE self)
88
148
  return LONG2FIX(ret);
89
149
  }
90
150
 
151
+ VALUE
152
+ semian_resource_tickets(VALUE self)
153
+ {
154
+ int ret;
155
+ semian_resource_t *res = NULL;
156
+
157
+ TypedData_Get_Struct(self, semian_resource_t, &semian_resource_type, res);
158
+ ret = semctl(res->sem_id, SI_SEM_CONFIGURED_TICKETS, GETVAL);
159
+ if (ret == -1) {
160
+ raise_semian_syscall_error("semctl()", errno);
161
+ }
162
+
163
+ return LONG2FIX(ret);
164
+ }
165
+
166
+ VALUE
167
+ semian_resource_workers(VALUE self)
168
+ {
169
+ int ret;
170
+ semian_resource_t *res = NULL;
171
+
172
+ TypedData_Get_Struct(self, semian_resource_t, &semian_resource_type, res);
173
+ ret = semctl(res->sem_id, SI_SEM_REGISTERED_WORKERS, GETVAL);
174
+ if (ret == -1) {
175
+ raise_semian_syscall_error("semctl()", errno);
176
+ }
177
+
178
+ return LONG2FIX(ret);
179
+ }
180
+
91
181
  VALUE
92
182
  semian_resource_id(VALUE self)
93
183
  {
@@ -97,17 +187,26 @@ semian_resource_id(VALUE self)
97
187
  }
98
188
 
99
189
  VALUE
100
- semian_resource_initialize(VALUE self, VALUE id, VALUE tickets, VALUE permissions, VALUE default_timeout)
190
+ semian_resource_key(VALUE self)
101
191
  {
102
- key_t key;
103
- int c_permissions;
192
+ semian_resource_t *res = NULL;
193
+ TypedData_Get_Struct(self, semian_resource_t, &semian_resource_type, res);
194
+ return rb_str_new_cstr(res->strkey);
195
+ }
196
+
197
+ VALUE
198
+ semian_resource_initialize(VALUE self, VALUE id, VALUE tickets, VALUE quota, VALUE permissions, VALUE default_timeout)
199
+ {
200
+ long c_permissions;
104
201
  double c_timeout;
202
+ double c_quota;
105
203
  int c_tickets;
106
- int created = 0;
107
204
  semian_resource_t *res = NULL;
108
205
  const char *c_id_str = NULL;
109
206
 
110
207
  // Check and cast arguments
208
+ check_tickets_xor_quota_arg(tickets, quota);
209
+ c_quota = check_quota_arg(quota);
111
210
  c_tickets = check_tickets_arg(tickets);
112
211
  c_permissions = check_permissions_arg(permissions);
113
212
  c_id_str = check_id_arg(id);
@@ -119,19 +218,10 @@ semian_resource_initialize(VALUE self, VALUE id, VALUE tickets, VALUE permission
119
218
  // Populate struct fields
120
219
  ms_to_timespec(c_timeout * 1000, &res->timeout);
121
220
  res->name = strdup(c_id_str);
221
+ res->quota = c_quota;
122
222
 
123
- // Get or create semaphore set
124
- // note that tickets = 0 will be used to acquire a semaphore set after it's been created elswhere
125
- key = generate_key(c_id_str);
126
- res->sem_id = c_tickets == 0 ? get_semaphore(key) : create_semaphore(key, c_permissions, &created);
127
- if (res->sem_id == -1) {
128
- raise_semian_syscall_error("semget()", errno);
129
- }
130
-
131
- set_semaphore_permissions(res->sem_id, c_permissions);
132
-
133
- // Configure semaphore ticket counts
134
- configure_tickets(res->sem_id, c_tickets, created);
223
+ // Initialize the semaphore set
224
+ initialize_semaphore_set(res, c_id_str, c_permissions, c_tickets, c_quota);
135
225
 
136
226
  return self;
137
227
  }
@@ -162,6 +252,33 @@ check_permissions_arg(VALUE permissions)
162
252
  return FIX2LONG(permissions);
163
253
  }
164
254
 
255
+ static void
256
+ check_tickets_xor_quota_arg(VALUE tickets, VALUE quota)
257
+ {
258
+ if ((TYPE(tickets) == T_NIL && TYPE(quota) == T_NIL) ||(TYPE(tickets) != T_NIL && TYPE(quota) != T_NIL)){
259
+ rb_raise(rb_eArgError, "Must pass exactly one of ticket or quota");
260
+ }
261
+ }
262
+
263
+ static double
264
+ check_quota_arg(VALUE quota)
265
+ {
266
+ double c_quota;
267
+
268
+ if (TYPE(quota) != T_NIL) {
269
+ if (TYPE(quota) != T_FIXNUM && TYPE(quota) != T_FLOAT) {
270
+ rb_raise(rb_eTypeError, "expected decimal type for quota");
271
+ }
272
+ if (NUM2DBL(quota) <= 0 || NUM2DBL(quota) > 1) {
273
+ rb_raise(rb_eArgError, "quota must be a decimal between 0 and 1");
274
+ }
275
+ c_quota = NUM2DBL(quota);
276
+ } else {
277
+ c_quota = -1.0f;
278
+ }
279
+ return c_quota;
280
+ }
281
+
165
282
  static int
166
283
  check_tickets_arg(VALUE tickets)
167
284
  {
@@ -7,7 +7,6 @@ Functions here are associated with rubyland operations.
7
7
  #define SEMIAN_RESOURCE_H
8
8
 
9
9
  #include "types.h"
10
- #include "tickets.h"
11
10
  #include "sysv_semaphores.h"
12
11
 
13
12
  // Ruby variables
@@ -16,12 +15,12 @@ int system_max_semaphore_count;
16
15
 
17
16
  /*
18
17
  * call-seq:
19
- * Semian::Resource.new(id, tickets, permissions, default_timeout) -> resource
18
+ * Semian::Resource.new(id, tickets, quota, permissions, default_timeout) -> resource
20
19
  *
21
20
  * Creates a new Resource. Do not create resources directly. Use Semian.register.
22
21
  */
23
22
  VALUE
24
- semian_resource_initialize(VALUE self, VALUE id, VALUE tickets, VALUE permissions, VALUE default_timeout);
23
+ semian_resource_initialize(VALUE self, VALUE id, VALUE tickets, VALUE quota, VALUE permissions, VALUE default_timeout);
25
24
 
26
25
  /*
27
26
  * call-seq:
@@ -50,6 +49,18 @@ semian_resource_acquire(int argc, VALUE *argv, VALUE self);
50
49
  VALUE
51
50
  semian_resource_destroy(VALUE self);
52
51
 
52
+ /*
53
+ * call-seq:
54
+ * resource.reset_registered_workers!() -> true
55
+ *
56
+ * Unregisters all registered workers for a resource, forcefully setting the worker count back to 0.
57
+ * This will purge the SEM_UNDO table.
58
+ *
59
+ * Use this method very carefully.
60
+ */
61
+ VALUE
62
+ semian_resource_reset_workers(VALUE self);
63
+
53
64
  /*
54
65
  * call-seq:
55
66
  * resource.count -> count
@@ -59,15 +70,57 @@ semian_resource_destroy(VALUE self);
59
70
  VALUE
60
71
  semian_resource_count(VALUE self);
61
72
 
73
+ /*
74
+ * call-seq:
75
+ * resource.tickets -> count
76
+ *
77
+ * Returns the configured number of tickets for a resource.
78
+ */
79
+ VALUE
80
+ semian_resource_tickets(VALUE self);
81
+
82
+ /*
83
+ * call-seq:
84
+ * resource.registered_workers -> count
85
+ *
86
+ * Returns the number of workers (processes) registered to use the resource
87
+ */
88
+ VALUE
89
+ semian_resource_workers(VALUE self);
90
+
62
91
  /*
63
92
  * call-seq:
64
93
  * resource.semid -> id
65
94
  *
66
95
  * Returns the SysV semaphore id of a resource.
96
+ * Note: This value varies from system to system, and between
97
+ * instances of semian.
67
98
  */
68
99
  VALUE
69
100
  semian_resource_id(VALUE self);
70
101
 
102
+ /*
103
+ * call-seq:
104
+ * resource.key -> id
105
+ *
106
+ * Returns the hex string representation of SysV semaphore key of a resource.
107
+ * Note: This is a unique identifier that is portable across system
108
+ * and instances of semian.
109
+ */
110
+ VALUE
111
+ semian_resource_key(VALUE self);
112
+
113
+ /*
114
+ * call-seq:
115
+ * resource.unregister_worker() -> true
116
+ *
117
+ * Unregisters a worker, which will affect quota calculations.
118
+ *
119
+ * Be careful to call this only once per process.
120
+ */
121
+ VALUE
122
+ semian_resource_unregister_worker(VALUE self);
123
+
71
124
  // Allocate a semian_resource_type struct for ruby memory management
72
125
  VALUE
73
126
  semian_resource_alloc(VALUE klass);
@@ -42,11 +42,16 @@ void Init_semian()
42
42
  eInternal = rb_const_get(cSemian, rb_intern("InternalError"));
43
43
 
44
44
  rb_define_alloc_func(cResource, semian_resource_alloc);
45
- rb_define_method(cResource, "initialize_semaphore", semian_resource_initialize, 4);
45
+ rb_define_method(cResource, "initialize_semaphore", semian_resource_initialize, 5);
46
46
  rb_define_method(cResource, "acquire", semian_resource_acquire, -1);
47
47
  rb_define_method(cResource, "count", semian_resource_count, 0);
48
48
  rb_define_method(cResource, "semid", semian_resource_id, 0);
49
+ rb_define_method(cResource, "key", semian_resource_key, 0);
50
+ rb_define_method(cResource, "tickets", semian_resource_tickets, 0);
51
+ rb_define_method(cResource, "registered_workers", semian_resource_workers, 0);
49
52
  rb_define_method(cResource, "destroy", semian_resource_destroy, 0);
53
+ rb_define_method(cResource, "reset_registered_workers!", semian_resource_reset_workers, 0);
54
+ rb_define_method(cResource, "unregister_worker", semian_resource_unregister_worker, 0);
50
55
 
51
56
  id_timeout = rb_intern("timeout");
52
57
 
@@ -1,4 +1,21 @@
1
- #include <sysv_semaphores.h>
1
+ #include "sysv_semaphores.h"
2
+
3
+ static key_t
4
+ generate_key(const char *name);
5
+
6
+ static void *
7
+ acquire_semaphore(void *p);
8
+
9
+ static int
10
+ wait_for_new_semaphore_set(key_t key, long permissions);
11
+
12
+ static void
13
+ initialize_new_semaphore_values(int sem_id, long permissions);
14
+
15
+ // Generate string rep for sem indices for debugging puproses
16
+ static const char *SEMINDEX_STRING[] = {
17
+ FOREACH_SEMINDEX(GENERATE_STRING)
18
+ };
2
19
 
3
20
  void
4
21
  raise_semian_syscall_error(const char *syscall, int error_num)
@@ -6,28 +23,45 @@ raise_semian_syscall_error(const char *syscall, int error_num)
6
23
  rb_raise(eSyscall, "%s failed, errno: %d (%s)", syscall, error_num, strerror(error_num));
7
24
  }
8
25
 
9
- key_t
10
- generate_key(const char *name)
26
+ void
27
+ initialize_semaphore_set(semian_resource_t* res, const char* id_str, long permissions, int tickets, double quota)
11
28
  {
12
- char semset_size_key[20];
13
- char *uniq_id_str;
14
29
 
15
- // It is necessary for the cardinatily of the semaphore set to be part of the key
16
- // or else sem_get will complain that we have requested an incorrect number of sems
17
- // for the desired key, and have changed the number of semaphores for a given key
18
- sprintf(semset_size_key, "_NUM_SEMS_%d", SI_NUM_SEMAPHORES);
19
- uniq_id_str = malloc(strlen(name)+strlen(semset_size_key)+1);
20
- strcpy(uniq_id_str, name);
21
- strcat(uniq_id_str, semset_size_key);
30
+ res->key = generate_key(id_str);
31
+ res->strkey = (char*) malloc((2 /*for 0x*/+ sizeof(uint64_t) /*actual key*/+ 1 /*null*/) * sizeof(char));
32
+ sprintf(res->strkey, "0x%08x", (unsigned int) res->key);
33
+ res->sem_id = semget(res->key, SI_NUM_SEMAPHORES, IPC_CREAT | IPC_EXCL | permissions);
34
+
35
+ /*
36
+ This approach is based on http://man7.org/tlpi/code/online/dist/svsem/svsem_good_init.c.html
37
+ which avoids race conditions when initializing semaphore sets.
38
+ */
39
+ if (res->sem_id != -1) {
40
+ // Happy path - we are the first worker, initialize the semaphore set.
41
+ initialize_new_semaphore_values(res->sem_id, permissions);
42
+ } else {
43
+ // Something went wrong
44
+ if (errno != EEXIST) {
45
+ raise_semian_syscall_error("semget() failed to initialize semaphore values", errno);
46
+ } else {
47
+ // The semaphore set already exists, ensure it is initialized
48
+ res->sem_id = wait_for_new_semaphore_set(res->key, permissions);
49
+ }
50
+ }
22
51
 
23
- union {
24
- unsigned char str[SHA_DIGEST_LENGTH];
25
- key_t key;
26
- } digest;
27
- SHA1((const unsigned char *) uniq_id_str, strlen(uniq_id_str), digest.str);
28
- free(uniq_id_str);
29
- /* TODO: compile-time assertion that sizeof(key_t) > SHA_DIGEST_LENGTH */
30
- return digest.key;
52
+ set_semaphore_permissions(res->sem_id, permissions);
53
+
54
+ /*
55
+ Ensure that a worker for this process is registered.
56
+ Note that from ruby we ensure that at most one worker may be registered per process.
57
+ */
58
+ if (perform_semop(res->sem_id, SI_SEM_REGISTERED_WORKERS, 1, SEM_UNDO, NULL) == -1) {
59
+ rb_raise(eInternal, "error incrementing registered workers, errno: %d (%s)", errno, strerror(errno));
60
+ }
61
+
62
+ sem_meta_lock(res->sem_id); // Sets otime for the first time by acquiring the sem lock
63
+ configure_tickets(res->sem_id, tickets, quota);
64
+ sem_meta_unlock(res->sem_id);
31
65
  }
32
66
 
33
67
  void
@@ -45,26 +79,6 @@ set_semaphore_permissions(int sem_id, long permissions)
45
79
  }
46
80
  }
47
81
 
48
- int
49
- create_semaphore(int key, long permissions, int *created)
50
- {
51
- int semid = 0;
52
- int flags = 0;
53
-
54
- *created = 0;
55
- flags = IPC_EXCL | IPC_CREAT | permissions;
56
-
57
- semid = semget(key, SI_NUM_SEMAPHORES, flags);
58
- if (semid >= 0) {
59
- *created = 1;
60
- } else if (semid == -1 && errno == EEXIST) {
61
- flags &= ~IPC_EXCL;
62
- semid = semget(key, SI_NUM_SEMAPHORES, flags);
63
- }
64
- return semid;
65
- }
66
-
67
-
68
82
  int
69
83
  perform_semop(int sem_id, short index, short op, short flags, struct timespec *ts)
70
84
  {
@@ -82,11 +96,11 @@ perform_semop(int sem_id, short index, short op, short flags, struct timespec *t
82
96
  }
83
97
 
84
98
  int
85
- get_max_tickets(int sem_id)
99
+ get_sem_val(int sem_id, int sem_index)
86
100
  {
87
- int ret = semctl(sem_id, SI_SEM_CONFIGURED_TICKETS, GETVAL);
101
+ int ret = semctl(sem_id, sem_index, GETVAL);
88
102
  if (ret == -1) {
89
- rb_raise(eInternal, "error getting max ticket count, errno: %d (%s)", errno, strerror(errno));
103
+ rb_raise(eInternal, "error getting value of %s for sem %d, errno: %d (%s)", SEMINDEX_STRING[sem_index], sem_id, errno, strerror(errno));
90
104
  }
91
105
  return ret;
92
106
  }
@@ -118,11 +132,102 @@ get_semaphore(int key)
118
132
 
119
133
  void *
120
134
  acquire_semaphore_without_gvl(void *p)
135
+ {
136
+ WITHOUT_GVL(acquire_semaphore, p, RUBY_UBF_IO, NULL);
137
+ return NULL;
138
+ }
139
+
140
+ static void *
141
+ acquire_semaphore(void *p)
121
142
  {
122
143
  semian_resource_t *res = (semian_resource_t *) p;
123
144
  res->error = 0;
145
+ #ifdef DEBUG
146
+ print_sem_vals(res->sem_id);
147
+ #endif
124
148
  if (perform_semop(res->sem_id, SI_SEM_TICKETS, -1, SEM_UNDO, &res->timeout) == -1) {
125
149
  res->error = errno;
126
150
  }
127
151
  return NULL;
128
152
  }
153
+
154
+ static key_t
155
+ generate_key(const char *name)
156
+ {
157
+ char semset_size_key[20];
158
+ char *uniq_id_str;
159
+
160
+ // It is necessary for the cardinatily of the semaphore set to be part of the key
161
+ // or else sem_get will complain that we have requested an incorrect number of sems
162
+ // for the desired key, and have changed the number of semaphores for a given key
163
+ sprintf(semset_size_key, "_NUM_SEMS_%d", SI_NUM_SEMAPHORES);
164
+ uniq_id_str = malloc(strlen(name)+strlen(semset_size_key)+1);
165
+ strcpy(uniq_id_str, name);
166
+ strcat(uniq_id_str, semset_size_key);
167
+
168
+ union {
169
+ unsigned char str[SHA_DIGEST_LENGTH];
170
+ key_t key;
171
+ } digest;
172
+ SHA1((const unsigned char *) uniq_id_str, strlen(uniq_id_str), digest.str);
173
+ free(uniq_id_str);
174
+ /* TODO: compile-time assertion that sizeof(key_t) > SHA_DIGEST_LENGTH */
175
+ return digest.key;
176
+ }
177
+
178
+
179
+ static void
180
+ initialize_new_semaphore_values(int sem_id, long permissions)
181
+ {
182
+ unsigned short init_vals[SI_NUM_SEMAPHORES];
183
+
184
+ init_vals[SI_SEM_TICKETS] = init_vals[SI_SEM_CONFIGURED_TICKETS] = 0;
185
+ init_vals[SI_SEM_REGISTERED_WORKERS] = 0;
186
+ init_vals[SI_SEM_LOCK] = 1;
187
+
188
+ if (semctl(sem_id, 0, SETALL, init_vals) == -1) {
189
+ raise_semian_syscall_error("semctl()", errno);
190
+ }
191
+ #ifdef DEBUG
192
+ print_sem_vals(sem_id);
193
+ #endif
194
+ }
195
+
196
+ static int
197
+ wait_for_new_semaphore_set(key_t key, long permissions)
198
+ {
199
+ int i;
200
+ int sem_id = -1;
201
+ union semun sem_opts;
202
+ struct semid_ds sem_ds;
203
+
204
+ sem_opts.buf = &sem_ds;
205
+ sem_id = semget(key, 1, permissions);
206
+
207
+ if (sem_id == -1){
208
+ raise_semian_syscall_error("semget()", errno);
209
+ }
210
+
211
+ for (i = 0; i < ((INTERNAL_TIMEOUT * MICROSECONDS_IN_SECOND) / INIT_WAIT); i++) {
212
+
213
+ if (semctl(sem_id, 0, IPC_STAT, sem_opts) == -1) {
214
+ raise_semian_syscall_error("semget()", errno);
215
+ }
216
+
217
+ // If a semop has been performed by someone else, the values must be initialized
218
+ if (sem_ds.sem_otime != 0) {
219
+ break;
220
+ }
221
+
222
+ #ifdef DEBUG
223
+ printf("Waiting for another process to initialize semaphore values, checked: %d times\n", i);
224
+ #endif
225
+ usleep(INIT_WAIT);
226
+ }
227
+
228
+ if (sem_ds.sem_otime == 0) {
229
+ rb_raise(eTimeout, "error: timeout waiting for semaphore values to initialize after %d checks", INTERNAL_TIMEOUT);
230
+ }
231
+
232
+ return sem_id;
233
+ }