1/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17/**
18 * Hangs onto idle live connections for a little while
19 */
20
21package android.net.http;
22
23import org.apache.http.HttpHost;
24
25import android.os.SystemClock;
26
27/**
28 * {@hide}
29 */
30class IdleCache {
31
32    class Entry {
33        HttpHost mHost;
34        Connection mConnection;
35        long mTimeout;
36    };
37
38    private final static int IDLE_CACHE_MAX = 8;
39
40    /* Allow five consecutive empty queue checks before shutdown */
41    private final static int EMPTY_CHECK_MAX = 5;
42
43    /* six second timeout for connections */
44    private final static int TIMEOUT = 6 * 1000;
45    private final static int CHECK_INTERVAL = 2 * 1000;
46    private Entry[] mEntries = new Entry[IDLE_CACHE_MAX];
47
48    private int mCount = 0;
49
50    private IdleReaper mThread = null;
51
52    /* stats */
53    private int mCached = 0;
54    private int mReused = 0;
55
56    IdleCache() {
57        for (int i = 0; i < IDLE_CACHE_MAX; i++) {
58            mEntries[i] = new Entry();
59        }
60    }
61
62    /**
63     * Caches connection, if there is room.
64     * @return true if connection cached
65     */
66    synchronized boolean cacheConnection(
67            HttpHost host, Connection connection) {
68
69        boolean ret = false;
70
71        if (HttpLog.LOGV) {
72            HttpLog.v("IdleCache size " + mCount + " host "  + host);
73        }
74
75        if (mCount < IDLE_CACHE_MAX) {
76            long time = SystemClock.uptimeMillis();
77            for (int i = 0; i < IDLE_CACHE_MAX; i++) {
78                Entry entry = mEntries[i];
79                if (entry.mHost == null) {
80                    entry.mHost = host;
81                    entry.mConnection = connection;
82                    entry.mTimeout = time + TIMEOUT;
83                    mCount++;
84                    if (HttpLog.LOGV) mCached++;
85                    ret = true;
86                    if (mThread == null) {
87                        mThread = new IdleReaper();
88                        mThread.start();
89                    }
90                    break;
91                }
92            }
93        }
94        return ret;
95    }
96
97    synchronized Connection getConnection(HttpHost host) {
98        Connection ret = null;
99
100        if (mCount > 0) {
101            for (int i = 0; i < IDLE_CACHE_MAX; i++) {
102                Entry entry = mEntries[i];
103                HttpHost eHost = entry.mHost;
104                if (eHost != null && eHost.equals(host)) {
105                    ret = entry.mConnection;
106                    entry.mHost = null;
107                    entry.mConnection = null;
108                    mCount--;
109                    if (HttpLog.LOGV) mReused++;
110                    break;
111                }
112            }
113        }
114        return ret;
115    }
116
117    synchronized void clear() {
118        for (int i = 0; mCount > 0 && i < IDLE_CACHE_MAX; i++) {
119            Entry entry = mEntries[i];
120            if (entry.mHost != null) {
121                entry.mHost = null;
122                entry.mConnection.closeConnection();
123                entry.mConnection = null;
124                mCount--;
125            }
126        }
127    }
128
129    private synchronized void clearIdle() {
130        if (mCount > 0) {
131            long time = SystemClock.uptimeMillis();
132            for (int i = 0; i < IDLE_CACHE_MAX; i++) {
133                Entry entry = mEntries[i];
134                if (entry.mHost != null && time > entry.mTimeout) {
135                    entry.mHost = null;
136                    entry.mConnection.closeConnection();
137                    entry.mConnection = null;
138                    mCount--;
139                }
140            }
141        }
142    }
143
144    private class IdleReaper extends Thread {
145
146        public void run() {
147            int check = 0;
148
149            setName("IdleReaper");
150            android.os.Process.setThreadPriority(
151                    android.os.Process.THREAD_PRIORITY_BACKGROUND);
152            synchronized (IdleCache.this) {
153                while (check < EMPTY_CHECK_MAX) {
154                    try {
155                        IdleCache.this.wait(CHECK_INTERVAL);
156                    } catch (InterruptedException ex) {
157                    }
158                    if (mCount == 0) {
159                        check++;
160                    } else {
161                        check = 0;
162                        clearIdle();
163                    }
164                }
165                mThread = null;
166            }
167            if (HttpLog.LOGV) {
168                HttpLog.v("IdleCache IdleReaper shutdown: cached " + mCached +
169                          " reused " + mReused);
170                mCached = 0;
171                mReused = 0;
172            }
173        }
174    }
175}
176