Skip to content

Instantly share code, notes, and snippets.

@basinilya
Last active September 17, 2017 10:05
Show Gist options
  • Save basinilya/26f07a1b945423f832674f419ce08d29 to your computer and use it in GitHub Desktop.
Save basinilya/26f07a1b945423f832674f419ce08d29 to your computer and use it in GitHub Desktop.
ConcurrentAuthenticator
package org.foo.concurrentauthenticator;
import java.net.Authenticator;
import java.net.PasswordAuthentication;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
/**
* java.net.Authenticator disallows simultaneous password prompting from different threads, because
* the callback runs inside a synchronized block. This implementation circumvents it by calling
* {@link Object#wait()} and losing the monitor ownership. The side effect is that the actual
* password prompt has to run in a separate thread.
*/
public abstract class ConcurrentAuthenticator extends Authenticator implements Cloneable {
protected abstract PasswordAuthentication getPasswordAuthentication(final Thread callerThread);
private final ExecutorService executor;
public ConcurrentAuthenticator() {
this(mkExecutor());
}
protected static ExecutorService mkExecutor() {
return Executors.newCachedThreadPool(new ThreadFactory() {
final ThreadFactory parent = Executors.defaultThreadFactory();
@Override
public Thread newThread(final Runnable r) {
final Thread res = parent.newThread(r);
res.setDaemon(true);
return res;
}
});
}
public ConcurrentAuthenticator(final ExecutorService executor) {
this.executor = executor;
}
protected PasswordAuthentication unlockAndGetPasswordAuthentication(final Thread callerThread,
final ConcurrentAuthenticator selfCopy) {
// On android-19 it's static synchronized or no synchronized at all
final Object lock = chooseLock(0, ConcurrentAuthenticator.this, Authenticator.class);
try {
class Task implements Callable<PasswordAuthentication> {
boolean done;
@Override
public PasswordAuthentication call() throws Exception {
try {
return selfCopy.getPasswordAuthentication(callerThread);
} finally {
synchronized (lock) {
done = true;
lock.notifyAll();
}
}
}
}
final Task task = new Task();
final Future<PasswordAuthentication> fut = executor.submit(task);
synchronized (lock) {
while (!task.done) {
lock.wait();
}
}
return fut.get();
} catch (final InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
} catch (final ExecutionException e) {
throw toRTE(e.getCause());
}
}
private static RuntimeException toRTE(final Throwable cause) {
if (cause instanceof RuntimeException) {
return (RuntimeException) cause;
}
if (cause instanceof Exception) {
return new RuntimeException(cause);
}
if (cause instanceof Error) {
throw (Error) cause;
}
throw new Error(cause);
}
private static Object chooseLock(final int index, final Object... candidates) {
if (index >= candidates.length) {
return candidates[0];
}
try {
final Object o = candidates[index];
o.notify();
return o;
} catch (final IllegalMonitorStateException e) {
return chooseLock(index + 1, candidates);
}
}
@Override
protected final PasswordAuthentication getPasswordAuthentication() {
try {
final ConcurrentAuthenticator selfCopy = (ConcurrentAuthenticator) clone();
return unlockAndGetPasswordAuthentication(Thread.currentThread(), selfCopy);
} catch (final CloneNotSupportedException e) {
throw new RuntimeException("this can't be", e);
}
}
@Override
public String toString() {
return "A [requestingHost=" + getRequestingHost() + ", requestingSite="
+ getRequestingSite() + ", requestingPort=" + getRequestingPort()
+ ", requestingProtocol=" + getRequestingProtocol() + ", requestingPrompt="
+ getRequestingPrompt() + ", requestingScheme=" + getRequestingScheme()
+ ", requestingURL=" + getRequestingURL() + ", requestorType=" + getRequestorType()
+ "]";
}
}
package org.foo.concurrentauthenticator;
import java.io.InputStream;
import java.net.PasswordAuthentication;
import java.net.URL;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ConcurrentAuthenticatorTest {
public static void main(final String[] args) throws Exception {
final ExecutorService executor = Executors.newCachedThreadPool();
((ThreadPoolExecutor) executor).setKeepAliveTime(3, TimeUnit.SECONDS);
final ConcurrentAuthenticator inst = new ConcurrentAuthenticatorUnsafe() {
@Override
protected PasswordAuthentication getPasswordAuthentication(final Thread callerThread) {
String path = null;
final URL u = getRequestingURL();
path = u == null ? getRequestingPrompt() : u.getPath();
System.out.println("requesting auth for " + path);
try {
Thread.sleep(2000);
} catch (final InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
System.out.println("got auth for " + path);
final String[] parts = path.split("/");
final String user = parts[parts.length - 2];
final String password = parts[parts.length - 1];
return new PasswordAuthentication(user, password.toCharArray());
}
};
ConcurrentAuthenticator.setDefault(inst);
try {
final Future<Void> fut1 = executor.submit(mkTest("user1", "password1"));
final Callable<Void> test2 = mkTest("user2", "password2");
// final Future<Void> fut2 = executor.submit(test2);
test2.call();
fut1.get();
// fut2.get();
} finally {
executor.shutdown();
}
// executor.awaitTermination(1, TimeUnit.MINUTES);
}
private static Callable<Void> mkTest(final String user, final String password) throws Exception {
return new Callable<Void>() {
@Override
public Void call() throws Exception {
final URL u =
new URL("http://httpbin.org/basic-auth/" + user + "/" + password + "");
downloadContent(u);
// requestAuth(u);
return null;
}
};
}
static void requestAuth(final URL u) {
if ("".length() == 0) {
ConcurrentAuthenticator.requestPasswordAuthentication(null, null, 0, null, u.getPath(),
null);
} else {
ConcurrentAuthenticator.requestPasswordAuthentication(null, null, 0, null, null, null,
u, null);
}
}
static void downloadContent(final URL u) throws Exception {
try (InputStream in = u.openStream()) {
int nb;
final byte[] buf = new byte[1024];
while ((nb = in.read(buf)) != -1) {
System.out.write(buf, 0, nb);
}
}
}
}
package org.foo.concurrentauthenticator;
import java.lang.reflect.Field;
import java.net.PasswordAuthentication;
import sun.misc.Unsafe;
/**
* java.net.Authenticator disallows simultaneous password prompting from different threads, because
* the callback runs inside a synchronized block. This implementation circumvents it by calling
* {@link Unsafe#monitorExit(Object)}
*/
@SuppressWarnings("restriction")
public abstract class ConcurrentAuthenticatorUnsafe extends ConcurrentAuthenticator {
private static final Unsafe UNSAFE;
static {
Unsafe uns = null;
try {
final Field singleoneInstanceField = Unsafe.class.getDeclaredField("theUnsafe");
singleoneInstanceField.setAccessible(true);
uns = (Unsafe) singleoneInstanceField.get(null);
} catch (final Exception e) {
e.printStackTrace();
}
UNSAFE = uns;
}
public ConcurrentAuthenticatorUnsafe() {
super(UNSAFE == null ? mkExecutor() : null);
}
@Override
protected PasswordAuthentication unlockAndGetPasswordAuthentication(final Thread callerThread,
final ConcurrentAuthenticator selfCopy) {
if (UNSAFE == null) {
return super.unlockAndGetPasswordAuthentication(callerThread, selfCopy);
} else {
final Class<?> authClazz = ConcurrentAuthenticator.class.getSuperclass();
final int n = unlock(ConcurrentAuthenticatorUnsafe.this);
final int m = unlock(authClazz);
try {
return selfCopy.getPasswordAuthentication(callerThread);
} catch (final Exception e) {
throw new RuntimeException(e);
} finally {
relock(authClazz, m);
relock(ConcurrentAuthenticatorUnsafe.this, n);
}
}
}
private static void relock(final Object o, final int times) {
int n = times;
for (; n > 0; n--) {
UNSAFE.monitorEnter(o);
}
}
private static int unlock(final Object o) {
int n = 0;
try {
for (;; n++) {
UNSAFE.monitorExit(o);
}
} catch (final IllegalMonitorStateException e) {
}
return n;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment