Skip to content

Commit

Permalink
Avoid circularity through class loading in circularity lock locking m…
Browse files Browse the repository at this point in the history
…echanism.
  • Loading branch information
raphw committed Jul 11, 2024
1 parent 15af295 commit 4606274
Showing 1 changed file with 98 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2571,47 +2571,100 @@ public void release() {
}

/**
* A default implementation of a circularity lock. Since class loading already synchronizes on a class loader,
* it suffices to apply a thread-local lock.
* A circularity lock that surrounds the locking mechanism with a global lock to prevent that the
* locking mechanism itself loads classes and causes a circularity issue.
*/
class Default implements CircularityLock {
abstract class WithInnerClassLoadingLock implements CircularityLock {

/**
* A map of threads to an unused boolean to emulate a thread-local state without using
* thread locals. This avoids using thread-local maps and does not interfere with Java
* fibers in case that an instrumentation is executed from a virtual thread where thread
* locals are not permitted.
* The default size of the global class loading lock array.
*/
private final ConcurrentMap<Thread, Boolean> threads = new ConcurrentHashMap<Thread, Boolean>();
protected static int DEFAULT_SIZE = 100;

/**
* An additional global lock that avoids circularity errors cause by class loading
* by the locking mechanism.
*/
private final AtomicBoolean open = new AtomicBoolean();
private final AtomicBoolean[] lock;

/**
* Creates a default circularity lock.
* Creates a circularity lock with a global outer lock.
*
* @param size The amount of locks used in parallel or {@code 0} if no global locks should be used.
*/
public Default() {
open.compareAndSet(false, true); // trigger expected class loading
protected WithInnerClassLoadingLock(int size) {
lock = new AtomicBoolean[size];
for (int index = 0; index < size; index++) {
AtomicBoolean lock = new AtomicBoolean();
lock.compareAndSet(false, true); // use same method as in the locking code.
this.lock[index] = lock;
}
}

/**
* {@inheritDoc}
*/
public boolean acquire() {
if (open.compareAndSet(true, false)) {
if (lock.length == 0) {
return doAcquire();
}
AtomicBoolean lock = this.lock.length == 1
? this.lock[0]
: this.lock[System.identityHashCode(Thread.currentThread()) % this.lock.length];
if (lock.compareAndSet(true, false)) {
try {
return threads.putIfAbsent(Thread.currentThread(), true) == null;
return doAcquire();
} finally {
open.set(true);
lock.set(true);
}
} else {
return false;
}
}

/**
* Acquires the actual lock for the current thread.
*
* @return {@code true} if the lock was acquired successfully, {@code false} if it is already hold.
*/
protected abstract boolean doAcquire();
}

/**
* A default implementation of a circularity lock. Since class loading already synchronizes on a class loader,
* it suffices to apply a thread-local lock.
*/
class Default extends WithInnerClassLoadingLock {

/**
* A map of threads to an unused boolean to emulate a thread-local state without using
* thread locals. This avoids using thread-local maps and does not interfere with Java
* fibers in case that an instrumentation is executed from a virtual thread where thread
* locals are not permitted.
*/
private final ConcurrentMap<Thread, Boolean> threads = new ConcurrentHashMap<Thread, Boolean>();

/**
* Creates a default lock with a default size for the amount of global locks.
*/
public Default() {
super(WithInnerClassLoadingLock.DEFAULT_SIZE);
}

/**
* Creates a default lock with the supplied number of global locks.
*
* @param size The amount of locks used in parallel or {@code 0} if no global locks should be used.
*/
public Default(int size) {
super(size);
}

@Override
protected boolean doAcquire() {
return threads.putIfAbsent(Thread.currentThread(), true) == null;
}

/**
* {@inheritDoc}
*/
Expand All @@ -2633,7 +2686,7 @@ protected boolean isLocked() {
* A circularity lock that holds a global monitor and does not permit concurrent access.
*/
@HashCodeAndEqualsPlugin.Enhance
class Global implements CircularityLock {
class Global extends WithInnerClassLoadingLock {

/**
* The lock to hold.
Expand All @@ -2651,46 +2704,53 @@ class Global implements CircularityLock {
private final TimeUnit timeUnit;

/**
* An additional global lock that avoids circularity errors cause by class loading
* by the locking mechanism.
* Creates a new global circularity lock that does not wait for a release and a
* default size for the amount of global locks.
*/
public Global() {
this(DEFAULT_SIZE);
}

/**
* Creates a new global circularity lock with a default size for the amount of global locks.
*
* @param time The time to wait for the lock.
* @param timeUnit The time's time unit.
*/
private final AtomicBoolean open = new AtomicBoolean();
public Global(long time, TimeUnit timeUnit) {
this(DEFAULT_SIZE, time, timeUnit);
}

/**
* Creates a new global circularity lock that does not wait for a release.
*
* @param size The amount of locks used in parallel or {@code 0} if no global locks should be used.
*/
public Global() {
this(0, TimeUnit.MILLISECONDS);
public Global(int size) {
this(size, 0, TimeUnit.MILLISECONDS);
}

/**
* Creates a new global circularity lock.
*
* @param size The amount of locks used in parallel or {@code 0} if no global locks should be used.
* @param time The time to wait for the lock.
* @param timeUnit The time's time unit.
*/
public Global(long time, TimeUnit timeUnit) {
public Global(int size, long time, TimeUnit timeUnit) {
super(size);
lock = new ReentrantLock();
this.time = time;
this.timeUnit = timeUnit;
open.compareAndSet(false, true); // trigger expected class loading
}

/**
* {@inheritDoc}
*/
public boolean acquire() {
if (open.compareAndSet(true, false)) {
try {
return time == 0
? lock.tryLock()
: lock.tryLock(time, timeUnit);
} catch (InterruptedException ignored) {
return false;
} finally {
open.set(true);
}
} else {
@Override
protected boolean doAcquire() {
try {
return time == 0
? lock.tryLock()
: lock.tryLock(time, timeUnit);
} catch (InterruptedException ignored) {
return false;
}
}
Expand Down

0 comments on commit 4606274

Please sign in to comment.