<div dir="ltr"><br><div class="gmail_extra"><br><br><div class="gmail_quote">On Fri, Jul 5, 2013 at 5:31 PM, William Burns <span dir="ltr">&lt;<a href="mailto:mudokonman@gmail.com" target="_blank">mudokonman@gmail.com</a>&gt;</span> wrote:<br>


<blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div><div>On Tue, Jul 2, 2013 at 5:23 AM, Dan Berindei &lt;<a href="mailto:dan.berindei@gmail.com" target="_blank">dan.berindei@gmail.com</a>&gt; wrote:<br>



&gt;<br>
&gt;<br>
&gt;<br>
&gt; On Fri, Jun 28, 2013 at 4:39 PM, William Burns &lt;<a href="mailto:mudokonman@gmail.com" target="_blank">mudokonman@gmail.com</a>&gt; wrote:<br>
&gt;&gt;<br>
&gt;&gt; On Fri, Jun 28, 2013 at 5:14 AM, Dan Berindei &lt;<a href="mailto:dan.berindei@gmail.com" target="_blank">dan.berindei@gmail.com</a>&gt;<br>
&gt;&gt; wrote:<br>
&gt;&gt; &gt;<br>
&gt;&gt; &gt; On Fri, Jun 28, 2013 at 12:17 AM, William Burns &lt;<a href="mailto:mudokonman@gmail.com" target="_blank">mudokonman@gmail.com</a>&gt;<br>
&gt;&gt; &gt; wrote:<br>
&gt;&gt; &gt;&gt;<br>
&gt;&gt; &gt;&gt; Trying to leave my points that would most likely have responses to<br>
&gt;&gt; &gt;&gt; second email so we can try to get back to a single thread :)<br>
&gt;&gt; &gt;&gt;<br>
&gt;&gt; &gt;<br>
&gt;&gt; &gt; No such luck :)<br>
&gt;&gt; &gt;<br>
&gt;&gt; &gt; Sorry for sending 2 replies in the first place, but it seemed more<br>
&gt;&gt; &gt; natural -<br>
&gt;&gt; &gt; I meant to comment on your proposal in one email and to describe my<br>
&gt;&gt; &gt; alternative proposal in the second email.<br>
&gt;&gt; &gt;<br>
&gt;&gt; &gt;&gt;<br>
&gt;&gt; &gt;&gt; On Thu, Jun 27, 2013 at 4:12 PM, Dan Berindei &lt;<a href="mailto:dan.berindei@gmail.com" target="_blank">dan.berindei@gmail.com</a>&gt;<br>
&gt;&gt; &gt;&gt; wrote:<br>
&gt;&gt; &gt;&gt; &gt;<br>
&gt;&gt; &gt;&gt; &gt; On Thu, Jun 27, 2013 at 4:18 PM, William Burns &lt;<a href="mailto:mudokonman@gmail.com" target="_blank">mudokonman@gmail.com</a>&gt;<br>
&gt;&gt; &gt;&gt; &gt; wrote:<br>
&gt;&gt; &gt;&gt; &gt;&gt;<br>
&gt;&gt; &gt;&gt; &gt;&gt; First off I apologize for the length.<br>
&gt;&gt; &gt;&gt; &gt;&gt;<br>
&gt;&gt; &gt;&gt; &gt;&gt; There have been a few Jiras recently that have identified L1<br>
&gt;&gt; &gt;&gt; &gt;&gt; consistency<br>
&gt;&gt; &gt;&gt; &gt;&gt; issues with both TX and non TX sync caches.  Async caches with L1<br>
&gt;&gt; &gt;&gt; &gt;&gt; have<br>
&gt;&gt; &gt;&gt; &gt;&gt; their<br>
&gt;&gt; &gt;&gt; &gt;&gt; own issues as well, but I only wanted to talk about sync caches.<br>
&gt;&gt; &gt;&gt; &gt;&gt;<br>
&gt;&gt; &gt;&gt; &gt;&gt; <a href="https://issues.jboss.org/browse/ISPN-3197" target="_blank">https://issues.jboss.org/browse/ISPN-3197</a><br>
&gt;&gt; &gt;&gt; &gt;&gt; <a href="https://issues.jboss.org/browse/ISPN-2965" target="_blank">https://issues.jboss.org/browse/ISPN-2965</a><br>
&gt;&gt; &gt;&gt; &gt;&gt; <a href="https://issues.jboss.org/browse/ISPN-2990" target="_blank">https://issues.jboss.org/browse/ISPN-2990</a><br>
&gt;&gt; &gt;&gt; &gt;&gt;<br>
&gt;&gt; &gt;&gt; &gt;&gt; I have proposed a solution in<br>
&gt;&gt; &gt;&gt; &gt;&gt; <a href="https://github.com/infinispan/infinispan/pull/1922" target="_blank">https://github.com/infinispan/infinispan/pull/1922</a> which should<br>
&gt;&gt; &gt;&gt; &gt;&gt; start<br>
&gt;&gt; &gt;&gt; &gt;&gt; L1<br>
&gt;&gt; &gt;&gt; &gt;&gt; consistency down the right track.  There are quite a few comments on<br>
&gt;&gt; &gt;&gt; &gt;&gt; it<br>
&gt;&gt; &gt;&gt; &gt;&gt; if<br>
&gt;&gt; &gt;&gt; &gt;&gt; you want to look into it more, but because of that I am moving this<br>
&gt;&gt; &gt;&gt; &gt;&gt; to<br>
&gt;&gt; &gt;&gt; &gt;&gt; the<br>
&gt;&gt; &gt;&gt; &gt;&gt; dev mailing list.<br>
&gt;&gt; &gt;&gt; &gt;&gt;<br>
&gt;&gt; &gt;&gt; &gt;&gt; The key changes in the PR are the following (non-tx):<br>
&gt;&gt; &gt;&gt; &gt;&gt;<br>
&gt;&gt; &gt;&gt; &gt;&gt; 1. Concurrent reads for a key that can retrieve a remote value are<br>
&gt;&gt; &gt;&gt; &gt;&gt; &quot;corralled&quot; into a single thread of execution for that given key.<br>
&gt;&gt; &gt;&gt; &gt;&gt; This<br>
&gt;&gt; &gt;&gt; &gt;&gt; would reduce network traffic with concurrent gets for the same key.<br>
&gt;&gt; &gt;&gt; &gt;&gt; Note<br>
&gt;&gt; &gt;&gt; &gt;&gt; the &quot;corralling&quot; only happens on a per key basis.<br>
&gt;&gt; &gt;&gt; &gt;<br>
&gt;&gt; &gt;&gt; &gt;<br>
&gt;&gt; &gt;&gt; &gt; Get commands on owners should not be serialized. Get commands on<br>
&gt;&gt; &gt;&gt; &gt; non-owners<br>
&gt;&gt; &gt;&gt; &gt; should not be serialized either, if the key already exists in L1. So<br>
&gt;&gt; &gt;&gt; &gt; I&#39;d<br>
&gt;&gt; &gt;&gt; &gt; say<br>
&gt;&gt; &gt;&gt; &gt; L1ReadSynchronizer should be L1WriteSynchronizer instead :)<br>
&gt;&gt; &gt;&gt;<br>
&gt;&gt; &gt;&gt; You are suggesting to check the context to see if the key is present<br>
&gt;&gt; &gt;&gt; before attempting the synchronizer right?  Reading your second email<br>
&gt;&gt; &gt;&gt; that seems the case :)<br>
&gt;&gt; &gt;&gt;<br>
&gt;&gt; &gt;<br>
&gt;&gt; &gt; Nope, I meant we should check the data container (aka the L1 cache). But<br>
&gt;&gt; &gt; obviously we have to check the invocation context first in a tx cache,<br>
&gt;&gt; &gt; if<br>
&gt;&gt; &gt; the tx read the key before it should see the same value.<br>
&gt;&gt; &gt;<br>
&gt;&gt; I was thinking because the non-tx wraps the value if it is in the data<br>
&gt;&gt; container before hand.  But actually for non-tx it seems I should<br>
&gt;&gt; check the data container only and tx I should only check the ctx (to<br>
&gt;&gt; guarantee read consistency)<br>
&gt;<br>
&gt;<br>
&gt; For tx you probably have to check the context first, and the data container<br>
&gt; only if you don&#39;t find the entry in the context (non-existent entries that<br>
&gt; were previously read in the tx should have a NullMarkerEntry in the<br>
&gt; context).<br>
&gt;<br>
<br>
</div></div>Good to know.<br></blockquote><div><br></div><div>I think Pedro is removing NullMarkerEntry in <a href="https://github.com/infinispan/infinispan/pull/1937/files">https://github.com/infinispan/infinispan/pull/1937/files</a>, though.<br>

