diff --git a/README.md b/README.md index 6e51e9db1..4fbe45e75 100644 --- a/README.md +++ b/README.md @@ -372,32 +372,24 @@ ChronicleMap cityPostalCodes = ChronicleMap .name("city-postal-codes-map") .averageKey("Amsterdam") .entries(50_000) - .recoverPersistedTo(cityPostalCodesFile, true); -// or -ChronicleMap cityPostalCodes = ChronicleMap - .of(CharSequence.class, PostalCodeRange.class) - .name("city-postal-codes-map") - // assuming ChronicleMapBuilder configurations at the moment of - // cityPostalCodes Chronicle Map creation are not known .recoverPersistedTo(cityPostalCodesFile, false); ``` -The second parameter in `recoverPersistedTo()` method is called `sameBuilderConfig`, it should be -`true` if `ChronicleMapBuilder` is configured in exactly the same way, as when the Chronicle Map was -created, and using the same version of Chronicle Map library, or `false`, if initial configurations -are not known, of current version of Chronicle Map library differs from the version, used to create -this Chronicle Map initially. +The second parameter in `recoverPersistedTo()` method is called +`sameBuilderConfigAndLibraryVersion`, it could be `true` only if `ChronicleMapBuilder` is configured +in exactly the same way, as when the Chronicle Map (persisted to the given file) was created, **and +using the same version of Chronicle Map library**, or `false`, if initial configurations are not +known, *or current version of Chronicle Map library could be different from the version, used to +create this Chronicle Map initially.* -If `sameBuilderConfig` is `true`, `recoverPersistedTo()` checks that the recovered Chronicle Map's -header memory (containing serialized configurations) is not corrupted, because it "knows" all the -right configurations and what should be written to the header. If the header is corrupted, it -recovers it. +If `sameBuilderConfigAndLibraryVersion` is `true`, `recoverPersistedTo()` "knows" all the right +configurations and what should be written to the header. It checks if the recovered Chronicle Map's +header memory (containing serialized configurations) is corrupted or not. If the header is +corrupted, it is overridden, and the recovery process continues. -If `sameBuilderConfig` is `false`, `recoverPersistedTo()` relies on the configurations written to -the Chronicle Map's header, assuming it is not corrupted. If it is corrupted, at best it will lead -to a runtime exception thrown from `recoverPersistedTo()` without touching the data, stored in the -Chronicle Map (but this effectively means inability to recover the Chronicle Map), at worst it will -proceed "successfully" but will corrupt the Chronicle Map further. +If `sameBuilderConfigAndLibraryVersion` is `false`, `recoverPersistedTo()` relies on the +configurations written to the Chronicle Map's header, assuming it is not corrupted. If it is +corrupted, `ChronicleHashRecoveryFailedException` is thrown. However, the subject header memory is never updated on ordinary operations with Chronicle Map, so it couldn't be corrupted if an accessing process crashed, or the operating system crashed, or even the @@ -405,19 +397,19 @@ machine lost power. Only hardware memory or disk corruption or a bug in the file to Chronicle Map header memory corruption. `.recoverPersistedTo()` is harmless if the previous process accessing the Chronicle Map terminated -normally, however this is a computationally expensive procedure that should generally be avoided. +normally, however *this is a computationally expensive procedure that should generally be avoided.* -To share configuration, Chronicle Map creation and recovery code you could use -`.createOrRecoverPersistedTo(persistenceFile)` method in `ChronicleMapBuilder`, which is equivalent -to `createPersistedTo(persistenceFile)` call, if the persistence file doesn't yet exist, and to -`recoverPersistedTo(persistenceFile, true)`, if the file already exists, e. g.: +Chronicle Map creation and recovery could be conveniently merged using a single call: +`.createOrRecoverPersistedTo(persistenceFile, sameLibraryVersion)` in `ChronicleMapBuilder`, which +acts like `createPersistedTo(persistenceFile)`, if the persistence file doesn't yet exist, and like +`recoverPersistedTo(persistenceFile, sameLibraryVersion)`, if the file already exists, e. g.: ```java ChronicleMap cityPostalCodes = ChronicleMap .of(CharSequence.class, PostalCodeRange.class) .averageKey("Amsterdam") .entries(50_000) - .createOrRecoverPersistedTo(cityPostalCodesFile); + .createOrRecoverPersistedTo(cityPostalCodesFile, false); ``` If the Chronicle Map is configured to store entry checksums along with entries, recovery procedure diff --git a/src/main/java/net/openhft/chronicle/hash/ChronicleHashBuilder.java b/src/main/java/net/openhft/chronicle/hash/ChronicleHashBuilder.java index a2b6dd96c..b8c46098c 100755 --- a/src/main/java/net/openhft/chronicle/hash/ChronicleHashBuilder.java +++ b/src/main/java/net/openhft/chronicle/hash/ChronicleHashBuilder.java @@ -533,7 +533,7 @@ public interface ChronicleHashBuilder, * @see ChronicleHash#file() * @see ChronicleHash#close() * @see #create() - * @see #createOrRecoverPersistedTo(File) + * @see #createOrRecoverPersistedTo(File, boolean) * @see #recoverPersistedTo(File, boolean) */ H createPersistedTo(File file) throws IOException; @@ -541,9 +541,14 @@ public interface ChronicleHashBuilder, /** * Recovers and opens the hash container, persisted to the specified file, or creates a new one * from this builder, if the file doesn't exist yet, and maps its off-heap memory to the file. - * In other words, this methods behaves like {@link #createPersistedTo(File)}, if the given - * file doesn't exist, and {@link #recoverPersistedTo(File, boolean) - * recoverPersistedTo(file, true)}, if the file exists. + * This method is equivalent to {@link #createOrRecoverPersistedTo(File, boolean) + * createOrRecoverPersistedTo(file, true)}. + * + *

