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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 86eb12415dc2e681f9a24df85bc1b8b6498da3833e50d217bc1e69e2c8696cf0
4
- data.tar.gz: c919fbbf5eb02a44ac38c914187215559a794f007f8ea5e33ff91a9ebd13b3f9
3
+ metadata.gz: 27fa3d35b047fbe54483763821e246e2ce6f1403d8b3354d5759c4bdf1b43b2b
4
+ data.tar.gz: '06303901cc95e293dee76cd712283a4a7db1536771133e83563c969a6f3598e5'
5
5
  SHA512:
6
- metadata.gz: 5cebfd79bbaad7b77f17399c745674dd1b850fd8742e5a87af320326bf77273979c2cceec2fef292f6779f6100b088aab22d7aed9be80f46a7b5eadb2c6790c6
7
- data.tar.gz: 1d093b46109d85c20ace2570e652608f6cba7cb3d18a79443f9dbca002f479c208d2ab45026355b6637ac0b506c906ea9d0379eaee62fe1f2eff5a14152be5c5
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::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
 
@@ -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 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);
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
- static VALUE
255
- cleanup_semian_resource_acquire(VALUE self)
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);
@@ -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);
@@ -66,7 +66,7 @@ enum SEMINDEX_ENUM {
66
66
  FOREACH_SEMINDEX(GENERATE_ENUM)
67
67
  };
68
68
 
69
- extern VALUE eSyscall, eTimeout, eInternal;
69
+ extern VALUE eSyscall, eTimeout, eInternal, eSemaphoreMissing;
70
70
 
71
71
  // Helper for syscall verbose debugging
72
72
  void
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
- return query_options["semian"] if query_options.key?("semian")
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, :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,
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
- return options["semian"] if options.key?("semian")
154
+
155
+ options["semian"] if options.key?("semian")
155
156
  end
156
157
 
157
158
  def raise_if_out_of_memory(reply)
@@ -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 ||= begin
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))
@@ -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.21.3"
4
+ VERSION = "0.22.0"
5
5
  end
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.21.3
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-02-19 00:00:00.000000000 Z
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: '0'
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.6
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: []