[
https://issues.apache.org/jira/browse/IGNITE-29276?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Reza Taroosheh updated IGNITE-29276:
------------------------------------
Summary: An interrupted ATOMIC put whose interceptor returns a new
Externalizable class is reported as a corrupted B+Tree and stops the node
(was: An interrupted ATOMIC update is reported as a corrupted B+Tree if the
interceptor returns a value of a new Externalizable class)
> An interrupted ATOMIC put whose interceptor returns a new Externalizable
> class is reported as a corrupted B+Tree and stops the node
> -----------------------------------------------------------------------------------------------------------------------------------
>
> Key: IGNITE-29276
> URL: https://issues.apache.org/jira/browse/IGNITE-29276
> Project: Ignite
> Issue Type: Bug
> Reporter: Reza Taroosheh
> Assignee: Reza Taroosheh
> Priority: Major
> Fix For: 2.20
>
>
> h3. Problem
> In an ATOMIC cache, {{CacheInterceptor.onBeforePut()}} is called inside
> {{BPlusTree.invoke()}}, from
> {{GridCacheMapEntry.AtomicCacheUpdateClosure.update()}}. If it returns a
> value of an {{Externalizable}} class that isn't registered yet, the value is
> marshaled there by {{prepareForCache()}}, and the thread waits for the class
> registration inside the tree. An interrupt ends the wait with a
> {{BinaryObjectException}}, {{BPlusTree.invoke()}} reports it as
> {{CorruptedTreeException}}, and the failure handler stops the node, although
> nothing is corrupted.
> IGNITE-29241 fixes the same problem for {{put}}, {{putAll}}, {{invoke}} and
> {{invokeAll}} by making the bytes of the new value before the tree. It
> doesn't cover the interceptor, because on this path the interceptor's value
> only exists inside the tree.
> h3. Reproducer
> In {{AtomicPutInterruptTest}} (added by IGNITE-29241):
> {code:java}
> /** The interceptor replaces the new value with an Externalizable value
> inside the cache update. */
> @Test
> public void
> testInterruptedPutWithInterceptorReturningUnregisteredExternalizableClass()
> throws Exception {
> IgniteCache<Integer, Object> cache = grid(0).createCache(new
> CacheConfiguration<Integer, Object>("interceptor")
> .setAtomicityMode(ATOMIC)
> .setInterceptor(new ExternalizableInterceptor()));
> checkInterruptedUpdateFails(() -> cache.put(1, 1));
> cache.put(1, 2);
> assertEquals(2, ((ExternalizableValue)cache.get(1)).val);
> }
> /** Replaces the new value with an Externalizable value, so the value is
> created inside the cache update. */
> private static class ExternalizableInterceptor extends
> CacheInterceptorAdapter<Integer, Object> {
> /** {@inheritDoc} */
> @Override public Object onBeforePut(Cache.Entry<Integer, Object> entry,
> Object newVal) {
> return new ExternalizableValue((Integer)newVal);
> }
> }
> {code}
> Expected: the put fails, and the failure handler isn't called.
> Actual: the failure handler is called with {{CorruptedTreeException}}, caused
> by {{BinaryObjectException: Failed to register class}} from
> {{AtomicCacheUpdateClosure.update()}} -> {{prepareForCache()}}, caused by
> {{IgniteInterruptedCheckedException}}.
> h3. Notes
> * Moving {{onBeforePut()}} before the tree isn't a simple change: inside the
> tree it runs after the checks that can still reject the update (version
> check, filter, conflict resolution, expiry), so calling it earlier would also
> call it for updates that are rejected later.
> * In {{GridDhtAtomicCache.updateWithBatch()}} (multi-key update with a
> write-through store), the interceptor already runs before the tree, so the
> bytes of its value can be made right after it, as IGNITE-29241 does for the
> entry processor's value.
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)