FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Add a simple deadlock detector for require locks. by headius · Pull Request #4095 · jruby/jruby · GitHub

/ jruby Public
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension .java  (1) .rb  (1) All 2 file types selected
Viewed files
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Unified
Split
Hide whitespace
Diff view
Unified
Split
Hide whitespace
    • delete_edge.rb
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URI;
Expand All @@ -44,7 +47,9 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
import java.util.jar.JarFile;
Expand Down Expand Up @@ -197,6 +202,7 @@ public LoadService(Ruby runtime) {
}

this.librarySearcher = new LibrarySearcher(this);
this.requireLocks = new RequireLocks(runtime);
}

/**
Expand Down Expand Up @@ -404,18 +410,32 @@ private enum RequireState {
LOADED, ALREADY_LOADED, CIRCULAR
}

private final RequireLocks requireLocks = new RequireLocks();
private final RequireLocks requireLocks;

private static final class RequireLocks {
private final ConcurrentHashMap<String, ReentrantLock> pool;
// global lock for require must be fair
//private final ReentrantLock globalLock;
private final ConcurrentHashMap<String, RequireLock> pool = new ConcurrentHashMap<>(8, 0.75f, 2);
private final Ruby runtime;

public enum LockResult { LOCKED, CIRCULAR }

private RequireLocks() {
this.pool = new ConcurrentHashMap<>(8, 0.75f, 2);
//this.globalLock = new ReentrantLock(true);
private class RequireLock extends ReentrantLock {
private final String file;

public RequireLock(String file) {
this.file = file;
}

public Thread getOwner() {
return super.getOwner();
}

public String getFile() {
return file;
}
}

private RequireLocks(Ruby runtime) {
this.runtime = runtime;
}

/**
Expand All @@ -429,21 +449,49 @@ private RequireLocks() {
* returns false without getting a lock. Otherwise true.
*/
private LockResult lock(String requireName) {
ReentrantLock lock = pool.get(requireName);
RequireLock lock = pool.get(requireName);

if (lock == null) {
ReentrantLock newLock = new ReentrantLock();
RequireLock newLock = new RequireLock(requireName);
lock = pool.putIfAbsent(requireName, newLock);
if (lock == null) lock = newLock;
}

if (lock.isHeldByCurrentThread()) return LockResult.CIRCULAR;

lock.lock();
return lockWithDeadlockDetection(lock);
}

return LockResult.LOCKED;
private LockResult lockWithDeadlockDetection(RequireLock lock) {
while (true) {
// Try to lock for a variable amount of time and check again.
// We use a variable amount of time to decrease the likelihood that the deadlocking threads will
// always appear to be running and not waiting on a lock.
try {
boolean locked = lock.tryLock(500 + (int)(random.nextDouble() * 100), TimeUnit.MILLISECONDS);

if (locked) return LockResult.LOCKED;
} catch (InterruptedException ie) {
// ignore, proceed back to deadlock check
}

// failed to acquire lock, see if there's a deadlock involved
Thread owner = lock.getOwner();
if (owner != null) {
// already locked, scan for deadlocks
ThreadMXBean tmxb = ManagementFactory.getThreadMXBean();
ThreadInfo ownerInfo = tmxb.getThreadInfo(new long[] {owner.getId()}, false, true)[0];

if (ownerInfo != null && ownerInfo.getLockOwnerId() == Thread.currentThread().getId()) {
// deadlock detected; owner thread is waiting on a lock we own
throw runtime.newLoadError("threads \"" + owner.getName() + "\" and \"" + Thread.currentThread().getName() + "\" will deadlock requiring \"" + lock.file + "\"");
}
}
}
}

private static final Random random = new Random(System.currentTimeMillis());

/**
* Unlock the lock for the specified requireName.
*
Expand Down
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# frozen_string_literal: true
require 'rubygems/resolver/molinillo/lib/molinillo/dependency_graph/action'
module Gem::Resolver::Molinillo
class DependencyGraph
# @!visibility private
# (see DependencyGraph#delete_edge)
class DeleteEdge < Action
# @!group Action

# (see Action.action_name)
def self.action_name
:delete_edge
end

# (see Action#up)
def up(graph)
edge = make_edge(graph)
edge.origin.outgoing_edges.delete(edge)
edge.destination.incoming_edges.delete(edge)
end

# (see Action#down)
def down(graph)
edge = make_edge(graph)
edge.origin.outgoing_edges << edge
edge.destination.incoming_edges << edge
edge
end

# @!group DeleteEdge

# @return [String] the name of the origin of the edge
attr_reader :origin_name

# @return [String] the name of the destination of the edge
attr_reader :destination_name

# @return [Object] the requirement that the edge represents
attr_reader :requirement

# @param [DependencyGraph] graph the graph to find vertices from
# @return [Edge] The edge this action adds
def make_edge(graph)
Edge.new(
graph.vertex_named(origin_name),
graph.vertex_named(destination_name),
requirement
)
end

# Initialize an action to add an edge to a dependency graph
# @param [String] origin_name the name of the origin of the edge
# @param [String] destination_name the name of the destination of the edge
# @param [Object] requirement the requirement that the edge represents
def initialize(origin_name, destination_name, requirement)
@origin_name = origin_name
@destination_name = destination_name
@requirement = requirement
end
end
end
end

Back | FazBrowse Home | New Git URL