sidekiq 6.5.12 → 7.2.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (109) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +224 -20
  3. data/README.md +43 -35
  4. data/bin/multi_queue_bench +271 -0
  5. data/bin/sidekiq +3 -8
  6. data/bin/sidekiqload +204 -118
  7. data/bin/sidekiqmon +3 -0
  8. data/lib/sidekiq/api.rb +187 -135
  9. data/lib/sidekiq/capsule.rb +127 -0
  10. data/lib/sidekiq/cli.rb +59 -75
  11. data/lib/sidekiq/client.rb +66 -37
  12. data/lib/sidekiq/component.rb +4 -1
  13. data/lib/sidekiq/config.rb +287 -0
  14. data/lib/sidekiq/deploy.rb +62 -0
  15. data/lib/sidekiq/embedded.rb +61 -0
  16. data/lib/sidekiq/fetch.rb +11 -14
  17. data/lib/sidekiq/job.rb +371 -10
  18. data/lib/sidekiq/job_logger.rb +2 -2
  19. data/lib/sidekiq/job_retry.rb +36 -18
  20. data/lib/sidekiq/job_util.rb +51 -15
  21. data/lib/sidekiq/launcher.rb +71 -65
  22. data/lib/sidekiq/logger.rb +2 -27
  23. data/lib/sidekiq/manager.rb +9 -11
  24. data/lib/sidekiq/metrics/query.rb +7 -4
  25. data/lib/sidekiq/metrics/shared.rb +8 -7
  26. data/lib/sidekiq/metrics/tracking.rb +27 -21
  27. data/lib/sidekiq/middleware/chain.rb +19 -18
  28. data/lib/sidekiq/middleware/current_attributes.rb +52 -20
  29. data/lib/sidekiq/monitor.rb +16 -3
  30. data/lib/sidekiq/paginator.rb +2 -2
  31. data/lib/sidekiq/processor.rb +46 -51
  32. data/lib/sidekiq/rails.rb +15 -10
  33. data/lib/sidekiq/redis_client_adapter.rb +23 -66
  34. data/lib/sidekiq/redis_connection.rb +15 -117
  35. data/lib/sidekiq/scheduled.rb +22 -23
  36. data/lib/sidekiq/testing.rb +32 -41
  37. data/lib/sidekiq/transaction_aware_client.rb +11 -5
  38. data/lib/sidekiq/version.rb +2 -1
  39. data/lib/sidekiq/web/action.rb +8 -3
  40. data/lib/sidekiq/web/application.rb +108 -15
  41. data/lib/sidekiq/web/csrf_protection.rb +10 -7
  42. data/lib/sidekiq/web/helpers.rb +52 -38
  43. data/lib/sidekiq/web.rb +17 -16
  44. data/lib/sidekiq/worker_compatibility_alias.rb +13 -0
  45. data/lib/sidekiq.rb +76 -274
  46. data/sidekiq.gemspec +12 -10
  47. data/web/assets/javascripts/application.js +39 -0
  48. data/web/assets/javascripts/base-charts.js +106 -0
  49. data/web/assets/javascripts/dashboard-charts.js +182 -0
  50. data/web/assets/javascripts/dashboard.js +10 -232
  51. data/web/assets/javascripts/metrics.js +151 -115
  52. data/web/assets/stylesheets/application-dark.css +4 -0
  53. data/web/assets/stylesheets/application-rtl.css +10 -89
  54. data/web/assets/stylesheets/application.css +45 -298
  55. data/web/locales/ar.yml +70 -70
  56. data/web/locales/cs.yml +62 -62
  57. data/web/locales/da.yml +60 -53
  58. data/web/locales/de.yml +65 -65
  59. data/web/locales/el.yml +2 -7
  60. data/web/locales/en.yml +78 -70
  61. data/web/locales/es.yml +68 -68
  62. data/web/locales/fa.yml +65 -65
  63. data/web/locales/fr.yml +81 -67
  64. data/web/locales/gd.yml +99 -0
  65. data/web/locales/he.yml +65 -64
  66. data/web/locales/hi.yml +59 -59
  67. data/web/locales/it.yml +53 -53
  68. data/web/locales/ja.yml +67 -69
  69. data/web/locales/ko.yml +52 -52
  70. data/web/locales/lt.yml +66 -66
  71. data/web/locales/nb.yml +61 -61
  72. data/web/locales/nl.yml +52 -52
  73. data/web/locales/pl.yml +45 -45
  74. data/web/locales/pt-br.yml +79 -69
  75. data/web/locales/pt.yml +51 -51
  76. data/web/locales/ru.yml +67 -66
  77. data/web/locales/sv.yml +53 -53
  78. data/web/locales/ta.yml +60 -60
  79. data/web/locales/uk.yml +62 -61
  80. data/web/locales/ur.yml +64 -64
  81. data/web/locales/vi.yml +67 -67
  82. data/web/locales/zh-cn.yml +20 -18
  83. data/web/locales/zh-tw.yml +10 -1
  84. data/web/views/_footer.erb +17 -2
  85. data/web/views/_job_info.erb +18 -2
  86. data/web/views/_metrics_period_select.erb +12 -0
  87. data/web/views/_paging.erb +2 -0
  88. data/web/views/_poll_link.erb +1 -1
  89. data/web/views/_summary.erb +7 -7
  90. data/web/views/busy.erb +46 -35
  91. data/web/views/dashboard.erb +26 -5
  92. data/web/views/filtering.erb +7 -0
  93. data/web/views/metrics.erb +46 -24
  94. data/web/views/metrics_for_job.erb +41 -69
  95. data/web/views/morgue.erb +5 -9
  96. data/web/views/queue.erb +10 -14
  97. data/web/views/queues.erb +9 -3
  98. data/web/views/retries.erb +5 -9
  99. data/web/views/scheduled.erb +12 -13
  100. metadata +44 -38
  101. data/lib/sidekiq/delay.rb +0 -43
  102. data/lib/sidekiq/extensions/action_mailer.rb +0 -48
  103. data/lib/sidekiq/extensions/active_record.rb +0 -43
  104. data/lib/sidekiq/extensions/class_methods.rb +0 -43
  105. data/lib/sidekiq/extensions/generic_proxy.rb +0 -33
  106. data/lib/sidekiq/metrics/deploy.rb +0 -47
  107. data/lib/sidekiq/worker.rb +0 -370
  108. data/web/assets/javascripts/graph.js +0 -16
  109. /data/{LICENSE → LICENSE.txt} +0 -0
