CacheLRU.java 11.6 KB
Newer Older
1
/*
Thomas Mueller's avatar
Thomas Mueller committed
2
 * Copyright 2004-2009 H2 Group. Multiple-Licensed under the H2 License,
3 4 5 6 7 8 9
 * Version 1.0, and under the Eclipse Public License, Version 1.0
 * (http://h2database.com/html/license.html).
 * Initial Developer: H2 Group
 */
package org.h2.util;

import java.sql.SQLException;
10 11
import java.util.Map;
import java.util.WeakHashMap;
12 13 14 15 16 17 18 19 20 21

import org.h2.constant.SysProperties;
import org.h2.engine.Constants;
import org.h2.message.Message;

/**
 * A cache implementation based on the last recently used (LRU) algorithm.
 */
public class CacheLRU implements Cache {

22
    static final String TYPE_NAME = "LRU";
23 24

    private final CacheWriter writer;
25 26 27
    private final CacheObject head = new CacheHead();
    private final int len;
    private final int mask;
28 29 30 31 32
    private int maxSize;
    private CacheObject[] values;
    private int recordCount;
    private int sizeMemory;

33
    private CacheLRU(CacheWriter writer, int maxKb) {
34 35 36 37 38 39 40 41
        this.maxSize = maxKb * 1024 / 4;
        this.writer = writer;
        this.len = MathUtils.nextPowerOf2(maxSize / 64);
        this.mask = len - 1;
        MathUtils.checkPowerOf2(len);
        clear();
    }

42 43 44 45 46 47 48 49 50
    /**
     * Create a cache of the given type and size.
     *
     * @param writer the cache writer
     * @param cacheType the cache type
     * @param cacheSize the size
     * @return the cache object
     */
    public static Cache getCache(CacheWriter writer, String cacheType, int cacheSize) throws SQLException {
Thomas Mueller's avatar
Thomas Mueller committed
51
        Map<Integer, CacheObject> secondLevel = null;
52 53
        String prefix = null;
        if (cacheType.startsWith("SOFT_")) {
Thomas Mueller's avatar
Thomas Mueller committed
54
            secondLevel = new SoftHashMap<Integer, CacheObject>();
55 56 57
            cacheType = cacheType.substring("SOFT_".length());
            prefix = "SOFT_";
        } else if (cacheType.startsWith("WEAK_")) {
Thomas Mueller's avatar
Thomas Mueller committed
58
            secondLevel = new WeakHashMap<Integer, CacheObject>();
59 60 61 62
            cacheType = cacheType.substring("WEAK_".length());
            prefix = "WEAK_";
        }
        Cache cache;
63 64
        if ("TQ".equals(cacheType)) {
            cache = new CacheLRU(writer, cacheSize);
65 66 67 68 69 70 71 72 73 74 75
        } else if (CacheLRU.TYPE_NAME.equals(cacheType)) {
            cache = new CacheLRU(writer, cacheSize);
        } else {
            throw Message.getInvalidValueException(cacheType, "CACHE_TYPE");
        }
        if (secondLevel != null) {
            cache = new CacheSecondLevel(cache, prefix, secondLevel);
        }
        return cache;
    }

76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
    public void clear() {
        head.next = head.previous = head;
        // first set to null - avoiding out of memory
        values = null;
        values = new CacheObject[len];
        recordCount = 0;
        sizeMemory = 0;
    }

    public void put(CacheObject rec) throws SQLException {
        if (SysProperties.CHECK) {
            int pos = rec.getPos();
            for (int i = 0; i < rec.getBlockCount(); i++) {
                CacheObject old = find(pos + i);
                if (old != null) {
91
                    Message.throwInternalError("try to add a record twice pos:" + pos + " i:" + i);
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
                }
            }
        }
        int index = rec.getPos() & mask;
        rec.chained = values[index];
        values[index] = rec;
        recordCount++;
        sizeMemory += rec.getMemorySize();
        addToFront(rec);
        removeOldIfRequired();
    }