<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
<div><div><br>
&gt;&gt; &gt;&gt;<br>
&gt;&gt; &gt;&gt; &gt;<br>
&gt;&gt; &gt;&gt; &gt;&gt;<br>
&gt;&gt; &gt;&gt; &gt;&gt; 2. The single thread that is doing the remote get would update the<br>
&gt;&gt; &gt;&gt; &gt;&gt; L1<br>
&gt;&gt; &gt;&gt; &gt;&gt; if<br>
&gt;&gt; &gt;&gt; &gt;&gt; able (without locking) and make available the value to all the<br>
&gt;&gt; &gt;&gt; &gt;&gt; requests<br>
&gt;&gt; &gt;&gt; &gt;&gt; waiting on the get.<br>
&gt;&gt; &gt;&gt; &gt;<br>
&gt;&gt; &gt;&gt; &gt;<br>
&gt;&gt; &gt;&gt; &gt; Well, L1ReadSynchronizer does prevent other threads from modifying<br>
&gt;&gt; &gt;&gt; &gt; the<br>
&gt;&gt; &gt;&gt; &gt; same<br>
&gt;&gt; &gt;&gt; &gt; key, so we are locking the key - just not using LockManager.<br>
&gt;&gt; &gt;&gt; &gt; It would also require StateTransferLock.acquireSharedTopologyLock()<br>
&gt;&gt; &gt;&gt; &gt; to<br>
&gt;&gt; &gt;&gt; &gt; make<br>
&gt;&gt; &gt;&gt; &gt; sure it doesn&#39;t write an L1 entry after the node became a proper<br>
&gt;&gt; &gt;&gt; &gt; owner.<br>
&gt;&gt; &gt;&gt;<br>
&gt;&gt; &gt;&gt; Agree, when I was saying locking I was meaning through the use of the<br>
&gt;&gt; &gt;&gt; lock manager.<br>
&gt;&gt; &gt;&gt;<br>
&gt;&gt; &gt;&gt; &gt;<br>
&gt;&gt; &gt;&gt; &gt;&gt;<br>
&gt;&gt; &gt;&gt; &gt;&gt; 3. Invalidations that are received would first check to see if there<br>
&gt;&gt; &gt;&gt; &gt;&gt; is<br>
&gt;&gt; &gt;&gt; &gt;&gt; a<br>
&gt;&gt; &gt;&gt; &gt;&gt; current remote get occurring for it&#39;s keys.  If there is it will<br>
&gt;&gt; &gt;&gt; &gt;&gt; attempt to<br>
&gt;&gt; &gt;&gt; &gt;&gt; cancel the L1 write(s) before it occurs.  If it cannot cancel the L1<br>
&gt;&gt; &gt;&gt; &gt;&gt; write,<br>
&gt;&gt; &gt;&gt; &gt;&gt; then it must also wait on the current remote get completion and<br>
&gt;&gt; &gt;&gt; &gt;&gt; subsequently<br>
&gt;&gt; &gt;&gt; &gt;&gt; run the invalidation.  Note the cancellation would fail when the<br>
&gt;&gt; &gt;&gt; &gt;&gt; remote<br>
&gt;&gt; &gt;&gt; &gt;&gt; get<br>
&gt;&gt; &gt;&gt; &gt;&gt; was done and it is in the middle of updating the L1, so this would<br>
&gt;&gt; &gt;&gt; &gt;&gt; be<br>
&gt;&gt; &gt;&gt; &gt;&gt; very<br>
&gt;&gt; &gt;&gt; &gt;&gt; small window.<br>
&gt;&gt; &gt;&gt; &gt;<br>
&gt;&gt; &gt;&gt; &gt;<br>
&gt;&gt; &gt;&gt; &gt; I think it would be clearer to describe this as the L1 invalidation<br>
&gt;&gt; &gt;&gt; &gt; cancelling the remote get, not the L1 update, because the actual L1<br>
&gt;&gt; &gt;&gt; &gt; update<br>
&gt;&gt; &gt;&gt; &gt; can&#39;t be cancelled.<br>
&gt;&gt; &gt;&gt;<br>
&gt;&gt; &gt;&gt; When I say L1 update I meant the write to the data container after the<br>
&gt;&gt; &gt;&gt; remote get.  The invalidation can&#39;t stop the remote get, all it does<br>
&gt;&gt; &gt;&gt; is tell the caller that &quot;Hey don&#39;t write the remote value you<br>
&gt;&gt; &gt;&gt; retrieved into the L1.&quot;<br>
&gt;&gt; &gt;&gt;<br>
&gt;&gt; &gt;<br>
&gt;&gt; &gt; Oh right, the get command will still use the value it got from the<br>
&gt;&gt; &gt; remote<br>
&gt;&gt; &gt; node, it just won&#39;t write it.<br>
&gt;&gt; &gt; That makes me wonder, though, if something like this can happen:<br>
&gt;&gt; &gt;<br>
&gt;&gt; &gt; 1. A invokes get(k), starts a L1ReadSynchronizer and a remote get to B<br>
&gt;&gt; &gt; 2. B invokes put(k, v) and sends an invalidation command to A<br>
&gt;&gt; &gt; 3. The invalidation command cancels the L1 put on A<br>
&gt;&gt; &gt; 4. A invokes get(k) again, finds the L1ReadSynchronizer from step 1) and<br>
&gt;&gt; &gt; queues on it<br>
&gt;&gt; &gt; 5. Both get(k) commands return the same value, even though the value has<br>
&gt;&gt; &gt; changed on the owner(s).<br>
&gt;&gt; &gt;<br>
&gt;&gt;<br>
&gt;&gt; Yeah that was going to be covered in ISPN-2965.  This would be a<br>
&gt;&gt; problem even with locking since it could be a different node doing the<br>
&gt;&gt; get.<br>
&gt;&gt;<br>
&gt;&gt;<br>
&gt;&gt; <a href="https://github.com/wburns/infinispan/blob/ISPN-3197-singlethread/core/src/main/java/org/infinispan/interceptors/distribution/L1NonTxInterceptor.java#L341" target="_blank">https://github.com/wburns/infinispan/blob/ISPN-3197-singlethread/core/src/main/java/org/infinispan/interceptors/distribution/L1NonTxInterceptor.java#L341</a><br>



