On 24 Jun 2014, at 16:51, Mircea Markus <mmarkus(a)redhat.com> wrote:
On Jun 24, 2014, at 15:27, Galder Zamarreño <galder(a)redhat.com> wrote:
> Hi all,
>
> Last few days I’ve been having fun fixing [1] and the solution I’ve got to has some
links to [2].
>
> The problem is that when getCacheEntry() is called, the entry returned could be
partially correct. Up until now, we’d send back a cache entry back to the client that the
user could not modify, but it was linked to the data container cache entry, which is
mutable and hence could change on the fly. As a result of this, the value could sometimes
be updated but the version would belong to a previous value for example.
>
> A first attempt to fix it was to provide a true immutable cache entry view which was
initialised on construction, but even here there was the chance of having value and
version mistmatch, because getCacheEntry does not acquire locks, so an ongoing update
could result in the cache entry being constructed when the update was half way, so, it
would have the right value but the old version information.
>
> All this didn’t work well with the replaceIfUmodified logic in [3]. For example, you
could easily get this situation:
>
> Current Cache contents: key=k1, value=A, version=1
>
> T1: Hot Rod client calls: replace(k1, value=B, old-version=1)
> T2: Hot Rod client calls: replace(k1, value=B, old-version=1)
> T1: Server calls getCacheEntry and retrieves value=A,version=1
> T1: Cached and stream versions match (both are 1), so call replace(k1, old-value=A,
new-value=B)
> T1: Server updates value to B but version is still 1
> T2: Server calls getCacheEntry and retrieves value=B,version=1 (wrong!)
> T1: Cached and stream versions match (both are 1), so call replace(k1, old-value=B,
new-value=B)
> T1: Server updates version to 2
> T1: Returns Success, replace worked
> T2: Returns Success, replace worked
>
> The end result is that cache contained B, but both replaces returned true. This is
wrong and would fail to consistenly keep a counter in the value part. Imagine the value
being a counter of number of times the replace succeeded. In the test developed by
Takayoshi, N times replace() would return true, but the final value would be N-1 or N-2,
or N-5 :|
>
> To fix this, I’ve been working with Dan on some solutions and we’ve taken inspiration
of the new requirements appearing as a result of ISPN-2956. To be able to deal with
partial application of conditional operations properly, transactional caches are needed.
So, the solution that can be seen in [4] takes that, and creates a transaction around the
replaceIfUmodified and forces the getCacheEntry() call to acquire lock via
FORCE_WRITE_LOCK flag.
so the entire underlaying cache needs to be transactional then?
Yeah, it needs to be transactional, but the code I’ve written also deals with the fact
that the cache might not be transactional. I’ll probably add a WARN message when it’s not
transactional. This goes in hand with the recommendations for ISPN-2956, whereby failover
for conditional operations relying on return values require transactional caches to
properly deal with failover situations.
To sum up, if using conditional operations or CRUD methods with Flag.FORCE_RETURN_VALUE,
caches should be transactional. Moreover, to achieve concurrency guarantees of counter
tests such as the one tested for 4424, locking needs to be pessimistic too. If not using
conditional operations or CRUD methods without the flag, the cache could be
non-transactional.
> This solves the issue explained above, but of course it has an impact of the
performance. The test now runs about 1.5 or 2 times slower.
>
> This is probably the best that we can do in the 7.0 time frame, but there’s several
things that could improve this:
>
> 1. True immutable entries in the cache. If the entries in the cache were truly
immutable, there would be no risk of sending back a partially correct entry back to the
client.
>
> 2. A cache replace method that does not compare objects based on equality (the
current replace()), but a replace method that takes a function. The function could compare
that the old entry’s version and the cached entry’s version match. The function would only
be executed inside the inner container, with all the locks held properly. I already hinted
something similar in [5].
>
> Finally, this was not a problem when the value stored in Hot Rod was a ByteArrayValue
wrapping the byte array and the version, because the value was treated atomically, and in
hindsight, maybe adding getCacheEntry might have been premature, but this method has
proven useful for other use cases too (rolling upgrades…etc).
>
> Cheers,
>
> [1]
https://issues.jboss.org/browse/ISPN-4424
> [2]
https://issues.jboss.org/browse/ISPN-2956
> [3]
https://github.com/infinispan/infinispan/blob/master/server/core/src/main...
> [4]
https://github.com/galderz/infinispan/tree/t_4424
> [5]
https://github.com/infinispan/infinispan/blob/master/core/src/main/java/o...
> --
> Galder Zamarreño
> galder(a)redhat.com
>
twitter.com/galderz
>
>
> _______________________________________________
> infinispan-dev mailing list
> infinispan-dev(a)lists.jboss.org
>
https://lists.jboss.org/mailman/listinfo/infinispan-dev
Cheers,
--
Mircea Markus
Infinispan lead (
www.infinispan.org)
_______________________________________________
infinispan-dev mailing list
infinispan-dev(a)lists.jboss.org
https://lists.jboss.org/mailman/listinfo/infinispan-dev
--
Galder Zamarreño
galder(a)redhat.com
twitter.com/galderz