    public CacheObject update(int pos, CacheObject rec) throws SQLException {
        CacheObject old = find(pos);
        if (old == null) {
            put(rec);
        } else {
            if (SysProperties.CHECK) {
                if (old != rec) {
111
                    Message.throwInternalError("old!=record pos:" + pos + " old:" + old + " new:" + rec);
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
                }
            }
            removeFromLinkedList(rec);
            addToFront(rec);
        }
        return old;
    }

    private void removeOldIfRequired() throws SQLException {
        // a small method, to allow inlining
        if (sizeMemory >= maxSize) {
            removeOld();
        }
    }

    private void removeOld() throws SQLException {
        int i = 0;
Thomas Mueller's avatar
Thomas Mueller committed
129
        ObjectArray<CacheObject> changed = ObjectArray.newInstance();
130 131
        int mem = sizeMemory;
        int rc = recordCount;
132
        boolean flushed = false;
133
        CacheObject next = head.next;
134
        while (mem * 4 > maxSize * 3 && rc > Constants.CACHE_MIN_RECORDS) {
135 136
            CacheObject check = next;
            next = check.next;
137
            i++;
138 139 140 141 142 143 144 145 146 147 148
            if (i >= recordCount) {
                if (!flushed) {
                    writer.flushLog();
                    flushed = true;
                    i = 0;
                } else {
                    // can't remove any record, because the log is not written yet
                    // hopefully this does not happen frequently, but it can happen
                    writer.getTrace().info("Cannot remove records, cache size too small?");
                    break;
                }
149
            }
150
            if (SysProperties.CHECK && check == head) {
151
                Message.throwInternalError("try to remove head");
152 153 154 155
            }
            // we are not allowed to remove it if the log is not yet written
            // (because we need to log before writing the data)
            // also, can't write it if the record is pinned
156 157 158
            if (!check.canRemove()) {
                removeFromLinkedList(check);
                addToFront(check);
159 160
                continue;
            }
161 162 163 164
            rc--;
            mem -= check.getMemorySize();
            if (check.isChanged()) {
                changed.add(check);
165
            } else {
166
                remove(check.getPos());
167 168 169 170
            }
        }
        if (changed.size() > 0) {
            CacheObject.sort(changed);
171 172 173 174 175 176 177 178 179 180 181
            int max = maxSize;
            try {
                // temporary disable size checking,
                // to avoid stack overflow
                maxSize = Integer.MAX_VALUE;
                for (i = 0; i < changed.size(); i++) {
                    CacheObject rec = changed.get(i);
                    writer.writeBack(rec);
                }
            } finally {
                maxSize = max;
182
            }
183 184 185
            for (i = 0; i < changed.size(); i++) {
                CacheObject rec = changed.get(i);
                remove(rec.getPos());
186 187 188 189 190
                if (SysProperties.CHECK) {
                    if (rec.next != null) {
                        throw Message.throwInternalError();
                    }
                }
191
            }
192 193 194 195 196
        }
    }

    private void addToFront(CacheObject rec) {
        if (SysProperties.CHECK && rec == head) {
197
            Message.throwInternalError("try to move head");
198 199 200 201 202 203 204 205 206
        }
        rec.next = head;
        rec.previous = head.previous;
        rec.previous.next = rec;
        head.previous = rec;
    }

    private void removeFromLinkedList(CacheObject rec) {
        if (SysProperties.CHECK && rec == head) {
207
            Message.throwInternalError("try to remove head");
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
        }
        rec.previous.next = rec.next;
        rec.next.previous = rec.previous;
        // TODO cache: mystery: why is this required? needs more memory if we
        // don't do this
        rec.next = null;
        rec.previous = null;
    }

    public void remove(int pos) {
        int index = pos & mask;
        CacheObject rec = values[index];
        if (rec == null) {
            return;
        }
        if (rec.getPos() == pos) {
            values[index] = rec.chained;
        } else {
            CacheObject last;
            do {
                last = rec;
                rec = rec.chained;
                if (rec == null) {
                    return;
                }
            } while (rec.getPos() != pos);
            last.chained = rec.chained;
        }
        recordCount--;
        sizeMemory -= rec.getMemorySize();
        removeFromLinkedList(rec);
        if (SysProperties.CHECK) {
            rec.chained = null;
            if (find(pos) != null) {
242
                Message.throwInternalError("not removed!");
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292
            }
        }
    }