metadata CHANGED
@@ -1,72 +1,74 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sidekiq
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.5.12
4
+ version: 7.2.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Perham
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-10-10 00:00:00.000000000 Z
11
+ date: 2024-04-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: redis
14
+ name: redis-client
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "<"
18
- - !ruby/object:Gem::Version
19
- version: '5'
20
17
  - - ">="
21
18
  - !ruby/object:Gem::Version
22
- version: 4.5.0
19
+ version: 0.19.0
23
20
  type: :runtime
24
21
  prerelease: false
25
22
  version_requirements: !ruby/object:Gem::Requirement
26
23
  requirements:
27
- - - "<"
28
- - !ruby/object:Gem::Version
29
- version: '5'
30
24
  - - ">="
31
25
  - !ruby/object:Gem::Version
32
- version: 4.5.0
26
+ version: 0.19.0
33
27
  - !ruby/object:Gem::Dependency
34
28
  name: connection_pool
35
29
  requirement: !ruby/object:Gem::Requirement
36
30
  requirements:
37
- - - "<"
38
- - !ruby/object:Gem::Version
39
- version: '3'
40
31
  - - ">="
41
32
  - !ruby/object:Gem::Version
42
- version: 2.2.5
33
+ version: 2.3.0
43
34
  type: :runtime
44
35
  prerelease: false
45
36
  version_requirements: !ruby/object:Gem::Requirement
46
37
  requirements:
47
- - - "<"
48
- - !ruby/object:Gem::Version
49
- version: '3'
50
38
  - - ">="
