Skip to content

Commit 933dd83

Browse files
author
Glyn Normington
committed
Improved OOM kill support
Passing -XX:OnOutOfMemoryError='kill -9 %p' to Java via the standard Play application start script cannot be done. Bash ruins whatever combination of escapes are passed in and typically java fails saying that the option -9 is invalid. See http://stackoverflow.com/questions/5792049/xxonoutofmemoryerror- kill-9-p-problem for general, if inconclusive, discussion of the general problem. To work around this problem, introduce a killjava script which OpenJdk.compile places in the buildpack diagnostics directory. Specify this script on -XX:OnOutOfMemoryError. Also, improve test coverage. [#51899933]
1 parent 2ee51e7 commit 933dd83

5 files changed

Lines changed: 146 additions & 58 deletions

File tree

lib/java_buildpack/buildpack.rb

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ def initialize(app_dir)
4646
:app_dir => app_dir,
4747
:java_home => java_home,
4848
:java_opts => java_opts,
49-
:configuration => Buildpack.configuration(app_dir, jre)
49+
:configuration => Buildpack.configuration(app_dir, jre),
50+
:diagnostics => {:directory => @@diagnostics_dir, :log_file => @@buildpack_log_file}
5051
})
5152
end
5253

@@ -55,7 +56,8 @@ def initialize(app_dir)
5556
:app_dir => app_dir,
5657
:java_home => java_home,
5758
:java_opts => java_opts,
58-
:configuration => Buildpack.configuration(app_dir, framework)
59+
:configuration => Buildpack.configuration(app_dir, framework),
60+
:diagnostics => {:directory => @@diagnostics_dir, :log_file => @@buildpack_log_file}
5961
})
6062
end
6163

@@ -64,7 +66,8 @@ def initialize(app_dir)
6466
:app_dir => app_dir,
6567
:java_home => java_home,
6668
:java_opts => java_opts,
67-
:configuration => Buildpack.configuration(app_dir, container)
69+
:configuration => Buildpack.configuration(app_dir, container),
70+
:diagnostics => {:directory => @@diagnostics_dir, :log_file => @@buildpack_log_file}
6871
})
6972
end
7073

@@ -133,10 +136,14 @@ def self.log(log_title, log_data)
133136

134137
COMPONENTS_CONFIG = '../../config/components.yml'.freeze
135138

139+
DIAGNOSTICS_DIRECTORY = 'buildpack-diagnostics'.freeze
140+
141+
LOG_FILE_NAME = 'buildpack.log'.freeze
142+
136143
def self.create_log_file(app_dir)
137-
@@diagnostics_dir = File.expand_path("buildpack-diagnostics", app_dir)
144+
@@diagnostics_dir = File.expand_path(DIAGNOSTICS_DIRECTORY, app_dir)
138145
FileUtils.mkdir_p @@diagnostics_dir
139-
@@buildpack_log_file = File.expand_path("buildpack.log", @@diagnostics_dir)
146+
@@buildpack_log_file = File.expand_path(LOG_FILE_NAME, @@diagnostics_dir)
140147

141148
# Create new log file and write current time into it.
142149
File.open(@@buildpack_log_file, 'a') do |log_file|

lib/java_buildpack/jre/openjdk.rb

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ def initialize(context)
3737
@app_dir = context[:app_dir]
3838
@java_opts = context[:java_opts]
3939
@configuration = context[:configuration]
40+
@diagnostics_dir = context[:diagnostics][:directory]
4041
@version, @uri = OpenJdk.find_openjdk(@configuration)
4142

4243
context[:java_home].concat JAVA_HOME
@@ -61,18 +62,21 @@ def compile
6162
puts "(#{(Time.now - download_start_time).duration})"
6263
expand file
6364
end
65+
copy_resources
6466
end
6567

6668
# Build Java memory options and places then in +context[:java_opts]+
6769
#
6870
# @return [void]
6971
def release
70-
@java_opts << "-XX:OnOutOfMemoryError='kill -9 %p'"
72+
@java_opts << "-XX:OnOutOfMemoryError=#{@diagnostics_dir}/killjava"
7173
@java_opts.concat memory(@configuration)
7274
end
7375

7476
private
7577

78+
RESOURCES = '../../../resources/openjdk/diagnostics'.freeze
79+
7680
JAVA_HOME = '.java'.freeze
7781

