semian 0.21.3 → 0.22.0

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: 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: []