&gt;&gt;<br>
&gt;&gt; I also have a test currently that is disabled in the test class that<br>
&gt;&gt; reproduces this.<br>
&gt;&gt;<br>
&gt;&gt;<br>
&gt;&gt; <a href="https://github.com/wburns/infinispan/blob/ISPN-3197-singlethread/core/src/test/java/org/infinispan/distribution/DistSyncL1FuncTest.java#L351" target="_blank">https://github.com/wburns/infinispan/blob/ISPN-3197-singlethread/core/src/test/java/org/infinispan/distribution/DistSyncL1FuncTest.java#L351</a><br>



&gt;&gt;<br>
&gt;<br>
&gt; I think we&#39;re talking about slightly different things here. Even if you<br>
&gt; wanted to send another invalidation command after the entry was committed to<br>
&gt; the data container on the owner, you won&#39;t get a second get request on the<br>
&gt; owner because that get request has been corralled on the requestor:<br>
&gt;<br>
&gt; 1st get initiated -&gt; remote get sent -&gt; invalidation -&gt; remote get returns<br>
&gt; -&gt; 2nd get initiated -&gt; both get ops return with initial value<br>
<br>
</div></div>The 1st get removes it&#39;s value from the map when it is done, to<br>
prevent memory &quot;leaks&quot;.  So in this case the second get would actually<br>
require a new remote &quot;corral&quot;.<br></blockquote><div><br></div><div>Not if the 2nd get is initiated before we receive the response for the 1st get...<br></div><div><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">



<div><br>
&gt;<br>
&gt; I think you need to either remove the L1ReadSynchronizer from the map during<br>
&gt; invalidation, or to re-fetch the value if there was an invalidation, in<br>
&gt; order to avoid this.<br>
<br>
</div>I originally had it remove from the map during an invalidation, but it<br>
shouldn&#39;t be needed.  This would just cause an additional get to be<br>
sent remotely possibly.  Also consistency wise in either case if it<br>
got the new or old value while it is still in the middle of an update<br>
is still consistent and would follow the semantics that are described<br>
in the ConcurrentMap interface.  Gets by default for ConcurrentHashMap<br>
for example don&#39;t acquire locks unless there is no value in the map,<br>
so it is entirely possible for 2 threads to see a different value if<br>
the value wasn&#39;t flushed to memory yet (stored in CPU register).<br></blockquote><div><br></div><div>How about this scenario?<br></div><div><br></div><div><div>A: tx1@A: get(k)<br></div><div>A: tx1@A: invoke get(k) on B<br>

</div><div>B: tx1@A: execute get(k), reply with v1<br>
</div><div>B: tx2@B: put(k, v2)<br></div><div>B: tx2@B: invoke invalidate(k) on A<br></div><div>A: tx2@B: execute invalidate(k)<br></div><div>A: tx2@B: skip update for get(k)<br></div><div>B: tx2@B: report that tx2 was committed<br>


</div>A: tx3@A: get(k)<br><div>A: tx3@A: queue on tx1&#39;s remote call<br></div><div>A: tx1@A: receive v1 from B<br></div><div>A: tx1@A: return v1<br></div><div>A: tx3@A: return v1 as well<br><br></div>I&#39;d
 argue that it&#39;s not ok for tx3 to return v1, because it only started 
the get operation after tx2 was committed. E.g. in a web app without session 
affinity, we could handle a first request from user X on node B, 
writing k = v2, and a second request on node A, reading k = v1.<br><br></div><div>Of course, this only applies to tx caches in DIST_SYNC mode, with syncCommitPhase = true. With other cache configurations, we have plenty other opportunities for inconsistency to worry about this...<br>


