Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: jruby/jruby
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 69868055ddfa
Choose a base ref
...
head repository: jruby/jruby
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 4b6c24f3ee5f
Choose a head ref
  • 2 commits
  • 2 files changed
  • 2 contributors

Commits on Sep 2, 2015

  1. Close any open selectors when tearing down a runtime.

    This fixes a runtime leak in embedded scenarios where a runtime may
    never get garbage collected because it has a thread blocked on a
    select call. This leak exists in JRuby 1.7.x as well, but for some
    reason I'm seeing it more often in JRuby 9.x when running TorqueBox 3
    integration tests. Perhaps there is more IO that uses select in 9.x
    than 1.7.x?
    bbrowning committed Sep 2, 2015
    Copy the full SHA
    ce7d531 View commit details

Commits on Sep 22, 2015

  1. Merge branch 'selector-leak' of https://github.com/bbrowning/jruby in…

    …to bbrowning-selector-leak
    mkristian committed Sep 22, 2015
    Copy the full SHA
    4b6c24f View commit details
Showing with 54 additions and 9 deletions.
  1. +20 −3 core/src/main/java/org/jruby/util/io/SelectorPool.java
  2. +34 −6 spec/regression/embedded_runtime_leak_spec.rb
23 changes: 20 additions & 3 deletions core/src/main/java/org/jruby/util/io/SelectorPool.java
Original file line number Diff line number Diff line change
@@ -31,6 +31,7 @@

import java.io.IOException;
import java.nio.channels.Selector;
import java.util.ArrayList;
import java.util.List;

import java.nio.channels.spi.SelectorProvider;
@@ -52,6 +53,7 @@
*/
public class SelectorPool {
private final Map<SelectorProvider, List<Selector>> pool = new HashMap<SelectorProvider, List<Selector>>();
private final List<Selector> openSelectors = new ArrayList<Selector>();

/**
* Get a selector from the pool (or create a new one). Selectors come from
@@ -87,7 +89,8 @@ public synchronized void put(Selector selector) {
/**
* Clean up a pool.
*
* All selectors in a pool are closed and the pool gets empty.
* All selectors in a pool or handed out from the pool are closed
* and the pool gets emptied.
*
*/
public synchronized void cleanup() {
@@ -103,18 +106,32 @@ public synchronized void cleanup() {
}
}
pool.clear();

for (Selector selector : openSelectors) {
try {
selector.close();
} catch (IOException ioe) {
// ignore IOException at termination.
}
}
openSelectors.clear();
}

private Selector retrieveFromPool(SelectorProvider provider) throws IOException {
List<Selector> providerPool = pool.get(provider);
Selector selector;
if (providerPool != null && !providerPool.isEmpty()) {
return providerPool.remove(providerPool.size() - 1);
selector = providerPool.remove(providerPool.size() - 1);
} else {
selector = SelectorFactory.openWithRetryFrom(null, provider);
}

return SelectorFactory.openWithRetryFrom(null, provider);
openSelectors.add(selector);
return selector;
}

private void returnToPool(Selector selector) {
openSelectors.remove(selector);
if (selector.isOpen()) {
SelectorProvider provider = selector.provider();
List<Selector> providerPool = pool.get(provider);
40 changes: 34 additions & 6 deletions spec/regression/embedded_runtime_leak_spec.rb
Original file line number Diff line number Diff line change
@@ -1,20 +1,48 @@
describe "embedded runtimes" do
it "should not leak runtimes after tearing them down" do
num_runtimes = 10
create_runtimes(10) do |runtime|
# nothing to do actually - the runtimes will by default build up
# and OOM if they aren't being garbage collected
end
end

it "should not leak runtimes blocked on IO" do
create_runtimes(10) do |runtime|
latch = java.util.concurrent.CountDownLatch.new(1)
Thread.new do
latch.countDown
# we'll get an OOM if the blocking TCPServer#accept call below
# doesn't get interrupted during runtime teardown
runtime.evalScriptlet <<-EOS
require "socket"
server = TCPServer.new(0)
begin
server.accept
rescue Exception
end
EOS
end
latch.await # spawned thread has started
sleep 0.5 # give time for the new runtime to block on accept
end
end

def create_runtimes(num_runtimes)
mbean = java.lang.management.ManagementFactory.getMemoryMXBean
lambda do
expect do
num_runtimes.times do
instance = org.jruby.Ruby.newInstance
instance.evalScriptlet <<-EOS
runtime = org.jruby.Ruby.newInstance
runtime.evalScriptlet <<-EOS
# eat up some memory in each runtime
$arr = 500000.times.map { |i| "foobarbaz\#{i}" }
EOS
instance.tearDown(false)
yield runtime if block_given?
runtime.tearDown(false)
# Make sure GC can keep up
while mbean.getObjectPendingFinalizationCount > 0
sleep 0.2
end
end
end.should_not raise_error
end.not_to raise_error
end
end