51
39
  - !ruby/object:Gem::Version
52
- version: 2.2.5
40
+ version: 2.3.0
53
41
  - !ruby/object:Gem::Dependency
54
42
  name: rack
55
43
  requirement: !ruby/object:Gem::Requirement
56
44
  requirements:
57
- - - "~>"
45
+ - - ">="
58
46
  - !ruby/object:Gem::Version
59
- version: '2.0'
47
+ version: 2.2.4
60
48
  type: :runtime
61
49
  prerelease: false
62
50
  version_requirements: !ruby/object:Gem::Requirement
63
51
  requirements:
64
- - - "~>"
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: 2.2.4
55
+ - !ruby/object:Gem::Dependency
56
+ name: concurrent-ruby
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "<"
60
+ - !ruby/object:Gem::Version
61
+ version: '2'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "<"
65
67
  - !ruby/object:Gem::Version
66
- version: '2.0'
68
+ version: '2'
67
69
  description: Simple, efficient background processing for Ruby.
68
70
  email:
69
- - mperham@gmail.com
71
+ - info@contribsys.com
70
72
  executables:
71
73
  - sidekiq
72
74
  - sidekiqmon
@@ -74,8 +76,9 @@ extensions: []
74
76
  extra_rdoc_files: []
75
77
  files:
76
78
  - Changes.md
77
- - LICENSE
79
+ - LICENSE.txt
78
80
  - README.md
81
+ - bin/multi_queue_bench
79
82
  - bin/sidekiq
80
83
  - bin/sidekiqload
81
84
  - bin/sidekiqmon
@@ -85,14 +88,13 @@ files:
85
88
  - lib/generators/sidekiq/templates/job_test.rb.erb
86
89
  - lib/sidekiq.rb
87
90
  - lib/sidekiq/api.rb
91
+ - lib/sidekiq/capsule.rb
88
92
  - lib/sidekiq/cli.rb
89
93
  - lib/sidekiq/client.rb
90
94
  - lib/sidekiq/component.rb
91
- - lib/sidekiq/delay.rb
92
- - lib/sidekiq/extensions/action_mailer.rb
93
- - lib/sidekiq/extensions/active_record.rb
94
- - lib/sidekiq/extensions/class_methods.rb
95
- - lib/sidekiq/extensions/generic_proxy.rb
95
+ - lib/sidekiq/config.rb
96
+ - lib/sidekiq/deploy.rb
97
+ - lib/sidekiq/embedded.rb
96
98
  - lib/sidekiq/fetch.rb
97
99
  - lib/sidekiq/job.rb
98
100
  - lib/sidekiq/job_logger.rb
@@ -101,7 +103,6 @@ files:
101
103
  - lib/sidekiq/launcher.rb
102
104
  - lib/sidekiq/logger.rb
103
105
  - lib/sidekiq/manager.rb
104
- - lib/sidekiq/metrics/deploy.rb
105
106
  - lib/sidekiq/metrics/query.rb
106
107
  - lib/sidekiq/metrics/shared.rb
107
108
  - lib/sidekiq/metrics/tracking.rb
@@ -129,17 +130,18 @@ files:
129
130
  - lib/sidekiq/web/csrf_protection.rb
130
131
  - lib/sidekiq/web/helpers.rb
131
132
  - lib/sidekiq/web/router.rb
132
- - lib/sidekiq/worker.rb
133
+ - lib/sidekiq/worker_compatibility_alias.rb
133
134
  - sidekiq.gemspec
134
135
  - web/assets/images/apple-touch-icon.png
135
136
  - web/assets/images/favicon.ico
136
137
  - web/assets/images/logo.png
137
138
  - web/assets/images/status.png
138
139
  - web/assets/javascripts/application.js
140
+ - web/assets/javascripts/base-charts.js
139
141
  - web/assets/javascripts/chart.min.js
140
142
  - web/assets/javascripts/chartjs-plugin-annotation.min.js
143
+ - web/assets/javascripts/dashboard-charts.js
141
144
  - web/assets/javascripts/dashboard.js