</div><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
<div><br>
&gt;<br>
&gt; Of course, you still have to deal with ISPN-2965 and the fact that a get<br>
&gt; command can reach the owner after the invalidations were sent but before the<br>
&gt; entry is committed to the data container. Maybe it would be better if we&#39;d<br>
&gt; send L1 invalidations only after the entry was committed?<br>
<br>
</div>This was my thought before as well.  I talked briefly with mmarkus<br>
regarding this.  His thought was we wanted it to participate in the<br>
prepare step.  However looking at tx caches it only flushes the L1 on<br>
a commit, so I guess that isn&#39;t true.  For non-tx caches I am guessing<br>
we do this for performance reasons since we can have the L1<br>
invalidations and the other owner nodes update at the same time and<br>
then block on them all later.<br>
<br>
Doing the call after the data is committed is actually the fix I had<br>
for ISPN-2965.  My plan was that we would have both invalidation<br>
points.  One before the data was committed and one after the data<br>
container was updated and the latter would always be asynchronous:<br>
kind of best effort to remove any stragglers.  From your point though,<br>
I am debating if we still want the initial invalidation though.  I<br>
think the performance benefit would be nice though, as I would hope we<br>
would very very rarely require the later invalidation.  WDYT?<br></blockquote><div><br></div><div>You have a point about non-tx caches: sending the invalidation 
to non-owners in parallel with sending the write to backup owners is 
clearly faster then sending them sequentially. The chance of a remote get arriving at that precise time should be very small, so the additional invalidation command shouldn&#39;t be a problem.<br><br>OTOH, I think sending the second invalidation asynchronously would allow the same temporary inconsistency as the invalidation command not removing the get &quot;corral&quot; immediately when receiving the invalidation command. And for tx caches, invoking the invalidation command only after the entry was committed wouldn&#39;t add any costs.<br>

