gvltools 0.4.0 → 0.5.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: 5b6af92049391130d2248c5d14ec3c93e859f4ab92d6af9842e2a88c289b3e50
4
- data.tar.gz: 4681a32780409d180b6727a791cdc122a9a03d21eba7f28f2070c8de6e52ddb1
3
+ metadata.gz: 93f82e0950c9e2c02690e92984d20654d36dd91b3dffee80d0e85965a0bbc0b3
4
+ data.tar.gz: c6db2e33663b3c3fe290889eb91a71ac336d75a1005e90903b5c54e663446f70
5
5
  SHA512:
6
- metadata.gz: 005a90c02765c716c97f9165f777dd6b26b3853037e6c64f13e3255278db5a7c5d378855850758a7e391d9957389d18db7edb24a3658a46a5ec6105668238fa7
7
- data.tar.gz: e16793a8f452f958cd0dde468c5aff337a78719485c8657bc9f5e1e91bd393b0bc1a523e56b5a1e06246c963e8661ed34707ef49e05cb15d6e94e57865c9932c
6
+ metadata.gz: fcb609efc5575f85fd66fd57e223204a3206905b724940bf59dfc75e7421dab91faf0b9c7ed6e10083be728c17e5b083445ac59196fdfaa9ff629f4b05f53523
7
+ data.tar.gz: f71c390eaa0d6d72f8b06d58a70802f18ce7ec8347b84e42b4e5abcea5ec0c4a7bf598790080abd3b847e99537692caba4923651a2822d8cf1865a3588c5e962
data/.rubocop.yml CHANGED
@@ -1,5 +1,4 @@
1
1
  AllCops:
2
- TargetRubyVersion: 3.0
3
2
  NewCops: enable
4
3
  SuggestExtensions: false
5
4
 
@@ -25,6 +24,9 @@ Style/GlobalVars:
25
24
  Style/EmptyMethod:
26
25
  Enabled: false
27
26
 
27
+ Naming/PredicateMethod:
28
+ Enabled: false
29
+
28
30
  Style/IfUnlessModifier:
29
31
  Enabled: false