142
- - web/assets/javascripts/graph.js
143
145
  - web/assets/javascripts/metrics.js
144
146
  - web/assets/stylesheets/application-dark.css
145
147
  - web/assets/stylesheets/application-rtl.css
@@ -155,6 +157,7 @@ files:
155
157
  - web/locales/es.yml
156
158
  - web/locales/fa.yml
157
159
  - web/locales/fr.yml
160
+ - web/locales/gd.yml
158
161
  - web/locales/he.yml
159
162
  - web/locales/hi.yml
160
163
  - web/locales/it.yml
@@ -176,6 +179,7 @@ files:
176
179
  - web/locales/zh-tw.yml
177
180
  - web/views/_footer.erb
178
181
  - web/views/_job_info.erb
182
+ - web/views/_metrics_period_select.erb
179
183
  - web/views/_nav.erb
180
184
  - web/views/_paging.erb
181
185
  - web/views/_poll_link.erb
@@ -184,6 +188,7 @@ files:
184
188
  - web/views/busy.erb
185
189
  - web/views/dashboard.erb
186
190
  - web/views/dead.erb
191
+ - web/views/filtering.erb
187
192
  - web/views/layout.erb
188
193
  - web/views/metrics.erb
189
194
  - web/views/metrics_for_job.erb
@@ -199,10 +204,11 @@ licenses:
199
204
  - LGPL-3.0
200
205
  metadata:
201
206
  homepage_uri: https://sidekiq.org
202
- bug_tracker_uri: https://github.com/mperham/sidekiq/issues
203
- documentation_uri: https://github.com/mperham/sidekiq/wiki
204
- changelog_uri: https://github.com/mperham/sidekiq/blob/main/Changes.md
205
- source_code_uri: https://github.com/mperham/sidekiq
207
+ bug_tracker_uri: https://github.com/sidekiq/sidekiq/issues
208
+ documentation_uri: https://github.com/sidekiq/sidekiq/wiki
209
+ changelog_uri: https://github.com/sidekiq/sidekiq/blob/main/Changes.md
210
+ source_code_uri: https://github.com/sidekiq/sidekiq
211
+ rubygems_mfa_required: 'true'
206
212
  post_install_message:
207
213
  rdoc_options: []
208
214
  require_paths:
@@ -211,7 +217,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
211
217
  requirements:
212
218
  - - ">="
213
219
  - !ruby/object:Gem::Version
214
- version: 2.5.0
220
+ version: 2.7.0
215
221
  required_rubygems_version: !ruby/object:Gem::Requirement
216
222
  requirements:
217
223
  - - ">="
