Details
Description
Application with multiple Ignite client instances connected to different clusters with enabled indexing module fails to deserialise SQL query response.
This is the exception:
Caused by: org.apache.ignite.binary.BinaryObjectException: Cannot find metadata for object with compact footer (Ignite work directory might have been cleared after restart. Make sure that IGNITE_HOME does not point to a temp folder or any other folder that is destroyed/cleared on restarts) [typeId=-1468802099, schemaId=360499151, IGNITE_HOME=/opt/ignite]
at org.apache.ignite.internal.binary.BinaryReaderExImpl.getOrCreateSchema(BinaryReaderExImpl.java:2026) ~[ignite-core-2.8.1.jar!/:2.8.1]
at org.apache.ignite.internal.binary.BinaryReaderExImpl.<init>(BinaryReaderExImpl.java:293) ~[ignite-core-2.8.1.jar!/:2.8.1]
at org.apache.ignite.internal.binary.BinaryReaderExImpl.<init>(BinaryReaderExImpl.java:188) ~[ignite-core-2.8.1.jar!/:2.8.1]
at org.apache.ignite.internal.binary.BinaryObjectImpl.reader(BinaryObjectImpl.java:826) ~[ignite-core-2.8.1.jar!/:2.8.1]
at org.apache.ignite.internal.binary.BinaryObjectImpl.deserializeValue(BinaryObjectImpl.java:789) ~[ignite-core-2.8.1.jar!/:2.8.1]
at org.apache.ignite.internal.binary.BinaryObjectImpl.value(BinaryObjectImpl.java:142) ~[ignite-core-2.8.1.jar!/:2.8.1]
at org.apache.ignite.internal.processors.cache.CacheObjectUtils.unwrapBinary(CacheObjectUtils.java:176) ~[ignite-core-2.8.1.jar!/:2.8.1]
at org.apache.ignite.internal.processors.cache.CacheObjectUtils.unwrapBinaryIfNeeded(CacheObjectUtils.java:67) ~[ignite-core-2.8.1.jar!/:2.8.1]
at org.apache.ignite.internal.processors.cache.CacheObjectUtils.unwrapBinariesIfNeeded(CacheObjectUtils.java:135) ~[ignite-core-2.8.1.jar!/:2.8.1]
at org.apache.ignite.internal.processors.cache.CacheObjectUtils.unwrapBinariesIfNeeded(CacheObjectUtils.java:77) ~[ignite-core-2.8.1.jar!/:2.8.1]
at org.apache.ignite.internal.processors.query.GridQueryCacheObjectsIterator.next(GridQueryCacheObjectsIterator.java:66) ~[ignite-core-2.8.1.jar!/:2.8.1]
at org.apache.ignite.internal.processors.query.GridQueryCacheObjectsIterator.next(GridQueryCacheObjectsIterator.java:31) ~[ignite-core-2.8.1.jar!/:2.8.1]
at org.apache.ignite.internal.processors.cache.AutoClosableCursorIterator.next(AutoClosableCursorIterator.java:59) ~[ignite-core-2.8.1.jar!/:2.8.1]
The error has been identified: Injected BinaryContext is incorrect due to static use of JdbcUtils.serializer replaced by last IgniteH2Indexing instance on start method.
Solution: Use h2 DataHandler associated to GridKernalContext when calling ValueJavaObject.getNoCopy, this avoid using static Jdbc.serializer instance that could be eventually incorrect.
Proposal. Some simple changes into:
org.apache.ignite.internal.processors.query.h2.H2Utils
org.apache.ignite.internal.processors.query.h2.IgniteH2Indexing
org.apache.ignite.internal.processors.query.h2.twostep.msg.GridH2JavaObject
org.apache.ignite.internal.processors.query.h2.opt.GridH2ValueCacheObject
org.apache.ignite.internal.processors.query.h2.H2Utils:
@SuppressWarnings("ConstantConditions") public static Value wrap(CacheObjectValueContext coCtx, Object obj, int type) throws IgniteCheckedException { assert obj != null; .... case Value.JAVA_OBJECT: return ValueJavaObject.getNoCopy(obj, null, coCtx == null ? null: H2Utils.getH2Datahandler(coCtx.kernalContext())); ... } public static DataHandler getH2Datahandler(GridKernalContext ctx) { if (ctx == null) return null; return ((IgniteH2Indexing)ctx.query().getIndexing()).h2Datahandler(); } public static JavaObjectSerializer getH2Serializer(GridKernalContext ctx) { if (ctx == null) return null; return ((IgniteH2Indexing)ctx.query().getIndexing()).h2Serializer(); } public static DataHandler createH2Datahandler(GridKernalContext ctx) { return createH2Datahandler(createH2Serializer(ctx)); } public static DataHandler createH2Datahandler(JavaObjectSerializer serializer) { return new DataHandler() { @Override public String getDatabasePath() { throw new UnsupportedOperationException(); } @Override public FileStore openFile(String name, String mode, boolean mustExist) { throw new UnsupportedOperationException(); } @Override public void checkPowerOff() throws DbException { throw new UnsupportedOperationException(); } @Override public void checkWritingAllowed() throws DbException { throw new UnsupportedOperationException(); } @Override public int getMaxLengthInplaceLob() { throw new UnsupportedOperationException(); } @Override public String getLobCompressionAlgorithm(int type) { throw new UnsupportedOperationException(); } @Override public TempFileDeleter getTempFileDeleter() { throw new UnsupportedOperationException(); } @Override public Object getLobSyncObject() { throw new UnsupportedOperationException(); } @Override public SmallLRUCache<String, String[]> getLobFileListCache() { throw new UnsupportedOperationException(); } @Override public LobStorageInterface getLobStorage() { throw new UnsupportedOperationException(); } @Override public int readLob(long lobId, byte[] hmac, long offset, byte[] buff, int off, int length) { throw new UnsupportedOperationException(); } @Override public JavaObjectSerializer getJavaObjectSerializer() { return serializer; } @Override public CompareMode getCompareMode() { throw new UnsupportedOperationException(); } }; } public static JavaObjectSerializer createH2Serializer(GridKernalContext ctx) { return new JavaObjectSerializer() { @Override public byte[] serialize(Object obj) throws Exception { return U.marshal(ctx.config().getMarshaller(), obj); } @Override public Object deserialize(byte[] bytes) throws Exception { ClassLoader clsLdr = ctx != null ? U.resolveClassLoader(ctx.config()) : null; return U.unmarshal(ctx.config().getMarshaller(), bytes, clsLdr); } }; }
org.apache.ignite.internal.processors.query.h2.IgniteH2Indexing:
... private volatile DataHandler h2Datahandler; private volatile JavaObjectSerializer h2Serializer; ... /** * @return H2 datahandler. */ public DataHandler h2Datahandler(){ return h2Datahandler; } /** * @return H2 serializer. */ public JavaObjectSerializer h2Serializer(){ return h2Serializer; } @Override public void start(GridKernalContext ctx, GridSpinBusyLock busyLock) throws IgniteCheckedException { ... h2Serializer = H2Utils.createH2Serializer(ctx); h2Datahandler = H2Utils.createH2Datahandler(h2Serializer); if (JdbcUtils.serializer != null) U.warn(log, "Custom H2 serialization is already configured, will override."); JdbcUtils.serializer = h2Serializer; ... }
org.apache.ignite.internal.processors.query.h2.opt.GridH2ValueCacheObject:
... @Override public byte[] getBytesNoCopy() { if (obj.cacheObjectType() == CacheObject.TYPE_REGULAR) { // Result must be the same as `marshaller.marshall(obj.value(coctx, false));` try { return obj.valueBytes(valCtx); } catch (IgniteCheckedException e) { throw DbException.convert(e); } } // For user-provided and array types. return JdbcUtils.serialize(obj, valCtx == null ? null: H2Utils.getH2Datahandler(valCtx.kernalContext())); }
org.apache.ignite.internal.processors.query.h2.twostep.msg.GridH2JavaObject:
... /** {@inheritDoc} */ @Override public Value value(GridKernalContext ctx) { return ValueJavaObject.getNoCopy(null, b, H2Utils.getH2Datahandler(ctx)); }