    public CacheObject find(int pos) {
        CacheObject rec = values[pos & mask];
        while (rec != null && rec.getPos() != pos) {
            rec = rec.chained;
        }
        return rec;
    }

    public CacheObject get(int pos) {
        CacheObject rec = find(pos);
        if (rec != null) {
            removeFromLinkedList(rec);
            addToFront(rec);
        }
        return rec;
    }

//    private void testConsistency() {
//        int s = size;
//        HashSet set = new HashSet();
//        for(int i=0; i<values.length; i++) {
//            Record rec = values[i];
//            if(rec == null) {
//                continue;
//            }
//            set.add(rec);
//            while(rec.chained != null) {
//                rec = rec.chained;
//                set.add(rec);
//            }
//        }
//        Record rec = head.next;
//        while(rec != head) {
//            set.add(rec);
//            rec = rec.next;
//        }
//        rec = head.previous;
//        while(rec != head) {
//            set.add(rec);
//            rec = rec.previous;
//        }
//        if(set.size() != size) {
//            System.out.println("size="+size+" but el.size="+set.size());
//        }
//    }

Thomas Mueller's avatar
Thomas Mueller committed
293
    public ObjectArray<CacheObject> getAllChanged() {
294 295 296
//        if(Database.CHECK) {
//            testConsistency();
//        }
Thomas Mueller's avatar
Thomas Mueller committed
297
        ObjectArray<CacheObject> list = ObjectArray.newInstance();
298 299 300 301
        CacheObject rec = head.next;
        while (rec != head) {
            if (rec.isChanged()) {
                list.add(rec);
302
            }
303
            rec = rec.next;
304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379
        }
        return list;
    }

    public void setMaxSize(int maxKb) throws SQLException {
        int newSize = maxKb * 1024 / 4;
        maxSize = newSize < 0 ? 0 : newSize;
        // can not resize, otherwise existing records are lost
        // resize(maxSize);
        removeOldIfRequired();
    }

    public String getTypeName() {
        return TYPE_NAME;
    }

    public int  getMaxSize() {
        return maxSize;
    }

    public int getSize() {
        return sizeMemory;
    }

}

// Unmaintained reference code (very old)
//import java.util.Iterator;
//import java.util.LinkedHashMap;
//import java.util.Map;
//
//public class Cache extends LinkedHashMap {
//
//    final static int MAX_SIZE = 1 << 10;
//    private CacheWriter writer;
//
//    public Cache(CacheWriter writer) {
//        super(16, (float) 0.75, true);
//        this.writer = writer;
//    }
//
//    protected boolean removeEldestEntry(Map.Entry eldest) {
//        if(size() <= MAX_SIZE) {
//            return false;
//        }
//        Record entry = (Record) eldest.getValue();
//        if(entry.getDeleted()) {
//            return true;
//        }
//        if(entry.isChanged()) {
//            try {
////System.out.println("cache write "+entry.getPos());
//                writer.writeBack(entry);
//            } catch(SQLException e) {
//                // printStackTrace not needed
//                // if we use our own hashtable
//                e.printStackTrace();
//            }
//        }
//        return true;
//    }
//
//    public void put(Record rec) {
//        put(new Integer(rec.getPos()), rec);
//    }
//
//    public Record get(int pos) {
//        return (Record)get(new Integer(pos));
//    }
//
//    public void remove(int pos) {
//        remove(new Integer(pos));
//    }
//
//    public ObjectArray getAllChanged() {
//        Iterator it = values().iterator();
Thomas Mueller's avatar
Thomas Mueller committed
380
//        ObjectArray list = ObjectArray.newInstance();
381 382 383 384 385 386 387 388 389 390
//        while(it.hasNext()) {
//            Record rec = (Record)it.next();
//            if(rec.isChanged()) {
//                list.add(rec);
//            }
//        }
//        return list;
//    }
//}