This method couldn't be used, if the Chronicle Map was created using an older version + * of the Chronicle Map library, and then {@code createOrRecoverPersistedTo()} is called with + * a newer version of the Chronicle Map library. In this case, {@link + * #createOrRecoverPersistedTo(File, boolean) createOrRecoverPersistedTo(file, false)} should + * be used. * * @param file the persistence file for existing of future hash container * @return a {@code ChronicleHash} instance, mapped to the given instance @@ -553,10 +558,35 @@ public interface ChronicleHashBuilder, * calling this procedure. * @throws ChronicleHashRecoveryFailedException if recovery is impossible * @see #createPersistedTo(File) + * @see #createOrRecoverPersistedTo(File, boolean) * @see #recoverPersistedTo(File, boolean) */ H createOrRecoverPersistedTo(File file) throws IOException; + /** + * Recovers and opens the hash container, persisted to the specified file, or creates a new one + * from this builder, if the file doesn't exist yet, and maps its off-heap memory to the file. + * In other words, this methods behaves like {@link #createPersistedTo(File)}, if the given + * file doesn't exist, and {@link #recoverPersistedTo(File, boolean) + * recoverPersistedTo(file, sameLibraryVersion)}, if the file exists. + * + * @param file the persistence file for existing of future hash container + * @param sameLibraryVersion if this builder is configured with the same configurations, as the + * builder, which created the Chronicle Map in the given file initially, and the same version + * of the Chronicle Map library was used. In this case, the header of the file is overridden + * (with presumably the same configurations), protecting from {@link + * ChronicleHashRecoveryFailedException}, if the header is corrupted. + * @return a {@code ChronicleHash} instance, mapped to the given instance + * @throws IOException if any IO error occurs on reading data from the file, or related to + * off-heap memory allocation or file mapping, or establishing replication connections. Probably + * the file is corrupted on OS level, and should be recovered on that level first, before + * calling this procedure. + * @throws ChronicleHashRecoveryFailedException if recovery is impossible + * @see #createPersistedTo(File) + * @see #recoverPersistedTo(File, boolean) + */ + H createOrRecoverPersistedTo(File file, boolean sameLibraryVersion) throws IOException; + /** * Recovers and opens the hash container, persisted to the specified file. This method * should be used to open a persisted Chronicle Map after an accessor process crash (that might @@ -567,7 +597,8 @@ public interface ChronicleHashBuilder, * which were never put into the Chronicle Hash). * *

This method, unlike {@link #createPersistedTo(File)} or {@link - * #createOrRecoverPersistedTo(File)} methods, expects that the mapping file already exists. + * #createOrRecoverPersistedTo(File, boolean)} methods, expects that the mapping file already + * exists. * * "Recovery" of the hash container is changing the memory of the data structure so that after * the recovery the hash container is in some correct state: with "clean" locks, coherent @@ -583,21 +614,19 @@ public interface ChronicleHashBuilder, * *

It is strongly recommended to configure this builder with the same configurations, as * the builder, that created the given file for the first time, and pass {@code true} as the - * {@code sameBuilderConfig} argument (another requirement is running the same version of the - * Chronicle Map library). Otherwise, if the header of the given persisted Chronicle Hash file - * is corrupted, this method is likely to be unable to recover and throw {@link - * ChronicleHashRecoveryFailedException}, or even worse, to corrupt the file further. - * Fortunately, the header should never be corrupted on an "ordinary" process crash/termination - * or power loss, only on direct file corruption. + * {@code sameBuilderConfigAndLibraryVersion} argument (another requirement is running + * the same version of the Chronicle Map library). Otherwise, if the header of the given + * persisted Chronicle Hash file is corrupted, this method is likely to be unable to recover and + * throw {@link ChronicleHashRecoveryFailedException}, or even worse, to corrupt the file + * further. Fortunately, the header should never be corrupted on an "ordinary" process + * crash/termination or power loss, only on direct file corruption. * * @param file a hash container was mapped to the given file - * @param sameBuilderConfig if this builder is configured with the same configurations, as the - * builder, which created the file (the persisted Chronicle Hash - * instance) for the first time, and with the same version of the - * Chronicle Map library. In this case, the header of the file is - * overridden (with presumably the same configurations), protecting - * from {@link ChronicleHashRecoveryFailedException}, if the header is - * corrupted. + * @param sameBuilderConfigAndLibraryVersion if this builder is configured with the same + * configurations, as the builder, which created the file (the persisted Chronicle Hash + * instance) for the first time, and with the same version of the Chronicle Map library. + * In this case, the header of the file is overridden (with presumably the same configurations), + * protecting from {@link ChronicleHashRecoveryFailedException}, if the header is corrupted. * @return a recovered Chronicle Hash instance, mapped to the given file * @throws FileNotFoundException if the file doesn't exist * @throws IOException if any IO error occurs on reading data from the file, or related to @@ -605,10 +634,10 @@ public interface ChronicleHashBuilder, * the file is corrupted on OS level, and should be recovered on that level first, before * calling this procedure. * @throws ChronicleHashRecoveryFailedException if recovery is impossible - * @see #createOrRecoverPersistedTo(File) + * @see #createOrRecoverPersistedTo(File, boolean) * @see #createPersistedTo(File) */ - H recoverPersistedTo(File file, boolean sameBuilderConfig) throws IOException; + H recoverPersistedTo(File file, boolean sameBuilderConfigAndLibraryVersion) throws IOException; /** * @deprecated don't use private API in the client code diff --git a/src/main/java/net/openhft/chronicle/hash/ChronicleHashRecoveryFailedException.java b/src/main/java/net/openhft/chronicle/hash/ChronicleHashRecoveryFailedException.java index 991debf03..1a07d2203 100644 --- a/src/main/java/net/openhft/chronicle/hash/ChronicleHashRecoveryFailedException.java +++ b/src/main/java/net/openhft/chronicle/hash/ChronicleHashRecoveryFailedException.java @@ -21,7 +21,7 @@ import java.io.File; /** * This exception is thrown, when a Chronicle Hash recovery using {@link - * ChronicleHashBuilder#recoverPersistedTo(File, boolean)} method is impossible, for example, + * ChronicleHashBuilder#recoverPersistedTo(File, boolean)} method is impossible, for example, * if the persistence file is corrupted too much. * * @see ChronicleHashBuilder#recoverPersistedTo(File, boolean) diff --git a/src/main/java/net/openhft/chronicle/map/ChronicleMap.java b/src/main/java/net/openhft/chronicle/map/ChronicleMap.java index e4045e021..0967737bd 100755 --- a/src/main/java/net/openhft/chronicle/map/ChronicleMap.java +++ b/src/main/java/net/openhft/chronicle/map/ChronicleMap.java @@ -50,7 +50,7 @@ import java.util.concurrent.ConcurrentMap; * @param the map value type * @see ChronicleMapBuilder#create() * @see ChronicleMapBuilder#createPersistedTo(File) - * @see ChronicleMapBuilder#createOrRecoverPersistedTo(File) + * @see ChronicleMapBuilder#createOrRecoverPersistedTo(File, boolean) */ public interface ChronicleMap extends ConcurrentMap, ChronicleHash, MapSegmentContext, diff --git a/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilder.java b/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilder.java index 119d11735..208eb04ff 100755 --- a/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilder.java +++ b/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilder.java @@ -231,14 +231,14 @@ public final class ChronicleMapBuilder implements /** * When Chronicle Maps are created using {@link #createPersistedTo(File)} or - * {@link #recoverPersistedTo(File, boolean)} or {@link #createOrRecoverPersistedTo(File)} - * methods, file lock on the Chronicle Map's file is acquired, that shouldn't be done from - * concurrent threads within the same JVM process. So creation of Chronicle Maps - * persisted to the same File should be synchronized across JVM's threads. Simple way would be - * to synchronize on some static (lock) object, but would serialize all Chronicle Maps creations - * (persisted to any files), ConcurrentHashMap#compute() gives more scalability. - * ConcurrentHashMap is used effectively for lock striping only, because the entries are not - * even landing the map, because compute() always returns null. + * {@link #recoverPersistedTo(File, boolean)} or {@link + * #createOrRecoverPersistedTo(File, boolean)} methods, file lock on the Chronicle Map's file is + * acquired, that shouldn't be done from concurrent threads within the same JVM process. So + * creation of Chronicle Maps persisted to the same File should be synchronized across JVM's + * threads. Simple way would be to synchronize on some static (lock) object, but would serialize + * all Chronicle Maps creations (persisted to any files), ConcurrentHashMap#compute() gives more + * scalability. ConcurrentHashMap is used effectively for lock striping only, because the + * entries are not even landing the map, because compute() always returns null. */ private static void fileLockedIO( File file, FileChannel fileChannel, FileIOAction fileIOAction) throws IOException { @@ -1474,13 +1474,23 @@ public final class ChronicleMapBuilder implements @Override public ChronicleMap createOrRecoverPersistedTo(File file) throws IOException { - return file.exists() ? recoverPersistedTo(file, true) : createPersistedTo(file); + return createOrRecoverPersistedTo(file, true); } @Override - public ChronicleMap recoverPersistedTo(File file, boolean sameBuilderConfig) + public ChronicleMap createOrRecoverPersistedTo(File file, boolean sameLibraryVersion) throws IOException { - return clone().createWithFile(file, true, sameBuilderConfig); + if (file.exists()) { + return recoverPersistedTo(file, sameLibraryVersion); + } else { + return createPersistedTo(file); + } + } + + @Override + public ChronicleMap recoverPersistedTo( + File file, boolean sameBuilderConfigAndLibraryVersion) throws IOException { + return clone().createWithFile(file, true, sameBuilderConfigAndLibraryVersion); } @Override diff --git a/src/main/java/net/openhft/chronicle/set/ChronicleSetBuilder.java b/src/main/java/net/openhft/chronicle/set/ChronicleSetBuilder.java index 6d90efe26..e9c058109 100755 --- a/src/main/java/net/openhft/chronicle/set/ChronicleSetBuilder.java +++ b/src/main/java/net/openhft/chronicle/set/ChronicleSetBuilder.java @@ -311,10 +311,18 @@ public final class ChronicleSetBuilder } @Override - public ChronicleSet recoverPersistedTo(File file, boolean sameBuilderConfig) + public ChronicleSet createOrRecoverPersistedTo(File file, boolean sameLibraryVersion) throws IOException { ChronicleMap map = - chronicleMapBuilder.recoverPersistedTo(file, sameBuilderConfig); + chronicleMapBuilder.createOrRecoverPersistedTo(file, sameLibraryVersion); + return new SetFromMap<>((VanillaChronicleMap) map); + } + + @Override + public ChronicleSet recoverPersistedTo(File file, boolean sameBuilderConfigAndLibraryVersion) + throws IOException { + ChronicleMap map = + chronicleMapBuilder.recoverPersistedTo(file, sameBuilderConfigAndLibraryVersion); return new SetFromMap<>((VanillaChronicleMap) map); }