Changeset 1a8847d


Ignore:
Timestamp:
Apr 20, 2016 12:41:45 AM (4 years ago)
Author:
zzz <zzz@…>
Branches:
master
Children:
4d2c227
Parents:
e9cf4c2
Message:

Blockfile: Add method to change serialization schema for a skiplist
Fix delIndex() method, broken and previously unused
Improve javadocs
BlockfileNamingService?: New database version 4, allows for
multiple destinations per hostname
Disallow database version higher than supported

Location:
core/java/src/net
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • core/java/src/net/i2p/client/naming/BlockfileNamingService.java

    re9cf4c2 r1a8847d  
    5555 * "%%__INFO__%%" is the master database skiplist, containing one entry:
    5656 *     "info": a Properties, serialized with DataHelper functions:
    57  *             "version": "2"
     57 *             "version": "4"
    5858 *             "created": Java long time (ms)
    5959 *             "upgraded": Java long time (ms) (as of database version 2)
     
    7575 * The keys/values in these skiplists are as follows:
    7676 *      key: a UTF-8 String
    77  *      value: a DestEntry, which is a Properties (serialized with DataHelper)
    78  *             followed by a Destination (serialized as usual).
     77 *      value: a DestEntry, which is:
     78 *             a one-byte count of the Properties/Destination pairs to follow
     79 *               (as of database version 4, otherwise one)
     80 *             that many pairs of:
     81 *               Properties (serialized with DataHelper)
     82 *               Destination (serialized as usual).
    7983 *
    8084 *
     
    99103    private volatile boolean _isClosed;
    100104    private final boolean _readOnly;
     105    private String _version = "0";
    101106    private boolean _needsUpgrade;
    102107
    103108    private static final Serializer _infoSerializer = new PropertiesSerializer();
    104109    private static final Serializer _stringSerializer = new UTF8StringBytes();
    105     private static final Serializer _destSerializer = new DestEntrySerializer();
     110    private static final Serializer _destSerializerV1 = new DestEntrySerializer();
     111    private static final Serializer _destSerializerV4 = new DestEntrySerializerV4();
     112    // upgrade(), initExisting(), and initNew() will change this to _destSerializerV4
     113    private volatile Serializer _destSerializer = _destSerializerV1;
    106114    private static final Serializer _hashIndexSerializer = new IntBytes();
    107115
     
    117125    private static final String PROP_CREATED = "created";
    118126    private static final String PROP_UPGRADED = "upgraded";
    119     private static final String VERSION = "3";
     127    private static final String VERSION = "4";
    120128
    121129    private static final String PROP_ADDED = "a";
     
    170178                    try { raf.close(); } catch (IOException e) {}
    171179                }
    172                 File corrupt = new File(_context.getRouterDir(), HOSTS_DB + ".corrupt");
    173                 _log.log(Log.CRIT, "Corrupt or unreadable database " + f + ", moving to " + corrupt +
     180                File corrupt = new File(_context.getRouterDir(), HOSTS_DB + '.' + System.currentTimeMillis() + ".corrupt");
     181                _log.log(Log.CRIT, "Corrupt, unsupported version, or unreadable database " +
     182                                   f + ", moving to " + corrupt +
    174183                                   " and creating new database", ioe);
    175184                boolean success = f.renameTo(corrupt);
     
    184193                raf = new RAIFile(f, true, true);
    185194                SecureFileOutputStream.setPerms(f);
    186                 bf = init(raf);
     195                bf = initNew(raf);
    187196            } catch (IOException ioe) {
    188197                if (raf != null) {
     
    207216     *  creating a skiplist in the database for each.
    208217     */
    209     private BlockFile init(RAIFile f) throws IOException {
     218    private BlockFile initNew(RAIFile f) throws IOException {
    210219        long start = _context.clock().now();
     220        _version = VERSION;
     221        _destSerializer = _destSerializerV4;
    211222        try {
    212223            BlockFile rv = new BlockFile(f, true);
     
    302313
    303314            String version = info.getProperty(PROP_VERSION);
    304             _needsUpgrade = needsUpgrade(bf, version);
     315            if (version == null)
     316                throw new IOException("No version");
     317            if (VersionComparator.comp(version, VERSION) > 0)
     318                throw new IOException("Database version is " + version +
     319                                      " but this implementation only supports versions 1-" + VERSION +
     320                                      " Did you downgrade I2P??");
     321            _version = version;
     322            if (VersionComparator.comp(version, "4") >= 0)
     323                _destSerializer = _destSerializerV4;
     324            _needsUpgrade = needsUpgrade(bf);
    305325            if (_needsUpgrade) {
    306326                if (_log.shouldLog(Log.WARN))
    307                     _log.warn("Upgrading from database version " + version + " to " + VERSION +
    308                               " created " + (new Date(createdOn)).toString() +
     327                    _log.warn("Upgrading database from version " + _version + " to " + VERSION +
     328                              ", created " + (new Date(createdOn)).toString() +
    309329                              " containing lists: " + list);
    310330            } else {
    311331                if (_log.shouldLog(Log.INFO))
    312                     _log.info("Found database version " + version +
     332                    _log.info("Found database version " + _version +
    313333                              " created " + (new Date(createdOn)).toString() +
    314334                              " containing lists: " + list);
     
    334354     *  @since 0.8.9
    335355     */
    336     private boolean needsUpgrade(BlockFile bf, String version) throws IOException {
    337         if (version != null && VersionComparator.comp(version, VERSION) >= 0)
     356    private boolean needsUpgrade(BlockFile bf) throws IOException {
     357        if (VersionComparator.comp(_version, VERSION) >= 0)
    338358            return false;
    339359        if (!bf.file.canWrite()) {
    340             if (_log.shouldLog(Log.WARN))
    341                 _log.warn("Not upgrading read-only database version " + version);
     360            _log.logAlways(Log.WARN, "Not upgrading read-only database version " + _version);
    342361            return false;
    343362        }
     
    351370     *  Version 2->3: Re-populate reverse skiplist as version 2 didn't keep it updated
    352371     *                after the upgrade. No change to format.
     372     *  Version 3->4: Change format to support multiple destinations per hostname
    353373     *
    354374     *  @return true if upgraded successfully
     
    357377    private boolean upgrade() {
    358378        try {
    359             // wasn't there in version 1, is there in version 2
    360             SkipList rev = _bf.getIndex(REVERSE_SKIPLIST, _hashIndexSerializer, _infoSerializer);
    361             if (rev == null) {
     379            // version 1 -> version 2
     380            // Add reverse skiplist
     381            if (VersionComparator.comp(_version, "2") < 0) {
     382                SkipList rev = _bf.getIndex(REVERSE_SKIPLIST, _hashIndexSerializer, _infoSerializer);
     383                if (rev == null) {
     384                    rev = _bf.makeIndex(REVERSE_SKIPLIST, _hashIndexSerializer, _infoSerializer);
     385                    if (_log.shouldLog(Log.WARN))
     386                        _log.warn("Created reverse index");
     387                }
     388                setVersion("2");
     389            }
     390
     391            // version 2 -> version 3
     392            // no change in format, just regenerate skiplist
     393            if (VersionComparator.comp(_version, "3") < 0) {
     394                Map<String, Destination> entries = getEntries();
     395                int i = 0;
     396                for (Map.Entry<String, Destination> entry : entries.entrySet()) {
     397                     addReverseEntry(entry.getKey(), entry.getValue());
     398                     i++;
     399                }
     400                // i may be greater than skiplist keys if there are dups
    362401                if (_log.shouldLog(Log.WARN))
    363                     _log.warn("Created reverse index");
    364                 rev = _bf.makeIndex(REVERSE_SKIPLIST, _hashIndexSerializer, _infoSerializer);
    365             }
    366             Map<String, Destination> entries = getEntries();
    367             long start = System.currentTimeMillis();
    368             int i = 0;
    369             for (Map.Entry<String, Destination> entry : entries.entrySet()) {
    370                  addReverseEntry(entry.getKey(), entry.getValue());
    371                  i++;
    372             }
    373             // i may be greater than skiplist keys if there are dups
    374             if (_log.shouldLog(Log.WARN))
    375                 _log.warn("Updated reverse index with " + i + " entries");
    376             SkipList hdr = _bf.getIndex(INFO_SKIPLIST, _stringSerializer, _infoSerializer);
    377             if (hdr == null)
    378                 throw new IOException("No db header");
    379             Properties info = (Properties) hdr.get(PROP_INFO);
    380             if (info == null)
    381                 throw new IOException("No header info");
    382             info.setProperty(PROP_VERSION, VERSION);
    383             info.setProperty(PROP_UPGRADED, Long.toString(_context.clock().now()));
    384             hdr.put(PROP_INFO, info);
    385             if (_log.shouldLog(Log.WARN))
    386                 _log.warn("Upgraded to version " + VERSION + " in " +
    387                           DataHelper.formatDuration(System.currentTimeMillis() - start));
     402                    _log.warn("Updated reverse index with " + i + " entries");
     403                setVersion("3");
     404            }
     405
     406            // version 3 -> version 4
     407            // support multiple destinations per hostname
     408            if (VersionComparator.comp(_version, "4") < 0) {
     409                for (String list : _lists) {
     410                    try {
     411                        if (_log.shouldWarn())
     412                            _log.warn("Upgrading " + list + " from database version 3 to 4");
     413                        _bf.reformatIndex(list, _stringSerializer, _destSerializerV1,
     414                                          _stringSerializer, _destSerializerV4);
     415                    } catch (IOException ioe) {
     416                        _log.error("Failed upgrade of list " + list + " to version 4", ioe);
     417                    }
     418                }
     419                _destSerializer = _destSerializerV4;
     420                setVersion("4");
     421            }
     422
    388423            return true;
    389424        } catch (IOException ioe) {
     
    393428        }
    394429        return false;
     430    }
     431
     432    /**
     433     *  Save new version number in blockfile after upgrade.
     434     *  Blockfile must be writable, of course.
     435     *  Side effect: sets _version field
     436     *
     437     *  Caller must synchronize
     438     *  @since 0.9.26 pulled out of upgrade()
     439     */
     440    private void setVersion(String version) throws IOException {
     441        SkipList hdr = _bf.getIndex(INFO_SKIPLIST, _stringSerializer, _infoSerializer);
     442        if (hdr == null)
     443            throw new IOException("No db header");
     444        Properties info = (Properties) hdr.get(PROP_INFO);
     445        if (info == null)
     446            throw new IOException("No header info");
     447        info.setProperty(PROP_VERSION, version);
     448        info.setProperty(PROP_UPGRADED, Long.toString(_context.clock().now()));
     449        hdr.put(PROP_INFO, info);
     450        if (_log.shouldLog(Log.WARN))
     451            _log.warn("Upgraded database from version " + _version + " to version " + version);
     452        _version = version;
    395453    }
    396454
     
    13261384        /** may not be null */
    13271385        public Destination dest;
     1386        /** may be null - v4 only - same size as destList - may contain null entries */
     1387        public List<Properties> propsList;
     1388        /** may be null - v4 only - same size as propsList */
     1389        public List<Destination> destList;
    13281390
    13291391        @Override
     
    13571419                    // null properties is a two-byte length of 0.
    13581420                    baos.write(new byte[2]);
    1359                 }
     1421                }
    13601422                de.dest.writeBytes(baos);
    13611423            } catch (IOException ioe) {
     
    13881450
    13891451    /**
     1452     *  For multiple destinations per hostname
     1453     *  @since 0.9.26
     1454     */
     1455    private static class DestEntrySerializerV4 implements Serializer {
     1456
     1457        public byte[] getBytes(Object o) {
     1458            DestEntry de = (DestEntry) o;
     1459            ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
     1460            int sz = de.destList != null ? de.destList.size() : 1;
     1461            try {
     1462                baos.write((byte) sz);
     1463                for (int i = 0; i < sz; i++) {
     1464                    Properties p;
     1465                    Destination d;
     1466                    if (i == 0) {
     1467                        p = de.props;
     1468                        d = de.dest;
     1469                    } else {
     1470                        p = de.propsList.get(i);
     1471                        d = de.destList.get(i);
     1472                    }
     1473                    try {
     1474                        DataHelper.writeProperties(baos, p, true, false);
     1475                    } catch (DataFormatException dfe) {
     1476                        logError("DB Write Fail - properties too big?", dfe);
     1477                        baos.write(new byte[2]);
     1478                    }
     1479                    d.writeBytes(baos);
     1480                }
     1481            } catch (IOException ioe) {
     1482                logError("DB Write Fail", ioe);
     1483            } catch (DataFormatException dfe) {
     1484                logError("DB Write Fail", dfe);
     1485            }
     1486            return baos.toByteArray();
     1487        }
     1488
     1489        /** returns null on error */
     1490        public Object construct(byte[] b) {
     1491            DestEntry rv = new DestEntry();
     1492            ByteArrayInputStream bais = new ByteArrayInputStream(b);
     1493            try {
     1494                int sz = bais.read() & 0xff;
     1495                if (sz <= 0)
     1496                    throw new DataFormatException("bad dest count " + sz);
     1497                rv.props = DataHelper.readProperties(bais);
     1498                rv.dest = Destination.create(bais);
     1499                if (sz > 1) {
     1500                    rv.propsList = new ArrayList<Properties>(sz);
     1501                    rv.destList = new ArrayList<Destination>(sz);
     1502                    rv.propsList.add(rv.props);
     1503                    rv.destList.add(rv.dest);
     1504                    for (int i = 1; i < sz; i++) {
     1505                        rv.propsList.add(DataHelper.readProperties(bais));
     1506                        rv.destList.add(Destination.create(bais));
     1507                    }
     1508                }
     1509            } catch (IOException ioe) {
     1510                logError("DB Read Fail", ioe);
     1511                return null;
     1512            } catch (DataFormatException dfe) {
     1513                logError("DB Read Fail", dfe);
     1514                return null;
     1515            }
     1516            return rv;
     1517        }
     1518    }
     1519
     1520    /**
    13901521     *  Used to store entries that need deleting
    13911522     */
     
    14111542        BlockfileNamingService bns = new BlockfileNamingService(ctx);
    14121543/****
     1544        Properties sprops = new Properties();
     1545        String lname = "privatehosts.txt";
     1546        sprops.setProperty("list", lname);
     1547        System.out.println("List " + lname + " contains " + bns.size(sprops));
     1548        lname = "userhosts.txt";
     1549        sprops.setProperty("list", lname);
     1550        System.out.println("List " + lname + " contains " + bns.size(sprops));
     1551        lname = "hosts.txt";
     1552        sprops.setProperty("list", lname);
     1553        System.out.println("List " + lname + " contains " + bns.size(sprops));
     1554
    14131555        List<String> names = null;
    14141556        Properties props = new Properties();
     
    14801622        System.out.println("BFNS took " + DataHelper.formatDuration(System.currentTimeMillis() - start));
    14811623        System.out.println("Added " + found + " not added " + notfound);
    1482 
     1624        System.out.println("size() reports " + bns.size());
    14831625
    14841626
     
    14861628****/
    14871629        bns.close();
     1630        ctx.logManager().flush();
     1631        System.out.flush();
    14881632/****
    14891633        if (true) return;
  • core/java/src/net/metanotion/io/block/BlockFile.java

    re9cf4c2 r1a8847d  
    3333import java.io.IOException;
    3434import java.io.RandomAccessFile;
     35import java.util.ArrayList;
    3536import java.util.HashMap;
    3637import java.util.Iterator;
     38import java.util.List;
    3739import java.util.Set;
    3840
     
    432434
    433435        /**
     436         *  Open a skiplist if it exists.
     437         *  Returns null if the skiplist does not exist.
     438         *  Empty skiplists are not preserved after close.
     439         *
    434440         *  If the file is writable, this runs an integrity check and repair
    435441         *  on first open.
     442         *
     443         *  @return null if not found
    436444         */
    437445        public BSkipList getIndex(String name, Serializer key, Serializer val) throws IOException {
     
    455463        }
    456464
     465        /**
     466         *  Create and open a new skiplist if it does not exist.
     467         *  Throws IOException if it already exists.
     468         *
     469         *  @throws IOException if already exists or other errors
     470         */
    457471        public BSkipList makeIndex(String name, Serializer key, Serializer val) throws IOException {
    458472                if(metaIndex.get(name) != null) { throw new IOException("Index already exists"); }
     
    465479        }
    466480
     481        /**
     482         *  Delete a skiplist if it exists.
     483         *  Must be open. Throws IOException if exists but is closed.
     484         *  Broken before 0.9.26.
     485         *
     486         *  @throws IOException if it is closed.
     487         */
    467488        public void delIndex(String name) throws IOException {
    468                 Integer page = (Integer) metaIndex.remove(name);
    469                 if (page == null) { return; }
    470                 Serializer nb = new IdentityBytes();
    471                 BSkipList bsl = new BSkipList(spanSize, this, page.intValue(), nb, nb, true);
     489                if (metaIndex.get(name) == null)
     490                    return;
     491                BSkipList bsl = openIndices.get(name);
     492                if (bsl == null)
     493                        throw new IOException("Cannot delete closed skiplist, open it first: " + name);
    472494                bsl.delete();
    473         }
    474 
    475         /**
     495                openIndices.remove(name);
     496                metaIndex.remove(name);
     497        }
     498
     499        /**
     500         *  Close a skiplist if it is open.
     501         *
    476502         *  Added I2P
    477503         */
     
    483509
    484510        /**
     511         *  Reformat a skiplist with new Serializers if it exists.
     512         *  The skiplist must be closed.
     513         *  Throws IOException if the skiplist is open.
     514         *  The skiplist will remain closed after completion.
     515         *
     516         *  @throws IOException if it is open or on errors
     517         *  @since 0.9.26
     518         */
     519        public void reformatIndex(String name, Serializer oldKey, Serializer oldVal,
     520                                  Serializer newKey, Serializer newVal) throws IOException {
     521                if (openIndices.containsKey(name))
     522                        throw new IOException("Cannot reformat open skiplist " + name);
     523                BSkipList old = getIndex(name, oldKey, oldVal);
     524                if (old == null)
     525                        return;
     526                long start = System.currentTimeMillis();
     527                String tmpName = "---tmp---" + name + "---tmp---";
     528                BSkipList tmp = makeIndex(tmpName, newKey, newVal);
     529
     530                // It could be much more efficient to do this at the
     531                // SkipSpan layer but that's way too hard.
     532                final int loop = 32;
     533                List<Comparable> keys = new ArrayList<Comparable>(loop);
     534                List<Object> vals = new ArrayList<Object>(loop);
     535                while (true) {
     536                        SkipIterator iter = old.iterator();
     537                        for (int i = 0; iter.hasNext() && i < loop; i++) {
     538                                keys.add(iter.nextKey());
     539                                vals.add(iter.next());
     540                        }
     541                        // save state, as deleting corrupts the iterator
     542                        boolean done = !iter.hasNext();
     543                        for (int i = 0; i < keys.size(); i++) {
     544                                tmp.put(keys.get(i), vals.get(i));
     545                        }
     546                        for (int i = keys.size() - 1; i >= 0; i--) {
     547                                old.remove(keys.get(i));
     548                        }
     549                        if (done)
     550                                break;
     551                        keys.clear();
     552                        vals.clear();
     553                }
     554
     555                delIndex(name);
     556                closeIndex(name);
     557                closeIndex(tmpName);
     558                Integer page = (Integer) metaIndex.get(tmpName);
     559                metaIndex.put(name, page);
     560                metaIndex.remove(tmpName);
     561                if (log.shouldWarn())
     562                        log.warn("reformatted list: " + name + " in " +
     563                                 (System.currentTimeMillis() - start) + "ms");
     564        }
     565
     566        /**
     567         *  Closes all open skiplists and then the blockfile itself.
     568         *
    485569         *  Note (I2P)
    486         *  Does NOT close the RAF / RAI.
     570        *  Does NOT close the RAF / RAI.
    487571         */
    488572        public void close() throws IOException {
  • core/java/src/net/metanotion/io/block/index/BSkipLevels.java

    re9cf4c2 r1a8847d  
    230230                }
    231231                if (bf.log.shouldLog(Log.DEBUG))
    232                         bf.log.debug("Killing " + this + ' ' + print(), new Exception());
     232                        bf.log.debug("Killing " + this + ' ' + print() /* , new Exception() */ );
    233233                isKilled = true;
    234234                bsl.levelHash.remove(Integer.valueOf(levelPage));
  • core/java/src/net/metanotion/io/block/index/BSkipList.java

    re9cf4c2 r1a8847d  
    152152                        curLevel = nextLevel;
    153153                }
    154                 stack.killInstance();
    155154
    156155                SkipSpan curSpan = first;
Note: See TracChangeset for help on using the changeset viewer.