Skip to content

Commit 15fb308

Browse files
authored
Cleanup gradle tasks and dependency installation (#10942)
- have `bootstrap` task do as little as possible: install gems in Gemfile.template that don't belong to groups - have test tasks depend on the `installTestGems` task instead of `bootstrap` - logstash es output is now a dependency because of license checking - fix out of memory problem in SharedHelpers.trap Also use release lockfile during installDefaultGems The release lockfile is only copied to Gemfile.lock if it doesn't exist. During the `installDefaultGems` task other plugin installation tasks already occurred, generating a lock file. This commit removes it before running the plugin installation.
1 parent 0c85857 commit 15fb308

File tree

4 files changed

+89
-45
lines changed

4 files changed

+89
-45
lines changed

Gemfile.template

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ gem "paquet", "~> 0.2"
1111
gem "pleaserun", "~>0.0.28"
1212
gem "rake", "~> 12"
1313
gem "ruby-progressbar", "~> 1"
14+
gem "logstash-output-elasticsearch"
1415
gem "childprocess", "~> 0.9", :group => :build
1516
gem "fpm", "~> 1.3.3", :group => :build
1617
gem "gems", "~> 1", :group => :build

build.gradle

Lines changed: 19 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -119,46 +119,24 @@ clean {
119119
delete "${projectDir}/build/rubyDependencies.csv"
120120
}
121121

122-
task bootstrap {}
123-
124-
project(":logstash-core") {
125-
["rubyTests", "test"].each { tsk ->
126-
tasks.getByPath(":logstash-core:" + tsk).configure {
127-
dependsOn bootstrap
128-
}
129-
}
130-
}
131-
132-
task installDefaultGems(dependsOn: downloadAndInstallJRuby) {
133-
inputs.files file("${projectDir}/Gemfile.template")
134-
inputs.files fileTree("${projectDir}/rakelib")
135-
inputs.files file("${projectDir}/versions.yml")
136-
outputs.file("${projectDir}/Gemfile")
137-
outputs.file("${projectDir}/Gemfile.lock")
138-
outputs.dir("${projectDir}/logstash-core/lib/jars")
139-
outputs.dir("${projectDir}/vendor/bundle/jruby/2.5.0")
122+
task bootstrap {
140123
doLast {
141-
gem(projectDir, buildDir, "rake", "12.3.1", "${projectDir}/vendor/bundle/jruby/2.5.0")
142-
gem(projectDir, buildDir, "json", "1.8.6", "${projectDir}/vendor/bundle/jruby/2.5.0")
143-
rake(projectDir, buildDir, 'plugin:install-default')
124+
rake(projectDir, buildDir, 'plugin:install-base')
144125
}
145126
}
146127

147128
def assemblyDeps = [downloadAndInstallJRuby, assemble] + subprojects.collect {
148129
it.tasks.findByName("assemble")
149130
}
150131

132+
task installDefaultGems(dependsOn: assemblyDeps) {
133+
doLast {
134+
rake(projectDir, buildDir, 'plugin:install-default')
135+
}
136+
}
137+
151138
task installTestGems(dependsOn: assemblyDeps) {
152-
inputs.files file("${projectDir}/Gemfile.template")
153-
inputs.files fileTree("${projectDir}/rakelib")
154-
inputs.files file("${projectDir}/versions.yml")
155-
outputs.file("${projectDir}/Gemfile")
156-
outputs.file("${projectDir}/Gemfile.lock")
157-
outputs.dir("${projectDir}/logstash-core/lib/jars")
158-
outputs.dir("${projectDir}/vendor/bundle/jruby/2.5.0")
159139
doLast {
160-
gem(projectDir, buildDir, "rake", "12.3.1", "${projectDir}/vendor/bundle/jruby/2.5.0")
161-
gem(projectDir, buildDir, "json", "1.8.6", "${projectDir}/vendor/bundle/jruby/2.5.0")
162140
rake(projectDir, buildDir, 'plugin:install-development-dependencies')
163141
}
164142
}
@@ -175,7 +153,6 @@ task assembleTarDistribution(dependsOn: assemblyDeps) {
175153
inputs.files fileTree("${projectDir}/x-pack")
176154
outputs.files file("${buildDir}/logstash-${project.version}-SNAPSHOT.tar.gz")
177155
doLast {
178-
gem(projectDir, buildDir, "rake", "12.3.1", "${projectDir}/vendor/bundle/jruby/2.5.0")
179156
rake(projectDir, buildDir, 'artifact:tar')
180157
}
181158
}
@@ -226,6 +203,14 @@ task assembleOssZipDistribution(dependsOn: assemblyDeps) {
226203
}
227204
}
228205

206+
project(":logstash-core") {
207+
["rubyTests", "test"].each { tsk ->
208+
tasks.getByPath(":logstash-core:" + tsk).configure {
209+
dependsOn installTestGems
210+
}
211+
}
212+
}
213+
229214
def logstashBuildDir = "${buildDir}/logstash-${project.version}-SNAPSHOT"
230215

231216
task unpackTarDistribution(dependsOn: assembleTarDistribution, type: Copy) {
@@ -318,13 +303,13 @@ task generateLicenseReportInputs() {
318303
}
319304
}
320305

321-
task generatePluginsVersion(dependsOn: bootstrap) {
306+
task generatePluginsVersion(dependsOn: installDefaultGems) {
322307
doLast {
323308
rake(projectDir, buildDir, 'generate_plugins_version')
324309
}
325310
}
326311

327-
bootstrap.dependsOn installTestGems
312+
bootstrap.dependsOn assemblyDeps
328313

329314
runIntegrationTests.shouldRunAfter tasks.getByPath(":logstash-core:test")
330315
check.dependsOn runIntegrationTests
@@ -421,7 +406,7 @@ if (!oss) {
421406
project(":logstash-xpack") {
422407
["rubyTests", "rubyIntegrationTests", "test"].each { tsk ->
423408
tasks.getByPath(":logstash-xpack:" + tsk).configure {
424-
dependsOn bootstrap
409+
dependsOn installTestGems
425410
}
426411
}
427412
tasks.getByPath(":logstash-xpack:rubyIntegrationTests").configure {

rakelib/plugin.rake

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,18 @@ namespace "plugin" do
88
LogStash::PluginManager::Main.run("bin/logstash-plugin", ["install"] + args)
99
end
1010

11+
task "install-base" => "bootstrap" do
12+
puts("[plugin:install-base] Installing base dependencies")
13+
install_plugins("--development", "--preserve")
14+
task.reenable # Allow this task to be run again
15+
end
16+
17+
def remove_lockfile
18+
if ::File.exist?(LogStash::Environment::LOCKFILE)
19+
::File.delete(LogStash::Environment::LOCKFILE)
20+
end
21+
end
22+
1123
task "install-development-dependencies" => "bootstrap" do
1224
puts("[plugin:install-development-dependencies] Installing development dependencies")
1325
install_plugins("--development", "--preserve")
@@ -26,6 +38,8 @@ namespace "plugin" do
2638

2739
task "install-default" => "bootstrap" do
2840
puts("[plugin:install-default] Installing default plugins")
41+
42+
remove_lockfile # because we want to use the release lockfile
2943
install_plugins("--no-verify", "--preserve", *LogStash::RakeLib::DEFAULT_PLUGINS)
3044

3145
task.reenable # Allow this task to be run again

rakelib/plugins_docs_dependencies.rake

Lines changed: 55 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -83,15 +83,7 @@ class PluginVersionWorking
8383

8484
plugins_to_install.each do |plugin|
8585
begin
86-
builder = Bundler::Dsl.new
87-
gemfile = LogStash::Gemfile.new(File.new(LogStash::Environment::GEMFILE_PATH, "r+")).load
88-
gemfile.update(plugin)
89-
90-
builder.eval_gemfile("bundler file", gemfile.generate())
91-
definition = builder.to_definition(LogStash::Environment::LOCKFILE, {})
92-
definition.resolve_remotely!
93-
from = PLUGIN_METADATA.fetch(plugin, {}).fetch("default-plugins", false) ? :default : :missing
94-
extract_versions(definition, successful_dependencies, from)
86+
try_plugin(plugin, successful_dependencies)
9587
puts "Successfully installed: #{plugin}"
9688
rescue => e
9789
puts "Failed to install: #{plugin}"
@@ -107,13 +99,25 @@ class PluginVersionWorking
10799
puts "Failures: #{failures.size}/#{total}"
108100
end
109101

102+
def try_plugin(plugin, successful_dependencies)
103+
builder = Bundler::Dsl.new
104+
gemfile = LogStash::Gemfile.new(File.new(LogStash::Environment::GEMFILE_PATH, "r+")).load
105+
gemfile.update(plugin)
106+
107+
builder.eval_gemfile("bundler file", gemfile.generate())
108+
definition = builder.to_definition(LogStash::Environment::LOCKFILE, {})
109+
definition.resolve_remotely!
110+
from = PLUGIN_METADATA.fetch(plugin, {}).fetch("default-plugins", false) ? :default : :missing
111+
extract_versions(definition, successful_dependencies, from)
112+
end
113+
110114
def extract_versions(definition, dependencies, from)
111115
#definition.specs.select { |spec| spec.metadata && spec.metadata["logstash_plugin"] == "true" }.each do |spec|
112116
#
113117
# Bundler doesn't seem to provide us with `spec.metadata` for remotely
114118
# discovered plugins (via rubygems.org api), so we have to choose by
115119
# a name pattern instead of by checking spec.metadata["logstash_plugin"]
116-
definition.specs.select { |spec| spec.name =~ /^logstash-(input|filter|output|codec)-/ }.each do |spec|
120+
definition.resolve.select { |spec| spec.name =~ /^logstash-(input|filter|output|codec)-/ }.each do |spec|
117121
dependencies[spec.name] ||= []
118122
dependencies[spec.name] << VersionDependencies.new(spec.version, from)
119123
end
@@ -135,5 +139,45 @@ task :generate_plugins_version do
135139
require "pluginmanager/gemfile"
136140
require "bootstrap/environment"
137141

142+
# This patch comes after an investigation of `generate_plugins_version`
143+
# causing OOM during `./gradlew generatePluginsVersion`.
144+
# Why does this patch fix the issue? Hang on, this is going to be wild ride:
145+
# In this rake task we compute a manifest that tells us, for each logstash plugin,
146+
# what is the latest version that can be installed.
147+
# We do this by (again for each plugin):
148+
# * adding the plugin to the current Gemfile
149+
# * instantiate a `Bundler::Dsl` instance with said Gemfile
150+
# * retrieve a Bundler::Definition by passing in the Gemfile.lock
151+
# * call `definition.resolve_remotely!
152+
#
153+
# Now, these repeated calls to `resolve_remotely!` on new instances of Definitions
154+
# cause the out of memory. Resolving remote dependencies uses Bundler::Worker instances
155+
# who trap the SIGINT signal in their `initializer` [1]. This shared helper method creates a closure that is
156+
# passed to `Signal.trap`, and capture the return [2], which is the previous proc (signal handler).
157+
# Since the variable that stores the return from `Signal.trap` is present in the binding, multiple calls
158+
# to this helper cause each new closures to reference the previous one. The size of each binding
159+
# accumulates and OOM occurs after 70-100 iterations.
160+
# This is easy to replicate by looping over `Bundler::SharedHelpers.trap("INT") { 1 }`.
161+
#
162+
# This workaround removes the capture of the previous binding. Not calling all the previous handlers
163+
# may cause some threads to not be cleaned up, but this rake task has a short life so everything
164+
# ends up being cleaned up on exit anyway.
165+
# We're confining this patch to this task only as this is the only place where we need to resolve
166+
# dependencies many many times.
167+
#
168+
# You're still here? You're awesome :) Thanks for reading!
169+
#
170+
# [1] https://github.com/bundler/bundler/blob/d9d75807196b91f454de48d5afd0c43b395243a3/lib/bundler/worker.rb#L29
171+
# [2] https://github.com/bundler/bundler/blob/d9d75807196b91f454de48d5afd0c43b395243a3/lib/bundler/shared_helpers.rb#L173
172+
module ::Bundler
173+
module SharedHelpers
174+
def trap(signal, override = false, &block)
175+
Signal.trap(signal) do
176+
block.call
177+
end
178+
end
179+
end
180+
end
181+
138182
PluginVersionWorking.new.generate
139-
end
183+
end

0 commit comments

Comments
 (0)