236 lines
9.6 KiB
Java
236 lines
9.6 KiB
Java
/*
|
|
* Copyright (c) 2010, The Linux Foundation. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
* * Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* * Neither the name of The Linux Foundation nor
|
|
* the names of its contributors may be used to endorse or promote
|
|
* products derived from this software without specific prior written
|
|
* permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
* NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
|
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
|
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
|
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
|
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
package android.webkit;
|
|
|
|
import android.os.Handler;
|
|
import android.util.Log;
|
|
import android.os.Message;
|
|
import android.os.Process;
|
|
|
|
import java.lang.Thread;
|
|
import java.lang.InterruptedException;
|
|
import java.net.UnknownHostException;
|
|
import java.util.concurrent.Executors;
|
|
import java.util.concurrent.ExecutorService;
|
|
import java.util.concurrent.Semaphore;
|
|
import java.util.HashMap;
|
|
import java.util.Iterator;
|
|
import java.util.Map;
|
|
import android.content.Context;
|
|
|
|
final class DnsResolver {
|
|
|
|
private static final String LOGTAG = "webcore";
|
|
|
|
/* Max Thread pool size is taken from data published by google
|
|
* that mentions max hosts per webpage = 8 */
|
|
private final int MAX_DNS_RESOLVER_THREAD_POOL_SIZE = 8;
|
|
|
|
/* This number is derived by considering various factors such as OS DNS cache size,
|
|
* Browser Cache size and realistic need to spent time on DNS prefetch
|
|
*/
|
|
private final int MAX_PARALLEL_DNS_QUERIES_PER_PAGE = 64;
|
|
|
|
private volatile boolean mDnsResolverThreadPoolRunning = false;
|
|
|
|
private volatile boolean mShutDownInProgress = false;
|
|
|
|
private final Context mContext;
|
|
|
|
private static DnsResolver sDnsResolver;
|
|
|
|
private HashMap mHostNamesToBeResolved;
|
|
|
|
private ExecutorService mDnsResolverThreadPool;
|
|
|
|
private static int mDnsResolverRefCount = 0;
|
|
|
|
/* Lock to synchronize the access to threadpool */
|
|
private static Object mThreadPoolLock = new Object();
|
|
|
|
public static synchronized DnsResolver createDnsResolver(Context context) {
|
|
if(context == null) {
|
|
Log.e(LOGTAG, "Could not create DNS Resolver: NULL context");
|
|
return null;
|
|
}
|
|
if (DebugFlags.WEB_VIEW_CORE)
|
|
Log.v(LOGTAG, "Creating DNS resolver");
|
|
if (sDnsResolver == null) {
|
|
sDnsResolver = new DnsResolver(context);
|
|
}
|
|
++mDnsResolverRefCount;
|
|
return sDnsResolver;
|
|
}
|
|
|
|
public static DnsResolver getInstance() {
|
|
return sDnsResolver;
|
|
}
|
|
|
|
private DnsResolver(Context context) {
|
|
mContext = context;
|
|
createDnsResolverThreadPool();
|
|
}
|
|
|
|
private boolean shouldPerformDnsPrefetch() {
|
|
boolean status = true;
|
|
/* DNS prefetch should not be started if proxy is setup */
|
|
if(Network.getInstance(mContext) != null) {
|
|
status = !Network.getInstance(mContext).isValidProxySet();
|
|
if (DebugFlags.WEB_VIEW_CORE && !status)
|
|
Log.v(LOGTAG, "DNS prefetch not started because proxy is set");
|
|
}
|
|
return status;
|
|
}
|
|
|
|
private void createDnsResolverThreadPool() {
|
|
final Runnable startDnsResolver = new Runnable() {
|
|
public void run() {
|
|
/* DNS resolver priority should be same as of HTTP thread pool */
|
|
Process.setThreadPriority( android.os.Process.THREAD_PRIORITY_DEFAULT +
|
|
android.os.Process.THREAD_PRIORITY_LESS_FAVORABLE);
|
|
mDnsResolverThreadPool = Executors.newFixedThreadPool(MAX_DNS_RESOLVER_THREAD_POOL_SIZE);
|
|
mHostNamesToBeResolved = new HashMap();
|
|
boolean bResolvedPriorityHostNames = false;
|
|
int dnsQueryCounter = 0;
|
|
int numHosts = 0;
|
|
while(!mShutDownInProgress) {
|
|
synchronized(mHostNamesToBeResolved) {
|
|
numHosts = mHostNamesToBeResolved.size();
|
|
}
|
|
if(numHosts <= 0) {
|
|
try {
|
|
dnsQueryCounter = 0;
|
|
bResolvedPriorityHostNames = false;
|
|
mDnsResolverThreadPoolRunning = true;
|
|
synchronized(mThreadPoolLock) {
|
|
mThreadPoolLock.wait();
|
|
}
|
|
} catch(java.lang.InterruptedException e) {
|
|
}
|
|
}
|
|
else {
|
|
synchronized(mHostNamesToBeResolved) {
|
|
Iterator iterator = mHostNamesToBeResolved.entrySet().iterator();
|
|
while(iterator.hasNext() && mDnsResolverThreadPoolRunning && (dnsQueryCounter < MAX_PARALLEL_DNS_QUERIES_PER_PAGE)) {
|
|
Map.Entry entry = (Map.Entry) iterator.next();
|
|
final String hostName = (String)entry.getKey();
|
|
final String priority = (String)entry.getValue();
|
|
if( (!bResolvedPriorityHostNames && priority.equalsIgnoreCase("1")) ||
|
|
( bResolvedPriorityHostNames && priority.equalsIgnoreCase("0"))
|
|
) {
|
|
++dnsQueryCounter;
|
|
iterator.remove();
|
|
Runnable task = new Runnable() {
|
|
public void run() {
|
|
try {
|
|
java.net.InetAddress.getByName(hostName);
|
|
} catch(java.net.UnknownHostException e) {
|
|
}
|
|
}
|
|
};
|
|
mDnsResolverThreadPool.execute (task);
|
|
}
|
|
}
|
|
if(!mDnsResolverThreadPoolRunning || (dnsQueryCounter >= MAX_PARALLEL_DNS_QUERIES_PER_PAGE)) {
|
|
mHostNamesToBeResolved.clear();
|
|
}
|
|
bResolvedPriorityHostNames = (bResolvedPriorityHostNames) ? false:true;
|
|
}
|
|
}
|
|
}
|
|
mDnsResolverThreadPool.shutdown();
|
|
sDnsResolver = null;
|
|
}
|
|
};
|
|
Thread dnsResolver = new Thread(startDnsResolver);
|
|
dnsResolver.setName("DNS resolver");
|
|
dnsResolver.start();
|
|
}
|
|
|
|
public synchronized void destroyDnsResolver() {
|
|
if (DebugFlags.WEB_VIEW_CORE)
|
|
Log.v(LOGTAG, "Destroying DNS Resolver");
|
|
--mDnsResolverRefCount;
|
|
if(mDnsResolverRefCount == 0) {
|
|
mShutDownInProgress = true;
|
|
mDnsResolverThreadPoolRunning = false;
|
|
synchronized(mThreadPoolLock) {
|
|
mThreadPoolLock.notifyAll();
|
|
}
|
|
}
|
|
}
|
|
|
|
public void resolveDnsForHost(String hostName, String priority) {
|
|
if(hostName == null) {
|
|
return;
|
|
}
|
|
if(!shouldPerformDnsPrefetch())
|
|
return;
|
|
|
|
synchronized(mHostNamesToBeResolved) {
|
|
if(mHostNamesToBeResolved.size() > 0 ) {
|
|
return;
|
|
}
|
|
mHostNamesToBeResolved.put(hostName,priority);
|
|
}
|
|
resumeDnsResolverThreadPool();
|
|
}
|
|
|
|
public void resolveDnsForHostMap(HashMap hostMap) {
|
|
if(hostMap == null) {
|
|
return;
|
|
}
|
|
if(!shouldPerformDnsPrefetch())
|
|
return;
|
|
|
|
synchronized (mHostNamesToBeResolved) {
|
|
mHostNamesToBeResolved.putAll(hostMap);
|
|
}
|
|
resumeDnsResolverThreadPool();
|
|
}
|
|
|
|
/* pause will flush all pending DNS queries at the DNS resolver */
|
|
public void pauseDnsResolverThreadPool() {
|
|
mDnsResolverThreadPoolRunning = false;
|
|
}
|
|
|
|
/* resume will start DNS resolver executing DNS queries */
|
|
public void resumeDnsResolverThreadPool() {
|
|
mDnsResolverThreadPoolRunning = true;
|
|
synchronized(mThreadPoolLock) {
|
|
mThreadPoolLock.notifyAll();
|
|
}
|
|
}
|
|
|
|
/* returns the max number of DNS queries that can be made in background for a page */
|
|
public int getMaxParallelDnsQueryPerPage() {
|
|
return MAX_PARALLEL_DNS_QUERIES_PER_PAGE;
|
|
}
|
|
}
|