cutoff 1.0.0 → 1.2.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 +4 -4
- data/CHANGELOG.md +41 -1
- data/lib/cutoff/patch/mysql2.rb +18 -1
- data/lib/cutoff/patch/net_http.rb +30 -9
- data/lib/cutoff/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8e267a7090c7eee2e054ccc060c18a38a4f6d27277477d7fcfc3263b7f16091a
|
|
4
|
+
data.tar.gz: f7a34c730fe930080da8a539b5c864b4b4af467a042c7ddf075ba7df7242da79
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1853882757fc2da5bae7c836423893dcd2b673f09ff4ebb191dcababd8b44fbf114617664a7974bf67b2a615dbb2fb2f8c2da31876bf5a580d47f9b908d9af79
|
|
7
|
+
data.tar.gz: 75eec1648d14ac62203989bb51ab2b4e3e6710006c0c072d4e98933c367ebd96fde88b7a33705983f1ba09cc44b5e486dacde80e06b9e47291a804883c16872b
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,44 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.2.0] - 2026-08-07
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- The Mysql2 patch now finds the first `SELECT` when a statement begins with a
|
|
15
|
+
parenthesized query block, as in `(SELECT ...) UNION (SELECT ...)`.
|
|
16
|
+
Previously the leading `(` was treated as an unrecognized token and the
|
|
17
|
+
parser bailed out, so `MAX_EXECUTION_TIME` was silently never inserted and
|
|
18
|
+
the query ran with no server-side ceiling. MySQL applies a hint in the first
|
|
19
|
+
query block to the whole statement, so these queries are now capped
|
|
20
|
+
correctly.
|
|
21
|
+
- Fixed a check in the Mysql2 patch that used `String#casecmp` where a boolean
|
|
22
|
+
was expected. Because `casecmp` returns `0` for a match (which is truthy in
|
|
23
|
+
Ruby), the guard never fired and any leading token merely *starting with*
|
|
24
|
+
`select` (such as `selections`) had a hint comment spliced into it,
|
|
25
|
+
producing invalid SQL. It now uses `casecmp?`.
|
|
26
|
+
|
|
27
|
+
### Upgrading
|
|
28
|
+
|
|
29
|
+
Queries beginning with a parenthesized query block were previously never given
|
|
30
|
+
a `MAX_EXECUTION_TIME` hint, so they ran unbounded on the server even with an
|
|
31
|
+
active cutoff. They are now capped, which means a query that used to slowly
|
|
32
|
+
succeed may instead raise `Mysql2::Error`. That is the intended behavior of the
|
|
33
|
+
patch, but it can surface as new errors on upgrade. If you have queries in this
|
|
34
|
+
shape, check that their callers handle the error, or give them a longer cutoff.
|
|
35
|
+
|
|
36
|
+
Statements beginning with `WITH` (common table expressions) are still not
|
|
37
|
+
given a hint.
|
|
38
|
+
|
|
39
|
+
## [1.1.0] - 2026-05-12
|
|
40
|
+
|
|
41
|
+
### Fixed
|
|
42
|
+
|
|
43
|
+
- Net::HTTP patch now also re-applies on `begin_transport`, so internal retries
|
|
44
|
+
(Net::HTTP retries idempotent requests once by default on transient errors
|
|
45
|
+
like Net::ReadTimeout) respect the cutoff instead of silently doubling the
|
|
46
|
+
effective deadline.
|
|
47
|
+
|
|
10
48
|
## [1.0.0] - 2026-05-12
|
|
11
49
|
|
|
12
50
|
This release marks Cutoff's API as stable. There are no behavior changes
|
|
@@ -98,7 +136,9 @@ to `Timeout::Error`. `CutoffError` changes from a class to a module.
|
|
|
98
136
|
- Cutoff class
|
|
99
137
|
- Mysql2 patch
|
|
100
138
|
|
|
101
|
-
[Unreleased]: https://github.com/justinhoward/cutoff/compare/v1.
|
|
139
|
+
[Unreleased]: https://github.com/justinhoward/cutoff/compare/v1.2.0...HEAD
|
|
140
|
+
[1.2.0]: https://github.com/justinhoward/cutoff/compare/v1.1.0...v1.2.0
|
|
141
|
+
[1.1.0]: https://github.com/justinhoward/cutoff/compare/v1.0.0...v1.1.0
|
|
102
142
|
[1.0.0]: https://github.com/justinhoward/cutoff/compare/v0.5.2...v1.0.0
|
|
103
143
|
[0.5.2]: https://github.com/justinhoward/cutoff/compare/v0.5.1...v0.5.2
|
|
104
144
|
[0.5.1]: https://github.com/justinhoward/cutoff/compare/v0.5.0...v0.5.1
|
data/lib/cutoff/patch/mysql2.rb
CHANGED
|
@@ -69,6 +69,8 @@ class Cutoff
|
|
|
69
69
|
hint_comment
|
|
70
70
|
elsif token.start_with?('/*')
|
|
71
71
|
block_comment
|
|
72
|
+
elsif !@found_select && token.start_with?('(')
|
|
73
|
+
open_paren
|
|
72
74
|
elsif token.match?(/^select/i)
|
|
73
75
|
select
|
|
74
76
|
else
|
|
@@ -133,6 +135,21 @@ class Cutoff
|
|
|
133
135
|
@scanner.terminate
|
|
134
136
|
end
|
|
135
137
|
|
|
138
|
+
def open_paren
|
|
139
|
+
# A statement can begin with a parenthesized query block, as in
|
|
140
|
+
# `(SELECT ...) UNION (SELECT ...)`. MySQL accepts an optimizer hint
|
|
141
|
+
# at the start of the first block and applies MAX_EXECUTION_TIME to
|
|
142
|
+
# the whole statement, so consume the parentheses and keep looking
|
|
143
|
+
# for the select.
|
|
144
|
+
#
|
|
145
|
+
# Go back to the beginning of the token and skip only the parens
|
|
146
|
+
# (and any whitespace after them) so the rest of the token is
|
|
147
|
+
# re-examined as its own token. This always advances the scanner by
|
|
148
|
+
# at least one character, so the token loop can't spin.
|
|
149
|
+
@scanner.unscan
|
|
150
|
+
@scanner.skip(/\(+\s*/)
|
|
151
|
+
end
|
|
152
|
+
|
|
136
153
|
def select
|
|
137
154
|
# If we encounter a select, we're ready to place our hint comment
|
|
138
155
|
@scanner.unscan
|
|
@@ -140,7 +157,7 @@ class Cutoff
|
|
|
140
157
|
|
|
141
158
|
# Make sure our word is actually select
|
|
142
159
|
# We only checked that it starts with select before
|
|
143
|
-
return other unless word.casecmp('select')
|
|
160
|
+
return other unless word.casecmp?('select')
|
|
144
161
|
|
|
145
162
|
@found_select = true
|
|
146
163
|
@hint_pos = @scanner.pos
|
|
@@ -25,22 +25,43 @@ class Cutoff
|
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
# Same as the original start, but adds a checkpoint for starting HTTP
|
|
28
|
-
# requests and sets network timeouts to the remaining time
|
|
28
|
+
# requests and sets network timeouts to the remaining time.
|
|
29
|
+
#
|
|
30
|
+
# Also applies the same logic to begin_transport, which is called on
|
|
31
|
+
# every HTTP attempt including internal retries. Net::HTTP#transport_request
|
|
32
|
+
# silently retries idempotent requests (GET, HEAD, PUT, DELETE, OPTIONS,
|
|
33
|
+
# TRACE) up to max_retries (default 1) on transient errors like
|
|
34
|
+
# Net::ReadTimeout. Without re-applying the cutoff in begin_transport,
|
|
35
|
+
# the retry path goes through #connect (not #start), reuses the original
|
|
36
|
+
# read_timeout, and effectively doubles the deadline you set.
|
|
29
37
|
#
|
|
30
38
|
# @method start
|
|
39
|
+
# @method begin_transport
|
|
31
40
|
# @see Net::HTTP#start
|
|
41
|
+
# @see Net::HTTP#begin_transport
|
|
32
42
|
module_eval(<<~RUBY, __FILE__, __LINE__ + 1)
|
|
33
43
|
def start
|
|
34
|
-
|
|
35
|
-
remaining = cutoff.seconds_remaining
|
|
36
|
-
#{gen_timeout_method('open_timeout')}
|
|
37
|
-
#{gen_timeout_method('read_timeout')}
|
|
38
|
-
#{gen_timeout_method('write_timeout') if use_write_timeout?}
|
|
39
|
-
#{gen_timeout_method('continue_timeout')}
|
|
40
|
-
Cutoff.checkpoint!(:net_http)
|
|
41
|
-
end
|
|
44
|
+
_cutoff_apply_to_net_http
|
|
42
45
|
super
|
|
43
46
|
end
|
|
47
|
+
|
|
48
|
+
def begin_transport(req)
|
|
49
|
+
_cutoff_apply_to_net_http
|
|
50
|
+
super
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def _cutoff_apply_to_net_http
|
|
56
|
+
return unless (cutoff = Cutoff.current) && cutoff.selected?(:net_http)
|
|
57
|
+
|
|
58
|
+
remaining = cutoff.seconds_remaining
|
|
59
|
+
#{gen_timeout_method('open_timeout')}
|
|
60
|
+
#{gen_timeout_method('read_timeout')}
|
|
61
|
+
#{gen_timeout_method('write_timeout') if use_write_timeout?}
|
|
62
|
+
#{gen_timeout_method('continue_timeout')}
|
|
63
|
+
Cutoff.checkpoint!(:net_http)
|
|
64
|
+
end
|
|
44
65
|
RUBY
|
|
45
66
|
end
|
|
46
67
|
end
|
data/lib/cutoff/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: cutoff
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Justin Howard
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-08-07 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rspec
|
|
@@ -84,7 +84,7 @@ licenses:
|
|
|
84
84
|
- MIT
|
|
85
85
|
metadata:
|
|
86
86
|
changelog_uri: https://github.com/justinhoward/cutoff/blob/master/CHANGELOG.md
|
|
87
|
-
documentation_uri: https://www.rubydoc.info/gems/cutoff/1.
|
|
87
|
+
documentation_uri: https://www.rubydoc.info/gems/cutoff/1.2.0
|
|
88
88
|
rubygems_mfa_required: 'true'
|
|
89
89
|
post_install_message:
|
|
90
90
|
rdoc_options: []
|