semian 0.22.0.RC.1 → 0.22.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: afd87705b68966cef884eeeb1d5df4d052eeb86f2ff7d43c9499435795341461
4
- data.tar.gz: 911a0772662b81e21dcb475c9b981ceb0fbaeadf26edbbca70ab8adbf5be4068
3
+ metadata.gz: cdc3f7ce1f123bfc666e600050ee264cd4398e4e607f4f2dc1b91b008b7d6f3d
4
+ data.tar.gz: 5f8b87e92a43267aa10158cd27a7e3e931bb4a51a6ef301d8a9af9d0ddcbabd5
5
5
  SHA512:
6
- metadata.gz: 8edb70d967cb15e12b919b3a7e6585f98848e2d569683d590d2d37e32eb57b65cd9f425931a3bf1575d91fc6c50e9d43901340cb33a34a671a585956b947514f
7
- data.tar.gz: b880f4f52891cbc4fd3d97221053bddc741aa72a98b986826b8ca08454c73ccbe714cff311966387f40b83e2f48a6d2db4ddf23407ff275e55bfb8c1730b5673
6
+ metadata.gz: b2bc7b89dde896156164abbac527ccdc242d635ccfa78190afc48a30f07b8b2fb89c5660fffe6ea35d1accfe132fa23c50550f633777d6879d8d077f2554e985
7
+ data.tar.gz: 7c2fdf0a5c4db2c3d7b141279cb330c6cc612c55932fa54e628d9da919a2de8174c115eec67886fe40ce3cf6aac8feedca5870a0c767a84459e42d99b64fe8b7
data/README.md CHANGED
@@ -270,23 +270,23 @@ SEMIAN_PARAMETERS = {
270
270
  dynamic: true,
271
271
  }
272
272
 
273
- class CurrentSemianSubResource < ActiveSupport::Attributes
274
- attribute :name
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.name)
280
- name << "_#{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.name` is set accordingly:
287
- # CurrentSemianSubResource.set(name: "sub_resource_1") { Net::HTTP.get_response(URI("http://example.com")) }
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(name: "sub_resource_2") { Net::HTTP.get_response(URI("http://example.com")) }
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
@@ -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
- semian_resource_acquire(int argc, VALUE *argv, VALUE self)
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
 
@@ -75,7 +68,19 @@ semian_resource_acquire(int argc, VALUE *argv, VALUE self)
75
68
  wait_time = LONG2NUM(res.wait_time);
76
69
  }
77
70
 
78
- return rb_ensure(rb_yield, wait_time, cleanup_semian_resource_acquire, self);
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);
79
84
  }
80
85
 
81
86
  VALUE
@@ -253,8 +258,8 @@ semian_resource_in_use(VALUE self)
253
258
  return Qtrue;
254
259
  }
255
260
 
256
- static VALUE
257
- cleanup_semian_resource_acquire(VALUE self)
261
+ VALUE
262
+ semian_resource_release_semaphore(VALUE self)
258
263
  {
259
264
  semian_resource_t *res = NULL;
260
265
  TypedData_Get_Struct(self, semian_resource_t, &semian_resource_type, res);
@@ -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
@@ -60,6 +60,8 @@ void Init_semian()
60
60
  rb_define_alloc_func(cResource, semian_resource_alloc);
61
61
  rb_define_method(cResource, "initialize_semaphore", semian_resource_initialize, 5);
62
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);
63
65
  rb_define_method(cResource, "count", semian_resource_count, 0);
64
66
  rb_define_method(cResource, "semid", semian_resource_id, 0);
65
67
  rb_define_method(cResource, "key", semian_resource_key, 0);
@@ -29,7 +29,7 @@ module Semian
29
29
  ResourceBusyError = ::ActiveRecord::ConnectionAdapters::TrilogyAdapter::ResourceBusyError
30
30
  CircuitOpenError = ::ActiveRecord::ConnectionAdapters::TrilogyAdapter::CircuitOpenError
31
31
 
32
- QUERY_ALLOWLIST = %r{\A(?:/\*.*?\*/)?\s*(ROLLBACK|COMMIT|RELEASE\s+SAVEPOINT)}i
32
+ QUERY_ALLOWLIST = %r{\A(?:/\*.*?\*/)?\s*(ROLLBACK|COMMIT|RELEASE\s+SAVEPOINT)}i.freeze
33
33
 
34
34
  # The common case here is NOT to have transaction management statements, therefore
35
35
  # we are exploiting the fact that Active Record will use COMMIT/ROLLBACK as
data/lib/semian/grpc.rb CHANGED
@@ -36,10 +36,11 @@ module Semian
36
36
  end
37
37
 
38
38
  class << self
39
- attr_accessor :exceptions
40
39
  attr_reader :semian_configuration
41
40
 
41
+ # rubocop:disable ThreadSafety/ClassInstanceVariable
42
42
  def semian_configuration=(configuration)
43
+ # Only allow setting the configuration once in boot time
43
44
  raise Semian::GRPC::SemianConfigurationChangedError unless @semian_configuration.nil?
44
45
 
45
46
  @semian_configuration = configuration
@@ -48,6 +49,7 @@ module Semian
48
49
  def retrieve_semian_configuration(host)