7882
KEY_MEMORY_HEURISTICS = 'memory_heuristics'
@@ -116,6 +120,11 @@ def pre_8
116120
@version < JavaBuildpack::Util::TokenizedVersion.new("1.8.0")
117121
end
118122

123+
def copy_resources
124+
resources = File.expand_path(RESOURCES, File.dirname(__FILE__))
125+
system "cp -r #{resources}/* #{@diagnostics_dir}/."
126+
end
127+
119128
end
120129

121130
end
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#!/usr/bin/env ruby
2+
# Cloud Foundry Java Buildpack
3+
# Copyright (c) 2013 the original author or authors.
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
require 'time'
18+
19+
$stdout.sync = true
20+
21+
module Kill
22+
23+
def Kill.log(log_title, log_data)
24+
log_file = "#{File.dirname(__FILE__)}/buildpack.log"
25+
File.open(log_file, 'a') do |log_file|
26+
log_file.write "#{log_title} @ #{Kill.time_in_millis}::\n"
27+
log_file.write(log_data)
28+
end
29+
end
30+
31+
def Kill.time_in_millis
32+
Time.now.xmlschema(3).sub(/T/, ' ')
33+
end
34+
35+
end
36+
37+
begin
38+
Kill.log("#{__FILE__} attempting to kill Java processes",
39+
`pkill -9 -a -l -f .*-XX:OnOutOfMemoryError=.*#{File.basename(__FILE__)}`)
40+
rescue => e
41+
Kill.log("#{__FILE__} failed with exception", "#{e.inspect}, #{e.backtrace}\n")
42+
abort e.message
43+
end
44+

spec/java_buildpack/buildpack_spec.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,18 @@ module JavaBuildpack
9898

9999
expect(payload).to eq({'addons' => [], 'config_vars' => {}, 'default_process_types' => { 'web' => 'test-command' }}.to_yaml)
100100
end
101+
102+
it 'should load configuration file matching JRE class name' do
103+
stub_jre1.stub(:detect).and_return('stub-jre-1')
104+
File.stub(:exists?).with(File.expand_path('config/stubjre1.yml')).and_return(true)
105+
File.stub(:exists?).with(File.expand_path('config/stubjre2.yml')).and_return(false)
106+
File.stub(:exists?).with(File.expand_path('config/stubframework1.yml')).and_return(false)
107+
File.stub(:exists?).with(File.expand_path('config/stubframework2.yml')).and_return(false)
108+
File.stub(:exists?).with(File.expand_path('config/stubcontainer1.yml')).and_return(false)
109+
File.stub(:exists?).with(File.expand_path('config/stubcontainer2.yml')).and_return(false)
110+
YAML.stub(:load_file).with(File.expand_path('config/stubjre1.yml')).and_return('x' => 'y')
111+
buildpack.detect
112+
end
101113
end
102114

103115
end

spec/java_buildpack/jre/openjdk_spec.rb

Lines changed: 68 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,19 @@ module JavaBuildpack::Jre
3333
end
3434

3535
it 'should detect with id of openjdk-<version>' do
36-
JavaBuildpack::Repository::ConfiguredItem.stub(:find_item).and_return(DETAILS_PRE_8)
36+
Dir.mktmpdir do |root|
37+
JavaBuildpack::Repository::ConfiguredItem.stub(:find_item).and_return(DETAILS_PRE_8)
3738

38-
detected = OpenJdk.new(
39-
:app_dir => '',
40-
:java_home => '',
41-
:java_opts => [],
42-
:configuration => {}
43-
).detect
39+
detected = OpenJdk.new(
40+
:app_dir => '',
41+
:java_home => '',
42+
:java_opts => [],
43+
:configuration => {},
44+
:diagnostics => {:directory => root}
45+
).detect
4446

45-
expect(detected).to eq('openjdk-1.7.0')
47+
expect(detected).to eq('openjdk-1.7.0')
48+
end
4649
end
4750

4851
it 'should extract Java from a GZipped TAR' do
@@ -52,10 +55,11 @@ module JavaBuildpack::Jre
5255
application_cache.stub(:get).with('test-uri').and_yield(File.open('spec/fixtures/stub-java.tar.gz'))
5356

5457
OpenJdk.new(
55-
:app_dir => root,
56-
:configuration => {},
57-
:java_home => '',
58-
:java_opts => []
58+
:app_dir => root,
59+
:configuration => {},
60+
:java_home => '',
61+
:java_opts => [],
62+
:diagnostics => {:directory => root}
5963
).compile
6064