data/lib/sidekiq/delay.rb DELETED
@@ -1,43 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Sidekiq # :nodoc:
4
- module Extensions
5
- def self.enable_delay!
6
- warn "Sidekiq's Delayed Extensions will be removed in Sidekiq 7.0", uplevel: 1
7
-
8
- if defined?(::ActiveSupport)
9
- require "sidekiq/extensions/active_record"
10
- require "sidekiq/extensions/action_mailer"
11
-
12
- # Need to patch Psych so it can autoload classes whose names are serialized
13
- # in the delayed YAML.
14
- Psych::Visitors::ToRuby.prepend(Sidekiq::Extensions::PsychAutoload)
15
-
16
- ActiveSupport.on_load(:active_record) do
17
- include Sidekiq::Extensions::ActiveRecord
18
- end
19
- ActiveSupport.on_load(:action_mailer) do
20
- extend Sidekiq::Extensions::ActionMailer
21
- end
22
- end
23
-
24
- require "sidekiq/extensions/class_methods"
25
- Module.__send__(:include, Sidekiq::Extensions::Klass)
26
- end
27
-
28
- module PsychAutoload
29
- def resolve_class(klass_name)
30
- return nil if !klass_name || klass_name.empty?
31
- # constantize
32
- names = klass_name.split("::")
33
- names.shift if names.empty? || names.first.empty?
34
-
35
- names.inject(Object) do |constant, name|
36
- constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
37
- end
38
- rescue NameError
39
- super
40
- end
41
- end
42
- end
43
- end
@@ -1,48 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "sidekiq/extensions/generic_proxy"
4
-
5
- module Sidekiq
6
- module Extensions
7
- ##
8
- # Adds +delay+, +delay_for+ and +delay_until+ methods to ActionMailer to offload arbitrary email
9
- # delivery to Sidekiq.
10
- #
11
- # @example
12
- # UserMailer.delay.send_welcome_email(new_user)
13
- # UserMailer.delay_for(5.days).send_welcome_email(new_user)
14
- # UserMailer.delay_until(5.days.from_now).send_welcome_email(new_user)
15
- class DelayedMailer
16
- include Sidekiq::Worker
17
-
18
- def perform(yml)
19
- (target, method_name, args) = YAML.load(yml)
20
- msg = target.public_send(method_name, *args)
21
- # The email method can return nil, which causes ActionMailer to return
22
- # an undeliverable empty message.
23
- if msg
24
- msg.deliver_now
25
- else
26
- raise "#{target.name}##{method_name} returned an undeliverable mail object"
27
- end
28
- end
29
- end
30
-
31
- module ActionMailer
32
- def sidekiq_delay(options = {})
33
- Proxy.new(DelayedMailer, self, options)
34
- end
35
-
36
- def sidekiq_delay_for(interval, options = {})
37
- Proxy.new(DelayedMailer, self, options.merge("at" => Time.now.to_f + interval.to_f))
38
- end
39
-
40
- def sidekiq_delay_until(timestamp, options = {})
41
- Proxy.new(DelayedMailer, self, options.merge("at" => timestamp.to_f))
42
- end
43
- alias_method :delay, :sidekiq_delay
44
- alias_method :delay_for, :sidekiq_delay_for
45
- alias_method :delay_until, :sidekiq_delay_until
46
- end
47
- end
48
- end
@@ -1,43 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "sidekiq/extensions/generic_proxy"
4
-
5
- module Sidekiq
6
- module Extensions
7
- ##
8
- # Adds +delay+, +delay_for+ and +delay_until+ methods to ActiveRecord to offload instance method
9
- # execution to Sidekiq.
10
- #
11
- # @example
12
- # User.recent_signups.each { |user| user.delay.mark_as_awesome }
13
- #
14
- # Please note, this is not recommended as this will serialize the entire
15
- # object to Redis. Your Sidekiq jobs should pass IDs, not entire instances.
16
- # This is here for backwards compatibility with Delayed::Job only.
17
- class DelayedModel
18
- include Sidekiq::Worker
19
-
20
- def perform(yml)
21
- (target, method_name, args) = YAML.load(yml)
22
- target.__send__(method_name, *args)
23
- end
24
- end
25
-
26
- module ActiveRecord
27
- def sidekiq_delay(options = {})
28
- Proxy.new(DelayedModel, self, options)
29
- end
30
-
31
- def sidekiq_delay_for(interval, options = {})
32
- Proxy.new(DelayedModel, self, options.merge("at" => Time.now.to_f + interval.to_f))
33
- end
34
-
35
- def sidekiq_delay_until(timestamp, options = {})
36
- Proxy.new(DelayedModel, self, options.merge("at" => timestamp.to_f))
37
- end
38
- alias_method :delay, :sidekiq_delay
39
- alias_method :delay_for, :sidekiq_delay_for
40
- alias_method :delay_until, :sidekiq_delay_until
41
- end
42
- end
43
- end
@@ -1,43 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "sidekiq/extensions/generic_proxy"
4
-
5
- module Sidekiq
6
- module Extensions
7
- ##
8
- # Adds `delay`, `delay_for` and `delay_until` methods to all Classes to offload class method
9
- # execution to Sidekiq.
10
- #
11
- # @example
12
- # User.delay.delete_inactive
13
- # Wikipedia.delay.download_changes_for(Date.today)
14
- #
15
- class DelayedClass
16
- include Sidekiq::Worker
17
-
18
- def perform(yml)
19
- (target, method_name, args) = YAML.load(yml)
20
- target.__send__(method_name, *args)
21
- end
22
- end
23
-
24
- module Klass
25
- def sidekiq_delay(options = {})
26
- Proxy.new(DelayedClass, self, options)
27
- end
28
-
29
- def sidekiq_delay_for(interval, options = {})
30
- Proxy.new(DelayedClass, self, options.merge("at" => Time.now.to_f + interval.to_f))
31
- end
32
-
33
- def sidekiq_delay_until(timestamp, options = {})
34
- Proxy.new(DelayedClass, self, options.merge("at" => timestamp.to_f))
35
- end
36
- alias_method :delay, :sidekiq_delay
37
- alias_method :delay_for, :sidekiq_delay_for
38
- alias_method :delay_until, :sidekiq_delay_until
39
- end
40
- end
41
- end
42
-
43
- Module.__send__(:include, Sidekiq::Extensions::Klass) unless defined?(::Rails)
@@ -1,33 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "yaml"
4
-
5
- module Sidekiq
6
- module Extensions
7
- SIZE_LIMIT = 8_192
8
-
9
- class Proxy < BasicObject
10
- def initialize(performable, target, options = {})
11
- @performable = performable
12
- @target = target
13
- @opts = options.transform_keys(&:to_s)
14
- end
15
-
16
- def method_missing(name, *args)
17
- # Sidekiq has a limitation in that its message must be JSON.
18
- # JSON can't round trip real Ruby objects so we use YAML to
19
- # serialize the objects to a String. The YAML will be converted
20
- # to JSON and then deserialized on the other side back into a
21
- # Ruby object.
22
- obj = [@target, name, args]
23
- marshalled = ::YAML.dump(obj)
24
- if marshalled.size > SIZE_LIMIT
25
- ::Sidekiq.logger.warn { "#{@target}.#{name} job argument is #{marshalled.bytesize} bytes, you should refactor it to reduce the size" }
26
- end
27
- @performable.client_push({"class" => @performable,
28
- "args" => [marshalled],
29
- "display_class" => "#{@target}.#{name}"}.merge(@opts))
30
- end
31
- end
32
- end
33
- end
@@ -1,47 +0,0 @@
1
- require "sidekiq"
2
- require "time"
3
-
4
- # This file is designed to be required within the user's
5
- # deployment script; it should need a bare minimum of dependencies.
6
- #
7
- # require "sidekiq/metrics/deploy"
8
- # gitdesc = `git log -1 --format="%h %s"`.strip
9
- # d = Sidekiq::Metrics::Deploy.new
10
- # d.mark(label: gitdesc)
11
- #
12
- # Note that you cannot mark more than once per minute. This is a feature, not a bug.
13
- module Sidekiq
14
- module Metrics
15
- class Deploy
16
- MARK_TTL = 90 * 24 * 60 * 60 # 90 days
17
-
18
- def initialize(pool = Sidekiq.redis_pool)
19
- @pool = pool
20
- end
21
-
22
- def mark(at: Time.now, label: "")
23
- # we need to round the timestamp so that we gracefully
24
- # handle an excepted common error in marking deploys:
25
- # having every process mark its deploy, leading
26
- # to N marks for each deploy. Instead we round the time
27
- # to the minute so that multple marks within that minute
28
- # will all naturally rollup into one mark per minute.
29
- whence = at.utc
30
- floor = Time.utc(whence.year, whence.month, whence.mday, whence.hour, whence.min, 0)
31
- datecode = floor.strftime("%Y%m%d")
32
- key = "#{datecode}-marks"
33
- @pool.with do |c|
34
- c.pipelined do |pipe|
35
- pipe.hsetnx(key, floor.iso8601, label)
36
- pipe.expire(key, MARK_TTL)
37
- end
38
- end
39
- end
40
-
41
- def fetch(date = Time.now.utc.to_date)
42
- datecode = date.strftime("%Y%m%d")
43
- @pool.with { |c| c.hgetall("#{datecode}-marks") }
44
- end
45
- end
46
- end
47
- end