30
32
 
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.3.0
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.5.0] - 2026-06-15
4
+
5
+ - Fixed a `LocalTimer` deadlock with fiber schedulers by not allocating (and potentially triggering GC) during the `RESUMED` event (#34).
6
+ - Store the `LocalTimer` counter in a thread instance variable instead of fiber-local storage, so it is shared across fibers of the same thread (#37).
7
+
8
+ ## [0.4.0] - 2024-01-19
9
+
3
10
  - Fixed compatibility with Ruby 3.3.0.
4
11
  - Added `GVLTools::LocalTimer.for(thread)` to access another thread counter (Ruby 3.3+ only).
5
12
 
data/Gemfile.lock CHANGED
@@ -1,41 +1,45 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- gvltools (0.4.0)
4
+ gvltools (0.5.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
- ast (2.4.2)
10
- json (2.7.1)
11
- language_server-protocol (3.17.0.3)
12
- minitest (5.21.2)
13
- parallel (1.24.0)
14
- parser (3.3.0.2)
9
+ ast (2.4.3)
10
+ json (2.19.8)
11
+ language_server-protocol (3.17.0.5)
12
+ lint_roller (1.1.0)
13
+ minitest (5.27.0)
14
+ parallel (2.1.0)
15
+ parser (3.3.11.1)
15
16
  ast (~> 2.4.1)
16
17
  racc
17
- racc (1.7.3)
18
+ prism (1.9.0)
19
+ racc (1.8.1)
18
20
  rainbow (3.1.1)
19
- rake (13.0.6)
20
- rake-compiler (1.2.0)
21
+ rake (13.4.2)
22
+ rake-compiler (1.3.1)
21
23
  rake
22
- regexp_parser (2.9.0)
23
- rexml (3.2.6)
24
- rubocop (1.59.0)
24
+ regexp_parser (2.12.0)
25
+ rubocop (1.87.0)
25
26
  json (~> 2.3)
26
- language_server-protocol (>= 3.17.0)
27
- parallel (~> 1.10)
28
- parser (>= 3.2.2.4)
27
+ language_server-protocol (~> 3.17.0.2)
28
+ lint_roller (~> 1.1.0)
29
+ parallel (>= 1.10)
30
+ parser (>= 3.3.0.2)
29
31
  rainbow (>= 2.2.2, < 4.0)
30
- regexp_parser (>= 1.8, < 3.0)
31
- rexml (>= 3.2.5, < 4.0)
32
- rubocop-ast (>= 1.30.0, < 2.0)
32
+ regexp_parser (>= 2.9.3, < 3.0)
33
+ rubocop-ast (>= 1.49.0, < 2.0)
33
34
  ruby-progressbar (~> 1.7)
34
- unicode-display_width (>= 2.4.0, < 3.0)
35
- rubocop-ast (1.30.0)
36
- parser (>= 3.2.1.0)
35
+ unicode-display_width (>= 2.4.0, < 4.0)
36
+ rubocop-ast (1.49.1)
37
+ parser (>= 3.3.7.2)
38
+ prism (~> 1.7)
37
39
  ruby-progressbar (1.13.0)
38
- unicode-display_width (2.5.0)
40
+ unicode-display_width (3.2.0)
41
+ unicode-emoji (~> 4.1)
42
+ unicode-emoji (4.2.0)
39
43
 
40
44
  PLATFORMS
41
45
  aarch64-linux
@@ -50,4 +54,4 @@ DEPENDENCIES
50
54
  rubocop (~> 1.21)
51
55
 
52
56
  BUNDLED WITH
53
- 2.3.14
57
+ 2.5.3
data/README.md CHANGED
@@ -44,6 +44,8 @@ It is particularly useful to detect wether an application use too many threads,
44
44
  For instance as a Rack middleware:
45
45
 
46
46
  ```ruby
47
+ # lib/gvl_instrumentation_middleware.rb
48
+
47
49
  class GVLInstrumentationMiddleware
48
50
  def initialize(app)
49
51
  @app = app
@@ -59,6 +61,15 @@ class GVLInstrumentationMiddleware
59
61
  end
60
62
  ```
61
63
 
64
+ ```ruby
65
+ # config/initializers/gvl_instrumentation.rb
66
+
67
+ GVLTools::LocalTimer.enable
68
+
69
+ require "gvl_instrumentation_middleware"
70
+ config.middleware.use GVLInstrumentationMiddleware
71
+ ```
72
+
62
73
  Starting from Ruby 3.3, a thread local timer can be accessed from another thread:
63
74
 
64
75
  ```ruby
@@ -26,6 +26,7 @@ typedef struct {
26
26
 
27
27
  #ifdef HAVE_RB_INTERNAL_THREAD_SPECIFIC_GET // 3.3+
28
28
  static int thread_storage_key = 0;
29
+ static ID id_local_state;
29
30
 
30
31
  static size_t thread_local_state_memsize(const void *data) {
31
32
  return sizeof(thread_local_state);
@@ -45,7 +46,8 @@ static inline thread_local_state *GT_LOCAL_STATE(VALUE thread, bool allocate) {
45
46
  thread_local_state *state = rb_internal_thread_specific_get(thread, thread_storage_key);
46
47
  if (!state && allocate) {
47
48
  VALUE wrapper = TypedData_Make_Struct(rb_cLocalTimer, thread_local_state, &thread_local_state_type, state);
48
- rb_thread_local_aset(thread, rb_intern("__gvltools_local_state"), wrapper);
49
+ // Anchor to the thread object, not fiber-local storage, so it outlives the allocating fiber.
50
+ rb_ivar_set(thread, id_local_state, wrapper);
49
51
  RB_GC_GUARD(wrapper);
50
52
  rb_internal_thread_specific_set(thread, thread_storage_key, state);
51
53
  }
@@ -99,6 +101,20 @@ static VALUE gt_enable_metric(VALUE module, VALUE metric) {
99
101
  RUBY_INTERNAL_THREAD_EVENT_STARTED | RUBY_INTERNAL_THREAD_EVENT_EXITED | RUBY_INTERNAL_THREAD_EVENT_READY | RUBY_INTERNAL_THREAD_EVENT_RESUMED,
100
102
  NULL
101
103
  );
104
+
105
+ #ifdef HAVE_RB_INTERNAL_THREAD_SPECIFIC_GET
106
+ // Eagerly allocate the per-thread state for every thread that already
107
+ // exists at enable time. Threads created later get their state allocated from the
108
+ // STARTED event (also GVL-held). This guarantees the RESUMED event always finds an
109
+ // existing state and never has to allocate. Allocating from RESUMED is
110
+ // illegal because it doesn't necessarily run with the GVL.
111
+ VALUE threads = rb_funcall(rb_const_get(rb_cObject, rb_intern("Thread")), rb_intern("list"), 0);
112
+ long count = RARRAY_LEN(threads);
113
+ for (long i = 0; i < count; i++) {
114
+ GT_LOCAL_STATE(RARRAY_AREF(threads, i), true);
115
+ }
116
+ RB_GC_GUARD(threads);
117
+ #endif
102
118
  }
103
119
  return Qtrue;
104
120
  }
@@ -138,7 +154,7 @@ static VALUE local_timer_m_reset(VALUE module) {
138
154
  #ifdef HAVE_RB_INTERNAL_THREAD_SPECIFIC_GET
139
155
  static VALUE local_timer_for(VALUE module, VALUE thread) {
140
156
  GT_LOCAL_STATE(thread, true);
141
- return rb_thread_local_aref(thread, rb_intern("__gvltools_local_state"));
157
+ return rb_ivar_get(thread, id_local_state);
142
158
  }
143
159
 
144
160
  static VALUE local_timer_monotonic_time(VALUE timer) {
@@ -207,9 +223,9 @@ static void gt_thread_callback(rb_event_flag_t event, const rb_internal_thread_e
207
223
  }
208
224
  }
209
225
  break;
210
- case RUBY_INTERNAL_THREAD_EVENT_RESUMED: {
211
- thread_local_state *state = GT_EVENT_LOCAL_STATE(event_data, true);
212
- if (!state->was_ready) {
226
+ case RUBY_INTERNAL_THREAD_EVENT_RESUMED: { // Must not allocate
227
+ thread_local_state *state = GT_EVENT_LOCAL_STATE(event_data, false);
228
+ if (!state || !state->was_ready) {
213
229
  break; // In case we registered the hook while some threads were already waiting on the GVL
214
230
  }
215
231
 
@@ -240,6 +256,7 @@ static void gt_thread_callback(rb_event_flag_t event, const rb_internal_thread_e
240
256
  void Init_instrumentation(void) {
241
257
  #ifdef HAVE_RB_INTERNAL_THREAD_SPECIFIC_GET // 3.3+
242
258
  thread_storage_key = rb_internal_thread_specific_key_create();
259
+ id_local_state = rb_intern("__gvltools_local_state");
243
260
  #endif
244
261
 
245
262
  VALUE rb_mGVLTools = rb_const_get(rb_cObject, rb_intern("GVLTools"));
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GVLTools
4
- VERSION = "0.4.0"
4
+ VERSION = "0.5.0"
5
5
  end
metadata CHANGED
@@ -1,16 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gvltools
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jean Boussier
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2024-01-19 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies: []
13
- description:
14
12
  email:
15
13
  - jean.boussier@gmail.com
16
14
  executables: []
@@ -19,6 +17,7 @@ extensions:
19
17
  extra_rdoc_files: []
20
18
  files:
21
19
  - ".rubocop.yml"
20
+ - ".ruby-version"
22
21
  - CHANGELOG.md
23
22
  - Gemfile
24
23
  - Gemfile.lock
@@ -38,7 +37,6 @@ metadata:
38
37
  homepage_uri: https://github.com/Shopify/gvltools
39
38
  source_code_uri: https://github.com/Shopify/gvltools
40
39
  changelog_uri: https://github.com/Shopify/gvltools/blob/master/CHANGELOG.md
41
- post_install_message:
42
40
  rdoc_options: []
43
41
  require_paths:
44
42
  - lib
@@ -53,8 +51,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
53
51
  - !ruby/object:Gem::Version
54
52
  version: '0'
55
53
  requirements: []
56
- rubygems_version: 3.5.3
57
- signing_key:
54
+ rubygems_version: 4.0.10
58
55
  specification_version: 4
59
56
  summary: Set of GVL instrumentation tools
60
57
  test_files: []