semian 0.22.0.RC.1 → 0.22.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 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: []