semian 0.21.3 → 0.22.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 +4 -4
- data/README.md +7 -7
- data/ext/semian/resource.c +18 -11
- data/ext/semian/resource.h +29 -0
- data/ext/semian/semian.c +14 -1
- data/ext/semian/sysv_semaphores.h +1 -1
- data/lib/semian/mysql2.rb +4 -1
- data/lib/semian/protected_resource.rb +1 -1
- data/lib/semian/redis.rb +2 -1
- data/lib/semian/redis_client.rb +9 -3
- data/lib/semian/resource.rb +23 -10
- data/lib/semian/version.rb +1 -1
- data/lib/semian.rb +17 -1
- metadata +7 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 27fa3d35b047fbe54483763821e246e2ce6f1403d8b3354d5759c4bdf1b43b2b
|
4
|
+
data.tar.gz: '06303901cc95e293dee76cd712283a4a7db1536771133e83563c969a6f3598e5'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 31e47dcea63ba9fc861272d9b8c2d591eb5f2d0783b1d8cde667812ba1cc4307ea52b533f8dc3542d011fb98c770ed011ff3a84c3e1ee7239635b01c0717b1ad
|
7
|
+
data.tar.gz: e32d7f17f6d73c95418fe6b80f1dce3423271423d61c8acd1fda356dab02aac41c24637d9a0939d39e5ade7cb015a2e47182361ea5c9187b1b39d93d59058510
|
data/README.md
CHANGED
@@ -270,23 +270,23 @@ SEMIAN_PARAMETERS = {
|
|
270
270
|
dynamic: true,
|
271
271
|
}
|
272
272
|
|
273
|
-
class CurrentSemianSubResource < ActiveSupport::
|
274
|
-
attribute :
|
273
|
+
class CurrentSemianSubResource < ActiveSupport::CurrentAttributes
|
274
|
+
attribute :sub_name
|
275
275
|
end
|
276
276
|
|
277
277
|
Semian::NetHTTP.semian_configuration = proc do |host, port|
|
278
278
|
name = "#{host}_#{port}"
|
279
|
-
if (sub_resource_name = CurrentSemianSubResource.
|
280
|
-
name << "_#{
|
279
|
+
if (sub_resource_name = CurrentSemianSubResource.sub_name)
|
280
|
+
name << "_#{sub_resource_name}"
|
281
281
|
end
|
282
282
|
SEMIAN_PARAMETERS.merge(name: name)
|
283
283
|
end
|
284
284
|
|
285
285
|
# Two requests to example.com can use two different semian resources,
|
286
|
-
# as long as `CurrentSemianSubResource.
|
287
|
-
# CurrentSemianSubResource.set(
|
286
|
+
# as long as `CurrentSemianSubResource.sub_name` is set accordingly:
|
287
|
+
# CurrentSemianSubResource.set(sub_name: "sub_resource_1") { Net::HTTP.get_response(URI("http://example.com")) }
|
288
288
|
# and:
|
289
|
-
# CurrentSemianSubResource.set(
|
289
|
+
# CurrentSemianSubResource.set(sub_name: "sub_resource_2") { Net::HTTP.get_response(URI("http://example.com")) }
|
290
290
|
```
|
291
291
|
|
292
292
|
For most purposes, `"#{host}_#{port}"` is a good default `name`. Custom `name` formats
|
data/ext/semian/resource.c
CHANGED
@@ -5,9 +5,6 @@ ID id_wait_time;
|
|
5
5
|
ID id_timeout;
|
6
6
|
int system_max_semaphore_count;
|
7
7
|
|
8
|
-
static VALUE
|
9
|
-
cleanup_semian_resource_acquire(VALUE self);
|
10
|
-
|
11
8
|
static void
|
12
9
|
check_tickets_xor_quota_arg(VALUE tickets, VALUE quota);
|
13
10
|
|
@@ -33,15 +30,11 @@ static const rb_data_type_t
|
|
33
30
|
semian_resource_type;
|
34
31
|
|
35
32
|
VALUE
|
36
|
-
|
33
|
+
semian_resource_acquire_semaphore(int argc, VALUE *argv, VALUE self)
|
37
34
|
{
|
38
35
|
semian_resource_t *self_res = NULL;
|
39
36
|
semian_resource_t res = { 0 };
|
40
37
|
|
41
|
-
if (!rb_block_given_p()) {
|
42
|
-
rb_raise(rb_eArgError, "acquire requires a block");
|
43
|
-
}
|
44
|
-
|
45
38
|
TypedData_Get_Struct(self, semian_resource_t, &semian_resource_type, self_res);
|
46
39
|
res = *self_res;
|
47
40
|
|
@@ -63,6 +56,8 @@ semian_resource_acquire(int argc, VALUE *argv, VALUE self)
|
|
63
56
|
if (res.error != 0) {
|
64
57
|
if (res.error == EAGAIN) {
|
65
58
|
rb_raise(eTimeout, "timed out waiting for resource '%s'", res.name);
|
59
|
+
} else if (res.error == EINVAL && get_semaphore(res.sem_id) == -1 && errno == ENOENT) {
|
60
|
+
rb_raise(eSemaphoreMissing, "semaphore array '%s' no longer exists", res.name);
|
66
61
|
} else {
|
67
62
|
raise_semian_syscall_error("semop()", res.error);
|
68
63
|
}
|
@@ -73,7 +68,19 @@ semian_resource_acquire(int argc, VALUE *argv, VALUE self)
|
|
73
68
|
wait_time = LONG2NUM(res.wait_time);
|
74
69
|
}
|
75
70
|
|
76
|
-
return
|
71
|
+
return wait_time;
|
72
|
+
}
|
73
|
+
|
74
|
+
VALUE
|
75
|
+
semian_resource_acquire(int argc, VALUE *argv, VALUE self)
|
76
|
+
{
|
77
|
+
if (!rb_block_given_p()) {
|
78
|
+
rb_raise(rb_eArgError, "acquire requires a block");
|
79
|
+
}
|
80
|
+
|
81
|
+
VALUE wait_time = semian_resource_acquire_semaphore(argc, argv, self);
|
82
|
+
|
83
|
+
return rb_ensure(rb_yield, wait_time, semian_resource_release_semaphore, self);
|
77
84
|
}
|
78
85
|
|
79
86
|
VALUE
|
@@ -251,8 +258,8 @@ semian_resource_in_use(VALUE self)
|
|
251
258
|
return Qtrue;
|
252
259
|
}
|
253
260
|
|
254
|
-
|
255
|
-
|
261
|
+
VALUE
|
262
|
+
semian_resource_release_semaphore(VALUE self)
|
256
263
|
{
|
257
264
|
semian_resource_t *res = NULL;
|
258
265
|
TypedData_Get_Struct(self, semian_resource_t, &semian_resource_type, res);
|
data/ext/semian/resource.h
CHANGED
@@ -33,10 +33,39 @@ semian_resource_initialize(VALUE self, VALUE id, VALUE tickets, VALUE quota, VAL
|
|
33
33
|
*
|
34
34
|
* If no timeout argument is provided, the default timeout passed to Semian.register will be used.
|
35
35
|
*
|
36
|
+
* The given block is executed with the semaphore held and, when the block
|
37
|
+
* exits, the semaphore is automatically released.
|
36
38
|
*/
|
37
39
|
VALUE
|
38
40
|
semian_resource_acquire(int argc, VALUE *argv, VALUE self);
|
39
41
|
|
42
|
+
/*
|
43
|
+
* call-seq:
|
44
|
+
* resource.acquire_semaphore(timeout: default_timeout) -> wait_time
|
45
|
+
*
|
46
|
+
* Acquires a resource. The call will block for <code>timeout</code> seconds if a ticket
|
47
|
+
* is not available. If no ticket is available within the timeout period, Semian::TimeoutError
|
48
|
+
* will be raised.
|
49
|
+
*
|
50
|
+
* If no timeout argument is provided, the default timeout passed to Semian.register will be used.
|
51
|
+
*
|
52
|
+
* Note: The caller is responsible for releasing the semaphore when done by calling release_semaphore.
|
53
|
+
*/
|
54
|
+
VALUE
|
55
|
+
semian_resource_acquire_semaphore(int argc, VALUE *argv, VALUE self);
|
56
|
+
|
57
|
+
/*
|
58
|
+
* call-seq:
|
59
|
+
* resource.release_semaphore() -> nil
|
60
|
+
*
|
61
|
+
* Releases a resource previously acquired with acquire_semaphore.
|
62
|
+
*
|
63
|
+
* Note: The method is NOT idempotent. The caller must ensure that the method is called exactly
|
64
|
+
* as many times as acquire_semaphore.
|
65
|
+
*/
|
66
|
+
VALUE
|
67
|
+
semian_resource_release_semaphore(VALUE self);
|
68
|
+
|
40
69
|
/*
|
41
70
|
* call-seq:
|
42
71
|
* resource.destroy() -> true
|
data/ext/semian/semian.c
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
#include "semian.h"
|
2
2
|
|
3
|
-
VALUE eSyscall, eTimeout, eInternal;
|
3
|
+
VALUE eSyscall, eTimeout, eInternal, eSemaphoreMissing;
|
4
4
|
|
5
5
|
void Init_semian()
|
6
6
|
{
|
@@ -46,9 +46,22 @@ void Init_semian()
|
|
46
46
|
eInternal = rb_const_get(cSemian, rb_intern("InternalError"));
|
47
47
|
rb_global_variable(&eInternal);
|
48
48
|
|
49
|
+
/* Document-class: Semian::SemaphoreMissingError
|
50
|
+
*
|
51
|
+
* Indicates that some time after initialization, a semaphore array was no longer
|
52
|
+
* present when we tried to access it. This can happen because semaphores were
|
53
|
+
* deleted using the <code>ipcrm</code> command line tool, the
|
54
|
+
* <code>semctl(..., IPC_RMID)</code> syscall, or systemd's <code>RemoveIPC</code>
|
55
|
+
* feature.
|
56
|
+
*/
|
57
|
+
eSemaphoreMissing = rb_const_get(cSemian, rb_intern("SemaphoreMissingError"));
|
58
|
+
rb_global_variable(&eSemaphoreMissing);
|
59
|
+
|
49
60
|
rb_define_alloc_func(cResource, semian_resource_alloc);
|
50
61
|
rb_define_method(cResource, "initialize_semaphore", semian_resource_initialize, 5);
|
51
62
|
rb_define_method(cResource, "acquire", semian_resource_acquire, -1);
|
63
|
+
rb_define_method(cResource, "acquire_semaphore", semian_resource_acquire_semaphore, -1);
|
64
|
+
rb_define_method(cResource, "release_semaphore", semian_resource_release_semaphore, 0);
|
52
65
|
rb_define_method(cResource, "count", semian_resource_count, 0);
|
53
66
|
rb_define_method(cResource, "semid", semian_resource_id, 0);
|
54
67
|
rb_define_method(cResource, "key", semian_resource_key, 0);
|
data/lib/semian/mysql2.rb
CHANGED
@@ -28,6 +28,8 @@ module Semian
|
|
28
28
|
/Too many connections/i,
|
29
29
|
/closed MySQL connection/i,
|
30
30
|
/Timeout waiting for a response/i,
|
31
|
+
/No matching servers with free connections/i,
|
32
|
+
/Max connect timeout reached while reaching hostgroup/i,
|
31
33
|
)
|
32
34
|
|
33
35
|
ResourceBusyError = ::Mysql2::ResourceBusyError
|
@@ -136,7 +138,8 @@ module Semian
|
|
136
138
|
|
137
139
|
def raw_semian_options
|
138
140
|
return query_options[:semian] if query_options.key?(:semian)
|
139
|
-
|
141
|
+
|
142
|
+
query_options["semian"] if query_options.key?("semian")
|
140
143
|
end
|
141
144
|
end
|
142
145
|
end
|
@@ -4,7 +4,7 @@ module Semian
|
|
4
4
|
class ProtectedResource
|
5
5
|
extend Forwardable
|
6
6
|
|
7
|
-
def_delegators :@bulkhead, :
|
7
|
+
def_delegators :@bulkhead, :count, :semid, :tickets, :registered_workers
|
8
8
|
def_delegators :@circuit_breaker,
|
9
9
|
:reset,
|
10
10
|
:mark_failed,
|
data/lib/semian/redis.rb
CHANGED
@@ -151,7 +151,8 @@ module Semian
|
|
151
151
|
|
152
152
|
def raw_semian_options
|
153
153
|
return options[:semian] if options.key?(:semian)
|
154
|
-
|
154
|
+
|
155
|
+
options["semian"] if options.key?("semian")
|
155
156
|
end
|
156
157
|
|
157
158
|
def raise_if_out_of_memory(reply)
|
data/lib/semian/redis_client.rb
CHANGED
@@ -33,19 +33,25 @@ class RedisClient
|
|
33
33
|
CircuitOpenError = Class.new(SemianError)
|
34
34
|
|
35
35
|
module SemianConfig
|
36
|
-
attr_reader :raw_semian_options
|
37
|
-
|
38
36
|
def initialize(semian: nil, **kwargs)
|
39
37
|
super(**kwargs)
|
40
38
|
|
41
39
|
@raw_semian_options = semian
|
42
40
|
end
|
43
41
|
|
42
|
+
def raw_semian_options
|
43
|
+
@raw_semian_options.respond_to?(:call) ? @raw_semian_options.call(host, port) : @raw_semian_options
|
44
|
+
end
|
45
|
+
|
44
46
|
def semian_identifier
|
45
|
-
@semian_identifier
|
47
|
+
return @semian_identifier if @semian_identifier
|
48
|
+
|
49
|
+
identifier = begin
|
46
50
|
name = (semian_options && semian_options[:name]) || "#{host}:#{port}/#{db}"
|
47
51
|
:"redis_#{name}"
|
48
52
|
end
|
53
|
+
@semian_identifier = identifier unless semian_options && semian_options[:dynamic]
|
54
|
+
identifier
|
49
55
|
end
|
50
56
|
|
51
57
|
define_method(:semian_options, Semian::Adapter.instance_method(:semian_options))
|
data/lib/semian/resource.rb
CHANGED
@@ -9,6 +9,12 @@ module Semian
|
|
9
9
|
def instance(name, **kwargs)
|
10
10
|
Semian.resources[name] ||= ProtectedResource.new(name, new(name, **kwargs), nil)
|
11
11
|
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def redefinable(method_name)
|
16
|
+
alias_method(method_name, method_name) # Silence method redefinition warnings
|
17
|
+
end
|
12
18
|
end
|
13
19
|
|
14
20
|
def initialize(name, tickets: nil, quota: nil, permissions: Semian.default_permissions, timeout: 0)
|
@@ -26,41 +32,48 @@ module Semian
|
|
26
32
|
@name = name
|
27
33
|
end
|
28
34
|
|
29
|
-
def reset_registered_workers!
|
35
|
+
redefinable def reset_registered_workers!
|
30
36
|
end
|
31
37
|
|
32
|
-
def destroy
|
38
|
+
redefinable def destroy
|
33
39
|
end
|
34
40
|
|
35
|
-
def unregister_worker
|
41
|
+
redefinable def unregister_worker
|
36
42
|
end
|
37
43
|
|
38
|
-
def acquire(*)
|
44
|
+
redefinable def acquire(*)
|
39
45
|
wait_time = 0
|
40
46
|
yield wait_time
|
41
47
|
end
|
42
48
|
|
43
|
-
def
|
49
|
+
redefinable def acquire_semaphore
|
50
|
+
0
|
51
|
+
end
|
52
|
+
|
53
|
+
redefinable def release_semaphore
|
54
|
+
end
|
55
|
+
|
56
|
+
redefinable def count
|
44
57
|
0
|
45
58
|
end
|
46
59
|
|
47
|
-
def tickets
|
60
|
+
redefinable def tickets
|
48
61
|
0
|
49
62
|
end
|
50
63
|
|
51
|
-
def registered_workers
|
64
|
+
redefinable def registered_workers
|
52
65
|
0
|
53
66
|
end
|
54
67
|
|
55
|
-
def semid
|
68
|
+
redefinable def semid
|
56
69
|
0
|
57
70
|
end
|
58
71
|
|
59
|
-
def key
|
72
|
+
redefinable def key
|
60
73
|
"0x00000000"
|
61
74
|
end
|
62
75
|
|
63
|
-
def in_use?
|
76
|
+
redefinable def in_use?
|
64
77
|
false
|
65
78
|
end
|
66
79
|
end
|
data/lib/semian/version.rb
CHANGED
data/lib/semian.rb
CHANGED
@@ -100,6 +100,7 @@ module Semian
|
|
100
100
|
TimeoutError = Class.new(BaseError)
|
101
101
|
InternalError = Class.new(BaseError)
|
102
102
|
OpenCircuitError = Class.new(BaseError)
|
103
|
+
SemaphoreMissingError = Class.new(BaseError)
|
103
104
|
|
104
105
|
attr_accessor :maximum_lru_size, :minimum_lru_time, :default_permissions, :namespace
|
105
106
|
|
@@ -274,6 +275,21 @@ module Semian
|
|
274
275
|
@thread_safe = thread_safe
|
275
276
|
end
|
276
277
|
|
278
|
+
THREAD_BULKHEAD_DISABLED_VAR = :semian_bulkheads_disabled
|
279
|
+
private_constant(:THREAD_BULKHEAD_DISABLED_VAR)
|
280
|
+
|
281
|
+
def bulkheads_disabled_in_thread?(thread)
|
282
|
+
thread.thread_variable_get(THREAD_BULKHEAD_DISABLED_VAR)
|
283
|
+
end
|
284
|
+
|
285
|
+
def disable_bulkheads_for_thread(thread)
|
286
|
+
old_value = thread.thread_variable_get(THREAD_BULKHEAD_DISABLED_VAR)
|
287
|
+
thread.thread_variable_set(THREAD_BULKHEAD_DISABLED_VAR, true)
|
288
|
+
yield
|
289
|
+
ensure
|
290
|
+
thread.thread_variable_set(THREAD_BULKHEAD_DISABLED_VAR, old_value)
|
291
|
+
end
|
292
|
+
|
277
293
|
private
|
278
294
|
|
279
295
|
def create_circuit_breaker(name, **options)
|
@@ -317,7 +333,7 @@ module Semian
|
|
317
333
|
end
|
318
334
|
|
319
335
|
def create_bulkhead(name, **options)
|
320
|
-
return if ENV.key?("SEMIAN_BULKHEAD_DISABLED")
|
336
|
+
return if ENV.key?("SEMIAN_BULKHEAD_DISABLED") || bulkheads_disabled_in_thread?(Thread.current)
|
321
337
|
return unless options.fetch(:bulkhead, true)
|
322
338
|
|
323
339
|
permissions = options[:permissions] || default_permissions
|
metadata
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: semian
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.22.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Scott Francis
|
8
8
|
- Simon Eskildsen
|
9
9
|
- Dale Hamel
|
10
|
-
autorequire:
|
10
|
+
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2024-
|
13
|
+
date: 2024-11-05 00:00:00.000000000 Z
|
14
14
|
dependencies: []
|
15
15
|
description: |2
|
16
16
|
A Ruby C extention that is used to control access to shared resources
|
@@ -64,7 +64,7 @@ metadata:
|
|
64
64
|
documentation_uri: https://github.com/Shopify/semian
|
65
65
|
homepage_uri: https://github.com/Shopify/semian
|
66
66
|
source_code_uri: https://github.com/Shopify/semian
|
67
|
-
post_install_message:
|
67
|
+
post_install_message:
|
68
68
|
rdoc_options: []
|
69
69
|
require_paths:
|
70
70
|
- lib
|
@@ -72,15 +72,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
72
72
|
requirements:
|
73
73
|
- - ">="
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version:
|
75
|
+
version: 2.7.0
|
76
76
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
77
77
|
requirements:
|
78
78
|
- - ">="
|
79
79
|
- !ruby/object:Gem::Version
|
80
80
|
version: '0'
|
81
81
|
requirements: []
|
82
|
-
rubygems_version: 3.5.
|
83
|
-
signing_key:
|
82
|
+
rubygems_version: 3.5.22
|
83
|
+
signing_key:
|
84
84
|
specification_version: 4
|
85
85
|
summary: Bulkheading for Ruby with SysV semaphores
|
86
86
|
test_files: []
|