</div><div><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
<div><div><br>
&gt;<br>
&gt;&gt;<br>
&gt;&gt; &gt;<br>
&gt;&gt; &gt;&gt;<br>
&gt;&gt; &gt;&gt; &gt;<br>
&gt;&gt; &gt;&gt; &gt; We also have to remove the logic in AbstractLockingInterceptor that<br>
&gt;&gt; &gt;&gt; &gt; skips L1<br>
&gt;&gt; &gt;&gt; &gt; invalidation for a key if it can&#39;t acquire a lock with a 0 timeout.<br>
&gt;&gt; &gt;&gt; &gt;<br>
&gt;&gt; &gt;&gt; &gt;&gt;<br>
&gt;&gt; &gt;&gt; &gt;&gt; 4. Local writes will also do the same thing as the invalidation with<br>
&gt;&gt; &gt;&gt; &gt;&gt; cancelling or waiting.  Note that non tx local writes only do L1<br>
&gt;&gt; &gt;&gt; &gt;&gt; invalidations and don&#39;t write the value to the data container.<br>
&gt;&gt; &gt;&gt; &gt;&gt; Reasons<br>
&gt;&gt; &gt;&gt; &gt;&gt; why<br>
&gt;&gt; &gt;&gt; &gt;&gt; I found at <a href="https://issues.jboss.org/browse/ISPN-3214" target="_blank">https://issues.jboss.org/browse/ISPN-3214</a><br>
&gt;&gt; &gt;&gt; &gt;<br>
&gt;&gt; &gt;&gt; &gt;<br>
&gt;&gt; &gt;&gt; &gt; I didn&#39;t know about ISPN-3214 or that non-tx writes don&#39;t write to<br>
&gt;&gt; &gt;&gt; &gt; L1,<br>
&gt;&gt; &gt;&gt; &gt; but<br>
&gt;&gt; &gt;&gt; &gt; it sounds fair.<br>
&gt;&gt; &gt;&gt;<br>
&gt;&gt; &gt;&gt; Yeah I really wanted that to work, but without some additional checks<br>
&gt;&gt; &gt;&gt; such as versioned data, I don&#39;t see a way to do this without locking<br>
&gt;&gt; &gt;&gt; at the primary node like tx caches.<br>
&gt;&gt; &gt;&gt;<br>
&gt;&gt; &gt;<br>
&gt;&gt; &gt; In theory, the primary owner could send a synchronous RPC back to the<br>
&gt;&gt; &gt; originator while it is holding the lock, saying &quot;ok, you can now write<br>
&gt;&gt; &gt; the<br>
&gt;&gt; &gt; value to L1&quot;. But I don&#39;t think the slowdown from an additional RPC<br>
&gt;&gt; &gt; would be<br>
&gt;&gt; &gt; acceptable.<br>
&gt;&gt; &gt;<br>
&gt;&gt;<br>
&gt;&gt; I would agree ;(<br>
&gt;&gt;<br>
&gt;&gt; &gt;&gt;<br>
&gt;&gt; &gt;&gt; &gt;<br>
&gt;&gt; &gt;&gt; &gt;&gt;<br>
&gt;&gt; &gt;&gt; &gt;&gt; 5. Writes that require the previous value and don&#39;t have it in the<br>
&gt;&gt; &gt;&gt; &gt;&gt; L1<br>
&gt;&gt; &gt;&gt; &gt;&gt; would also do it&#39;s get operations using the same &quot;corralling&quot;<br>
&gt;&gt; &gt;&gt; &gt;&gt; method.<br>
&gt;&gt; &gt;&gt; &gt;<br>
&gt;&gt; &gt;&gt; &gt;<br>
&gt;&gt; &gt;&gt; &gt; The remoteGetBeforeWrites are a bit different - they don&#39;t happen on<br>
&gt;&gt; &gt;&gt; &gt; non-owners, they only happen on writeCH-owners that didn&#39;t receive<br>
&gt;&gt; &gt;&gt; &gt; that<br>
&gt;&gt; &gt;&gt; &gt; entry via state transfer yet. They put the value in the<br>
&gt;&gt; &gt;&gt; &gt; InvocationContext,<br>
&gt;&gt; &gt;&gt; &gt; but they don&#39;t write it to the data container - nor do they<br>
&gt;&gt; &gt;&gt; &gt; invalidate<br>
&gt;&gt; &gt;&gt; &gt; the<br>
&gt;&gt; &gt;&gt; &gt; L1 entry, if it exists.<br>
&gt;&gt; &gt;&gt;<br>
&gt;&gt; &gt;&gt; Ah yes that is true, but only for non tx caches it seems.<br>
&gt;&gt; &gt;&gt;<br>
&gt;&gt; &gt;<br>
&gt;&gt; &gt; Right, I wasn&#39;t considering the fact that a conditional command may<br>
&gt;&gt; &gt; fail...<br>
&gt;&gt; &gt; I think if that happens, even in non-tx caches EntryWrappingInterceptor<br>
&gt;&gt; &gt; may<br>
&gt;&gt; &gt; write the entry to the data container as an L1 entry. If we move the L1<br>
&gt;&gt; &gt; writes to the L1 interceptor, we must ensure that<br>
&gt;&gt; &gt; EntryWrappingInterceptor<br>
&gt;&gt; &gt; doesn&#39;t write anything to L1 any more.<br>
&gt;&gt; &gt;<br>
&gt;&gt;<br>
&gt;&gt; We still need EntryWrappingInterceptor to write to the L1 cache for tx<br>
&gt;&gt; caches I would think.  We only want to write to the L1 after the tx is<br>
&gt;&gt; committed for write commands.  Read commands would be fine writing to<br>
&gt;&gt; the container immediately.  Also I would hope conditional writes<br>
&gt;&gt; wouldn&#39;t update the context with the new value until we knew the write<br>
&gt;&gt; was successful, should prevent bad data being written there.<br>
&gt;&gt;<br>
&gt;<br>
&gt; The L1 interceptor could still only write to the data container during the<br>
&gt; commit phase. I was thinking of separating the regular writes from the L1<br>
&gt; writes because L1 writes would need additional synchronization, but maybe<br>
&gt; that&#39;s not the case.<br>
&gt;<br>
<br>
</div></div>For non-tx caches I wouldn&#39;t think it would matter as it is just an<br>
invalidation.  At worse case it would cause an additional get.<br>
<br>
For tx caches it should still be okay as well since the invalidation<br>
would be done in the scope of a cache holding the write lock, so the<br>
only updates that would occur would be a single invalidation with a<br>
get or a single write with a get.  If the get is done before, then<br>
either will overwrite.  If the get is done after it will be<br>
invalidated again with ISPN-2965.<br>
<br>
Is there something here you can think of in addition?<br></blockquote><div><br></div><div></div><div>Well, the additional invalidation for ISPN-2965 won&#39;t necessarily be done while holding a lock, but I don&#39;t see a problem with it either.<br>


</div><div><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
<div><div><br>
&gt;&gt;<br>
&gt;&gt; &gt;<br>
&gt;&gt; &gt;&gt;<br>
&gt;&gt; &gt;&gt; &gt;<br>
&gt;&gt; &gt;&gt; &gt;&gt;<br>
&gt;&gt; &gt;&gt; &gt;&gt;<br>
&gt;&gt; &gt;&gt; &gt;&gt; 4/5 are not currently implemented in PR.<br>
&gt;&gt; &gt;&gt; &gt;&gt;<br>
&gt;&gt; &gt;&gt; &gt;&gt; This approach would use no locking for non tx caches for all L1<br>
&gt;&gt; &gt;&gt; &gt;&gt; operations.  The synchronization point would be done through the<br>
&gt;&gt; &gt;&gt; &gt;&gt; &quot;corralling&quot; method and invalidations/writes communicating to it.<br>
&gt;&gt; &gt;&gt; &gt;&gt;<br>
&gt;&gt; &gt;&gt; &gt;&gt; Transactional caches would do almost the same thing as non-tx.  Note<br>
&gt;&gt; &gt;&gt; &gt;&gt; these<br>
&gt;&gt; &gt;&gt; &gt;&gt; changes are not done in any way yet.<br>
&gt;&gt; &gt;&gt; &gt;&gt;<br>
&gt;&gt; &gt;&gt; &gt;&gt; 1. Gets would now update the L1 immediately after retrieving the<br>
&gt;&gt; &gt;&gt; &gt;&gt; value<br>
&gt;&gt; &gt;&gt; &gt;&gt; without locking, but still using the &quot;corralling&quot; technique that<br>
&gt;&gt; &gt;&gt; &gt;&gt; non-tx<br>
&gt;&gt; &gt;&gt; &gt;&gt; does.  Previously the L1 update from a get was transactional.  This<br>
&gt;&gt; &gt;&gt; &gt;&gt; actually<br>
&gt;&gt; &gt;&gt; &gt;&gt; would remedy issue [1]<br>
&gt;&gt; &gt;&gt; &gt;&gt;<br>
&gt;&gt; &gt;&gt; &gt;&gt; 2. Writes currently acquire the remote lock when committing, which<br>
&gt;&gt; &gt;&gt; &gt;&gt; is<br>
&gt;&gt; &gt;&gt; &gt;&gt; why<br>
&gt;&gt; &gt;&gt; &gt;&gt; tx caches are able to update the L1 with the value.  Writes would do<br>
&gt;&gt; &gt;&gt; &gt;&gt; the<br>
&gt;&gt; &gt;&gt; &gt;&gt; same cancellation/wait method as non-tx.<br>
&gt;&gt; &gt;&gt; &gt;&gt;<br>
&gt;&gt; &gt;<br>
&gt;&gt; &gt;<br>
&gt;&gt; &gt; Hmm, I don&#39;t think your current approach for L1 invalidations would work<br>
&gt;&gt; &gt; for<br>
&gt;&gt; &gt; L1 writes, because the actual write to the data container is not<br>
&gt;&gt; &gt; synchronized (well, technically you still have the 0-timeout locking for<br>
&gt;&gt; &gt; invalidation commands, but I think you&#39;re planning to remove that). So<br>
&gt;&gt; &gt; it&#39;s<br>
&gt;&gt; &gt; possible for an L1 write and an L1 invalidation to wait for the same<br>
&gt;&gt; &gt; remote<br>
&gt;&gt; &gt; get and then to get executed in the wrong order.<br>
&gt;&gt; &gt;<br>
&gt;&gt;<br>
&gt;&gt; For non-tx that doesn&#39;t matter as a write and L1 invalidation both do<br>
&gt;&gt; invalidation.<br>
&gt;&gt;<br>
&gt;&gt; For tx it should be fine as well since it will hold the primary owner<br>
&gt;&gt; lock for the write duration, which includes L1 writes (so only that tx<br>
&gt;&gt; is writing it&#39;s value).  Tx caches don&#39;t process the L1 invalidation<br>
&gt;&gt; message when it comes back since they were the originator also.<br>
&gt;&gt;<br>
&gt;<br>
&gt; True, in most cases different writers would be synchronized via the lock on<br>
&gt; the primary owner. There are 2 situations where that doesn&#39;t happen, though:<br>
&gt; * When syncCommitPhase = false, the primary lock may be released before the<br>
&gt; entry is actually committed to L1.<br>
&gt; * During state transfer, the received state is applied without holding a<br>
&gt; lock on the primary, or even a local lock. State transfer only uses<br>
&gt; StateConsumerImpl.updatedKeys for synchronization, which turns out to be a<br>
&gt; problem with regular entries as well:<br>
&gt; <a href="https://issues.jboss.org/browse/ISPN-3287" target="_blank">https://issues.jboss.org/browse/ISPN-3287</a><br>
&gt;<br>
<br>
</div></div>Yeah to be honest I was going to log a Jira a while back for<br>
syncCommitPhase = false causing L1 inconsistencies.  I didn&#39;t since<br>
realistically there is no easy way to guarantee consistency with<br>
async, until we have some way of knowing what order the updates were<br>
completed from the owner&#39;s perspective.<br></blockquote><div><br></div><div>Agree, syncCommitPhase = false has its own issues. <br><br>I was talking with Mircea about that, and how we may be able to fix (part of) the issues by making syncCommitPhase = false behave more like syncCommitPhase = true. I.e. we could return immediately to the user, but spin another thread to invoke the commit synchronously and the tx completion command to release locks asynchronously. I think that would make things easier with L1 as well.<br>


<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
<br>
ISPN-3287 seems like we will want to look into that one more closely<br>
as to how that will work with L1 at some point, I agree.<br>
<div><div><br>
&gt;&gt;<br>
&gt;&gt; &gt;<br>
&gt;&gt; &gt;&gt;<br>
&gt;&gt; &gt;&gt; &gt;&gt; 3. Writes that require the previous value and don&#39;t have it in the<br>
&gt;&gt; &gt;&gt; &gt;&gt; L1<br>
&gt;&gt; &gt;&gt; &gt;&gt; would also do it&#39;s get operations using the same method.<br>
&gt;&gt; &gt;&gt; &gt;<br>
&gt;&gt; &gt;&gt; &gt;<br>
&gt;&gt; &gt;&gt; &gt; Just like for non-tx caches, I don&#39;t think these remote gets have to<br>
&gt;&gt; &gt;&gt; &gt; be<br>
&gt;&gt; &gt;&gt; &gt; stored in L1.<br>
&gt;&gt; &gt;&gt;<br>
&gt;&gt; &gt;&gt; Tx caches do the remote get and could cache the L1 value immediately.<br>
&gt;&gt; &gt;&gt; This would help if the transaction is rolled back or a conditional<br>
&gt;&gt; &gt;&gt; operation failed etc.  There are some locking concerns, here but I<br>
&gt;&gt; &gt;&gt; will leave that the other post.<br>
&gt;&gt; &gt;&gt;<br>
&gt;&gt; &gt;<br>
&gt;&gt; &gt; Right, the L1 entry could be immediately written to the data container,<br>
&gt;&gt; &gt; but<br>
&gt;&gt; &gt; not if it complicates things too much: most writes should be successful<br>
&gt;&gt; &gt; anyway.<br>
&gt;&gt; &gt;<br>
&gt;&gt;<br>
&gt;&gt; I agree the writes should be successful, but there is no telling if<br>
&gt;&gt; the transaction will be committed.  Something else outside of<br>
&gt;&gt; infinispan could error causing a rollback or at worst case 2PC could<br>
&gt;&gt; fail prepare (not touching heuristic failures).<br>
&gt;&gt;<br>
&gt;&gt; Also this would help plug up this hole:<br>
&gt;&gt;<br>
&gt;&gt; <a href="https://issues.jboss.org/browse/ISPN-2965?focusedCommentId=12779780&amp;page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-12779780" target="_blank">https://issues.jboss.org/browse/ISPN-2965?focusedCommentId=12779780&amp;page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-12779780</a><br>



&gt;&gt;<br>
&gt;<br>
&gt; Sorry, I wasn&#39;t clear... I meant if 99.9% of conditional writes write the<br>
&gt; initial value to L1 and then immediately write the new value, perhaps the<br>
&gt; additional overhead of an extra write to the data container is not worth the<br>
&gt; benefit of having the value in L1 in the 0.1% of cases where the write is<br>
&gt; not successful or the tx fails.<br>
<br>
</div></div>The overhead of writing to the data container should be relatively<br>
small though as we already did the remote get anyways and everything<br>
is local.<br>
<br>
I agree Infinispan conditional writes could be successful 99.9% or<br>
more even.  My concern with failures would be someone explicitly<br>
rolling back the transaction due to conflict or processing error in<br>
say user code, which can be quite common, at least in the systems I<br>
have seen :P<br></blockquote><div><br></div><div>Fair enough.<br> <br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
<div><div><br>
&gt;<br>
&gt; Of course, we don&#39;t have proper statistics about the percentage of<br>
&gt; conditional writes that succeed in real-life applications, so in the end<br>
&gt; it&#39;s your decision.<br>
&gt;<br>
&gt;&gt;<br>
&gt;&gt; &gt;&gt; &gt;<br>
&gt;&gt; &gt;&gt; &gt;&gt;<br>
&gt;&gt; &gt;&gt; &gt;&gt; 4. For tx cache [2] would also have to be done.<br>
&gt;&gt; &gt;&gt; &gt;&gt;<br>
&gt;&gt; &gt;&gt; &gt;&gt; [1] -<br>
&gt;&gt; &gt;&gt; &gt;&gt;<br>
&gt;&gt; &gt;&gt; &gt;&gt;<br>
&gt;&gt; &gt;&gt; &gt;&gt; <a href="https://issues.jboss.org/browse/ISPN-2965?focusedCommentId=12779780&amp;page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-12779780" target="_blank">https://issues.jboss.org/browse/ISPN-2965?focusedCommentId=12779780&amp;page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-12779780</a><br>



&gt;&gt; &gt;&gt; &gt;&gt; [2] - <a href="https://issues.jboss.org/browse/ISPN-1540" target="_blank">https://issues.jboss.org/browse/ISPN-1540</a><br>
&gt;&gt; &gt;&gt; &gt;&gt;<br>
&gt;&gt; &gt;&gt; &gt;&gt; Also rehashing is another issue, but we should be able to acquire<br>
&gt;&gt; &gt;&gt; &gt;&gt; the<br>
&gt;&gt; &gt;&gt; &gt;&gt; state transfer lock before updating the L1 on a get, just like when<br>
&gt;&gt; &gt;&gt; &gt;&gt; an<br>
&gt;&gt; &gt;&gt; &gt;&gt; entry<br>
&gt;&gt; &gt;&gt; &gt;&gt; is committed to the data container.<br>
&gt;&gt; &gt;&gt; &gt;&gt;<br>
&gt;&gt; &gt;&gt; &gt;<br>
&gt;&gt; &gt;&gt; &gt; The same for L1 invalidations - we don&#39;t want to remove real entries<br>
&gt;&gt; &gt;&gt; &gt; from<br>
&gt;&gt; &gt;&gt; &gt; the data container after the local node became an owner.<br>
&gt;&gt; &gt;&gt;<br>
&gt;&gt; &gt;&gt; Yeah actually as you mentioned this, it sounds like a hole currently<br>
&gt;&gt; &gt;&gt; even.  I don&#39;t know if this case can happen, but what if you received<br>
&gt;&gt; &gt;&gt; a L1 invalidation and then got a rehash event right before it was<br>
&gt;&gt; &gt;&gt; committing it to the container?  It seems the L1 commit to the<br>
&gt;&gt; &gt;&gt; container would block until it could get the shared topology lock and<br>
&gt;&gt; &gt;&gt; after it could then removes the value.  I probably need to dig into<br>
&gt;&gt; &gt;&gt; the state transfer stuff deeper to know myself.<br>
&gt;&gt; &gt;&gt;<br>
&gt;&gt; &gt;<br>
&gt;&gt; &gt; I think that&#39;s ok, because the value is stale, and any future<br>
&gt;&gt; &gt; get/conditional write command will request the new value from the<br>
&gt;&gt; &gt; previous<br>
&gt;&gt; &gt; owners.<br>
&gt;&gt; &gt;<br>
&gt;&gt; &gt; I don&#39;t think the new value can arrive via a StateResponseCommand and be<br>
&gt;&gt; &gt; applied to the data container before the invalidation command manages to<br>
&gt;&gt; &gt; commit, because the put command sends the invalidations first<br>
&gt;&gt; &gt; (synchronously) and only then commits on the owner.<br>
&gt;&gt; &gt;<br>
&gt;&gt;<br>
&gt;&gt; I defer to your judgement here ;-)<br>
&gt;&gt;<br>
&gt;<br>
&gt; On a second read, that second paragraph doesn&#39;t make a lot of sense...<br>
&gt;<br>
&gt; To rephrase: the InvalidateL1Command could remove an entry after it was<br>
&gt; written via state transfer. Since the invalidation is synchronous, that<br>
&gt; means the commit command will be forwarded to the new owner, and it will<br>
&gt; write the new value gain. If any tx tries to read the key in the meantime,<br>
&gt; it will re-read it from the old owners, so that shouldn&#39;t be a problem.<br>
&gt;<br>
&gt; But if there are 2 old owners (let&#39;s call them A and B), the commit command<br>
&gt; forwarded from A could be executed on the new owner just before the<br>
&gt; invalidation command from B. And once the forwarded commit from A is<br>
&gt; executed, the forwarded commit from B for the same tx won&#39;t do anything, and<br>
&gt; k will be lost. Even if state transfer receives k after that, it won&#39;t<br>
&gt; overwrite it because it&#39;s in the StateConsumerImpl.updatedKeys set.<br>
&gt;<br>
&gt; There is another hitch with async commit - it&#39;s possible that the new owner<br>
&gt; didn&#39;t receive the transaction data via state transfer (because it requested<br>
&gt; transaction data from the B, and the transaction had already been committed<br>
&gt; there). Then any forwarded commit won&#39;t do anything and the entry will be<br>
&gt; lost.<br>
&gt;<br>
&gt;<br>
&gt; Going back to my proposal of adding an ownership check before actually<br>
&gt; removing the L1 entry (and while holding the shared topology lock)...<br>
&gt;<br>
&gt; Say nodes [A, B] were the previous owners of key k, C has k in its L1 cache,<br>
&gt; and [B, C] are the new owners (so the owners in the write CH are [A, B, C]).<br>
&gt; If there is a transaction modifying key k, the timing could be something<br>
&gt; like this:<br>
&gt;<br>
&gt; 1. The commit command finishes on A and B before they install the new<br>
&gt; topology. Because StateRequestCommands block until the state provider<br>
&gt; installs the new topology, that means node C didn&#39;t request the state for k<br>
&gt; yet, so the invalidateL1(k) command runs on C before the state transfer<br>
&gt; put(k, v) command.<br>
&gt;<br>
&gt; However, there is a chance that C already installed the new topology before<br>
&gt; executing the invalidate(k) command, so if we do just the ownership check in<br>
&gt; the L1 interceptor, the key won&#39;t be deleted. That&#39;s not ok, because we&#39;ll<br>
&gt; have a stale value until state transfer overwrites it. And even worse, if we<br>
&gt; had a remove(k) instead of a put(k, v), C would only remove the stale value<br>
&gt; for k when it expired.<br>
&gt;<br>
&gt; We may need to clear all the keys for which C is becoming an owner after<br>
&gt; installing the new topology, but before requesting transaction data (which<br>
&gt; means before processing transactions with the new topology id as well), when<br>
&gt; L1 is enabled.<br>
<br>
</div></div>Yeah I think any keys that are moving owners it would be best just to<br>
remove those from the L1 and requestors.  Or maybe that is too heavy<br>
handed?<br></blockquote><div><br></div><div>Nope, what we already do is very similar, we just don&#39;t clear the entries owned by the node in the new CH. See StateConsumerImpl.invalidateSegments().<br><br>BTW, I was assuming here that we send the invalidation command from all the owners, but like Pedro pointed out we&#39;re only sending it from the primary owner. So we need to revisit these scenarios once we decide on how to treat <br>


<br>One thing I&#39;m worrying about, except I haven&#39;t figured out if it can really happen yet, is that DistributionLogic.commitEntry can write an the
 same entry as a regular entry or as an L1 entry, depending on whether 
the local node is an owner. If we were to 
call it before installing a new topology to write an L1 entry and it wrote a regular entry 
instead, after the new topology was installed, we might end up with a stale value.<br><br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
<div><div><br>
&gt;<br>
&gt; 2. The commit starts on A and B before the new topology is installed, but by<br>
&gt; the time it finishes on A, A installs the new topology.<br>
&gt; StateTransferInterceptor then forwards the commit command to C, and on C it<br>
&gt; writes the new value (eventually waiting for C to receive the transaction<br>
&gt; data from A). With sync commit + tx completion notification, C is guaranteed<br>
&gt; to receive the tx whether it requests it from A or B.<br>
&gt;<br>
&gt; If StateConsumer receives a value for k later, it won&#39;t overwrite it, except<br>
&gt; for this bug: <a href="https://issues.jboss.org/browse/ISPN-3287" target="_blank">https://issues.jboss.org/browse/ISPN-3287</a><br>
&gt;<br>
&gt; There is a chance that the forwarded commit command from A reaches C around<br>
&gt; the same time with the invalidateL1(k) command from B. But the commit<br>
&gt; command is sent with the new topology id, so it will only execute after C<br>
&gt; installed the new topology as well. If the invalidateL1(k) command executes<br>
&gt; at the same time, the ownership check will prevent it from doing anything.<br>
&gt; (Without the ownership check we could remove key k just after writing it.)<br>
&gt;<br>
&gt; With async commit it&#39;s more complicated: if C requested the transaction data<br>
&gt; from B, and B had already finished committing by the time it sent<br>
&gt; transaction data, the forwarded commit on C won&#39;t do anything. Instead, C<br>
&gt; should receive the new value via state transfer, going back to case 1.<br>
&gt;<br>
&gt; 3. The commit is initiated after the new topology is installed. The commit<br>
&gt; command is sent to C directly, it waits there for C to receive transaction<br>
&gt; data from A or B, and it will write the new value. Since the commit was sent<br>
&gt; with the new topology id, it will block on A and B until they install the<br>
&gt; new topology, and won&#39;t trigger any invalidateL1(k) command to C.<br>
&gt;<br>
&gt; With async commit there is still a chance that A and/or B already finish the<br>
&gt; commit by the time C requests the transaction data, and C will only receive<br>
&gt; the new value via state transfer - just like in case 1.<br>
&gt;<br>
&gt;&gt;<br>
&gt;&gt; &gt;&gt;<br>
&gt;&gt; &gt;&gt; &gt;<br>
&gt;&gt; &gt;&gt; &gt;&gt;<br>
&gt;&gt; &gt;&gt; &gt;&gt; Any comments/concerns would be appreciated.<br>
&gt;&gt; &gt;&gt; &gt;&gt;<br>
&gt;&gt; &gt;&gt; &gt;&gt; Thanks,<br>
&gt;&gt; &gt;&gt; &gt;&gt;<br>
&gt;&gt; &gt;&gt; &gt;&gt;  - Will<br>
&gt;&gt; &gt;&gt; &gt;&gt;<br>
&gt;&gt; &gt;&gt; &gt;&gt; _______________________________________________<br>
&gt;&gt; &gt;&gt; &gt;&gt; infinispan-dev mailing list<br>
&gt;&gt; &gt;&gt; &gt;&gt; <a href="mailto:infinispan-dev@lists.jboss.org" target="_blank">infinispan-dev@lists.jboss.org</a><br>
&gt;&gt; &gt;&gt; &gt;&gt; <a href="https://lists.jboss.org/mailman/listinfo/infinispan-dev" target="_blank">https://lists.jboss.org/mailman/listinfo/infinispan-dev</a><br>
&gt;&gt; &gt;&gt; &gt;<br>
&gt;&gt; &gt;&gt; &gt;<br>
&gt;&gt; &gt;&gt; &gt;<br>
&gt;&gt; &gt;&gt; &gt; _______________________________________________<br>
&gt;&gt; &gt;&gt; &gt; infinispan-dev mailing list<br>
&gt;&gt; &gt;&gt; &gt; <a href="mailto:infinispan-dev@lists.jboss.org" target="_blank">infinispan-dev@lists.jboss.org</a><br>
&gt;&gt; &gt;&gt; &gt; <a href="https://lists.jboss.org/mailman/listinfo/infinispan-dev" target="_blank">https://lists.jboss.org/mailman/listinfo/infinispan-dev</a><br>
&gt;&gt; &gt;&gt; _______________________________________________<br>
&gt;&gt; &gt;&gt; infinispan-dev mailing list<br>
&gt;&gt; &gt;&gt; <a href="mailto:infinispan-dev@lists.jboss.org" target="_blank">infinispan-dev@lists.jboss.org</a><br>
&gt;&gt; &gt;&gt; <a href="https://lists.jboss.org/mailman/listinfo/infinispan-dev" target="_blank">https://lists.jboss.org/mailman/listinfo/infinispan-dev</a><br>
&gt;&gt; &gt;<br>
&gt;&gt; &gt;<br>
&gt;&gt; &gt;<br>
&gt;&gt; &gt; _______________________________________________<br>
&gt;&gt; &gt; infinispan-dev mailing list<br>
&gt;&gt; &gt; <a href="mailto:infinispan-dev@lists.jboss.org" target="_blank">infinispan-dev@lists.jboss.org</a><br>
&gt;&gt; &gt; <a href="https://lists.jboss.org/mailman/listinfo/infinispan-dev" target="_blank">https://lists.jboss.org/mailman/listinfo/infinispan-dev</a><br>
&gt;&gt; _______________________________________________<br>
&gt;&gt; infinispan-dev mailing list<br>
&gt;&gt; <a href="mailto:infinispan-dev@lists.jboss.org" target="_blank">infinispan-dev@lists.jboss.org</a><br>
&gt;&gt; <a href="https://lists.jboss.org/mailman/listinfo/infinispan-dev" target="_blank">https://lists.jboss.org/mailman/listinfo/infinispan-dev</a><br>
&gt;<br>
&gt;<br>
&gt;<br>
&gt; _______________________________________________<br>
&gt; infinispan-dev mailing list<br>
&gt; <a href="mailto:infinispan-dev@lists.jboss.org" target="_blank">infinispan-dev@lists.jboss.org</a><br>
&gt; <a href="https://lists.jboss.org/mailman/listinfo/infinispan-dev" target="_blank">https://lists.jboss.org/mailman/listinfo/infinispan-dev</a><br>
_______________________________________________<br>
infinispan-dev mailing list<br>
<a href="mailto:infinispan-dev@lists.jboss.org" target="_blank">infinispan-dev@lists.jboss.org</a><br>
<a href="https://lists.jboss.org/mailman/listinfo/infinispan-dev" target="_blank">https://lists.jboss.org/mailman/listinfo/infinispan-dev</a><br>
</div></div></blockquote></div><br></div></div>