chewy 8.3.1 → 8.4.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 +4 -4
- data/CHANGELOG.md +24 -1
- data/README.md +17 -0
- data/lib/chewy/elastic_client.rb +15 -0
- data/lib/chewy/index/compiled.rb +17 -11
- data/lib/chewy/index/import/bulk_builder.rb +1 -1
- data/lib/chewy/strategy/delayed_sidekiq/redis_script.rb +37 -0
- data/lib/chewy/strategy/delayed_sidekiq/scheduler.rb +2 -1
- data/lib/chewy/strategy/delayed_sidekiq/worker.rb +1 -1
- data/lib/chewy/version.rb +1 -1
- data/lib/chewy.rb +15 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c7ffb0b83cbd361f40168edad592083762af5035dc02ca60f205f16c6224a9a6
|
|
4
|
+
data.tar.gz: ca56cddc2b59646fbaa9188c707c1d9f23d9dabf2166e1a1ff168d83adf6e6ef
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 91751c0575275edd79e08a945fed07b775a6cd0cd216c3fcba95fbab4085b22dd476d23485c63ee93d740b3f2b8e08bc742789e6f60313fcc49d43013bd48ebe
|
|
7
|
+
data.tar.gz: 4de8630df6346a7eccbbd14ca0cc2b5d84d636e314c58840cae605433a570e6a9b6c07bf8d2f27b695808efcdc491b41006fd4a18038412ee485fe36669711bb
|
data/CHANGELOG.md
CHANGED
|
@@ -8,7 +8,29 @@
|
|
|
8
8
|
|
|
9
9
|
### Changes
|
|
10
10
|
|
|
11
|
-
## 8.
|
|
11
|
+
## 8.4.1 (2026-06-19)
|
|
12
|
+
|
|
13
|
+
### New Features
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* [#1043](https://github.com/toptal/chewy/pull/1043)Fixed `field ..., value: proc(&:method)` (and other splat-declaring procs) raising `ArgumentError: wrong number of arguments` during import. The compiled compose path introduced in 8.4.0 forwarded `crutches`/`context` as extra positional arguments to splat procs; `Symbol#to_proc` then passed them on to the method. Splat procs are now called with the object alone, matching the plain compose path. ([@AlfonsoUceda][])
|
|
18
|
+
|
|
19
|
+
### Changes
|
|
20
|
+
|
|
21
|
+
## 8.4.0 (2026-06-17)
|
|
22
|
+
|
|
23
|
+
### New Features
|
|
24
|
+
|
|
25
|
+
* [#1036](https://github.com/toptal/chewy/issues/1036): Add `Chewy.close_client` and `Chewy::ElasticClient#close` to explicitly close connections to Elasticsearch, avoiding file descriptor leaks in long-lived multi-threaded processes (e.g. Sidekiq). ([@AlfonsoUceda][])
|
|
26
|
+
|
|
27
|
+
### Bug Fixes
|
|
28
|
+
|
|
29
|
+
* ([#1034](https://github.com/toptal/chewy/issues/1034)): Fixed `delayed_sidekiq` strategy raising `TypeError: Unsupported command argument type: Array` on Sidekiq 7+. The LUA scripts now run via `EVALSHA` through a portable helper instead of redis-rb's keyword `eval`, which the redis-client-backed client Sidekiq 7+ yields does not support. ([@AlfonsoUceda][])
|
|
30
|
+
|
|
31
|
+
### Changes
|
|
32
|
+
|
|
33
|
+
## 8.3.1 (2026-06-05)
|
|
12
34
|
|
|
13
35
|
### New Features
|
|
14
36
|
|
|
@@ -898,6 +920,7 @@
|
|
|
898
920
|
[@AgeevAndrew]: https://github.com/AgeevAndrew
|
|
899
921
|
[@aglushkov]: https://github.com/aglushkov
|
|
900
922
|
[@AlexVPopov]: https://github.com/AlexVPopov
|
|
923
|
+
[@AlfonsoUceda]: https://github.com/AlfonsoUceda
|
|
901
924
|
[@AndreySavelyev]: https://github.com/AndreySavelyev
|
|
902
925
|
[@afg419]: https://github.com/afg419
|
|
903
926
|
[@arion]: https://github.com/arion
|
data/README.md
CHANGED
|
@@ -133,6 +133,23 @@ development:
|
|
|
133
133
|
ca_file: './tmp/http_ca.crt'
|
|
134
134
|
```
|
|
135
135
|
|
|
136
|
+
### Closing connections
|
|
137
|
+
|
|
138
|
+
`Chewy.client` is memoized per thread, so every thread that touches Chewy gets
|
|
139
|
+
its own client with its own connections to Elasticsearch. Neither
|
|
140
|
+
`elasticsearch-ruby` nor `elastic-transport` expose a public way to close those
|
|
141
|
+
connections, so they are only released when Ruby's garbage collector reclaims
|
|
142
|
+
the client. In long-lived, multi-threaded processes that frequently spawn and
|
|
143
|
+
discard threads (e.g. Sidekiq, which replaces a thread on job failure), this can
|
|
144
|
+
leak file descriptors.
|
|
145
|
+
|
|
146
|
+
Use `Chewy.close_client` to close the current thread's connections and drop its
|
|
147
|
+
client. The next `Chewy.client` call rebuilds a fresh one:
|
|
148
|
+
|
|
149
|
+
```ruby
|
|
150
|
+
Chewy.close_client
|
|
151
|
+
```
|
|
152
|
+
|
|
136
153
|
### Index
|
|
137
154
|
|
|
138
155
|
Create `app/chewy/users_index.rb` with User Index:
|
data/lib/chewy/elastic_client.rb
CHANGED
|
@@ -12,6 +12,21 @@ module Chewy
|
|
|
12
12
|
@elastic_client = elastic_client
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
+
# Closes the underlying connections to Elasticsearch.
|
|
16
|
+
#
|
|
17
|
+
# Neither elasticsearch-ruby nor elastic-transport expose a public method
|
|
18
|
+
# to close connections, so they are only released when Ruby's garbage
|
|
19
|
+
# collector reclaims the client instance. This reaches down to the Faraday
|
|
20
|
+
# connection of every transport connection and closes it explicitly, which
|
|
21
|
+
# is useful to avoid file descriptor leaks in long-lived processes that
|
|
22
|
+
# build a client per thread (e.g. Sidekiq workers).
|
|
23
|
+
def close
|
|
24
|
+
@elastic_client.transport.connections.each do |connection|
|
|
25
|
+
faraday = connection.connection
|
|
26
|
+
faraday.close if faraday.respond_to?(:close)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
15
30
|
private
|
|
16
31
|
|
|
17
32
|
def method_missing(name, *args, **kwargs, &block)
|
data/lib/chewy/index/compiled.rb
CHANGED
|
@@ -187,17 +187,24 @@ module Chewy
|
|
|
187
187
|
idx = @procs.size
|
|
188
188
|
@procs << v
|
|
189
189
|
procs_ref = "compiled_procs[#{idx}]"
|
|
190
|
-
|
|
191
|
-
case v.arity
|
|
192
|
-
when 0
|
|
190
|
+
if v.arity.zero?
|
|
193
191
|
"#{obj_var}.instance_exec(&#{procs_ref})"
|
|
192
|
+
elsif v.parameters.any? { |type, _| type == :rest }
|
|
193
|
+
# Procs that declare a splat — Symbol#to_proc (`proc(&:method)`,
|
|
194
|
+
# parameters `[[:req], [:rest]]`) and `->(*args)` — must NOT
|
|
195
|
+
# receive crutches/context as extra positional args: a symbol
|
|
196
|
+
# proc would forward them to the method (`obj.method(crutches,
|
|
197
|
+
# context)` => "wrong number of arguments"). Mirror the plain
|
|
198
|
+
# Base#value_by_proc negative-arity branch (`value.call(*object)`)
|
|
199
|
+
# so the compiled and fallback paths behave identically.
|
|
200
|
+
"#{procs_ref}.call(*#{obj_var})"
|
|
194
201
|
else
|
|
195
202
|
# Pass only as many of (object, crutches, context) as the
|
|
196
203
|
# proc actually declares. This keeps lambdas with optional
|
|
197
204
|
# args (negative arity like `->(o, c=nil)`) from being
|
|
198
205
|
# called with too many arguments. Anything beyond context
|
|
199
206
|
# truncates to all three.
|
|
200
|
-
args = [obj_var, 'crutches', 'context'].first(
|
|
207
|
+
args = [obj_var, 'crutches', 'context'].first(positional_param_count(v))
|
|
201
208
|
"#{procs_ref}.call(#{args.join(', ')})"
|
|
202
209
|
end
|
|
203
210
|
else
|
|
@@ -212,14 +219,13 @@ module Chewy
|
|
|
212
219
|
end
|
|
213
220
|
end
|
|
214
221
|
|
|
215
|
-
# Number of
|
|
216
|
-
# required + optional
|
|
217
|
-
#
|
|
222
|
+
# Number of (object, crutches, context) args to pass to a splat-free
|
|
223
|
+
# proc: its required + optional positional parameters, capped at the
|
|
224
|
+
# three available. Splat procs are handled by the caller and never
|
|
225
|
+
# reach here.
|
|
218
226
|
def positional_param_count(proc)
|
|
219
|
-
|
|
220
|
-
required
|
|
221
|
-
has_splat = params.any? { |type, _| type == :rest }
|
|
222
|
-
has_splat ? 3 : [required, 3].min
|
|
227
|
+
required = proc.parameters.count { |type, _| %i[req opt].include?(type) }
|
|
228
|
+
[required, 3].min
|
|
223
229
|
end
|
|
224
230
|
|
|
225
231
|
def safe_identifier?(name)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Chewy
|
|
4
|
+
class Strategy
|
|
5
|
+
class DelayedSidekiq
|
|
6
|
+
# Runs a LUA script through whatever client `Sidekiq.redis` yields.
|
|
7
|
+
#
|
|
8
|
+
# redis-rb (Sidekiq <= 6) accepts `eval(script, keys:, argv:)`, but the
|
|
9
|
+
# real Sidekiq 7+ runtime yields a redis-client-backed CompatClient that
|
|
10
|
+
# has no `#eval` and forwards the keyword args as a nested array, raising
|
|
11
|
+
# `TypeError: Unsupported command argument type: Array` (issue #971).
|
|
12
|
+
#
|
|
13
|
+
# `evalsha(sha, keys_array, argv_array)` is the one form both clients
|
|
14
|
+
# expand to the flat `EVALSHA sha numkeys *keys *argv` shape, so we load
|
|
15
|
+
# the script once and call it by digest, reloading on NOSCRIPT.
|
|
16
|
+
module RedisScript
|
|
17
|
+
def self.call(redis, script, keys:, argv:)
|
|
18
|
+
reloaded = false
|
|
19
|
+
begin
|
|
20
|
+
shas[script] ||= redis.script(:load, script)
|
|
21
|
+
redis.evalsha(shas[script], keys, argv)
|
|
22
|
+
rescue StandardError => e
|
|
23
|
+
raise if reloaded || !e.message.to_s.include?('NOSCRIPT')
|
|
24
|
+
|
|
25
|
+
reloaded = true
|
|
26
|
+
shas.delete(script) # flushed cache or failover: reload and retry once
|
|
27
|
+
retry
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.shas
|
|
32
|
+
@shas ||= {}
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -10,6 +10,7 @@ require_relative '../../index'
|
|
|
10
10
|
module Chewy
|
|
11
11
|
class Strategy
|
|
12
12
|
class DelayedSidekiq
|
|
13
|
+
require_relative 'redis_script'
|
|
13
14
|
require_relative 'worker'
|
|
14
15
|
|
|
15
16
|
LUA_SCRIPT = <<~LUA
|
|
@@ -98,7 +99,7 @@ module Chewy
|
|
|
98
99
|
def postpone
|
|
99
100
|
::Sidekiq.redis do |redis|
|
|
100
101
|
# do the redis stuff in a single command to avoid concurrency issues
|
|
101
|
-
if
|
|
102
|
+
if RedisScript.call(redis, LUA_SCRIPT, keys: [timechunk_key, timechunks_key], argv: [serialize_data, at, ttl])
|
|
102
103
|
::Sidekiq::Client.push(
|
|
103
104
|
'queue' => sidekiq_queue,
|
|
104
105
|
'at' => at + margin,
|
|
@@ -39,7 +39,7 @@ module Chewy
|
|
|
39
39
|
options[:refresh] = !Chewy.disable_refresh_async if Chewy.disable_refresh_async
|
|
40
40
|
|
|
41
41
|
::Sidekiq.redis do |redis|
|
|
42
|
-
members =
|
|
42
|
+
members = RedisScript.call(redis, LUA_SCRIPT, keys: [], argv: [type, score, Scheduler::KEY_PREFIX])
|
|
43
43
|
|
|
44
44
|
# extract ids and fields & do the reset of records
|
|
45
45
|
ids, fields = extract_ids_and_fields(members)
|
data/lib/chewy/version.rb
CHANGED
data/lib/chewy.rb
CHANGED
|
@@ -102,6 +102,21 @@ module Chewy
|
|
|
102
102
|
Chewy.current[:chewy_client] ||= Chewy::ElasticClient.new
|
|
103
103
|
end
|
|
104
104
|
|
|
105
|
+
# Closes the current thread's client connections to Elasticsearch and
|
|
106
|
+
# drops the thread-local client, so the next `Chewy.client` call builds a
|
|
107
|
+
# fresh one.
|
|
108
|
+
#
|
|
109
|
+
# Useful in long-lived multi-threaded processes (e.g. Sidekiq) where the
|
|
110
|
+
# per-thread client would otherwise keep its connections open until the
|
|
111
|
+
# dead thread is garbage collected, leaking file descriptors.
|
|
112
|
+
def close_client
|
|
113
|
+
client = Chewy.current[:chewy_client]
|
|
114
|
+
return unless client
|
|
115
|
+
|
|
116
|
+
client.close
|
|
117
|
+
Chewy.current[:chewy_client] = nil
|
|
118
|
+
end
|
|
119
|
+
|
|
105
120
|
# Sends wait_for_status request to ElasticSearch with status
|
|
106
121
|
# defined in configuration.
|
|
107
122
|
#
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: chewy
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 8.
|
|
4
|
+
version: 8.4.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Toptal, LLC
|
|
@@ -174,6 +174,7 @@ files:
|
|
|
174
174
|
- lib/chewy/strategy/base.rb
|
|
175
175
|
- lib/chewy/strategy/bypass.rb
|
|
176
176
|
- lib/chewy/strategy/delayed_sidekiq.rb
|
|
177
|
+
- lib/chewy/strategy/delayed_sidekiq/redis_script.rb
|
|
177
178
|
- lib/chewy/strategy/delayed_sidekiq/scheduler.rb
|
|
178
179
|
- lib/chewy/strategy/delayed_sidekiq/worker.rb
|
|
179
180
|
- lib/chewy/strategy/lazy_sidekiq.rb
|
|
@@ -202,7 +203,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
202
203
|
- !ruby/object:Gem::Version
|
|
203
204
|
version: '0'
|
|
204
205
|
requirements: []
|
|
205
|
-
rubygems_version: 4.0.
|
|
206
|
+
rubygems_version: 4.0.14
|
|
206
207
|
specification_version: 4
|
|
207
208
|
summary: Elasticsearch ODM client wrapper
|
|
208
209
|
test_files: []
|