6165
java = File.join(root, '.java', 'bin', 'java')
@@ -64,57 +68,69 @@ module JavaBuildpack::Jre
6468
end
6569

6670
it 'adds the JAVA_HOME to java_home' do
67-
JavaBuildpack::Repository::ConfiguredItem.stub(:find_item).and_return(DETAILS_PRE_8)
68-
69-
java_home = ''
70-
OpenJdk.new(
71-
:app_dir => '/application-directory',
72-
:java_home => java_home,
73-
:java_opts => [],
74-
:configuration => {}
75-
)
71+
Dir.mktmpdir do |root|
72+
JavaBuildpack::Repository::ConfiguredItem.stub(:find_item).and_return(DETAILS_PRE_8)
7673

77-
expect(java_home).to eq('.java')
74+
java_home = ''
75+
OpenJdk.new(
76+
:app_dir => '/application-directory',
77+
:java_home => java_home,
78+
:java_opts => [],
79+
:configuration => {},
80+
:diagnostics => {:directory => root}
81+
)
82+
83+
expect(java_home).to eq('.java')
84+
end
7885
end
7986

8087
it 'should fail when ConfiguredItem.find_item fails' do
81-
JavaBuildpack::Repository::ConfiguredItem.stub(:find_item).and_raise('test error')
82-
expect { OpenJdk.new(
83-
:app_dir => '',
84-
:java_home => '',
85-
:java_opts => [],
86-
:configuration => {}
87-
).detect }.to raise_error(/OpenJDK\ JRE\ error:\ test\ error/)
88+
Dir.mktmpdir do |root|
89+
JavaBuildpack::Repository::ConfiguredItem.stub(:find_item).and_raise('test error')
90+
expect { OpenJdk.new(
91+
:app_dir => '',
92+
:java_home => '',
93+
:java_opts => [],
94+
:configuration => {},
95+
:diagnostics => {:directory => root}
96+
).detect }.to raise_error(/OpenJDK\ JRE\ error:\ test\ error/)
97+
end
8898
end
8999

90100
it 'should add memory options to java_opts' do
91-
JavaBuildpack::Repository::ConfiguredItem.stub(:find_item).and_return(DETAILS_PRE_8)
92-
MemoryHeuristicsOpenJDKPre8.stub(:new).and_return(memory_heuristic)
93-
94-
java_opts = []
95-
OpenJdk.new(
96-
:app_dir => '/application-directory',
97-
:java_home => '',
98-
:java_opts => java_opts,
99-
:configuration => {}
100-
).release
101-
102-
expect(java_opts).to include('opt-1')
103-
expect(java_opts).to include('opt-2')
101+
Dir.mktmpdir do |root|
102+
JavaBuildpack::Repository::ConfiguredItem.stub(:find_item).and_return(DETAILS_PRE_8)
103+
MemoryHeuristicsOpenJDKPre8.stub(:new).and_return(memory_heuristic)
104+
105+
java_opts = []
106+
OpenJdk.new(
107+
:app_dir => '/application-directory',
108+
:java_home => '',
109+
:java_opts => java_opts,
110+
:configuration => {},
111+
:diagnostics => {:directory => root}
112+
).release
113+
114+
expect(java_opts).to include('opt-1')
115+
expect(java_opts).to include('opt-2')
116+
end
104117
end
105118

106119
it 'adds OnOutOfMemoryError to java_opts' do
107-
JavaBuildpack::Repository::ConfiguredItem.stub(:find_item).and_return(DETAILS_PRE_8)
108-
109-
java_opts = []
110-
OpenJdk.new(
111-
:app_dir => '',
112-
:java_home => '',
113-
:java_opts => java_opts,
114-
:configuration => {}
115-
).release
120+
Dir.mktmpdir do |root|
121+
JavaBuildpack::Repository::ConfiguredItem.stub(:find_item).and_return(DETAILS_PRE_8)
116122

117-
expect(java_opts.join(' ')).to match(/-XX:OnOutOfMemoryError='kill -9 %p'/)
123+
java_opts = []
124+
OpenJdk.new(
125+
:app_dir => '',
126+
:java_home => '',
127+
:java_opts => java_opts,
128+
:configuration => {},
129+
:diagnostics => {:directory => root}
130+
).release
131+
132+
expect(java_opts.join(' ')).to match(/-XX:OnOutOfMemoryError=#{root}\/killjava/)
133+
end
118134
end
119135

120136
end

0 commit comments

Comments
 (0)