49
50
  @semian_configuration.call(host) if @semian_configuration.respond_to?(:call)
50
51
  end
52
+ # rubocop:enable ThreadSafety/ClassInstanceVariable
51
53
  end
52
54
 
53
55
  def raw_semian_options
data/lib/semian/mysql2.rb CHANGED
@@ -39,7 +39,7 @@ module Semian
39
39
  DEFAULT_HOST = "localhost"
40
40
  DEFAULT_PORT = 3306
41
41
 
42
- QUERY_ALLOWLIST = %r{\A(?:/\*.*?\*/)?\s*(ROLLBACK|COMMIT|RELEASE\s+SAVEPOINT)}i
42
+ QUERY_ALLOWLIST = %r{\A(?:/\*.*?\*/)?\s*(ROLLBACK|COMMIT|RELEASE\s+SAVEPOINT)}i.freeze
43
43
 
44
44
  class << self
45
45
  # The naked methods are exposed as `raw_query` and `raw_connect` for instrumentation purpose
@@ -55,10 +55,12 @@ module Semian
55
55
  end
56
56
 
57
57
  class << self
58
- attr_accessor :exceptions
58
+ attr_accessor :exceptions # rubocop:disable ThreadSafety/ClassAndModuleAttributes
59
59
  attr_reader :semian_configuration
60
60
 
61
+ # rubocop:disable ThreadSafety/ClassInstanceVariable
61
62
  def semian_configuration=(configuration)
63
+ # Only allow setting the configuration once in boot time
62
64
  raise Semian::NetHTTP::SemianConfigurationChangedError unless @semian_configuration.nil?
63
65
 
64
66
  @semian_configuration = configuration
@@ -67,6 +69,7 @@ module Semian
67
69
  def retrieve_semian_configuration(host, port)
68
70
  @semian_configuration.call(host, port) if @semian_configuration.respond_to?(:call)
69
71
  end
72
+ # rubocop:enable ThreadSafety/ClassInstanceVariable
70
73
 
71
74
  def reset_exceptions
72
75
  self.exceptions = Semian::NetHTTP::DEFAULT_ERRORS.dup
@@ -4,7 +4,7 @@ module Semian
4
4
  class ProtectedResource
5
5
  extend Forwardable
6
6
 
7
- def_delegators :@bulkhead, :destroy, :count, :semid, :tickets, :registered_workers
7
+ def_delegators :@bulkhead, :count, :semid, :tickets, :registered_workers
8
8
  def_delegators :@circuit_breaker,
9
9
  :reset,
10
10
  :mark_failed,
@@ -94,7 +94,7 @@ module Semian
94
94
  end
95
95
 
96
96
  module RedisClient
97
- EXCEPTIONS = [::RedisClient::ConnectionError, ::RedisClient::OutOfMemoryError]
97
+ EXCEPTIONS = [::RedisClient::ConnectionError, ::RedisClient::OutOfMemoryError].freeze
98
98
 
99
99
  include Semian::Adapter
100
100
  include RedisClientCommon
@@ -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 count
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Semian
4
- VERSION = "0.22.0.RC.1"
4
+ VERSION = "0.22.1"
5
5
  end
data/lib/semian.rb CHANGED
@@ -275,6 +275,21 @@ module Semian
275
275
  @thread_safe = thread_safe
276
276
  end
277
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
+
278
293
  private
279
294
 
280
295
  def create_circuit_breaker(name, **options)
@@ -318,7 +333,7 @@ module Semian
318
333
  end
319
334
 
320
335
  def create_bulkhead(name, **options)
321
- return if ENV.key?("SEMIAN_BULKHEAD_DISABLED")
336
+ return if ENV.key?("SEMIAN_BULKHEAD_DISABLED") || bulkheads_disabled_in_thread?(Thread.current)
322
337
  return unless options.fetch(:bulkhead, true)
323
338
 
324
339
  permissions = options[:permissions] || default_permissions
metadata CHANGED
@@ -1,16 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: semian
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.22.0.RC.1
4
+ version: 0.22.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Scott Francis
8
8
  - Simon Eskildsen
9
9
  - Dale Hamel
10
- autorequire:
11
10
  bindir: bin
12
11
  cert_chain: []
13
- date: 2024-07-02 00:00:00.000000000 Z
12
+ date: 2025-02-10 00:00:00.000000000 Z
14
13
  dependencies: []
15
14
  description: |2
16
15
  A Ruby C extention that is used to control access to shared resources
@@ -64,7 +63,6 @@ metadata:
64
63
  documentation_uri: https://github.com/Shopify/semian
65
64
  homepage_uri: https://github.com/Shopify/semian
66
65
  source_code_uri: https://github.com/Shopify/semian
67
- post_install_message:
68
66
  rdoc_options: []
69
67
  require_paths:
70
68
  - lib
@@ -79,8 +77,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
79
77
  - !ruby/object:Gem::Version
80
78
  version: '0'
81
79
  requirements: []
82
- rubygems_version: 3.5.14
83
- signing_key:
80
+ rubygems_version: 3.6.3
84
81
  specification_version: 4
85
82
  summary: Bulkheading for Ruby with SysV semaphores
86
83
  test_files: []